#include "z_en_bom_chu.h" #include "overlays/actors/ovl_En_Bom/z_en_bom.h" #include "objects/gameplay_keep/gameplay_keep.h" #define FLAGS ACTOR_FLAG_4 #define BOMBCHU_SCALE 0.01f void EnBomChu_Init(Actor* thisx, GlobalContext* globalCtx); void EnBomChu_Destroy(Actor* thisx, GlobalContext* globalCtx); void EnBomChu_Update(Actor* thisx, GlobalContext* globalCtx); void EnBomChu_Draw(Actor* thisx, GlobalContext* globalCtx); void EnBomChu_WaitForRelease(EnBomChu* this, GlobalContext* globalCtx); void EnBomChu_Move(EnBomChu* this, GlobalContext* globalCtx); void EnBomChu_WaitForKill(EnBomChu* this, GlobalContext* globalCtx); const ActorInit En_Bom_Chu_InitVars = { ACTOR_EN_BOM_CHU, ACTORCAT_EXPLOSIVE, FLAGS, OBJECT_GAMEPLAY_KEEP, sizeof(EnBomChu), (ActorFunc)EnBomChu_Init, (ActorFunc)EnBomChu_Destroy, (ActorFunc)EnBomChu_Update, (ActorFunc)EnBomChu_Draw, NULL, }; static ColliderJntSphElementInit sJntSphElemInit[] = { { { ELEMTYPE_UNK0, { 0x00000000, 0x00, 0x00 }, { 0xFFCFFFFF, 0x00, 0x00 }, TOUCH_NONE, BUMP_ON, OCELEM_ON, }, { 1, { { 0, 0, 0 }, 12 }, 100 }, }, }; static ColliderJntSphInit sJntSphInit = { { COLTYPE_NONE, AT_NONE, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_1 | OC1_TYPE_2, OC2_TYPE_2, COLSHAPE_JNTSPH, }, ARRAY_COUNT(sJntSphElemInit), sJntSphElemInit, }; static InitChainEntry sInitChain[] = { ICHAIN_U8(targetMode, 2, ICHAIN_CONTINUE), ICHAIN_VEC3F_DIV1000(scale, 1000 * BOMBCHU_SCALE, ICHAIN_STOP), }; void EnBomChu_Init(Actor* thisx, GlobalContext* globalCtx) { static u8 p1StartColor[] = { 250, 0, 0, 250 }; static u8 p2StartColor[] = { 200, 0, 0, 130 }; static u8 p1EndColor[] = { 150, 0, 0, 100 }; static u8 p2EndColor[] = { 100, 0, 0, 50 }; EnBomChu* this = (EnBomChu*)thisx; EffectBlureInit1 blureInit; s32 i; Actor_ProcessInitChain(&this->actor, sInitChain); Collider_InitJntSph(globalCtx, &this->collider); Collider_SetJntSph(globalCtx, &this->collider, &this->actor, &sJntSphInit, this->colliderElements); this->collider.elements[0].dim.worldSphere.radius = this->collider.elements[0].dim.modelSphere.radius; 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 = 16; blureInit.unkFlag = 0; blureInit.calcMode = 0; Effect_Add(globalCtx, &this->blure1Index, EFFECT_BLURE1, 0, 0, &blureInit); Effect_Add(globalCtx, &this->blure2Index, EFFECT_BLURE1, 0, 0, &blureInit); this->actor.room = -1; this->timer = 120; this->actionFunc = EnBomChu_WaitForRelease; } void EnBomChu_Destroy(Actor* thisx, GlobalContext* globalCtx) { EnBomChu* this = (EnBomChu*)thisx; Effect_Delete(globalCtx, this->blure1Index); Effect_Delete(globalCtx, this->blure2Index); Collider_DestroyJntSph(globalCtx, &this->collider); } void EnBomChu_Explode(EnBomChu* this, GlobalContext* globalCtx) { EnBom* bomb; s32 i; bomb = (EnBom*)Actor_Spawn(&globalCtx->actorCtx, globalCtx, ACTOR_EN_BOM, this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, BOMB_BODY); if (bomb != NULL) { bomb->timer = 0; } this->timer = 1; this->actor.speedXZ = 0.0f; if (this->actor.yDistToWater > 0.0f) { for (i = 0; i < 40; i++) { EffectSsBubble_Spawn(globalCtx, &this->actor.world.pos, 1.0f, 5.0f, 30.0f, 0.25f); } } this->actionFunc = EnBomChu_WaitForKill; } void EnBomChu_CrossProduct(Vec3f* a, Vec3f* b, Vec3f* dest) { dest->x = (a->y * b->z) - (a->z * b->y); dest->y = (a->z * b->x) - (a->x * b->z); dest->z = (a->x * b->y) - (a->y * b->x); } void EnBomChu_UpdateFloorPoly(EnBomChu* this, CollisionPoly* floorPoly, GlobalContext* globalCtx) { Vec3f normal; Vec3f vec; f32 angle; f32 magnitude; f32 normDotUp; MtxF mf; if (CVar_GetS32("gBombchusOOB", 0) && floorPoly == NULL) { EnBomChu_Explode(this, globalCtx); return; } this->actor.floorPoly = floorPoly; normal.x = COLPOLY_GET_NORMAL(floorPoly->normal.x); normal.y = COLPOLY_GET_NORMAL(floorPoly->normal.y); normal.z = COLPOLY_GET_NORMAL(floorPoly->normal.z); normDotUp = DOTXYZ(normal, this->axisUp); if (!(fabsf(normDotUp) >= 1.0f)) { angle = Math_FAcosF(normDotUp); if (!(angle < 0.001f)) { EnBomChu_CrossProduct(&this->axisUp, &normal, &vec); //! @bug this function expects a unit vector but `vec` is not normalized Matrix_RotateAxis(angle, &vec, MTXMODE_NEW); Matrix_MultVec3f(&this->axisLeft, &vec); this->axisLeft = vec; EnBomChu_CrossProduct(&this->axisLeft, &normal, &this->axisForwards); magnitude = Math3D_Vec3fMagnitude(&this->axisForwards); if (magnitude < 0.001f) { EnBomChu_Explode(this, globalCtx); return; } this->axisForwards.x *= 1.0f / magnitude; this->axisForwards.y *= 1.0f / magnitude; this->axisForwards.z *= 1.0f / magnitude; this->axisUp = normal; // mf = (axisLeft | axisUp | axisForwards) mf.xx = this->axisLeft.x; mf.yx = this->axisLeft.y; mf.zx = this->axisLeft.z; mf.xy = normal.x; mf.yy = normal.y; mf.zy = normal.z; mf.xz = this->axisForwards.x; mf.yz = this->axisForwards.y; mf.zz = this->axisForwards.z; Matrix_MtxFToYXZRotS(&mf, &this->actor.world.rot, 0); // A hack for preventing bombchus from sticking to ledges. // The visual rotation reverts the sign inversion (shape.rot.x = -world.rot.x). // The better fix would be making func_8002D908 compute XYZ velocity better, // or not using it and make the bombchu compute its own velocity. this->actor.world.rot.x = -this->actor.world.rot.x; } } } void EnBomChu_WaitForRelease(EnBomChu* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); if (this->timer != 0) { this->timer--; } if (this->timer == 0) { EnBomChu_Explode(this, globalCtx); return; } if (Actor_HasNoParent(&this->actor, globalCtx)) { this->actor.world.pos = player->actor.world.pos; Actor_UpdateBgCheckInfo(globalCtx, &this->actor, 0.0f, 0.0f, 0.0f, 4); this->actor.shape.rot.y = player->actor.shape.rot.y; // rot.y = 0 -> +z (forwards in model space) this->axisForwards.x = Math_SinS(this->actor.shape.rot.y); this->axisForwards.y = 0.0f; this->axisForwards.z = Math_CosS(this->actor.shape.rot.y); // +y (up in model space) this->axisUp.x = 0.0f; this->axisUp.y = 1.0f; this->axisUp.z = 0.0f; // rot.y = 0 -> +x (left in model space) this->axisLeft.x = Math_SinS(this->actor.shape.rot.y + 0x4000); this->axisLeft.y = 0; this->axisLeft.z = Math_CosS(this->actor.shape.rot.y + 0x4000); this->actor.speedXZ = 8.0f; //! @bug there is no NULL check on the floor poly. If the player is out of bounds the floor poly will be NULL //! and will cause a crash inside this function. EnBomChu_UpdateFloorPoly(this, this->actor.floorPoly, globalCtx); this->actor.flags |= ACTOR_FLAG_0; // make chu targetable func_8002F850(globalCtx, &this->actor); this->actionFunc = EnBomChu_Move; } } void EnBomChu_Move(EnBomChu* this, GlobalContext* globalCtx) { CollisionPoly* polySide; CollisionPoly* polyUpDown; s32 bgIdSide; s32 bgIdUpDown; s32 i; f32 lineLength; Vec3f posA; Vec3f posB; Vec3f posSide; Vec3f posUpDown; this->actor.speedXZ = 8.0f; lineLength = this->actor.speedXZ * 2.0f; if (this->timer != 0) { this->timer--; } if ((this->timer == 0) || (this->collider.base.acFlags & AC_HIT) || ((this->collider.base.ocFlags1 & OC1_HIT) && (this->collider.base.oc->category != ACTORCAT_PLAYER))) { EnBomChu_Explode(this, globalCtx); return; } posA.x = this->actor.world.pos.x + (this->axisUp.x * 2.0f); posA.y = this->actor.world.pos.y + (this->axisUp.y * 2.0f); posA.z = this->actor.world.pos.z + (this->axisUp.z * 2.0f); posB.x = this->actor.world.pos.x - (this->axisUp.x * 4.0f); posB.y = this->actor.world.pos.y - (this->axisUp.y * 4.0f); posB.z = this->actor.world.pos.z - (this->axisUp.z * 4.0f); if (BgCheck_EntityLineTest1(&globalCtx->colCtx, &posA, &posB, &posUpDown, &polyUpDown, true, true, true, true, &bgIdUpDown) && !(func_80041DB8(&globalCtx->colCtx, polyUpDown, bgIdUpDown) & 0x30) && // && not crawl space? !SurfaceType_IsIgnoredByProjectiles(&globalCtx->colCtx, polyUpDown, bgIdUpDown)) { // forwards posB.x = (this->axisForwards.x * lineLength) + posA.x; posB.y = (this->axisForwards.y * lineLength) + posA.y; posB.z = (this->axisForwards.z * lineLength) + posA.z; if (BgCheck_EntityLineTest1(&globalCtx->colCtx, &posA, &posB, &posSide, &polySide, true, true, true, true, &bgIdSide) && !(func_80041DB8(&globalCtx->colCtx, polySide, bgIdSide) & 0x30) && !SurfaceType_IsIgnoredByProjectiles(&globalCtx->colCtx, polySide, bgIdSide)) { EnBomChu_UpdateFloorPoly(this, polySide, globalCtx); this->actor.world.pos = posSide; this->actor.floorBgId = bgIdSide; this->actor.speedXZ = 0.0f; } else { if (this->actor.floorPoly != polyUpDown) { EnBomChu_UpdateFloorPoly(this, polyUpDown, globalCtx); } this->actor.world.pos = posUpDown; this->actor.floorBgId = bgIdUpDown; } } else { this->actor.speedXZ = 0.0f; lineLength *= 3.0f; posA = posB; for (i = 0; i < 3; i++) { if (i == 0) { // backwards posB.x = posA.x - (this->axisForwards.x * lineLength); posB.y = posA.y - (this->axisForwards.y * lineLength); posB.z = posA.z - (this->axisForwards.z * lineLength); } else if (i == 1) { // left posB.x = posA.x + (this->axisLeft.x * lineLength); posB.y = posA.y + (this->axisLeft.y * lineLength); posB.z = posA.z + (this->axisLeft.z * lineLength); } else { // right posB.x = posA.x - (this->axisLeft.x * lineLength); posB.y = posA.y - (this->axisLeft.y * lineLength); posB.z = posA.z - (this->axisLeft.z * lineLength); } if (BgCheck_EntityLineTest1(&globalCtx->colCtx, &posA, &posB, &posSide, &polySide, true, true, true, true, &bgIdSide) && !(func_80041DB8(&globalCtx->colCtx, polySide, bgIdSide) & 0x30) && !SurfaceType_IsIgnoredByProjectiles(&globalCtx->colCtx, polySide, bgIdSide)) { EnBomChu_UpdateFloorPoly(this, polySide, globalCtx); this->actor.world.pos = posSide; this->actor.floorBgId = bgIdSide; break; } } if (i == 3) { // no collision nearby EnBomChu_Explode(this, globalCtx); } } Math_ScaledStepToS(&this->actor.shape.rot.x, -this->actor.world.rot.x, 0x800); Math_ScaledStepToS(&this->actor.shape.rot.y, this->actor.world.rot.y, 0x800); Math_ScaledStepToS(&this->actor.shape.rot.z, this->actor.world.rot.z, 0x800); func_8002F8F0(&this->actor, NA_SE_IT_BOMBCHU_MOVE - SFX_FLAG); } void EnBomChu_WaitForKill(EnBomChu* this, GlobalContext* globalCtx) { if (this->timer != 0) { this->timer--; } if (this->timer == 0) { Actor_Kill(&this->actor); } } /** * Transform coordinates from model space to world space, according to current orientation. * `posModel` is expected to already be at world scale (1/100 compared to model scale) */ void EnBomChu_ModelToWorld(EnBomChu* this, Vec3f* posModel, Vec3f* dest) { f32 x = posModel->x + this->visualJitter; dest->x = this->actor.world.pos.x + (this->axisLeft.x * x) + (this->axisUp.x * posModel->y) + (this->axisForwards.x * posModel->z); dest->y = this->actor.world.pos.y + (this->axisLeft.y * x) + (this->axisUp.y * posModel->y) + (this->axisForwards.y * posModel->z); dest->z = this->actor.world.pos.z + (this->axisLeft.z * x) + (this->axisUp.z * posModel->y) + (this->axisForwards.z * posModel->z); } void EnBomChu_SpawnRipples(EnBomChu* this, GlobalContext* globalCtx, f32 y) { Vec3f pos; pos.x = this->actor.world.pos.x; pos.y = y; pos.z = this->actor.world.pos.z; EffectSsGRipple_Spawn(globalCtx, &pos, 70, 500, 0); EffectSsGRipple_Spawn(globalCtx, &pos, 70, 500, 4); EffectSsGRipple_Spawn(globalCtx, &pos, 70, 500, 8); } void EnBomChu_Update(Actor* thisx, GlobalContext* globalCtx2) { static Vec3f blureP1Model = { 0.0f, 7.0f, -6.0f }; static Vec3f blureP2LeftModel = { 12.0f, 0.0f, -5.0f }; static Vec3f blureP2RightModel = { -12.0f, 0.0f, -5.0f }; GlobalContext* globalCtx = globalCtx2; EnBomChu* this = (EnBomChu*)thisx; s16 yaw; f32 sin; f32 cos; f32 tempX; Vec3f blureP1; Vec3f blureP2; WaterBox* waterBox; f32 waterY; if (this->actor.floorBgId != BGCHECK_SCENE) { yaw = this->actor.shape.rot.y; func_800433A4(&globalCtx->colCtx, this->actor.floorBgId, &this->actor); if (yaw != this->actor.shape.rot.y) { yaw = this->actor.shape.rot.y - yaw; sin = Math_SinS(yaw); cos = Math_CosS(yaw); tempX = this->axisForwards.x; this->axisForwards.x = (this->axisForwards.z * sin) + (cos * tempX); this->axisForwards.z = (this->axisForwards.z * cos) - (sin * tempX); tempX = this->axisUp.x; this->axisUp.x = (this->axisUp.z * sin) + (cos * tempX); this->axisUp.z = (this->axisUp.z * cos) - (sin * tempX); tempX = this->axisLeft.x; this->axisLeft.x = (this->axisLeft.z * sin) + (cos * tempX); this->axisLeft.z = (this->axisLeft.z * cos) - (sin * tempX); } } this->actionFunc(this, globalCtx); func_8002D97C(&this->actor); this->collider.elements[0].dim.worldSphere.center.x = this->actor.world.pos.x; this->collider.elements[0].dim.worldSphere.center.y = this->actor.world.pos.y; this->collider.elements[0].dim.worldSphere.center.z = this->actor.world.pos.z; CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->collider.base); if (this->actionFunc != EnBomChu_WaitForRelease) { CollisionCheck_SetOC(globalCtx, &globalCtx->colChkCtx, &this->collider.base); } Actor_SetFocus(&this->actor, 0.0f); if (this->actionFunc == EnBomChu_Move) { this->visualJitter = (5.0f + (Rand_ZeroOne() * 3.0f)) * Math_SinS(((Rand_ZeroOne() * (f32)0x200) + (f32)0x3000) * this->timer); EnBomChu_ModelToWorld(this, &blureP1Model, &blureP1); EnBomChu_ModelToWorld(this, &blureP2LeftModel, &blureP2); EffectBlure_AddVertex(Effect_GetByIndex(this->blure1Index), &blureP1, &blureP2); EnBomChu_ModelToWorld(this, &blureP2RightModel, &blureP2); EffectBlure_AddVertex(Effect_GetByIndex(this->blure2Index), &blureP1, &blureP2); waterY = this->actor.world.pos.y; if (WaterBox_GetSurface1(globalCtx, &globalCtx->colCtx, this->actor.world.pos.x, this->actor.world.pos.z, &waterY, &waterBox)) { this->actor.yDistToWater = waterY - this->actor.world.pos.y; if (this->actor.yDistToWater < 0.0f) { if (this->actor.bgCheckFlags & 0x20) { EnBomChu_SpawnRipples(this, globalCtx, waterY); } this->actor.bgCheckFlags &= ~0x20; } else { if (!(this->actor.bgCheckFlags & 0x20) && (this->timer != 120)) { EnBomChu_SpawnRipples(this, globalCtx, waterY); } else { EffectSsBubble_Spawn(globalCtx, &this->actor.world.pos, 0.0f, 3.0f, 15.0f, 0.25f); } this->actor.bgCheckFlags |= 0x20; } } else { this->actor.bgCheckFlags &= ~0x20; this->actor.yDistToWater = BGCHECK_Y_MIN; } } } void EnBomChu_Draw(Actor* thisx, GlobalContext* globalCtx) { s32 pad; EnBomChu* this = (EnBomChu*)thisx; f32 colorIntensity; s32 blinkHalfPeriod; s32 blinkTime; OPEN_DISPS(globalCtx->state.gfxCtx); func_80093D18(globalCtx->state.gfxCtx); func_8002EBCC(&this->actor, globalCtx, 0); if (this->timer >= 40) { blinkTime = this->timer % 20; blinkHalfPeriod = 10; } else if (this->timer >= 10) { blinkTime = this->timer % 10; blinkHalfPeriod = 5; } else { blinkTime = this->timer & 1; blinkHalfPeriod = 1; } if (blinkTime > blinkHalfPeriod) { blinkTime = 2 * blinkHalfPeriod - blinkTime; } colorIntensity = blinkTime / (f32)blinkHalfPeriod; gDPSetEnvColor(POLY_OPA_DISP++, 9.0f + (colorIntensity * 209.0f), 9.0f + (colorIntensity * 34.0f), 35.0f + (colorIntensity * -35.0f), 255); Matrix_Translate(this->visualJitter * (1.0f / BOMBCHU_SCALE), 0.0f, 0.0f, MTXMODE_APPLY); gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_OPA_DISP++, gBombchuDL); CLOSE_DISPS(globalCtx->state.gfxCtx); }