mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-08-13 17:03:47 -04:00
1090 lines
34 KiB
C
1090 lines
34 KiB
C
|
/*
|
||
|
* File: z_en_st.c
|
||
|
* Overlay: ovl_En_St
|
||
|
* Description: Skulltula (normal, big, invisible)
|
||
|
*/
|
||
|
|
||
|
#include "z_en_st.h"
|
||
|
#include "objects/object_st/object_st.h"
|
||
|
|
||
|
#define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_2 | ACTOR_FLAG_4 | ACTOR_FLAG_5)
|
||
|
|
||
|
void EnSt_Init(Actor* thisx, GlobalContext* globalCtx);
|
||
|
void EnSt_Destroy(Actor* thisx, GlobalContext* globalCtx);
|
||
|
void EnSt_Update(Actor* thisx, GlobalContext* globalCtx);
|
||
|
void EnSt_Draw(Actor* thisx, GlobalContext* globalCtx);
|
||
|
void EnSt_ReturnToCeiling(EnSt* this, GlobalContext* globalCtx);
|
||
|
void EnSt_MoveToGround(EnSt* this, GlobalContext* globalCtx);
|
||
|
void EnSt_StartOnCeilingOrGround(EnSt* this, GlobalContext* globalCtx);
|
||
|
void EnSt_WaitOnGround(EnSt* this, GlobalContext* globalCtx);
|
||
|
void EnSt_Die(EnSt* this, GlobalContext* globalCtx);
|
||
|
void EnSt_BounceAround(EnSt* this, GlobalContext* globalCtx);
|
||
|
void EnSt_FinishBouncing(EnSt* this, GlobalContext* globalCtx);
|
||
|
|
||
|
#include "overlays/ovl_En_St/ovl_En_St.h"
|
||
|
|
||
|
const ActorInit En_St_InitVars = {
|
||
|
ACTOR_EN_ST,
|
||
|
ACTORCAT_ENEMY,
|
||
|
FLAGS,
|
||
|
OBJECT_ST,
|
||
|
sizeof(EnSt),
|
||
|
(ActorFunc)EnSt_Init,
|
||
|
(ActorFunc)EnSt_Destroy,
|
||
|
(ActorFunc)EnSt_Update,
|
||
|
(ActorFunc)EnSt_Draw,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static ColliderCylinderInit sCylinderInit = {
|
||
|
{
|
||
|
COLTYPE_HIT6,
|
||
|
AT_NONE,
|
||
|
AC_ON | AC_TYPE_PLAYER,
|
||
|
OC1_NONE,
|
||
|
OC2_TYPE_1,
|
||
|
COLSHAPE_CYLINDER,
|
||
|
},
|
||
|
{
|
||
|
ELEMTYPE_UNK0,
|
||
|
{ 0x00000000, 0x00, 0x00 },
|
||
|
{ 0x00000000, 0x00, 0x00 },
|
||
|
TOUCH_ON | TOUCH_SFX_NORMAL,
|
||
|
BUMP_ON,
|
||
|
OCELEM_NONE,
|
||
|
},
|
||
|
{ 32, 50, -24, { 0, 0, 0 } },
|
||
|
};
|
||
|
|
||
|
static CollisionCheckInfoInit2 sColChkInit = { 2, 0, 0, 0, MASS_IMMOVABLE };
|
||
|
|
||
|
static ColliderCylinderInit sCylinderInit2 = {
|
||
|
{
|
||
|
COLTYPE_HIT6,
|
||
|
AT_NONE,
|
||
|
AC_NONE,
|
||
|
OC1_ON | OC1_TYPE_ALL,
|
||
|
OC2_TYPE_1,
|
||
|
COLSHAPE_CYLINDER,
|
||
|
},
|
||
|
{
|
||
|
ELEMTYPE_UNK0,
|
||
|
{ 0x00000000, 0x00, 0x00 },
|
||
|
{ 0x00000000, 0x00, 0x00 },
|
||
|
TOUCH_NONE,
|
||
|
BUMP_NONE,
|
||
|
OCELEM_ON,
|
||
|
},
|
||
|
{ 20, 60, -30, { 0, 0, 0 } },
|
||
|
};
|
||
|
|
||
|
static ColliderJntSphElementInit sJntSphElementsInit[1] = {
|
||
|
{
|
||
|
{
|
||
|
ELEMTYPE_UNK0,
|
||
|
{ 0xFFCFFFFF, 0x00, 0x04 },
|
||
|
{ 0x00000000, 0x00, 0x00 },
|
||
|
TOUCH_ON | TOUCH_SFX_NORMAL,
|
||
|
BUMP_NONE,
|
||
|
OCELEM_ON,
|
||
|
},
|
||
|
{ 1, { { 0, -240, 0 }, 28 }, 100 },
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static ColliderJntSphInit sJntSphInit = {
|
||
|
{
|
||
|
COLTYPE_HIT6,
|
||
|
AT_ON | AT_TYPE_ENEMY,
|
||
|
AC_NONE,
|
||
|
OC1_ON | OC1_TYPE_ALL,
|
||
|
OC2_TYPE_1,
|
||
|
COLSHAPE_JNTSPH,
|
||
|
},
|
||
|
1,
|
||
|
sJntSphElementsInit,
|
||
|
};
|
||
|
|
||
|
typedef enum {
|
||
|
/* 0 */ ENST_ANIM_0,
|
||
|
/* 1 */ ENST_ANIM_1,
|
||
|
/* 2 */ ENST_ANIM_2,
|
||
|
/* 3 */ ENST_ANIM_3,
|
||
|
/* 4 */ ENST_ANIM_4,
|
||
|
/* 5 */ ENST_ANIM_5,
|
||
|
/* 6 */ ENST_ANIM_6,
|
||
|
/* 7 */ ENST_ANIM_7
|
||
|
} EnStAnimation;
|
||
|
|
||
|
static AnimationInfo sAnimationInfo[] = {
|
||
|
{ &object_st_Anim_000304, 1.0f, 0.0f, -1.0f, ANIMMODE_LOOP_INTERP, 0.0f },
|
||
|
{ &object_st_Anim_005B98, 1.0f, 0.0f, -1.0f, ANIMMODE_ONCE_INTERP, -8.0f },
|
||
|
{ &object_st_Anim_000304, 4.0f, 0.0f, -1.0f, ANIMMODE_ONCE_INTERP, -8.0f },
|
||
|
{ &object_st_Anim_000304, 1.0f, 0.0f, -1.0f, ANIMMODE_LOOP_INTERP, -8.0f },
|
||
|
{ &object_st_Anim_0055A8, 1.0f, 0.0f, -1.0f, ANIMMODE_ONCE_INTERP, -8.0f },
|
||
|
{ &object_st_Anim_000304, 8.0f, 0.0f, -1.0f, ANIMMODE_LOOP_INTERP, -8.0f },
|
||
|
{ &object_st_Anim_000304, 6.0f, 0.0f, -1.0f, ANIMMODE_LOOP_INTERP, -8.0f },
|
||
|
{ &object_st_Anim_005B98, 2.0f, 0.0f, -1.0f, ANIMMODE_LOOP_INTERP, -8.0f },
|
||
|
};
|
||
|
|
||
|
void EnSt_SetupAction(EnSt* this, EnStActionFunc actionFunc) {
|
||
|
this->actionFunc = actionFunc;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Spawns `dustCnt` dust particles in a random pattern around the Skulltula
|
||
|
*/
|
||
|
void EnSt_SpawnDust(EnSt* this, GlobalContext* globalCtx, s32 dustCnt) {
|
||
|
Color_RGBA8 primColor = { 170, 130, 90, 255 };
|
||
|
Color_RGBA8 envColor = { 100, 60, 20, 0 };
|
||
|
Vec3f dustVel = { 0.0f, 0.0f, 0.0f };
|
||
|
Vec3f dustAccel = { 0.0f, 0.3f, 0.0f };
|
||
|
Vec3f dustPos;
|
||
|
s16 yAngle;
|
||
|
s32 i;
|
||
|
|
||
|
yAngle = (Rand_ZeroOne() - 0.5f) * 65536.0f;
|
||
|
dustPos.y = this->actor.floorHeight;
|
||
|
for (i = dustCnt; i >= 0; i--, yAngle += (s16)(0x10000 / dustCnt)) {
|
||
|
dustAccel.x = (Rand_ZeroOne() - 0.5f) * 4.0f;
|
||
|
dustAccel.z = (Rand_ZeroOne() - 0.5f) * 4.0f;
|
||
|
dustPos.x = this->actor.world.pos.x + (Math_SinS(yAngle) * 22.0f);
|
||
|
dustPos.z = this->actor.world.pos.z + (Math_CosS(yAngle) * 22.0f);
|
||
|
func_8002836C(globalCtx, &dustPos, &dustVel, &dustAccel, &primColor, &envColor, 120, 40, 10);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnSt_SpawnBlastEffect(EnSt* this, GlobalContext* globalCtx) {
|
||
|
Vec3f zeroVec = { 0.0f, 0.0f, 0.0f };
|
||
|
Vec3f blastPos;
|
||
|
|
||
|
blastPos.x = this->actor.world.pos.x;
|
||
|
blastPos.y = this->actor.floorHeight;
|
||
|
blastPos.z = this->actor.world.pos.z;
|
||
|
|
||
|
EffectSsBlast_SpawnWhiteCustomScale(globalCtx, &blastPos, &zeroVec, &zeroVec, 100, 220, 8);
|
||
|
}
|
||
|
|
||
|
void EnSt_SpawnDeadEffect(EnSt* this, GlobalContext* globalCtx) {
|
||
|
Vec3f zeroVec = { 0.0f, 0.0f, 0.0f };
|
||
|
Vec3f firePos;
|
||
|
|
||
|
firePos.x = this->actor.world.pos.x + ((Rand_ZeroOne() - 0.5f) * 60.0f);
|
||
|
firePos.y = (this->actor.world.pos.y + 10.0f) + ((Rand_ZeroOne() - 0.5f) * 45.0f);
|
||
|
firePos.z = this->actor.world.pos.z + ((Rand_ZeroOne() - 0.5f) * 60.0f);
|
||
|
EffectSsDeadDb_Spawn(globalCtx, &firePos, &zeroVec, &zeroVec, 100, 0, 255, 255, 255, 255, 255, 0, 0, 1, 9, true);
|
||
|
}
|
||
|
|
||
|
s32 EnSt_CreateBlureEffect(GlobalContext* globalCtx) {
|
||
|
EffectBlureInit1 blureInit;
|
||
|
u8 p1StartColor[] = { 255, 255, 255, 75 };
|
||
|
u8 p2StartColor[] = { 255, 255, 255, 75 };
|
||
|
u8 p1EndColor[] = { 255, 255, 255, 0 };
|
||
|
u8 p2EndColor[] = { 255, 255, 255, 0 };
|
||
|
s32 i;
|
||
|
s32 blureIdx;
|
||
|
|
||
|
for (i = 0; i < 4; i++) {
|
||
|
blureInit.p1StartColor[i] = p1StartColor[i];
|
||
|
blureInit.p2StartColor[i] = p2StartColor[i];
|
||
|
blureInit.p1EndColor[i] = p1EndColor[i];
|
||
|
blureInit.p2EndColor[i] = p2EndColor[i];
|
||
|
}
|
||
|
|
||
|
blureInit.elemDuration = 6;
|
||
|
blureInit.unkFlag = 0;
|
||
|
blureInit.calcMode = 3;
|
||
|
|
||
|
Effect_Add(globalCtx, &blureIdx, EFFECT_BLURE1, 0, 0, &blureInit);
|
||
|
return blureIdx;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks for the position of the ceiling above the Skulltula.
|
||
|
* If no ceiling is found it is set to 1000 units above the Skulltula
|
||
|
*/
|
||
|
s32 EnSt_CheckCeilingPos(EnSt* this, GlobalContext* globalCtx) {
|
||
|
CollisionPoly* poly;
|
||
|
s32 bgId;
|
||
|
Vec3f checkPos;
|
||
|
|
||
|
checkPos.x = this->actor.world.pos.x;
|
||
|
checkPos.y = this->actor.world.pos.y + 1000.0f;
|
||
|
checkPos.z = this->actor.world.pos.z;
|
||
|
if (!BgCheck_EntityLineTest1(&globalCtx->colCtx, &this->actor.world.pos, &checkPos, &this->ceilingPos, &poly, false,
|
||
|
false, true, true, &bgId)) {
|
||
|
return false;
|
||
|
}
|
||
|
this->unusedPos = this->actor.world.pos;
|
||
|
this->unusedPos.y -= 100.0f;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void EnSt_AddBlurVertex(EnSt* this) {
|
||
|
Vec3f v1 = { 834.0f, 834.0f, 0.0f };
|
||
|
Vec3f v2 = { 834.0f, -584.0f, 0.0f };
|
||
|
Vec3f v1Pos;
|
||
|
Vec3f v2Pos;
|
||
|
|
||
|
v1.x *= this->colliderScale;
|
||
|
v1.y *= this->colliderScale;
|
||
|
v1.z *= this->colliderScale;
|
||
|
|
||
|
v2.x *= this->colliderScale;
|
||
|
v2.y *= this->colliderScale;
|
||
|
v2.z *= this->colliderScale;
|
||
|
|
||
|
Matrix_Push();
|
||
|
Matrix_MultVec3f(&v1, &v1Pos);
|
||
|
Matrix_MultVec3f(&v2, &v2Pos);
|
||
|
Matrix_Pop();
|
||
|
EffectBlure_AddVertex(Effect_GetByIndex(this->blureIdx), &v1Pos, &v2Pos);
|
||
|
}
|
||
|
|
||
|
void EnSt_AddBlurSpace(EnSt* this) {
|
||
|
EffectBlure_AddSpace(Effect_GetByIndex(this->blureIdx));
|
||
|
}
|
||
|
|
||
|
void EnSt_SetWaitingAnimation(EnSt* this) {
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_3);
|
||
|
}
|
||
|
|
||
|
void EnSt_SetReturnToCeilingAnimation(EnSt* this) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_UP);
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_2);
|
||
|
}
|
||
|
|
||
|
void EnSt_SetLandAnimation(EnSt* this) {
|
||
|
this->actor.world.pos.y = this->actor.floorHeight + this->floorHeightOffset;
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_4);
|
||
|
this->sfxTimer = 0;
|
||
|
this->animFrames = this->skelAnime.animLength;
|
||
|
}
|
||
|
|
||
|
void EnSt_SetDropAnimAndVel(EnSt* this) {
|
||
|
if (this->takeDamageSpinTimer == 0) {
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_4);
|
||
|
this->animFrames = this->skelAnime.animLength;
|
||
|
}
|
||
|
this->sfxTimer = 0;
|
||
|
this->actor.velocity.y = -10.0f;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initalizes the Skulltula's 6 cylinders, and sphere collider.
|
||
|
*/
|
||
|
void EnSt_InitColliders(EnSt* this, GlobalContext* globalCtx) {
|
||
|
ColliderCylinderInit* cylinders[6] = {
|
||
|
&sCylinderInit, &sCylinderInit, &sCylinderInit, &sCylinderInit2, &sCylinderInit2, &sCylinderInit2,
|
||
|
};
|
||
|
|
||
|
s32 i;
|
||
|
s32 pad;
|
||
|
|
||
|
for (i = 0; i < ARRAY_COUNT(cylinders); i++) {
|
||
|
Collider_InitCylinder(globalCtx, &this->colCylinder[i]);
|
||
|
Collider_SetCylinder(globalCtx, &this->colCylinder[i], &this->actor, cylinders[i]);
|
||
|
}
|
||
|
|
||
|
this->colCylinder[0].info.bumper.dmgFlags = 0x0003F8F9;
|
||
|
this->colCylinder[1].info.bumper.dmgFlags = 0xFFC00706;
|
||
|
this->colCylinder[2].base.colType = COLTYPE_METAL;
|
||
|
this->colCylinder[2].info.bumperFlags = BUMP_ON | BUMP_HOOKABLE | BUMP_NO_AT_INFO;
|
||
|
this->colCylinder[2].info.elemType = ELEMTYPE_UNK2;
|
||
|
this->colCylinder[2].info.bumper.dmgFlags = 0xFFCC0706;
|
||
|
|
||
|
CollisionCheck_SetInfo2(&this->actor.colChkInfo, DamageTable_Get(2), &sColChkInit);
|
||
|
|
||
|
Collider_InitJntSph(globalCtx, &this->colSph);
|
||
|
Collider_SetJntSph(globalCtx, &this->colSph, &this->actor, &sJntSphInit, this->colSphItems);
|
||
|
}
|
||
|
|
||
|
void EnSt_CheckBodyStickHit(EnSt* this, GlobalContext* globalCtx) {
|
||
|
ColliderInfo* body = &this->colCylinder[0].info;
|
||
|
Player* player = GET_PLAYER(globalCtx);
|
||
|
|
||
|
if (player->unk_860 != 0) {
|
||
|
body->bumper.dmgFlags |= 2;
|
||
|
this->colCylinder[1].info.bumper.dmgFlags &= ~2;
|
||
|
this->colCylinder[2].info.bumper.dmgFlags &= ~2;
|
||
|
} else {
|
||
|
body->bumper.dmgFlags &= ~2;
|
||
|
this->colCylinder[1].info.bumper.dmgFlags |= 2;
|
||
|
this->colCylinder[2].info.bumper.dmgFlags |= 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnSt_SetBodyCylinderAC(EnSt* this, GlobalContext* globalCtx) {
|
||
|
Collider_UpdateCylinder(&this->actor, &this->colCylinder[0]);
|
||
|
CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->colCylinder[0].base);
|
||
|
}
|
||
|
|
||
|
void EnSt_SetLegsCylinderAC(EnSt* this, GlobalContext* globalCtx) {
|
||
|
s16 angleTowardsLink = ABS((s16)(this->actor.yawTowardsPlayer - this->actor.shape.rot.y));
|
||
|
|
||
|
if (angleTowardsLink < 0x3FFC) {
|
||
|
Collider_UpdateCylinder(&this->actor, &this->colCylinder[2]);
|
||
|
CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->colCylinder[2].base);
|
||
|
} else {
|
||
|
Collider_UpdateCylinder(&this->actor, &this->colCylinder[1]);
|
||
|
CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->colCylinder[1].base);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
s32 EnSt_SetCylinderOC(EnSt* this, GlobalContext* globalCtx) {
|
||
|
Vec3f cyloffsets[] = {
|
||
|
{ 40.0f, 0.0f, 0.0f },
|
||
|
{ 0.0f, 0.0f, 0.0f },
|
||
|
{ -40.0f, 0.0f, 0.0f },
|
||
|
};
|
||
|
Vec3f cylPos;
|
||
|
s32 i;
|
||
|
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
cylPos = this->actor.world.pos;
|
||
|
cyloffsets[i].x *= this->colliderScale;
|
||
|
cyloffsets[i].y *= this->colliderScale;
|
||
|
cyloffsets[i].z *= this->colliderScale;
|
||
|
Matrix_Push();
|
||
|
Matrix_Translate(cylPos.x, cylPos.y, cylPos.z, MTXMODE_NEW);
|
||
|
Matrix_RotateY((this->initalYaw / 32768.0f) * M_PI, MTXMODE_APPLY);
|
||
|
Matrix_MultVec3f(&cyloffsets[i], &cylPos);
|
||
|
Matrix_Pop();
|
||
|
this->colCylinder[i + 3].dim.pos.x = cylPos.x;
|
||
|
this->colCylinder[i + 3].dim.pos.y = cylPos.y;
|
||
|
this->colCylinder[i + 3].dim.pos.z = cylPos.z;
|
||
|
CollisionCheck_SetOC(globalCtx, &globalCtx->colChkCtx, &this->colCylinder[i + 3].base);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void EnSt_UpdateCylinders(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if ((this->actor.colChkInfo.health != 0) || (this->actionFunc == EnSt_FinishBouncing)) {
|
||
|
if (DECR(this->gaveDamageSpinTimer) == 0) {
|
||
|
EnSt_SetCylinderOC(this, globalCtx);
|
||
|
}
|
||
|
|
||
|
DECR(this->invulnerableTimer);
|
||
|
DECR(this->takeDamageSpinTimer);
|
||
|
|
||
|
if (this->invulnerableTimer == 0 && this->takeDamageSpinTimer == 0) {
|
||
|
EnSt_SetBodyCylinderAC(this, globalCtx);
|
||
|
EnSt_SetLegsCylinderAC(this, globalCtx);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
s32 EnSt_CheckHitLink(EnSt* this, GlobalContext* globalCtx) {
|
||
|
Player* player = GET_PLAYER(globalCtx);
|
||
|
s32 hit;
|
||
|
s32 i;
|
||
|
|
||
|
for (i = 0, hit = 0; i < 3; i++) {
|
||
|
if (((this->colCylinder[i + 3].base.ocFlags2 & OC2_HIT_PLAYER) != 0) == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
this->colCylinder[i + 3].base.ocFlags2 &= ~OC2_HIT_PLAYER;
|
||
|
hit = true;
|
||
|
}
|
||
|
|
||
|
if (!hit) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (this->swayTimer == 0) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_ROLL);
|
||
|
}
|
||
|
|
||
|
this->gaveDamageSpinTimer = 30;
|
||
|
globalCtx->damagePlayer(globalCtx, -8);
|
||
|
Audio_PlayActorSound2(&player->actor, NA_SE_PL_BODY_HIT);
|
||
|
func_8002F71C(globalCtx, &this->actor, 4.0f, this->actor.yawTowardsPlayer, 6.0f);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
s32 EnSt_CheckHitFrontside(EnSt* this) {
|
||
|
u8 acFlags = this->colCylinder[2].base.acFlags;
|
||
|
|
||
|
if (!!(acFlags & AC_HIT) == 0) {
|
||
|
// not hit
|
||
|
return false;
|
||
|
} else {
|
||
|
this->colCylinder[2].base.acFlags &= ~AC_HIT;
|
||
|
this->invulnerableTimer = 8;
|
||
|
this->playSwayFlag = 0;
|
||
|
this->swayTimer = 60;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
s32 EnSt_CheckHitBackside(EnSt* this, GlobalContext* globalCtx) {
|
||
|
ColliderCylinder* cyl = &this->colCylinder[0];
|
||
|
s32 flags = 0; // ac hit flags from colliders 0 and 1
|
||
|
s32 hit = false;
|
||
|
|
||
|
if (cyl->base.acFlags & AC_HIT) {
|
||
|
cyl->base.acFlags &= ~AC_HIT;
|
||
|
hit = true;
|
||
|
flags |= cyl->info.acHitInfo->toucher.dmgFlags;
|
||
|
}
|
||
|
|
||
|
cyl = &this->colCylinder[1];
|
||
|
if (cyl->base.acFlags & AC_HIT) {
|
||
|
cyl->base.acFlags &= ~AC_HIT;
|
||
|
hit = true;
|
||
|
flags |= cyl->info.acHitInfo->toucher.dmgFlags;
|
||
|
}
|
||
|
|
||
|
if (!hit) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
this->invulnerableTimer = 8;
|
||
|
if (this->actor.colChkInfo.damageEffect == 1) {
|
||
|
if (this->stunTimer == 0) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_GOMA_JR_FREEZE);
|
||
|
this->stunTimer = 120;
|
||
|
Actor_SetColorFilter(&this->actor, 0, 0xC8, 0, this->stunTimer);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
this->swayTimer = this->stunTimer = 0;
|
||
|
this->gaveDamageSpinTimer = 1;
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_3);
|
||
|
this->takeDamageSpinTimer = this->skelAnime.animLength;
|
||
|
Actor_SetColorFilter(&this->actor, 0x4000, 0xC8, 0, this->takeDamageSpinTimer);
|
||
|
if (Actor_ApplyDamage(&this->actor)) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_DAMAGE);
|
||
|
return false;
|
||
|
}
|
||
|
Enemy_StartFinishingBlow(globalCtx, &this->actor);
|
||
|
this->actor.flags &= ~ACTOR_FLAG_0;
|
||
|
this->groundBounces = 3;
|
||
|
this->deathTimer = 20;
|
||
|
this->actor.gravity = -1.0f;
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALWALL_DEAD);
|
||
|
|
||
|
if (flags & 0x1F820) {
|
||
|
// arrow, fire arrow, ice arrow, light arrow,
|
||
|
// and three unknows, unused arrows?
|
||
|
EnSt_SetupAction(this, EnSt_Die);
|
||
|
this->finishDeathTimer = 8;
|
||
|
} else {
|
||
|
EnSt_SetupAction(this, EnSt_BounceAround);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if the Skulltula's colliders have been hit, returns true if the hit has dealt damage to the Skulltula
|
||
|
*/
|
||
|
s32 EnSt_CheckColliders(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (EnSt_CheckHitFrontside(this)) {
|
||
|
// player has hit the front shield area of the Skulltula
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (globalCtx->actorCtx.unk_02 != 0) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (EnSt_CheckHitBackside(this, globalCtx)) {
|
||
|
// player has hit the backside of the Skulltula
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (this->stunTimer == 0 && this->takeDamageSpinTimer == 0) {
|
||
|
// check if the Skulltula has hit link.
|
||
|
EnSt_CheckHitLink(this, globalCtx);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void EnSt_SetColliderScale(EnSt* this) {
|
||
|
f32 scaleAmount = 1.0f;
|
||
|
f32 radius;
|
||
|
f32 height;
|
||
|
f32 yShift;
|
||
|
s32 i;
|
||
|
|
||
|
if (this->actor.params == 1) {
|
||
|
scaleAmount = 1.4f;
|
||
|
}
|
||
|
|
||
|
radius = this->colSph.elements[0].dim.modelSphere.radius;
|
||
|
radius *= scaleAmount;
|
||
|
this->colSph.elements[0].dim.modelSphere.radius = radius;
|
||
|
|
||
|
for (i = 0; i < 6; i++) {
|
||
|
yShift = this->colCylinder[i].dim.yShift;
|
||
|
radius = this->colCylinder[i].dim.radius;
|
||
|
height = this->colCylinder[i].dim.height;
|
||
|
yShift *= scaleAmount;
|
||
|
radius *= scaleAmount;
|
||
|
height *= scaleAmount;
|
||
|
|
||
|
this->colCylinder[i].dim.yShift = yShift;
|
||
|
this->colCylinder[i].dim.radius = radius;
|
||
|
this->colCylinder[i].dim.height = height;
|
||
|
}
|
||
|
Actor_SetScale(&this->actor, 0.04f * scaleAmount);
|
||
|
this->colliderScale = scaleAmount;
|
||
|
this->floorHeightOffset = 32.0f * scaleAmount;
|
||
|
}
|
||
|
|
||
|
s32 EnSt_SetTeethColor(EnSt* this, s16 redTarget, s16 greenTarget, s16 blueTarget, s16 minMaxStep) {
|
||
|
s16 red = this->teethR;
|
||
|
s16 green = this->teethG;
|
||
|
s16 blue = this->teethB;
|
||
|
|
||
|
minMaxStep = 255 / (s16)(0.6f * minMaxStep);
|
||
|
if (minMaxStep <= 0) {
|
||
|
minMaxStep = 1;
|
||
|
}
|
||
|
|
||
|
Math_SmoothStepToS(&red, redTarget, 1, minMaxStep, minMaxStep);
|
||
|
Math_SmoothStepToS(&green, greenTarget, 1, minMaxStep, minMaxStep);
|
||
|
Math_SmoothStepToS(&blue, blueTarget, 1, minMaxStep, minMaxStep);
|
||
|
this->teethR = red;
|
||
|
this->teethG = green;
|
||
|
this->teethB = blue;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
s32 EnSt_DecrStunTimer(EnSt* this) {
|
||
|
if (this->stunTimer == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
this->stunTimer--; //! @bug no return but v0 ends up being stunTimer before decrement
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates the yaw of the Skulltula, used for the shaking animation right before
|
||
|
* turning, and the actual turning to face away from the player, and then back to
|
||
|
* face the player
|
||
|
*/
|
||
|
void EnSt_UpdateYaw(EnSt* this, GlobalContext* globalCtx) {
|
||
|
u16 yawDir = 0;
|
||
|
Vec3s rot;
|
||
|
s16 yawDiff;
|
||
|
s16 timer;
|
||
|
s16 yawTarget;
|
||
|
|
||
|
// Shake towards the end of the stun.
|
||
|
if (this->stunTimer != 0) {
|
||
|
if (this->stunTimer < 30) {
|
||
|
if ((this->stunTimer % 2) != 0) {
|
||
|
this->actor.shape.rot.y += 0x800;
|
||
|
} else {
|
||
|
this->actor.shape.rot.y -= 0x800;
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this->swayTimer == 0 && this->deathTimer == 0 && this->finishDeathTimer == 0) {
|
||
|
// not swaying or dying
|
||
|
if (this->takeDamageSpinTimer != 0 || this->gaveDamageSpinTimer != 0) {
|
||
|
// Skulltula is doing a spinning animation
|
||
|
this->actor.shape.rot.y += 0x2000;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this->actionFunc != EnSt_WaitOnGround) {
|
||
|
// set the timers to turn away or turn towards the player
|
||
|
this->rotAwayTimer = 30;
|
||
|
this->rotTowardsTimer = 0;
|
||
|
}
|
||
|
|
||
|
if (this->rotAwayTimer != 0) {
|
||
|
// turn away from the player
|
||
|
this->rotAwayTimer--;
|
||
|
if (this->rotAwayTimer == 0) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_ROLL);
|
||
|
this->rotTowardsTimer = 30;
|
||
|
}
|
||
|
} else if (this->rotTowardsTimer != 0) {
|
||
|
// turn towards the player
|
||
|
this->rotTowardsTimer--;
|
||
|
if (this->rotTowardsTimer == 0) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_ROLL);
|
||
|
this->rotAwayTimer = 30;
|
||
|
}
|
||
|
yawDir = 0x8000;
|
||
|
}
|
||
|
|
||
|
// calculate the new yaw to or away from the player.
|
||
|
rot = this->actor.shape.rot;
|
||
|
yawTarget = (this->actionFunc == EnSt_WaitOnGround ? this->actor.yawTowardsPlayer : this->initalYaw);
|
||
|
yawDiff = rot.y - (yawTarget ^ yawDir);
|
||
|
if (ABS(yawDiff) <= 0x4000) {
|
||
|
Math_SmoothStepToS(&rot.y, yawTarget ^ yawDir, 4, 0x2000, 1);
|
||
|
} else {
|
||
|
rot.y += 0x2000;
|
||
|
}
|
||
|
|
||
|
this->actor.shape.rot = this->actor.world.rot = rot;
|
||
|
|
||
|
// Do the shaking animation.
|
||
|
if (yawDir == 0 && this->rotAwayTimer < 0xA) {
|
||
|
timer = this->rotAwayTimer;
|
||
|
} else if (yawDir == 0x8000 && this->rotTowardsTimer < 0xA) {
|
||
|
timer = this->rotTowardsTimer;
|
||
|
} else {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ((timer % 2) != 0) {
|
||
|
this->actor.shape.rot.y += 0x800;
|
||
|
} else {
|
||
|
this->actor.shape.rot.y -= 0x800;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks to see if the Skulltula is done bouncing on the ground,
|
||
|
* spawns dust particles as the Skulltula hits the ground
|
||
|
*/
|
||
|
s32 EnSt_IsDoneBouncing(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (this->actor.velocity.y > 0.0f || this->groundBounces == 0) {
|
||
|
// the Skulltula is moving upwards or the groundBounces is 0
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!(this->actor.bgCheckFlags & 1)) {
|
||
|
// the Skulltula is not on the ground.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND);
|
||
|
EnSt_SpawnDust(this, globalCtx, 10);
|
||
|
// creates an elastic bouncing effect, boucing up less for each hit on the ground.
|
||
|
this->actor.velocity.y = 6.0f / (4 - this->groundBounces);
|
||
|
this->groundBounces--;
|
||
|
if (this->groundBounces != 0) {
|
||
|
return false;
|
||
|
} else {
|
||
|
// make sure the Skulltula stays on the ground.
|
||
|
this->actor.velocity.y = 0.0f;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void EnSt_Bob(EnSt* this, GlobalContext* globalCtx) {
|
||
|
f32 ySpeedTarget = 0.5f;
|
||
|
|
||
|
if ((globalCtx->state.frames & 8) != 0) {
|
||
|
ySpeedTarget *= -1.0f;
|
||
|
}
|
||
|
Math_SmoothStepToF(&this->actor.velocity.y, ySpeedTarget, 0.4f, 1000.0f, 0.0f);
|
||
|
}
|
||
|
|
||
|
s32 EnSt_IsCloseToPlayer(EnSt* this, GlobalContext* globalCtx) {
|
||
|
Player* player = GET_PLAYER(globalCtx);
|
||
|
f32 yDist;
|
||
|
|
||
|
if (this->takeDamageSpinTimer != 0) {
|
||
|
// skull is spinning from damage.
|
||
|
return false;
|
||
|
} else if (this->actor.xzDistToPlayer > 160.0f) {
|
||
|
// player is more than 160 xz units from the Skulltula
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
yDist = this->actor.world.pos.y - player->actor.world.pos.y;
|
||
|
if (yDist < 0.0f || yDist > 400.0f) {
|
||
|
// player is above the Skulltula or more than 400 units below
|
||
|
// the Skulltula
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (player->actor.world.pos.y < this->actor.floorHeight) {
|
||
|
// player is below the Skulltula's ground position
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
s32 EnSt_IsCloseToInitalPos(EnSt* this) {
|
||
|
f32 velY = this->actor.velocity.y;
|
||
|
f32 checkY = this->actor.world.pos.y + (velY * 2.0f);
|
||
|
|
||
|
if (checkY >= this->actor.home.pos.y) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
s32 EnSt_IsCloseToGround(EnSt* this) {
|
||
|
f32 velY = this->actor.velocity.y;
|
||
|
f32 checkY = this->actor.world.pos.y + (velY * 2.0f);
|
||
|
|
||
|
if (checkY - this->actor.floorHeight <= this->floorHeightOffset) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Does the animation of the Skulltula swaying back and forth after the Skulltula
|
||
|
* has been hit in the front by a sword
|
||
|
*/
|
||
|
void EnSt_Sway(EnSt* this) {
|
||
|
Vec3f amtToTranslate;
|
||
|
Vec3f translatedPos;
|
||
|
f32 swayAmt;
|
||
|
s16 rotAngle;
|
||
|
|
||
|
if (this->swayTimer != 0) {
|
||
|
|
||
|
this->swayAngle += 0xA28;
|
||
|
this->swayTimer--;
|
||
|
|
||
|
if (this->swayTimer == 0) {
|
||
|
this->swayAngle = 0;
|
||
|
}
|
||
|
|
||
|
swayAmt = this->swayTimer * (7.0f / 15.0f);
|
||
|
rotAngle = Math_SinS(this->swayAngle) * (swayAmt * (65536.0f / 360.0f));
|
||
|
|
||
|
if (this->absPrevSwayAngle >= ABS(rotAngle) && this->playSwayFlag == 0) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_WAVE);
|
||
|
this->playSwayFlag = 1;
|
||
|
}
|
||
|
|
||
|
if (this->absPrevSwayAngle < ABS(rotAngle)) {
|
||
|
this->playSwayFlag = 0;
|
||
|
}
|
||
|
|
||
|
this->absPrevSwayAngle = ABS(rotAngle);
|
||
|
amtToTranslate.x = Math_SinS(rotAngle) * -200.0f;
|
||
|
amtToTranslate.y = Math_CosS(rotAngle) * -200.0f;
|
||
|
amtToTranslate.z = 0.0f;
|
||
|
Matrix_Push();
|
||
|
Matrix_Translate(this->ceilingPos.x, this->ceilingPos.y, this->ceilingPos.z, MTXMODE_NEW);
|
||
|
Matrix_RotateY(this->actor.world.rot.y * (M_PI / 32768.0f), MTXMODE_APPLY);
|
||
|
Matrix_MultVec3f(&amtToTranslate, &translatedPos);
|
||
|
Matrix_Pop();
|
||
|
this->actor.shape.rot.z = -(rotAngle * 2);
|
||
|
this->actor.world.pos.x = translatedPos.x;
|
||
|
this->actor.world.pos.z = translatedPos.z;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnSt_Init(Actor* thisx, GlobalContext* globalCtx) {
|
||
|
EnSt* this = (EnSt*)thisx;
|
||
|
s32 pad;
|
||
|
|
||
|
ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 14.0f);
|
||
|
SkelAnime_Init(globalCtx, &this->skelAnime, &object_st_Skel_005298, NULL, this->jointTable, this->morphTable, 30);
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_0);
|
||
|
this->blureIdx = EnSt_CreateBlureEffect(globalCtx);
|
||
|
EnSt_InitColliders(this, globalCtx);
|
||
|
if (thisx->params == 2) {
|
||
|
this->actor.flags |= ACTOR_FLAG_7;
|
||
|
}
|
||
|
if (this->actor.params == 1) {
|
||
|
this->actor.naviEnemyId = 0x05;
|
||
|
} else {
|
||
|
this->actor.naviEnemyId = 0x04;
|
||
|
}
|
||
|
EnSt_CheckCeilingPos(this, globalCtx);
|
||
|
this->actor.flags |= ACTOR_FLAG_14;
|
||
|
this->actor.flags |= ACTOR_FLAG_24;
|
||
|
EnSt_SetColliderScale(this);
|
||
|
this->actor.gravity = 0.0f;
|
||
|
this->initalYaw = this->actor.world.rot.y;
|
||
|
EnSt_SetupAction(this, EnSt_StartOnCeilingOrGround);
|
||
|
}
|
||
|
|
||
|
void EnSt_Destroy(Actor* thisx, GlobalContext* globalCtx) {
|
||
|
EnSt* this = (EnSt*)thisx;
|
||
|
s32 i;
|
||
|
|
||
|
Effect_Delete(globalCtx, this->blureIdx);
|
||
|
for (i = 0; i < 6; i++) {
|
||
|
Collider_DestroyCylinder(globalCtx, &this->colCylinder[i]);
|
||
|
}
|
||
|
Collider_DestroyJntSph(globalCtx, &this->colSph);
|
||
|
}
|
||
|
|
||
|
void EnSt_WaitOnCeiling(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (EnSt_IsCloseToPlayer(this, globalCtx)) {
|
||
|
EnSt_SetDropAnimAndVel(this);
|
||
|
EnSt_SetupAction(this, EnSt_MoveToGround);
|
||
|
} else {
|
||
|
EnSt_Bob(this, globalCtx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Skulltula is waiting on the ground for the player to move away, or for
|
||
|
* a collider to have contact
|
||
|
*/
|
||
|
void EnSt_WaitOnGround(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (this->takeDamageSpinTimer != 0) {
|
||
|
this->takeDamageSpinTimer--;
|
||
|
if (this->takeDamageSpinTimer == 0) {
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this->animFrames != 0) {
|
||
|
this->animFrames--;
|
||
|
if (this->animFrames == 0) {
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!EnSt_IsCloseToPlayer(this, globalCtx)) {
|
||
|
// Player is no longer within range, return to ceiling.
|
||
|
EnSt_SetReturnToCeilingAnimation(this);
|
||
|
EnSt_SetupAction(this, EnSt_ReturnToCeiling);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (DECR(this->sfxTimer) == 0) {
|
||
|
// play the "laugh" sfx every 64 frames.
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_LAUGH);
|
||
|
this->sfxTimer = 64;
|
||
|
}
|
||
|
|
||
|
// simply bob up and down.
|
||
|
EnSt_Bob(this, globalCtx);
|
||
|
}
|
||
|
|
||
|
void EnSt_LandOnGround(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (this->animFrames != 0) {
|
||
|
this->animFrames--;
|
||
|
if (this->animFrames == 0) {
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this->takeDamageSpinTimer != 0) {
|
||
|
this->takeDamageSpinTimer--;
|
||
|
if (this->takeDamageSpinTimer == 0) {
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this->sfxTimer++;
|
||
|
if (this->sfxTimer == 14) {
|
||
|
// play the sound effect of the Skulltula hitting the ground.
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_DOWN_SET);
|
||
|
}
|
||
|
|
||
|
if ((this->actor.floorHeight + this->floorHeightOffset) < this->actor.world.pos.y) {
|
||
|
// the Skulltula has hit the ground.
|
||
|
this->sfxTimer = 0;
|
||
|
EnSt_SetupAction(this, EnSt_WaitOnGround);
|
||
|
} else {
|
||
|
Math_SmoothStepToF(&this->actor.velocity.y, 2.0f, 0.3f, 1.0f, 0.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnSt_MoveToGround(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (this->takeDamageSpinTimer != 0) {
|
||
|
this->takeDamageSpinTimer--;
|
||
|
if (this->takeDamageSpinTimer == 0) {
|
||
|
Animation_ChangeByInfo(&this->skelAnime, sAnimationInfo, ENST_ANIM_5);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!EnSt_IsCloseToPlayer(this, globalCtx)) {
|
||
|
// the player moved out of range, return to the ceiling.
|
||
|
EnSt_SetReturnToCeilingAnimation(this);
|
||
|
EnSt_SetupAction(this, EnSt_ReturnToCeiling);
|
||
|
} else if (EnSt_IsCloseToGround(this)) {
|
||
|
// The Skulltula has become close to the ground.
|
||
|
EnSt_SpawnBlastEffect(this, globalCtx);
|
||
|
EnSt_SetLandAnimation(this);
|
||
|
EnSt_SetupAction(this, EnSt_LandOnGround);
|
||
|
} else if (DECR(this->sfxTimer) == 0) {
|
||
|
Audio_PlayActorSound2(&this->actor, NA_SE_EN_STALTU_DOWN);
|
||
|
this->sfxTimer = 3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnSt_ReturnToCeiling(EnSt* this, GlobalContext* globalCtx) {
|
||
|
f32 animPctDone = this->skelAnime.curFrame / (this->skelAnime.animLength - 1.0f);
|
||
|
|
||
|
if (animPctDone == 1.0f) {
|
||
|
EnSt_SetReturnToCeilingAnimation(this);
|
||
|
}
|
||
|
|
||
|
if (EnSt_IsCloseToPlayer(this, globalCtx)) {
|
||
|
// player came back into range
|
||
|
EnSt_SetDropAnimAndVel(this);
|
||
|
EnSt_SetupAction(this, EnSt_MoveToGround);
|
||
|
} else if (EnSt_IsCloseToInitalPos(this)) {
|
||
|
// the Skulltula is close to the initial postion.
|
||
|
EnSt_SetWaitingAnimation(this);
|
||
|
EnSt_SetupAction(this, EnSt_WaitOnCeiling);
|
||
|
} else {
|
||
|
// accelerate based on the current animation frame.
|
||
|
this->actor.velocity.y = 4.0f * animPctDone;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The Skulltula has been killed, bounce around
|
||
|
*/
|
||
|
void EnSt_BounceAround(EnSt* this, GlobalContext* globalCtx) {
|
||
|
this->actor.colorFilterTimer = this->deathTimer;
|
||
|
func_8002D868(&this->actor);
|
||
|
this->actor.world.rot.x += 0x800;
|
||
|
this->actor.world.rot.z -= 0x800;
|
||
|
this->actor.shape.rot = this->actor.world.rot;
|
||
|
if (EnSt_IsDoneBouncing(this, globalCtx)) {
|
||
|
this->actor.shape.yOffset = 400.0f;
|
||
|
this->actor.speedXZ = 1.0f;
|
||
|
this->actor.gravity = -2.0f;
|
||
|
EnSt_SetupAction(this, EnSt_FinishBouncing);
|
||
|
} else {
|
||
|
Math_SmoothStepToF(&this->actor.shape.yOffset, 400.0f, 0.4f, 10000.0f, 0.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finish up the bouncing animation, and rotate towards the final position
|
||
|
*/
|
||
|
void EnSt_FinishBouncing(EnSt* this, GlobalContext* globalCtx) {
|
||
|
Vec3f zeroVec = { 0.0f, 0.0f, 0.0f };
|
||
|
|
||
|
if (DECR(this->deathTimer) == 0) {
|
||
|
this->actor.velocity = zeroVec;
|
||
|
this->finishDeathTimer = 8;
|
||
|
EnSt_SetupAction(this, EnSt_Die);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (DECR(this->setTargetYawTimer) == 0) {
|
||
|
this->deathYawTarget = Math_Vec3f_Yaw(&this->actor.world.pos, &this->actor.home.pos);
|
||
|
this->setTargetYawTimer = 8;
|
||
|
}
|
||
|
|
||
|
Math_SmoothStepToS(&this->actor.world.rot.x, 0x3FFC, 4, 0x2710, 1);
|
||
|
Math_SmoothStepToS(&this->actor.world.rot.z, 0, 4, 0x2710, 1);
|
||
|
Math_SmoothStepToS(&this->actor.world.rot.y, this->deathYawTarget, 0xA, 0x2710, 1);
|
||
|
|
||
|
this->actor.shape.rot = this->actor.world.rot;
|
||
|
|
||
|
func_8002D868(&this->actor);
|
||
|
this->groundBounces = 2;
|
||
|
EnSt_IsDoneBouncing(this, globalCtx);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Spawn the enemy dying effects, and drop a random item
|
||
|
*/
|
||
|
void EnSt_Die(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (DECR(this->finishDeathTimer) != 0) {
|
||
|
EnSt_SpawnDeadEffect(this, globalCtx);
|
||
|
} else {
|
||
|
Item_DropCollectibleRandom(globalCtx, NULL, &this->actor.world.pos, 0xE0);
|
||
|
Actor_Kill(&this->actor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnSt_StartOnCeilingOrGround(EnSt* this, GlobalContext* globalCtx) {
|
||
|
if (!EnSt_IsCloseToGround(this)) {
|
||
|
this->rotAwayTimer = 60;
|
||
|
EnSt_SetupAction(this, EnSt_WaitOnCeiling);
|
||
|
EnSt_WaitOnCeiling(this, globalCtx);
|
||
|
} else {
|
||
|
EnSt_SetLandAnimation(this);
|
||
|
EnSt_SetupAction(this, EnSt_LandOnGround);
|
||
|
EnSt_LandOnGround(this, globalCtx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnSt_Update(Actor* thisx, GlobalContext* globalCtx) {
|
||
|
EnSt* this = (EnSt*)thisx;
|
||
|
s32 pad;
|
||
|
Color_RGBA8 color = { 0, 0, 0, 0 };
|
||
|
|
||
|
if (this->actor.flags & ACTOR_FLAG_15) {
|
||
|
SkelAnime_Update(&this->skelAnime);
|
||
|
} else if (!EnSt_CheckColliders(this, globalCtx)) {
|
||
|
// no collision has been detected.
|
||
|
|
||
|
if (this->stunTimer == 0) {
|
||
|
SkelAnime_Update(&this->skelAnime);
|
||
|
}
|
||
|
|
||
|
if (this->swayTimer == 0 && this->stunTimer == 0) {
|
||
|
func_8002D7EC(&this->actor);
|
||
|
}
|
||
|
|
||
|
Actor_UpdateBgCheckInfo(globalCtx, &this->actor, 0.0f, 0.0f, 0.0f, 4);
|
||
|
|
||
|
if ((this->stunTimer == 0) && (this->swayTimer == 0)) {
|
||
|
// run the current action if the Skulltula isn't stunned
|
||
|
// or swaying.
|
||
|
this->actionFunc(this, globalCtx);
|
||
|
} else if (this->stunTimer != 0) {
|
||
|
// decrement the stun timer.
|
||
|
EnSt_DecrStunTimer(this);
|
||
|
} else {
|
||
|
// sway the Skulltula.
|
||
|
EnSt_Sway(this);
|
||
|
}
|
||
|
|
||
|
EnSt_UpdateYaw(this, globalCtx);
|
||
|
|
||
|
if (this->actionFunc == EnSt_WaitOnGround) {
|
||
|
if ((globalCtx->state.frames & 0x10) != 0) {
|
||
|
color.r = 255;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EnSt_SetTeethColor(this, color.r, color.g, color.b, 8);
|
||
|
EnSt_UpdateCylinders(this, globalCtx);
|
||
|
Actor_SetFocus(&this->actor, 0.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
s32 EnSt_OverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dListP, Vec3f* pos, Vec3s* rot, void* thisx) {
|
||
|
EnSt* this = (EnSt*)thisx;
|
||
|
|
||
|
OPEN_DISPS(globalCtx->state.gfxCtx, "../z_en_st.c", 2260);
|
||
|
switch (limbIndex) {
|
||
|
case 1:
|
||
|
if (this->gaveDamageSpinTimer != 0 && this->swayTimer == 0) {
|
||
|
if (this->gaveDamageSpinTimer >= 2) {
|
||
|
EnSt_AddBlurVertex(this);
|
||
|
} else {
|
||
|
EnSt_AddBlurSpace(this);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 4:
|
||
|
// teeth
|
||
|
gDPPipeSync(POLY_OPA_DISP++);
|
||
|
gDPSetEnvColor(POLY_OPA_DISP++, this->teethR, this->teethG, this->teethB, 0);
|
||
|
break;
|
||
|
}
|
||
|
CLOSE_DISPS(globalCtx->state.gfxCtx, "../z_en_st.c", 2295);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void EnSt_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dListP, Vec3s* rot, void* thisx) {
|
||
|
EnSt* this = (EnSt*)thisx;
|
||
|
|
||
|
Collider_UpdateSpheres(limbIndex, &this->colSph);
|
||
|
}
|
||
|
|
||
|
void EnSt_Draw(Actor* thisx, GlobalContext* globalCtx) {
|
||
|
EnSt* this = (EnSt*)thisx;
|
||
|
|
||
|
EnSt_CheckBodyStickHit(this, globalCtx);
|
||
|
func_80093D18(globalCtx->state.gfxCtx);
|
||
|
SkelAnime_DrawOpa(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, EnSt_OverrideLimbDraw,
|
||
|
EnSt_PostLimbDraw, this);
|
||
|
}
|