diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index e913bf8ef3c..4287aa33d30 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -539,6 +539,21 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Disables bombs always rotating to face the camera. To be used in conjunction with mods that want to " "replace bombs with 3D objects.")); + AddWidget(path, "Disable Fairy Billboarding", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableFairyBillboarding")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip( + "Disables fairies always rotating to face the camera. Use when replacing fairies with 3D models.")); + AddWidget(path, "Hide Fairy Glow Plane", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableFairyGlow")) + .RaceDisable(false) + .PreFunc([](WidgetInfo& info) { + const bool billboardOff = CVarGetInteger(CVAR_ENHANCEMENT("DisableFairyBillboarding"), 0); + info.options->disabled = !billboardOff; + info.options->disabledTooltip = "Enable \"Disable Fairy Billboarding\" to toggle this."; + }) + .Options(CheckboxOptions().Tooltip("Hides the billboarded glow plane on fairies.\n" + "Requires a scene reload after changing.")); AddWidget(path, "Disable Grotto Fixed Rotation", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("DisableGrottoRotation")) .RaceDisable(false) diff --git a/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c b/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c index 96a9297b969..9978928c2a2 100644 --- a/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c +++ b/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c @@ -327,9 +327,16 @@ void EnElf_Init(Actor* thisx, PlayState* play) { ActorShape_Init(&thisx->shape, 0.0f, NULL, 15.0f); thisx->shape.shadowAlpha = 0xFF; + bool disableGlowPlane = CVarGetInteger(CVAR_ENHANCEMENT("DisableFairyBillboarding"), 0) && + CVarGetInteger(CVAR_ENHANCEMENT("DisableFairyGlow"), 0); + Lights_PointGlowSetInfo(&this->lightInfoGlow, thisx->world.pos.x, thisx->world.pos.y, thisx->world.pos.z, 255, 255, 255, 0); - this->lightNodeGlow = LightContext_InsertLight(play, &play->lightCtx, &this->lightInfoGlow); + if (!disableGlowPlane) { + this->lightNodeGlow = LightContext_InsertLight(play, &play->lightCtx, &this->lightInfoGlow); + } else { + this->lightNodeGlow = NULL; + } Lights_PointNoGlowSetInfo(&this->lightInfoNoGlow, thisx->world.pos.x, thisx->world.pos.y, thisx->world.pos.z, 255, 255, 255, 0); @@ -440,7 +447,9 @@ void EnElf_Destroy(Actor* thisx, PlayState* play) { s32 pad; EnElf* this = (EnElf*)thisx; - LightContext_RemoveLight(play, &play->lightCtx, this->lightNodeGlow); + if (this->lightNodeGlow != NULL) { + LightContext_RemoveLight(play, &play->lightCtx, this->lightNodeGlow); + } LightContext_RemoveLight(play, &play->lightCtx, this->lightNodeNoGlow); ResourceMgr_UnregisterSkeleton(&this->skelAnime); @@ -1455,6 +1464,8 @@ void func_80A053F0(Actor* thisx, PlayState* play) { void EnElf_Update(Actor* thisx, PlayState* play) { s32 pad; EnElf* this = (EnElf*)thisx; + bool disableGlowPlane = CVarGetInteger(CVAR_ENHANCEMENT("DisableFairyBillboarding"), 0) && + CVarGetInteger(CVAR_ENHANCEMENT("DisableFairyGlow"), 0); this->actionFunc(this, play); this->actor.shape.rot.y = this->unk_2BC; @@ -1463,6 +1474,16 @@ void EnElf_Update(Actor* thisx, PlayState* play) { if (this->fairyFlags & FAIRY_FLAG_BIG) { func_80A04D90(this, play); } + + // Keep glow in sync with toggle: remove when disabled, re-add when enabled. + if (disableGlowPlane) { + if (this->lightNodeGlow != NULL) { + LightContext_RemoveLight(play, &play->lightCtx, this->lightNodeGlow); + this->lightNodeGlow = NULL; + } + } else if (this->lightNodeGlow == NULL) { + this->lightNodeGlow = LightContext_InsertLight(play, &play->lightCtx, &this->lightInfoGlow); + } } s32 EnElf_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx, @@ -1472,6 +1493,7 @@ s32 EnElf_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p f32 scale; Vec3f mtxMult; EnElf* this = (EnElf*)thisx; + bool disableBillboarding = CVarGetInteger(CVAR_ENHANCEMENT("DisableFairyBillboarding"), 0); if (limbIndex == 8) { scale = ((Math_SinS(this->timer * 4096) * 0.1f) + 1.0f) * 0.012f; @@ -1494,6 +1516,12 @@ s32 EnElf_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p } } + // If billboarding is disabled, explicitly rotate the body limb (8) using the actor's yaw so 3D models + // that don't rely on the billboard segment still face the intended direction. + if (limbIndex == 8 && disableBillboarding) { + rot->y = rot->y + this->actor.shape.rot.y; + } + return false; } @@ -1505,6 +1533,10 @@ void EnElf_Draw(Actor* thisx, PlayState* play) { s32 pad1; Gfx* dListHead; Player* player = GET_PLAYER(play); + bool disableBillboarding = CVarGetInteger(CVAR_ENHANCEMENT("DisableFairyBillboarding"), 0); + Mtx* customBillboardMtx = NULL; + Mtx* prevBillboardSegment = play->billboardMtx; + MtxF customRotMtxF; if ((this->unk_2A8 != 8) && !(this->fairyFlags & 8)) { if (!(player->stateFlags1 & PLAYER_STATE1_FIRST_PERSON) || (kREG(90) < this->actor.projectedPos.z)) { @@ -1514,6 +1546,28 @@ void EnElf_Draw(Actor* thisx, PlayState* play) { Gfx_SetupDL_27Xlu(play->state.gfxCtx); + if (!disableBillboarding) { + // Keep fairies facing the camera unless explicitly disabled + Matrix_ReplaceRotation(&play->billboardMtxF); + } else { + // Provide a billboard segment aligned to the actor/root rotation so the body can face its heading + customBillboardMtx = Graph_Alloc(play->state.gfxCtx, sizeof(Mtx)); + if (customBillboardMtx != NULL) { + Matrix_Push(); + Matrix_Put(&gMtxFClear); + Matrix_RotateZYX(BINANG_TO_RAD(this->actor.shape.rot.x), BINANG_TO_RAD(this->actor.shape.rot.y), + BINANG_TO_RAD(this->actor.shape.rot.z), MTXMODE_APPLY); + Matrix_RotateZYX(BINANG_TO_RAD(this->skelAnime.jointTable[0].x), + BINANG_TO_RAD(this->skelAnime.jointTable[0].y), + BINANG_TO_RAD(this->skelAnime.jointTable[0].z), MTXMODE_APPLY); + Matrix_Get(&customRotMtxF); + Matrix_Pop(); + Matrix_MtxFToMtx(MATRIX_CHECKFLOATS(&customRotMtxF), customBillboardMtx); + gSPSegment(POLY_XLU_DISP++, 0x01, customBillboardMtx); + } + } + func_8002EBCC(thisx, play, 0); + envAlpha = (this->timer * 50) & 0x1FF; envAlpha = (envAlpha > 255) ? 511 - envAlpha : envAlpha; @@ -1536,6 +1590,10 @@ void EnElf_Draw(Actor* thisx, PlayState* play) { POLY_XLU_DISP = SkelAnime_DrawSkeleton2(play, &this->skelAnime, EnElf_OverrideLimbDraw, NULL, this, POLY_XLU_DISP); + if (disableBillboarding && customBillboardMtx != NULL) { + gSPSegment(POLY_XLU_DISP++, 0x01, prevBillboardSegment); + } + CLOSE_DISPS(play->state.gfxCtx); } }