// clang-format off // vtable order #include "JSystem/JParticle/JPAEmitter.h" #include "JSystem/JParticle/JPAParticle.h" #include "d/d_gfx.h" #include "m/m_heap.h" #include "rvl/GX/GXTypes.h" #include "toBeSorted/d_d3d.h" // clang-format on #include "toBeSorted/d_emitter.h" #include "JSystem/JParticle/JPADrawInfo.h" #include "JSystem/JParticle/JPAEmitter.h" #include "c/c_math.h" #include "common.h" #include "d/a/d_a_base.h" #include "d/a/obj/d_a_obj_base.h" #include "d/d_base.h" #include "d/d_heap.h" #include "d/d_light_env.h" #include "d/d_stage.h" #include "d/d_stage_mgr.h" #include "egg/core/eggHeap.h" #include "f/f_base.h" #include "f/f_profile_name.h" #include "m/m3d/m3d.h" #include "m/m_allocator.h" #include "m/m_angle.h" #include "m/m_color.h" #include "m/m_mtx.h" #include "m/m_vec.h" #include "nw4r/g3d/g3d_camera.h" #include "nw4r/g3d/g3d_state.h" #include "nw4r/g3d/res/g3d_resfile.h" #include "nw4r/g3d/res/g3d_resmat.h" #include "nw4r/g3d/res/g3d_resmdl.h" #include "nw4r/g3d/res/g3d_resshp.h" #include "nw4r/math/math_arithmetic.h" #include "sized_string.h" #include "toBeSorted/arc_managers/oarc_manager.h" #include "toBeSorted/d_d3d.h" #include "toBeSorted/d_particle.h" #include "toBeSorted/event_manager.h" #include "rvl/GX.h" #include "rvl/MTX.h" void float_ordering_1(s32 a) { (f32) a; } void float_ordering_2() { 255.0f; } void float_ordering_3(u32 a) { (f32) a; } void float_ordering_4() { 0.0f; } dJEffManager_c::EffectsList dJEffManager_c::sPlayingEffectsList; CommonEmitterCallback dJEffManager_c::sCommonEmitterCallbacks[2]; dShpEmitter_c dJEffManager_c::sShpEmitters[47]; dEmitterBase_c dJEffManager_c::sEmitter; dParticleFogProc_c dJEffManager_c::sFogProcs[12]; dEffect2D_c dJEffManager_c::s2DEffects[3]; // broken by -ipa file - probably TList nonsense? dEmitterCallback_c::~dEmitterCallback_c() { for (EmitterCallbackList::Iterator it = mEmitterList.GetBeginIter(); it != mEmitterList.GetEndIter();) { EmitterCallbackList::Iterator itCopy = it; ++itCopy; it->setEmitterCallback(nullptr); it = itCopy; } } void dEmitterCallback_c::remove(dEmitterBase_c *emitter) { if (mEmitterList.GetPosition(emitter) == mEmitterList.GetEndIter()) { return; } mEmitterList.remove(emitter); } // broken by -ipa file - probably TList nonsense? dParticleCallback_c::~dParticleCallback_c() { for (ParticleCallbackList::Iterator it = mEmitterList.GetBeginIter(); it != mEmitterList.GetEndIter();) { ParticleCallbackList::Iterator itCopy = it; ++itCopy; it->setParticleCallback(nullptr); it = itCopy; } } void dParticleCallback_c::remove(dEmitterBase_c *emitter) { if (mEmitterList.GetPosition(emitter) == mEmitterList.GetEndIter()) { return; } mEmitterList.remove(emitter); } dEmitterBase_c::~dEmitterBase_c() { deactivateEmitters(); if (mpEmitterCallback != nullptr) { mpEmitterCallback->remove(this); mpEmitterCallback = nullptr; } if (mpParticleCallback != nullptr) { mpParticleCallback->remove(this); mpParticleCallback = nullptr; } } JPABaseEmitter *dEmitterBase_c::GetNextEmitter(JPABaseEmitter *head) { // Portability hazard: u32->pointer cast return reinterpret_cast(head->getUserWork()); } bool dEmitterBase_c::createEmitters( u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2 ) { u16 iter = effectResourceId; JPABaseEmitter *head = nullptr; JPABaseEmitter *last; for (; iter != 0xFFFF; iter = dParticle::mgr_c::GetInstance()->getJpnData(iter)) { last = dParticle::mgr_c::GetInstance()->createEmitter( iter, dJEffManager_c::getGroupId(iter), position, rot, scale ); if (last != nullptr) { if (head != nullptr) { // Portability hazard: pointer->u32 cast head->setUserWork(reinterpret_cast(last)); } else { mpEmitterHead = last; } head = last; last->setEmitterCallBackPtr(mpEmitterCallback); last->setParticleCallBackPtr(mpParticleCallback); loadColors(last, c1, c2, idx1, idx2); } } if (mpEmitterHead != nullptr) { if (mpEmitterCallback != nullptr) { // why last and not head? mpEmitterCallback->create(last); } if (mpParticleCallback != nullptr) { // why last and not head? mpParticleCallback->create(last); } } return mpEmitterHead != nullptr; } void dEmitterBase_c::setEmitterCallback(dEmitterCallback_c *cb) { if (mpEmitterCallback != cb) { if (mpEmitterCallback != nullptr) { mpEmitterCallback->remove(this); for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setEmitterCallBackPtr(nullptr); } } mpEmitterCallback = cb; if (cb != nullptr) { cb->mEmitterList.append(this); } } } void dEmitterBase_c::setParticleCallback(dParticleCallback_c *cb) { if (mpParticleCallback != cb) { if (mpParticleCallback != nullptr) { mpParticleCallback->remove(this); for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setParticleCallBackPtr(nullptr); } } mpParticleCallback = cb; if (cb != nullptr) { cb->mEmitterList.append(this); } } } void dEmitterBase_c::setImmortal() { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->becomeImmortalEmitter(); } } void dEmitterBase_c::deactivateEmitters() { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setEmitterCallBackPtr(nullptr); emitter->setParticleCallBackPtr(nullptr); emitter->quitImmortalEmitter(); if (emitter->mMaxFrame == 0) { emitter->becomeInvalidEmitter(); } emitter->playCalcEmitter(); } mpEmitterHead = nullptr; } void dEmitterBase_c::stopCalcEmitters() { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->stopCalcEmitter(); } } void dEmitterBase_c::playCalcEmitters() { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->playCalcEmitter(); } } void dEmitterBase_c::stopDrawParticles() { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->stopDrawParticle(); } } void dEmitterBase_c::playDrawParticles() { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->playDrawParticle(); } } void dEmitterBase_c::setPosRotScale(const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setGlobalTranslation(position); if (rot != nullptr) { emitter->setGlobalRotation(*rot); } if (scale != nullptr) { emitter->setGlobalScale(*scale); } } } void dEmitterBase_c::setPosRotScaleWithScreenScale(const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale) { mVec3_c adjustedPosition(position.x * dGfx_c::getCurrentScreenTo4x3WidthScaleF(), position.y, position.z); setPosRotScale(adjustedPosition, rot, scale); } void dEmitterBase_c::setTransform(const mMtx_c &mtx) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setGlobalSRTMatrix(mtx); } } void dEmitterBase_c::loadColors(const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { loadColors(emitter, c1, c2, idx1, idx2); } } void dEmitterBase_c::setGlobalAlpha(u8 alpha) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setGlobalAlpha(alpha); } } void dEmitterBase_c::setRate(f32 rate) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setRate(rate); } } void dEmitterBase_c::setDynamicsScale(const mVec3_c &scale) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setGlobalDynamicsScale(scale); } } void dEmitterBase_c::setParticleScale(const mVec3_c &scale) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setGlobalParticleScale(scale); } } void dEmitterBase_c::setAwayFromCenterSpeed(f32 speed) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setAwayFromCenterSpeed(speed); } } void dEmitterBase_c::setVolumeSize(u16 size) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setVolumeSize(size); } } void dEmitterBase_c::setLifeTime(s16 lifetime) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setLifeTime(lifetime); } } void dEmitterBase_c::attachEmitterCallbackId(s32 id) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setEmitterCallBackPtr(&dJEffManager_c::sCommonEmitterCallbacks[id]); } } void dEmitterBase_c::bindShpEmitter(s32 id, bool unused) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setParticleCallBackPtr(&dJEffManager_c::sShpEmitters[id]); emitter->setGlobalAlpha(0); } } EffectsStruct::EffectsStruct() : mpOwner(nullptr), mFlags(0), mEffect(0) {} EffectsStruct::EffectsStruct(dBase_c *base) : mpOwner(base), mFlags(0), mEffect(0) {} EffectsStruct::~EffectsStruct() { mpOwner = nullptr; remove(false); } void EffectsStruct::remove(bool bForceDeleteEmitters) { offFlag(EMITTER_0x1); JPABaseEmitter *emitter = bForceDeleteEmitters ? mpEmitterHead : nullptr; deactivateEmitters(); for (; emitter != nullptr; emitter = GetNextEmitter(emitter)) { dParticle::mgr_c::GetInstance()->forceDeleteEmitter(emitter); } removeFromActiveEmittersList(); } void EffectsStruct::addToActiveEmittersList(u16 resourceId, bool bFlags) { mEffect = resourceId; mFlags = 0; setImmortal(); dJEffManager_c::sPlayingEffectsList.append(this); onFlag(EMITTER_0x1); if (bFlags) { onFlag(EMITTER_0x4); } if (dParticle::mgr_c::GetInstance()->getResUserWork(resourceId) & 0x4000) { onFlag(EMITTER_0x10); } } bool EffectsStruct::areAllEmittersDone() { bool allDone = true; if (mpEmitterHead != nullptr) { if (checkFlag(EMITTER_Fading) && mFadeTimer != 0) { return false; } for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { bool canDelete = emitter->isEnableDeleteEmitter(); if (!canDelete) { if (emitter->mMaxFrame == 0) { emitter->becomeInvalidEmitter(); } allDone = false; } } } return allDone; } void EffectsStruct::execute() { if (mpOwner != nullptr && (mpOwner->delete_request || mpOwner->lifecycle_state == fBase_c::TO_BE_DELETED)) { mpOwner = nullptr; } if (dJEffManager_c::shouldBePaused(mpOwner)) { if (!checkFlag(EMITTER_0x10)) { stopCalcEmitters(); if (mpOwner != nullptr && !mpOwner->isBasePropertyFlag(0x100)) { if (!mpEmitterHead->checkStatus(JPAEmtrStts_StopDraw)) { onFlag(EMITTER_0x20); stopDrawParticles(); } } else { if (checkFlag(EMITTER_0x20)) { offFlag(EMITTER_0x20); playDrawParticles(); } } } onFlag(EMITTER_0x1); } else { playCalcEmitters(); if (checkFlag(EMITTER_0x20)) { offFlag(EMITTER_0x20); playDrawParticles(); } if (checkFlag(EMITTER_Fading) && mFadeTimer != 0) { mFadeTimer--; } } } bool EffectsStruct::getOwnerPolyAttrs(s32 *pOut1, s32 *pOut2) { if (mpOwner != nullptr && mpOwner->group_type == fBase_c::ACTOR) { dAcBase_c *actor = static_cast(mpOwner); *pOut1 = actor->polyAttr0; *pOut2 = actor->polyAttr1; return true; } else { *pOut1 = 0; *pOut2 = 0; return false; } } void EffectsStruct::realizeAlpha() { setGlobalAlpha(mFadeTimer * (255.0f / mFadeDuration)); } void EffectsStruct::setFading(u8 lifetime) { if (!checkFlag(EMITTER_Fading)) { mFadeTimer = lifetime; onFlag(EMITTER_Fading); mFadeDuration = lifetime; } } bool dParticleFogProc_c::create(u32 idx, s32 prioOpa, s32 prioXlu, mHeapAllocator_c *alloc) { if (!d3d::UnkProc::create(prioOpa, prioXlu, alloc)) { return false; } mIdx = idx; field_0x1C = idx == 0 || idx == 2 || idx == 4 || idx == 6 || idx == 10; return true; } void dParticleFogProc_c::doDraw() { nw4r::g3d::Camera c = m3d::getCurrentCamera(); mMtx_c camMtx, texProjMtx; c.GetCameraMtx(camMtx); c.GetProjectionTexMtx(texProjMtx); JPADrawInfo info; MTXCopy(camMtx, info.mCamMtx); MTXCopy(texProjMtx, info.mPrjMtx); if (field_0x1C) { m3d::getFogMgr(0); // ignored, maybe an inline on FogMgr? nw4r::g3d::G3DState::LoadFog(0); } else { mColor c(0); GXSetFog(GX_FOG_NONE, c, 0.0f, 0.0f, 0.0f, 0.0f); GXSetFogRangeAdj(GX_FALSE, 0, nullptr); } GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA); dJEffManager_c::draw(&info, mIdx); } extern "C" bool fn_80054AD0(); void dEffect2D_c::draw() { f32 proj[GX_PROJECTION_SZ]; GXGetProjectionv(proj); f32 base = proj[1]; proj[1] = base / dGfx_c::get16x9to4x3WidthScaleF(); GXSetProjectionv(proj); f32 f = dGfx_c::getWidth16x9() / 2; f32 h = dGfx_c::getCurrentScreenHeightLimitF(); mMtx_c mtx1, mtx2; JPADrawInfo info; C_MTXLightOrtho(mtx1, -h, h, -f, f, 0.5f, 0.5f, 0.5f, 0.5f); MTXScale(mtx2, fn_80054AD0() ? dGfx_c::get16x9to4x3WidthScaleF() : 1.0f, 1.0f, 1.0f); MTXCopy(mtx2, info.mCamMtx); MTXCopy(mtx1, info.mPrjMtx); dJEffManager_c::draw(&info, mGroupId); proj[1] = base; GXSetProjectionv(proj); } void dEffect2D_c::create(u32 groupId, u8 prio) { mGroupId = groupId; setPriority(prio); } bool dMassObjEmitterCallback_c::start(const mVec3_c &v1, dAcObjBase_c *owner) { if (field_0x658 >= 50) { return false; } field_0x010[field_0x658] = v1; field_0x4C0[field_0x658] = owner; field_0x658++; return true; } void dMassObjEmitterCallback_c::executeAfter(JPABaseEmitter *emitter) { s32 createNumber = emitter->getCurrentCreateNumber(); if (createNumber > 0) { s32 i = 0; mVec3_c translate; emitter->getLocalTranslation(translate); if (field_0x654 != 0) { for (; i < field_0x654; i++) { mVec3_c newTranslate = mVec3_c(field_0x268[i] + translate); emitter->setGlobalTranslation(newTranslate); for (s32 j = 0; j < createNumber; j++) { JPABaseParticle *p = emitter->createParticle(); if (p != nullptr) { p->setOffsetPosition(newTranslate); } } } } } } void dMassObjEmitterCallback_c::execute() { for (s32 i = field_0x654 - 1; i >= 0; i--) { if (field_0x588[i] != nullptr) { if (!dJEffManager_c::shouldBePaused(field_0x588[i]) || field_0x588[i]->delete_request || field_0x588[i]->lifecycle_state == fBase_c::TO_BE_DELETED) { if (field_0x654 != 0 && i != field_0x654 - 1) { field_0x588[i] = field_0x588[field_0x654 - 1]; field_0x268[i] = field_0x268[field_0x654 - 1]; } else { field_0x588[i] = nullptr; } field_0x654--; } } else { field_0x654--; } } if (field_0x658 + field_0x654 > 50) { field_0x658 = 50 - field_0x654; } for (u32 i = 0; i < field_0x658; i++) { field_0x588[field_0x654] = field_0x4C0[i]; field_0x268[field_0x654] = field_0x010[i]; field_0x654++; } field_0x658 = 0; } #pragma opt_unroll_loops off void dMassObjEmitterCallback_c::clear() { // TODO: this unrolls the entire loops, // but the original code uses an unroll // factor of 10 (5 iterations). 2D arrays // work here but are probably fake. field_0x654 = 0; for (u32 i = 0; i < 50; i++) { field_0x588[i] = nullptr; } field_0x658 = 0; for (u32 i = 0; i < 50; i++) { field_0x4C0[i] = nullptr; } } #pragma opt_unroll_loops reset void dMassObjEmitter_c::create(u16 resourceId) { setEmitterCallback(&mCallback); if (createEmitters(resourceId, mVec3_c::Zero, nullptr, nullptr, nullptr, nullptr, 0, 0)) { for (JPABaseEmitter *emitter = mpEmitterHead; emitter != nullptr; emitter = GetNextEmitter(emitter)) { emitter->setStatus(JPAEmtrStts_Immortal); emitter->setStatus(JPAEmtrStts_StopEmit); } mCallback.clear(); } } void dShpEmitterProc::doDraw() { mMtx_c viewMtx; getViewMtx(viewMtx); s32 ctrl = 0; mLightSetId = mAlpha[0]; mMtx_c *pTransforms = mpTransforms; bool *pFlags = mpFlags; for (s32 idx = 0; idx < mCount;) { if (*pFlags == false) { mMtx_c mtx; mtx.copyFrom(*pTransforms); MTXConcat(viewMtx, mtx, mtx); mMtx_c mtx2; mtx2.copyFrom(mtx); mtx2.fn_802F1C40(0, 1); nw4r::math::MTX33 mtx3; for (int a = 0; a < 3; a++) { for (int b = 0; b < 3; b++) { mtx3.m[a][b] = mtx2.m[a][b]; } } if (mLightSetId != mAlpha[idx]) { mLightSetId = mAlpha[idx]; setupLight(); } mVec3_c v; mVec3_c zero(0.0f, 0.0f, 0.0f); MTXMultVec(*pTransforms, zero, v); dShpProcBase_c::draw(&v); nw4r::g3d::G3DState::SetViewPosNrmMtxArray(mtx, &mtx3, nullptr); nw4r::g3d::Draw1Mat1ShpDirectly(nw4r::g3d::ResMat(nullptr), mShp, mtx, mtx2, ctrl, nullptr, nullptr); if (ctrl == 0) { ctrl = 2; } } idx++; pTransforms++; pFlags++; } mpOwner->clear(); } bool dShpEmitter_c::create(mHeapAllocator_c *alloc) { if (!mProc.create(nw4r::g3d::ResMat(nullptr), nw4r::g3d::ResShp(nullptr), 100, alloc, false, nullptr)) { return false; } mProc.setOwner(this); return true; } void dShpEmitter_c::init(const char *mdlName, const char *arcName, bool priority) { clear(); void *ptr = OarcManager::GetInstance()->getSubEntryData(arcName, "g3d/model.brres"); if (ptr == nullptr) { mProc.setResMat(nw4r::g3d::ResMat(nullptr)); mProc.setResShp(nw4r::g3d::ResShp(nullptr)); return; } nw4r::g3d::ResFile f(ptr); if (strequals(arcName, "F300BrokenRockWall")) { dStage_c::bindStageResToFile(&f); } nw4r::g3d::ResMdl mdl = f.GetResMdl(mdlName); nw4r::g3d::ResMat mat = mdl.GetResMat(0); mProc.setResMat(mat); mProc.setResShp(mdl.GetResShp(0)); if (priority) { mProc.setPriorityDraw(0x1C, 4); } } void dShpEmitter_c::clear() { field_0xA0 = 0; mProc.setAllFlags(); } void dShpEmitter_c::draw(JPABaseEmitter *emitter, JPABaseParticle *particle) { // TODO for (s32 i = 0; i < 100; i++) { if (mProc.isDrawDisabled(i)) { mProc.setDrawDisabled(i, false); mVec3_c pos; mVec3_c velocity; particle->getGlobalPosition(pos); particle->getVelVec(velocity); mVec3_c v; if (cM::isZero(velocity.squareMagXZ())) { v = mVec3_c::Ex; } else { v = mVec3_c::Ey.cross(velocity); v.normalize(); } mMtx_c m; m.setAxisRotation(v, mAng((u16)particle->mRotateAngle).radian2()); m.setBase(3, pos); mMtx_c mtx2; mVec3_c scale( particle->getParticleScaleX() * emitter->getGlobalParticleScaleX(), particle->getParticleScaleY() * emitter->getGlobalParticleScaleY(), particle->getParticleScaleX() * emitter->getGlobalParticleScaleX() ); MTXScale(mtx2, scale.x, scale.y, scale.z); MTXConcat(m, mtx2, m); mProc.setTransform(i, m); mProc.setAlpha(i, emitter->getGlobalAlpha()); field_0xA0++; break; } } // Hide the particle - we're doing all the drawing ourselves particle->setInvisibleParticleFlag(); } void CommonEmitterCallback::draw(JPABaseEmitter *emitter) { emitter->loadTexture(field_0x04, GX_TEXMAP0); } void dMassObjEmitter_c::remove() { setEmitterCallback(nullptr); mpEmitterHead = nullptr; } extern "C" u32 sNumMassObjEmitters; // should be const? extern "C" dMassObjEmitter_c *CURRENT_EFFECT_MANAGER_INIT; const u32 dJEffManager_c::moreInts[] = {3, 5, 7, 8, 12, 13, 14, 1, 9, 11}; s32 dJEffManager_c::getGroupId(u16 resourceId) { u32 mask = dParticle::mgr_c::GetInstance()->getResUserWork(resourceId); s32 bit = 1; for (int i = 0; i < 10; i++) { if ((mask & (1 << i)) != 0) { bit = i; break; } } int i = moreInts[bit]; // TODO explain this if ((mask & 0x8000) != 0) { if (i == 3) { i = 2; } else if (i == 5) { i = 4; } else if (i == 7) { i = 6; } else if (i == 1) { i = 0; } else if (i == 11) { i = 10; } } return i; } // various grasses being cut extern const u16 sEffectResourceIds[]; void dJEffManager_c::setupEffects() { for (s32 idx = 0; idx < sNumMassObjEmitters; idx++) { sMassObjEmitters[idx].create(sEffectResourceIds[idx]); } sShpEmitters[TsuboA].init("FX_TsuboA", "Tubo", false); sShpEmitters[TsuboB].init("FX_TsuboB", "Tubo", false); sShpEmitters[BRockA].init("FX_BRockA", "FXBRockA", true); sShpEmitters[FruitA].init("FX_FruitA", "FruitA", false); sShpEmitters[GrassCoil].init("FX_GrassCoil", "GrassCoilPiece", false); sShpEmitters[BRockB].init("FX_BRockB", "RockFace", true); sShpEmitters[Beehive].init("FX_Beehive", "Bee", false); sShpEmitters[VeraRock].init("FX_VeraRock", "Bvd", false); sShpEmitters[Pumpkin].init("FX_Pumpkin", "Pumpkin", false); sShpEmitters[BRockC].init("FX_BRockC", "FX_BRockC", true); sShpEmitters[F300BrokenRockWall_00].init("FX_F300BrokenRockWall_00", "F300BrokenRockWall", true); sShpEmitters[F300BrokenRockWall_01].init("FX_F300BrokenRockWall_01", "F300BrokenRockWall", true); sShpEmitters[F300BrokenRockWall_01T].init("FX_F300BrokenRockWall_01T", "F300BrokenRockWall", true); sShpEmitters[LotusSeed].init("FX_LotusSeed", "LotusSeed", false); sShpEmitters[Beamoss].init("FX_Beamoss", "Beamos", false); sShpEmitters[Cakram].init("FX_Cakram", "Asura", false); sShpEmitters[Dodai].init("FX_Dodai", "Asura", false); sShpEmitters[LegParts].init("FX_LegParts", "Asura", false); sShpEmitters[AsuraFloorBrk].init("FX_AsuraFloorBrk", "Asura", false); sShpEmitters[BreakPillar].init("FX_BreakPillar", "BreakPillar", false); sShpEmitters[Barrel].init("FX_Barrel", "Barrel", false); sShpEmitters[BarrelBomb].init("FX_BarrelBomb", "BarrelBomb", false); sShpEmitters[RockMiddle].init("FX_RockMiddle", "RockCarryMiddle", false); sShpEmitters[BocoBone].init("FX_BocoBone", "Skull", false); sShpEmitters[BRockStopA].init("FX_BRockStopA", "BRockStopA", true); sShpEmitters[BWallD201].init("FX_BWallD201", "BWallD201", true); sShpEmitters[Gunho].init("FX_Gunho", "Gunho", false); sShpEmitters[TuboBig].init("FX_TuboBig", "TuboBig", false); sShpEmitters[Amos].init("FX_Amos", "Amos", false); sShpEmitters[HidokariBoneA].init("FX_HidokariBoneA", "HidokariS", false); sShpEmitters[HidokariBoneB].init("FX_HidokariBoneB", "HidokariS", false); sShpEmitters[Hydra].init("FX_Hydra", "Hydra", false); sShpEmitters[Captain].init("FX_Captain", "Captain", false); sShpEmitters[KibakoHang].init("FX_KibakoHang", "KibakoHang", true); sShpEmitters[IslTreIRock].init("FX_IslTreIRock", "IslTreIRock", false); sShpEmitters[BirdraceTarget].init("FX_BirdraceTarget", "BirdraceTarget", false); sShpEmitters[FlowerA00F].init("FX_FlowerA00F", "FlowerA00", false); sShpEmitters[FlowerA00L].init("FX_FlowerA00L", "FlowerA00", false); sShpEmitters[FlowerB00].init("FX_FlowerB00", "FlowerB00", false); sShpEmitters[FlowerB01].init("FX_FlowerB01", "FlowerB01", false); sShpEmitters[GirahimClaymore].init("FX_GirahimClaymore", "GirahimBt03", false); sShpEmitters[BirdKobunAEgg].init("FX_BirdKobunAEgg", "BirdEgg", false); sShpEmitters[BirdKobunBEgg].init("FX_BirdKobunBEgg", "BirdEgg", false); sShpEmitters[BirdRivalEgg].init("FX_BirdRivalEgg", "BirdEgg", false); sShpEmitters[BWallAF200].init("FX_BWallAF200", "BWallAF200", true); sShpEmitters[BWallBF200].init("FX_BWallBF200", "BWallBF200", true); sShpEmitters[BWallF210].init("FX_BWallF210", "BWallF210", true); } u32 dJEffManager_c::sInts[] = {0x28, 0x29, 0x87, 0x88, 0x89, 0x8A, 0x8C, 0x8D, 0x91, 0x86, 0x1, 0x2}; u32 dJEffManager_c::sInts2[] = {0x2, 0x87, 0x8B}; void dJEffManager_c::removeEffManagers() { for (s32 i = 0; i < sNumMassObjEmitters; i++) { sMassObjEmitters[i].remove(); } // TODO EffectsList::Iterator it = sPlayingEffectsList.GetBeginIter(); while (it != sPlayingEffectsList.GetEndIter()) { EffectsList::Iterator itCopy; ++itCopy; it->remove(true); it = itCopy; } dParticle::mgr_c::GetInstance()->forceDeleteAllEmitter(); for (s32 i = 0; i < 47; i++) { sShpEmitters[i].clear(); } } void dJEffManager_c::doCustomSkywardSwordThing(f32 x, f32 y) { EffectsList::Iterator it = sPlayingEffectsList.GetBeginIter(); while (it != sPlayingEffectsList.GetEndIter()) { if (it->getEmitterCallback() != nullptr && it->hasEmitters() && it->getGroupId() < 12) { it->getEmitterCallback()->vt_0x20(x, y); } ++it; } for (s32 i = 0; i < 12; i++) { dParticle::mgr_c::GetInstance()->doCustomSkywardSwordThing(i, x, y); } } void dJEffManager_c::execute() { for (EffectsList::Iterator it = sPlayingEffectsList.GetBeginIter(); it != sPlayingEffectsList.GetEndIter();) { EffectsList::Iterator itNext = it; ++itNext; it->execute(); if (it->checkFlag(EffectsStruct::EMITTER_Fading)) { it->realizeAlpha(); } if (it->checkFlag(EffectsStruct::EMITTER_0x1)) { it->offFlag(EffectsStruct::EMITTER_0x1); } else { if (it->areAllEmittersDone()) { it->remove(false); } else { it->onFlag(EffectsStruct::EMITTER_0x2); } } it = itNext; } if (dStageMgr_c::GetInstance() != nullptr) { CURRENT_EFFECT_MANAGER_INIT->setGlobalAlpha(dStageMgr_c::GetInstance()->getGlobalAlpha()); } for (s32 i = 0; i < sNumMassObjEmitters; i++) { mColor c = sMassObjEmitters[i].getField_0x67C(); sMassObjEmitters[i].loadColors(&c, nullptr, 0, 0); sMassObjEmitters[i].execute(); } if ((dBase_c::s_ExecuteControlFlags & 0x6F9) == 0) { for (int i = 0; i < 12; i++) { dParticle::mgr_c::GetInstance()->calc(i); } } else { dParticle::mgr_c::GetInstance()->calc(9); } dParticle::mgr_c::GetInstance()->calc(12); dParticle::mgr_c::GetInstance()->calc(13); dParticle::mgr_c::GetInstance()->calc(14); } void dJEffManager_c::draw() { for (s32 i = 0; i < 12; i++) { sFogProcs[i].entry(); } s2DEffects[0].addToDrawList(); s2DEffects[1].addToDrawList(); s2DEffects[2].addToDrawList(); for (s32 i = 0; i < 47; i++) { sShpEmitters[i].draw(); } } bool dJEffManager_c::shouldBePaused(dBase_c *owner) { return owner != nullptr && !owner->isBasePropertyFlag(dBase_c::BASE_PROP_0x4) && (EventManager::isInEvent() || owner->isProcControlFlag(fBase_c::DISABLE_EXECUTE) || // TODO execute control flags (owner->s_ExecuteControlFlags & 0x6fb)); } void dJEffManager_c::draw(const JPADrawInfo *info, u32 groupId) { dParticle::mgr_c::GetInstance()->draw(info, groupId); } bool dJEffManager_c::createEffManagers() { EGG::Heap *heap = dHeap::work1Heap.heap; ms_allocator = new (heap, 4) mHeapAllocator_c(); sMassObjEmitters = new (heap, 4) dMassObjEmitter_c[sNumMassObjEmitters]; for (s32 idx = 0; idx < sNumMassObjEmitters; idx++) { // TODO explain this if (sEffectResourceIds[idx] == PARTICLE_RESOURCE_ID_MAPPING_119_) { CURRENT_EFFECT_MANAGER_INIT = &sMassObjEmitters[idx]; break; } } ms_allocator->createFrmHeapToCurrent(-1, dHeap::work1Heap.heap, "dJEffManager_c::ms_allocator", 0x20, mHeap::OPT_NONE); for (s32 idx = 0; idx < 2; idx++) { sFogProcs[idx].create(idx, sInts[idx], -1, ms_allocator); } for (s32 idx = 2; idx < 12; idx++) { sFogProcs[idx].create(idx, -1, sInts[idx], ms_allocator); } for (s32 idx = 0; idx < 3; idx++) { s2DEffects[idx].create(idx + 12, sInts2[idx]); } for (s32 idx = 0; idx < 47; idx++) { sShpEmitters[idx].create(ms_allocator); } ms_allocator->adjustFrmHeapRestoreCurrent(); for (int i = 0; i < 2; i++) { sCommonEmitterCallbacks[i].init(i); } return true; } dEmitterBase_c *dJEffManager_c::spawnEffectInternal( u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2 ) { if (!sEmitter.createEmitters(effectResourceId, position, rot, scale, c1, c2, idx1, idx2)) { return nullptr; } return &sEmitter; } dEmitterBase_c *dJEffManager_c::spawnEffectInternal( u16 effectResourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2 ) { dEmitterBase_c *e = spawnEffectInternal(effectResourceId, mVec3_c::Zero, nullptr, nullptr, c1, c2, idx1, idx2); if (e != nullptr) { e->setTransform(transform); } return e; } dEmitterBase_c *dJEffManager_c::spawnEffect( u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2 ) { return spawnEffectInternal(effectResourceId, position, rot, scale, c1, c2, idx1, idx2); } dEmitterBase_c *dJEffManager_c::spawnUIEffect( u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2 ) { mVec3_c adjustedPosition(position.x * dGfx_c::getCurrentScreenTo4x3WidthScaleF(), position.y, position.z); return spawnEffectInternal(effectResourceId, adjustedPosition, rot, scale, c1, c2, 0, 0); } dEmitterBase_c *dJEffManager_c::spawnEffect( u16 effectResourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2 ) { return spawnEffectInternal(effectResourceId, transform, c1, c2, idx1, idx2); } bool EffectsStruct::createEffect( u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2 ) { return createEffect(true, resourceId, pos, rot, scale, c1, c2); } bool EffectsStruct::createUIEffect( u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2 ) { mVec3_c adjustedPosition(pos.x * dGfx_c::getCurrentScreenTo4x3WidthScaleF(), pos.y, pos.z); return createEffect(true, resourceId, adjustedPosition, rot, scale, c1, c2); } bool EffectsStruct::createEffect(u16 resourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2) { return createEffect(true, resourceId, transform, c1, c2); } bool EffectsStruct::createContinuousEffect( u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2 ) { return createEffect(false, resourceId, pos, rot, scale, c1, c2); } bool EffectsStruct::createContinuousUIEffect( u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2 ) { mVec3_c adjustedPosition(pos.x * dGfx_c::getCurrentScreenTo4x3WidthScaleF(), pos.y, pos.z); return createEffect(false, resourceId, adjustedPosition, rot, scale, c1, c2); } bool EffectsStruct::createContinuousEffect( u16 resourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2 ) { return createEffect(false, resourceId, transform, c1, c2); } bool dJEffManager_c::createMassObjEffect( u16 effectResourceId, const mVec3_c &v1, dAcObjBase_c *owner, const mColor *color ) { for (s32 i = 0; i < sNumMassObjEmitters; i++) { if (effectResourceId == sEffectResourceIds[i]) { if (color != nullptr) { sMassObjEmitters[i].setField_0x67C(*color); } else { sMassObjEmitters[i].setField_0x67C(mColor(0xFF, 0xFF, 0xFF, 0xFF)); } return sMassObjEmitters[i].start(v1, owner); } } return false; } void dEmitterBase_c::loadColors( JPABaseEmitter *emitter, const GXColor *color1, const GXColor *color2, s32 plltIdx1, s32 plltIdx2 ) { u8 r1 = 0xFF; u8 g1 = 0xFF; u8 b1 = 0xFF; u8 r2 = 0xFF; u8 g2 = 0xFF; u8 b2 = 0xFF; u8 r, g, b; dLightEnv_c &mgr = dLightEnv_c::GetInstance(); const mColor c1 = mgr.GetCurrentSpf().mParticleTransparentClr; const mColor c2 = mgr.GetCurrentSpf().mParticleSolidClr; u32 flags = emitter->getDynResUserWork(); u32 factor = flags >> 24; bool useBlack4 = false; if (factor > 100) { useBlack4 = true; factor = factor - 100; } f32 fFactor = factor / 100.0f; if (fFactor > 1.0f) { fFactor = 1.0f; } f32 scaleR, scaleG, scaleB; if (!useBlack4) { scaleR = 1.0f - (1.0f - (c1.r / 255.0f)) * fFactor; scaleG = 1.0f - (1.0f - (c1.g / 255.0f)) * fFactor; scaleB = 1.0f - (1.0f - (c1.b / 255.0f)) * fFactor; } else { scaleR = 1.0f - (1.0f - (c2.r / 255.0f)) * fFactor; scaleG = 1.0f - (1.0f - (c2.g / 255.0f)) * fFactor; scaleB = 1.0f - (1.0f - (c2.b / 255.0f)) * fFactor; } if (color1 != nullptr) { r1 = color1->r; g1 = color1->g; b1 = color1->b; } if (plltIdx1 > 0) { if ((flags & 0x1000) != 0) { const mColor &entry = mgr.getSmallEAF(plltIdx1 - 1, plltIdx2).field_0x00; r1 = entry.r; g1 = entry.g; b1 = entry.b; } else if ((flags & 0x2000) != 0) { const mColor &entry = mgr.getSmallEAF(plltIdx1 - 1, plltIdx2).field_0x08; r1 = entry.r; g1 = entry.g; b1 = entry.b; } } if (factor != 0) { r = r1 * scaleR * mgr.getfield_0x2F14(); g = g1 * scaleG * mgr.getfield_0x2F18(); b = b1 * scaleB * mgr.getfield_0x2F1C(); } else { r = r1 * scaleR; g = g1 * scaleG; b = b1 * scaleB; } emitter->setGlobalPrmColor(r, g, b); if (color2 != nullptr) { r2 = color2->r; g2 = color2->g; b2 = color2->b; } if (plltIdx1 > 0) { if ((flags & 0x1000) != 0) { const mColor &entry = mgr.getSmallEAF(plltIdx1 - 1, plltIdx2).field_0x04; r2 = entry.r; g2 = entry.g; b2 = entry.b; } else if ((flags & 0x2000) != 0) { const mColor &entry = mgr.getSmallEAF(plltIdx1 - 1, plltIdx2).field_0x0C; r2 = entry.r; g2 = entry.g; b2 = entry.b; } } if (factor != 0) { r = mgr.getfield_0x2F14() * r2 * scaleR; g = mgr.getfield_0x2F18() * g2 * scaleG; b = mgr.getfield_0x2F1C() * b2 * scaleB; } else { r = r2 * scaleR; g = g2 * scaleG; b = b2 * scaleB; } emitter->setGlobalEnvColor(r, g, b); } void EffectsStruct::removeFromActiveEmittersList() { if (dJEffManager_c::sPlayingEffectsList.GetPosition(this) != dJEffManager_c::sPlayingEffectsList.GetEndIter()) { dJEffManager_c::sPlayingEffectsList.remove(this); } } bool EffectsStruct::createEffect( bool bFlags, u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1, const GXColor *c2 ) { if (!bFlags && canReuse(resourceId)) { setPosRotScale(pos, rot, scale); s32 idx1 = 0; s32 idx2 = 0; getOwnerPolyAttrs(&idx1, &idx2); loadColors(c1, c2, idx1, idx2); onFlag(EMITTER_0x1); } else { remove(false); s32 idx1 = 0; s32 idx2 = 0; getOwnerPolyAttrs(&idx1, &idx2); if (createEmitters(resourceId, pos, rot, scale, c1, c2, idx1, idx2)) { addToActiveEmittersList(resourceId, bFlags); } } return hasEmitters(); } bool EffectsStruct::createEffect( bool bFlags, u16 resourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2 ) { if (!bFlags && canReuse(resourceId)) { s32 idx1 = 0; s32 idx2 = 0; getOwnerPolyAttrs(&idx1, &idx2); loadColors(c1, c2, idx1, idx2); onFlag(EMITTER_0x1); } else { remove(false); s32 idx1 = 0; s32 idx2 = 0; getOwnerPolyAttrs(&idx1, &idx2); if (createEmitters(resourceId, mVec3_c::Zero, nullptr, nullptr, c1, c2, idx1, idx2)) { addToActiveEmittersList(resourceId, bFlags); } } setTransform(transform); return hasEmitters(); } // TODO: Document PolyAttrs s32 dJEffManager_c::polyAttrsToGroundEffectIdx(s32 polyAttr0, s32 polyAttr1) { if (polyAttr0 == 0 || (polyAttr0 == 4 && polyAttr1 == 1) || (polyAttr0 == 9) || (polyAttr0 == 10 && polyAttr1 == 1) || (polyAttr0 == 12) || (polyAttr0 == 17 && polyAttr1 != 1) || (polyAttr0 == 13 && (polyAttr1 == 1 || polyAttr1 == 3)) || polyAttr0 == 18) { return 6; } else if (polyAttr0 == 17) { return 5; } else if (polyAttr0 == 6) { return 2; } else if (polyAttr0 == 4) { return 1; } else if (polyAttr0 == 3 || polyAttr0 == 15) { return 3; } // ??? s32 result = 0; if (polyAttr0 == 11) { result = 4; } return result; } dEmitterBase_c *dJEffManager_c::spawnGroundEffect( const mVec3_c &pos, u8 polyAttr0, u8 polyAttr1, const mVec3_c &v1, s32 unk, f32 scale, f32 groundHeightMaybe ) { static const u16 sEffArray[6][2] = { { PARTICLE_RESOURCE_ID_MAPPING_89_, PARTICLE_RESOURCE_ID_MAPPING_90_}, { PARTICLE_RESOURCE_ID_MAPPING_87_, PARTICLE_RESOURCE_ID_MAPPING_88_}, {PARTICLE_RESOURCE_ID_MAPPING_429_, PARTICLE_RESOURCE_ID_MAPPING_429_}, {PARTICLE_RESOURCE_ID_MAPPING_416_, PARTICLE_RESOURCE_ID_MAPPING_417_}, {PARTICLE_RESOURCE_ID_MAPPING_418_, PARTICLE_RESOURCE_ID_MAPPING_419_}, {PARTICLE_RESOURCE_ID_MAPPING_893_, PARTICLE_RESOURCE_ID_MAPPING_894_}, }; if (pos.y < groundHeightMaybe) { return nullptr; } s32 idx = polyAttrsToGroundEffectIdx(polyAttr0, polyAttr1); if (idx == 6) { return nullptr; } if (idx == 2 && unk == 0) { scale *= 1.5f; } mMtx_c mtx; mtx.makeRotationFromVecs(mVec3_c::Ey, v1, 1.0f); mAng rot(cM::rndF(65536.0f)); mtx.YrotM(rot); mtx.setTranslation(pos); mMtx_c scaleMtx; MTXScale(scaleMtx, scale, scale, scale); MTXConcat(mtx, scaleMtx, mtx); return spawnEffect(sEffArray[idx][unk], mtx, nullptr, nullptr, polyAttr0, polyAttr1); } void dWaterEffect_c::init(dAcObjBase_c *base, f32 height, f32 scale, f32 f3) { mEff.init(base); mHeight = height; mScale = scale; mDepth = f3; } void dWaterEffect_c::execute(f32 water, f32 ground) { dAcObjBase_c *ac = getActor(); bool b = getActorGroundPos(ac) < water && ground < water; if (b) { if (!mIsInWater) { mIsInWater = true; mVec3_c pos(ac->position.x, water, ac->position.z); mVec3_c scale(mScale, mScale, mScale); if (mIsSmall || water - ground < 50.0f) { // For small objects or shallow water, create a // particle FX upon entering water mAng3_c rot(0, cM::rndF(65536.0f), 0); dJEffManager_c::spawnEffect( PARTICLE_RESOURCE_ID_MAPPING_91_, pos, &rot, &scale, nullptr, nullptr, 0, 0 ); } else { // Otherwise spawn a "water spout" with a 3d model dAcObjBase_c::create(fProfile::OBJ_WATER_SPOUT, -1, 0, &pos, nullptr, &scale, -1); } } } else if (getActorGroundPos(ac) - 5.0f > water) { mIsInWater = false; } if (mIsInWater && getActorCeilPos(ac) > water) { // Spawn effect while in water mVec3_c pos(ac->position.x, water, ac->position.z); mVec3_c scale(mScale, mScale, mScale); mEff.createContinuousEffect(PARTICLE_RESOURCE_ID_MAPPING_127_, pos, nullptr, &scale, nullptr, nullptr); f32 rate = nw4r::math::FAbs(ac->forwardSpeed) * 0.02f; rate = rate > 0.95f ? 0.95f : rate; mEff.setRate(rate + 0.05f); } }