diff --git a/Source/Contents/CashEmotionMgr.cpp b/Source/Contents/CashEmotionMgr.cpp index 701fd0c..f5ef267 100644 --- a/Source/Contents/CashEmotionMgr.cpp +++ b/Source/Contents/CashEmotionMgr.cpp @@ -3,13 +3,16 @@ #include "Resource/ResourceMgr.h" #include "Resource/SkinPalette.h" #include "Resource/TextureNames.h" +#include "UI/Buttons/UIBitmapButton.h" #include "UI/Buttons/UICashEmotionSprBtn.h" #include "UI/CashEmotion/UICashEmotionPack.h" #include "UI/CashEmotion/UICashEmotionPiece.h" #include "UI/Chat/ChatSuggestions.h" +#include "UI/Edit/UIEditCtrl.h" #include "UI/Window/WindowBorder.h" #include +#include #include #include #include @@ -23,22 +26,34 @@ CCashEmotionMgr* g_cashEmotionMgr = nullptr; // GLOBAL: gl_24484 ChatSuggestions* g_chatSuggestions = nullptr; +class ICashEmotionAction { +public: + virtual ~ICashEmotionAction(); + virtual void Execute(int packId, int argument) = 0; +}; + class UICashEmotionMiniWnd : public UIWindow { public: - // OFFSET: 0x007C9200 + void Slot15(int width, int height) override; + void Paint() override; + int SendMsg(UIWindow* sender, int message, std::intptr_t argument1, std::intptr_t argument2, std::intptr_t argument3, std::intptr_t argument4) override; + unsigned int Select(int actionId); + void Select(CashEmotionCommand* command); + void Previous(); void Refresh(int first); + void Next(); + void SetIndex(int index); protected: /* 007C */ CashEmotionCommand* m_selected; - /* 0080 */ UICashEmotionPiece* m_first; - /* 0084 */ UICashEmotionPiece* m_second; - /* 0088 */ UICashEmotionPiece* m_third; - /* 008C */ UIButton* m_previous; - /* 0090 */ UIButton* m_next; - /* 0094 */ void* unknown_0094; + /* 0080 */ std::array m_pieces; + /* 008C */ UIBitmapButton* m_previous; + /* 0090 */ UIBitmapButton* m_next; + /* 0094 */ ICashEmotionAction* m_action; /* 0098 */ int m_page; /* 009C */ int m_selectedIndex; + UICashEmotionPieceContext* m_controls; }; // OFFSET: 0x00592290 @@ -608,7 +623,7 @@ void CCashEmotionMgr::SetOwnedPack(int packId, unsigned int expiry) { for (auto it = this->m_expiry.begin(); it != this->m_expiry.end(); ++it) { if (it->first == packId) { if (expiry) { - it->second.seconds = expiry; + it->second.Set(expiry); } else { this->m_expiry.erase(it); } @@ -617,7 +632,7 @@ void CCashEmotionMgr::SetOwnedPack(int packId, unsigned int expiry) { } if (expiry > 0) { - this->m_expiry.emplace_back(packId, Timestamp{static_cast<__time32_t>(expiry)}); + this->m_expiry.emplace_back(packId, expiry); } } @@ -648,42 +663,238 @@ bool CCashEmotionMgr::IsOwnedPack(int packId) { return std::find(this->m_ownedPacks.begin(), this->m_ownedPacks.end(), packId) != this->m_ownedPacks.end(); } +// OFFSET: 0x007C9200 +// FUNCTION: fn_09428 +void UICashEmotionMiniWnd::Select(CashEmotionCommand* command) { + this->m_selected = command; + this->m_page = 0; + this->m_selectedIndex = 0; + this->Refresh(0); + + if (this->m_previous) { + this->m_previous->Enable(0); + } + + if (this->m_selected && this->m_selected->Count() > 3) { + if (this->m_next) { + this->m_next->Enable(1); + } + } else if (this->m_next) { + this->m_next->Enable(0); + } +} + +// OFFSET: 0x007C9290 +// FUNCTION: fn_09429 +void UICashEmotionMiniWnd::Previous() { + if (this->m_selectedIndex != 0) { + this->SetIndex(this->m_selectedIndex - 1); + } +} + // OFFSET: 0x007C92B0 // FUNCTION: fn_09430 void UICashEmotionMiniWnd::Refresh(int first) { - if (this->m_first) { + if (this->m_pieces[0]) { if (this->m_selected) { std::pair entry = this->m_selected->Get(first); bool owned = entry.first == g_cashEmotionMgr->BasePackId() || g_cashEmotionMgr->IsOwnedPack(entry.first); - this->m_first->Set(entry.first, entry.second, false, false, !owned); + this->m_pieces[0]->Set(entry.first, entry.second, false, false, !owned); } else { - this->m_first->Set(-1, -1, false, false, false); + this->m_pieces[0]->Set(-1, -1, false, false, false); } } - if (this->m_second) { + if (this->m_pieces[1]) { if (this->m_selected) { std::pair entry = this->m_selected->Get(first + 1); bool owned = entry.first == g_cashEmotionMgr->BasePackId() || g_cashEmotionMgr->IsOwnedPack(entry.first); - this->m_second->Set(entry.first, entry.second, false, false, !owned); + this->m_pieces[1]->Set(entry.first, entry.second, false, false, !owned); } else { - this->m_second->Set(-1, -1, false, false, false); + this->m_pieces[1]->Set(-1, -1, false, false, false); } } - if (this->m_third) { + if (this->m_pieces[2]) { if (this->m_selected) { std::pair entry = this->m_selected->Get(first + 2); bool owned = entry.first == g_cashEmotionMgr->BasePackId() || g_cashEmotionMgr->IsOwnedPack(entry.first); - this->m_third->Set(entry.first, entry.second, false, false, !owned); + this->m_pieces[2]->Set(entry.first, entry.second, false, false, !owned); } else { - this->m_third->Set(-1, -1, false, false, false); + this->m_pieces[2]->Set(-1, -1, false, false, false); } } this->Invalidate(); } +// OFFSET: 0x007C94A0 +// FUNCTION: fn_09431 +void UICashEmotionMiniWnd::Slot15(int width, int height) { + int x = 24; + for (UICashEmotionPiece*& piece : this->m_pieces) { + piece = new (std::nothrow) UICashEmotionPiece(*this->m_context, *this->m_controls); + if (piece) { + piece->SetSize(42, 42); + piece->Move(x, 1); + this->AddChild(piece); + x += 49; + } + } + + this->m_previous = new (std::nothrow) UIBitmapButton(*this->m_context, this->m_controls->buttons); + if (this->m_previous) { + this->m_previous->SetBitmap("유저인터페이스\\cashemotion\\btn_arrow_l_normal.bmp", 0, 0); + this->m_previous->SetBitmap("유저인터페이스\\cashemotion\\btn_arrow_l_over.bmp", 1, 0); + this->m_previous->SetBitmap("유저인터페이스\\cashemotion\\btn_arrow_l_press.bmp", 2, 0); + this->m_previous->SetSize(this->m_previous->BitmapWidth(), this->m_previous->BitmapHeight()); + this->m_previous->Move(0, 0); + this->m_previous->SetId(1); + this->AddChild(this->m_previous); + } + + this->m_next = new (std::nothrow) UIBitmapButton(*this->m_context, this->m_controls->buttons); + if (this->m_next) { + this->m_next->SetBitmap("유저인터페이스\\cashemotion\\bth_arrow_r_normal.bmp", 0, 0); + this->m_next->SetBitmap("유저인터페이스\\cashemotion\\bth_arrow_r_over.bmp", 1, 0); + this->m_next->SetBitmap("유저인터페이스\\cashemotion\\bth_arrow_r_press.bmp", 2, 0); + int buttonWidth = this->m_next->BitmapWidth(); + this->m_next->SetSize(buttonWidth, this->m_next->BitmapHeight()); + this->m_next->Move(this->geometry.width - buttonWidth, 0); + this->m_next->SetId(2); + this->AddChild(this->m_next); + } +} + +// OFFSET: 0x007C96E0 +// FUNCTION: fn_09432 +void UICashEmotionMiniWnd::Paint() { + this->ClearSurface(0xFFFF00FF); + + auto* background = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\cashemotion\\bg_preview.bmp"))); + if (background) { + this->DrawBitmap(0, 0, &background->image, false); + } + + if (this->m_selectedIndex == -1) { + return; + } + + int slot = this->m_selectedIndex - this->m_page; + if (slot <= -1) { + return; + } + + UICashEmotionPiece* piece = this->m_pieces.at(slot); + if (!piece) { + return; + } + + auto* highlight = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\cashemotion\\bg_preview_select.bmp"))); + if (highlight) { + this->DrawBitmap(piece->geometry.x - 3, 0, &highlight->image, false); + } +} + +// OFFSET: 0x007C9790 +// FUNCTION: fn_09433 +void UICashEmotionMiniWnd::Next() { + if (this->m_selected && this->m_selected->Count() > this->m_selectedIndex + 1) { + this->SetIndex(this->m_selectedIndex + 1); + } +} + +// OFFSET: 0x007C97C0 +// FUNCTION: fn_09434 +void UICashEmotionMiniWnd::SetIndex(int index) { + if (index == this->m_selectedIndex) { + return; + } + + this->m_selectedIndex = index; + if (this->m_selected) { + bool enable = index > 0 && this->m_selected->Count() > 1; + if (this->m_previous) { + this->m_previous->Enable(enable); + } + + enable = this->m_selectedIndex + 1 < this->m_selected->Count() && this->m_selected->Count() > 1; + if (this->m_next) { + this->m_next->Enable(enable); + } + } + + if (this->m_selectedIndex >= this->m_page + 3) { + this->m_page = this->m_selectedIndex - 2; + this->Refresh(this->m_page); + } else if (this->m_selectedIndex < this->m_page) { + this->m_page = this->m_selectedIndex; + this->Refresh(this->m_page); + } + + this->Invalidate(); +} + +// OFFSET: 0x007C98A0 +// FUNCTION: fn_09435 +int UICashEmotionMiniWnd::SendMsg( + UIWindow* sender, int message, std::intptr_t argument1, std::intptr_t argument2, std::intptr_t argument3, std::intptr_t argument4 +) { + std::pair entry; + + switch (message) { + case 6: + if (argument1 == 1) { + this->Previous(); + } else if (argument1 == 2) { + this->Next(); + } + return 0; + case 60: + this->Invalidate(); + return 0; + case 174: + entry.first = static_cast(argument1); + entry.second = static_cast(argument2); + break; + case 176: + if (argument1 == 13) { + if (!this->m_selected) { + return 0; + } + entry = this->m_selected->Get(this->m_selectedIndex); + break; + } + if (argument1 == 37) { + this->Previous(); + } else if (argument1 == 39) { + this->Next(); + } + return 0; + default: + return UIWindow::SendMsg(sender, message, argument1, argument2, argument3, argument4); + } + + CashEmotionData* data = g_cashEmotionMgr->GetEmotionData(entry.first, entry.second); + if (!data) { + return 0; + } + + int packId = data->id; + if (packId == g_cashEmotionMgr->BasePackId() || g_cashEmotionMgr->IsOwnedPack(packId)) { + if (g_chatSuggestions) { + if (g_chatSuggestions->input) { + g_chatSuggestions->input->SetText(data->name.c_str()); + } + g_chatSuggestions->Show(0); + } + } else if (this->m_action) { + this->m_action->Execute(packId, 0); + } + + return 0; +} + // OFFSET: 0x007C9D60 // FUNCTION: fn_09439 UICashEmotionPack::~UICashEmotionPack() { diff --git a/Source/Contents/CashEmotionMgr.h b/Source/Contents/CashEmotionMgr.h index b82206c..5543943 100644 --- a/Source/Contents/CashEmotionMgr.h +++ b/Source/Contents/CashEmotionMgr.h @@ -26,6 +26,10 @@ public: void Add(int packId, int emotionId); std::pair Get(int index) const; + // INLINED + int Count() const { + return static_cast(this->entries.size()); + } /* 0000 */ std::string name; /* 0018 */ std::vector> entries; diff --git a/Source/Contents/Effects/AttachedEffect.cpp b/Source/Contents/Effects/AttachedEffect.cpp index 436805f..f2e312a 100644 --- a/Source/Contents/Effects/AttachedEffect.cpp +++ b/Source/Contents/Effects/AttachedEffect.cpp @@ -27,7 +27,7 @@ inline RagEffectState::RagEffectState() // INLINED inline RagEffectState::~RagEffectState() { for (EffectPrimitiveState* particle : this->related) { - particle->parent = nullptr; + particle->master = nullptr; } this->related.clear(); diff --git a/Source/Contents/Effects/EffectParticle.cpp b/Source/Contents/Effects/EffectParticle.cpp index 227ef69..e8104d3 100644 --- a/Source/Contents/Effects/EffectParticle.cpp +++ b/Source/Contents/Effects/EffectParticle.cpp @@ -1,5 +1,5 @@ #include "Contents/Effects/EffectParticle.h" -#include "Contents/Effects/RagEffectState.h" +#include "Contents/Effects/AttachedEffect.h" // OFFSET: 0x00BAD940 // FUNCTION: fn_19761 @@ -9,13 +9,13 @@ EffectParticle::EffectParticle(ResourceMgr& resources, JobNames& names) : CRende // OFFSET: 0x00C1DE70 // FUNCTION: fn_20076 EffectParticle* CreateEffectParticle( - RagEffectState& parent, const RenderObjectState& parentRender, int type, float x, float y, float z, ResourceMgr& resources, JobNames& names, + AttachedEffect& master, const RenderObjectState& masterRender, int type, float x, float y, float z, ResourceMgr& resources, JobNames& names, std::list& worldObjects ) { auto* particle = new EffectParticle(resources, names); - particle->state.Initialize(particle->render, parent, parentRender, type, x, y, z); - parent.related.push_back(&particle->state); + particle->state.Initialize(particle->render, master, masterRender, type, x, y, z); + master.state.related.push_back(&particle->state); worldObjects.push_back(particle); return particle; diff --git a/Source/Contents/Effects/EffectParticle.h b/Source/Contents/Effects/EffectParticle.h index acc3fcf..92afaf4 100644 --- a/Source/Contents/Effects/EffectParticle.h +++ b/Source/Contents/Effects/EffectParticle.h @@ -8,7 +8,7 @@ struct JobNames; class ResourceMgr; -struct RagEffectState; +struct AttachedEffect; struct EffectParticle : CRenderObject { EffectParticle(ResourceMgr& resources, JobNames& names); @@ -22,6 +22,9 @@ struct EffectParticle : CRenderObject { bool UpdateExit(); bool UpdateWarp(); void PrimBottom2(); + void PrimBottom(); + void PrimBottomOut(); + void PrimBubbleDrop(); bool AdvanceFrame(); void UpdateRecords(); @@ -30,7 +33,7 @@ struct EffectParticle : CRenderObject { }; EffectParticle* CreateEffectParticle( - RagEffectState& parent, const RenderObjectState& parentRender, int type, float x, float y, float z, ResourceMgr& resources, JobNames& names, + AttachedEffect& master, const RenderObjectState& masterRender, int type, float x, float y, float z, ResourceMgr& resources, JobNames& names, std::list& worldObjects ); diff --git a/Source/Contents/Effects/EffectPrimitiveColor.cpp b/Source/Contents/Effects/EffectPrimitiveColor.cpp index d7c0c76..7ad0277 100644 --- a/Source/Contents/Effects/EffectPrimitiveColor.cpp +++ b/Source/Contents/Effects/EffectPrimitiveColor.cpp @@ -1,5 +1,5 @@ #include "Contents/Effects/EffectPrimitiveState.h" -#include "Contents/Effects/RagEffectState.h" +#include "Contents/Effects/AttachedEffect.h" #include "Utility/Math/ScalarConversion.h" // OFFSET: 0x00BC5E00 @@ -45,7 +45,7 @@ void EffectPrimitiveState::UpdateColor() { } if (this->unknown_015C == 0) { - const int parentRemaining = this->parent->lifetime - this->parent->unknown_0178; + const int parentRemaining = this->master->state.lifetime - this->master->state.unknown_0178; const int remaining = this->lifetime - current; if (remaining > parentRemaining + 1) { diff --git a/Source/Contents/Effects/EffectPrimitiveMotion.cpp b/Source/Contents/Effects/EffectPrimitiveMotion.cpp index 7dff951..83eae37 100644 --- a/Source/Contents/Effects/EffectPrimitiveMotion.cpp +++ b/Source/Contents/Effects/EffectPrimitiveMotion.cpp @@ -6,6 +6,7 @@ #include "Resource/Sprites/ActionResource.h" #include "Contents/Effects/EffectParticle.h" #include "Contents/Effects/EffectPrimitiveState.h" +#include "Contents/Effects/AttachedEffect.h" #include "Contents/Effects/RagEffectState.h" #include "Contents/Sound/SoundMgr.h" #include "Resource/World/TerrainResource.h" @@ -49,6 +50,33 @@ namespace { ((matrix[5] * point.y + matrix[2] * point.x) + point.z * matrix[8]) + matrix[11] }; } + + // INLINED + void FaceCamera(EffectPrimitiveSlot* slots) { + const CView* view = static_cast(g_world.ActiveGameMode())->camera; + slots[0].alphaT = static_cast(360 - static_cast(view->camera.yaw)); + slots[1].alphaT = static_cast(360 - static_cast(view->camera.pitch) + 90); + + if (slots[1].alphaT >= 360) { + slots[1].alphaT -= 360; + } + } + + // INLINED + __forceinline void FollowTrail(EffectPrimitiveSlot& trail, const EffectPrimitiveSlot& previous, int fade) { + trail.fullDisplayAngle = previous.fullDisplayAngle; + trail.distance = previous.distance; + trail.height[8] = previous.height[8]; + trail.alphaB = previous.alphaB - fade; + + if (trail.alphaB < 0) { + trail.alphaB = 0; + } + + trail.vecBNow = previous.vecBNow; + trail.fullDisplayAngle = previous.fullDisplayAngle; + trail.flag1[0] = previous.flag1[0]; + } } // OFFSET: 0x00BB8C50 @@ -136,8 +164,8 @@ bool MoveEffectPrimitive(EffectPrimitiveState& particle, RenderObjectState& rend } if ((particle.flags & 0x400u) != 0) { - const float x = particle.parent->offset[0] - render.position[0]; - const float z = particle.parent->offset[2] - render.position[2]; + const float x = particle.master->state.offset[0] - render.position[0]; + const float z = particle.master->state.offset[2] - render.position[2]; const float distance = std::sqrt(z * z + x * x); if (3.0f >= distance) { @@ -188,16 +216,16 @@ bool MoveEffectPrimitive(EffectPrimitiveState& particle, RenderObjectState& rend } if ((particle.flags & 0x10u) != 0) { - particle.parent->offset[0] = particle.velocity.x + particle.offset.x; - particle.parent->offset[1] = particle.offset.y + particle.velocity.y; - particle.parent->offset[2] = particle.offset.z + particle.velocity.z; + particle.master->state.offset[0] = particle.velocity.x + particle.offset.x; + particle.master->state.offset[1] = particle.offset.y + particle.velocity.y; + particle.master->state.offset[2] = particle.offset.z + particle.velocity.z; } if ((particle.flags & 4u) != 0 && render.position[1] > context.ground.Height(render.position[0], render.position[2])) { particle.flags &= ~4u; particle.unknown_01E0 = particle.lifetime - 25; particle.unknown_02BC = 0; - particle.parent->unknown_0170 = 1; + particle.master->state.unknown_0170 = 1; } const int frame = particle.unknown_01E0; @@ -257,7 +285,7 @@ bool MoveEffectPrimitive(EffectPrimitiveState& particle, RenderObjectState& rend // OFFSET: 0x00BAE7D0 // FUNCTION: fn_19769 bool UpdateEffectPrimitive15(EffectPrimitiveState& particle, RenderObjectState& render, EffectMotionContext& context, int updateTick) { - if (!particle.parent) { + if (!particle.master) { return false; } @@ -280,39 +308,39 @@ bool ShrinkEffectPrimitiveSlots(EffectPrimitiveState& particle, RenderObjectStat for (int index = 0; index < 4; ++index) { EffectPrimitiveSlot& slot = particle.slots[index]; - if (slot.active != 1) { + if (slot.life != 1) { continue; } - float height = slot.unknown_0080 - 0.05f; - ++slot.frame; - slot.unknown_0080 = height; + float height = slot.distance - 0.05f; + ++slot.process; + slot.distance = height; if (height <= 0.0f) { - slot.unknown_0080 = 10.0f; - slot.unknown_0002 = 0; + slot.distance = 10.0f; + slot.alphaB = 0; height = 10.0f; } - slot.unknown_0008 = height; - slot.unknown_007E = static_cast(static_cast(90.0f - height * 9.0f)); + slot.maxHeight = height; + slot.riseAngle = static_cast(static_cast(90.0f - height * 9.0f)); - if (slot.frame >= particle.lifetime - 40) { - slot.unknown_0002 -= 3; + if (slot.process >= particle.lifetime - 40) { + slot.alphaB -= 3; - if (slot.unknown_0002 < 0) { - slot.unknown_0002 = 0; + if (slot.alphaB < 0) { + slot.alphaB = 0; } - } else if (slot.unknown_0002 < 70) { - ++slot.unknown_0002; + } else if (slot.alphaB < 70) { + ++slot.alphaB; } for (int value = 0; value < 21; ++value) { - if (slot.finished[value] == 0) { - slot.values[value] = slot.unknown_0008; + if (slot.flag1[value] == 0) { + slot.height[value] = slot.maxHeight; - if (slot.values[value] < 0.0f) { - slot.values[value] = 0; + if (slot.height[value] < 0.0f) { + slot.height[value] = 0; } } } @@ -331,21 +359,21 @@ bool PulseEffectPrimitiveSlots(EffectPrimitiveState& particle, RenderObjectState for (int index = 0; index < 4; ++index) { EffectPrimitiveSlot& slot = particle.slots[index]; - if (slot.active != 1) { + if (slot.life != 1) { continue; } - float phase = slot.unknown_0080 + 0.1f; - const int frame = ++slot.frame; - slot.unknown_0080 = phase; + float phase = slot.distance + 0.1f; + const int frame = ++slot.process; + slot.distance = phase; if (phase >= 13.0f) { - slot.unknown_0080 = 3.0f; - slot.unknown_0002 = 0; + slot.distance = 3.0f; + slot.alphaB = 0; phase = 3.0f; if (frame > 50) { - slot.active = 0; + slot.life = 0; } } @@ -355,23 +383,23 @@ bool PulseEffectPrimitiveSlots(EffectPrimitiveState& particle, RenderObjectState if (phase > 5.0f) { height = 10.0f - phase; - if (slot.unknown_0002 > 0) { - slot.unknown_0002 -= 2; + if (slot.alphaB > 0) { + slot.alphaB -= 2; } } else { - slot.unknown_0002 += 2; + slot.alphaB += 2; height = phase; } - slot.unknown_0008 = height * 2.0f; - slot.unknown_007E = static_cast(static_cast(90.0f - phase * 5.0f)); + slot.maxHeight = height * 2.0f; + slot.riseAngle = static_cast(static_cast(90.0f - phase * 5.0f)); for (int value = 0; value < 21; ++value) { - if (slot.finished[value] == 0) { - slot.values[value] = slot.unknown_0008; + if (slot.flag1[value] == 0) { + slot.height[value] = slot.maxHeight; - if (slot.values[value] < 0.0f) { - slot.values[value] = 0; + if (slot.height[value] < 0.0f) { + slot.height[value] = 0; } } } @@ -559,10 +587,10 @@ bool EffectParticle::AdvanceFrame() { // OFFSET: 0x00BB5E80 // FUNCTION: fn_19772 bool EffectParticle::UpdateFireWall() { - const RagEffectState* parent = this->state.parent; - this->render.position[0] = std::bit_cast(parent->unknown_013C[3]); - this->render.position[1] = std::bit_cast(parent->unknown_013C[4]); - this->render.position[2] = std::bit_cast(parent->unknown_013C[5]); + const AttachedEffect* master = this->state.master; + this->render.position[0] = master->render.position[0]; + this->render.position[1] = master->render.position[1]; + this->render.position[2] = master->render.position[2]; for (int index = 0; index < 4; ++index) { if (this->state.scale == 3.0f && index < 3) { @@ -571,38 +599,38 @@ bool EffectParticle::UpdateFireWall() { EffectPrimitiveSlot& slot = this->state.slots[index]; - if (slot.active != 1) { + if (slot.life != 1) { continue; } - ++slot.frame; + ++slot.process; if (index < 3) { - slot.angle = static_cast(slot.angle + index + 3); + slot.rotStart = static_cast(slot.rotStart + index + 3); } - if (slot.angle >= 360) { - slot.angle = static_cast(slot.angle - 360); + if (slot.rotStart >= 360) { + slot.rotStart = static_cast(slot.rotStart - 360); } const int limit = this->state.lifetime - 40; bool grown = false; - if (slot.frame < limit) { - if (slot.frame < 20) { + if (slot.process < limit) { + if (slot.process < 20) { if (index == 3) { - this->state.slots[3].unknown_0002 = static_cast(this->state.slots[3].unknown_0002 + 8); + this->state.slots[3].alphaB = static_cast(this->state.slots[3].alphaB + 8); - if (this->state.slots[3].unknown_0002 > 100) { - this->state.slots[3].unknown_0002 = 100; + if (this->state.slots[3].alphaB > 100) { + this->state.slots[3].alphaB = 100; } grown = true; } else { - slot.unknown_0002 = static_cast(slot.unknown_0002 + 10); + slot.alphaB = static_cast(slot.alphaB + 10); - if (slot.unknown_0002 > 180) { - slot.unknown_0002 = 180; + if (slot.alphaB > 180) { + slot.alphaB = 180; } } } @@ -612,16 +640,16 @@ bool EffectParticle::UpdateFireWall() { } } else { if (index < 3) { - slot.unknown_0002 = static_cast(slot.unknown_0002 - 5); + slot.alphaB = static_cast(slot.alphaB - 5); - if (slot.unknown_0002 < 0) { - slot.unknown_0002 = 0; + if (slot.alphaB < 0) { + slot.alphaB = 0; } } else { - slot.unknown_0002 = static_cast(slot.unknown_0002 - 3); + slot.alphaB = static_cast(slot.alphaB - 3); - if (slot.unknown_0002 < 0) { - slot.unknown_0002 = 0; + if (slot.alphaB < 0) { + slot.alphaB = 0; } grown = true; @@ -630,46 +658,46 @@ bool EffectParticle::UpdateFireWall() { if (!grown) { for (int sample = 0; sample < 21; ++sample) { - if (slot.finished[sample] == 0) { - if (slot.frame <= 90) { - slot.values[sample] = slot.unknown_0008 * ClientSine(sample * 9) * ClientSine(slot.frame); + if (slot.flag1[sample] == 0) { + if (slot.process <= 90) { + slot.height[sample] = slot.maxHeight * ClientSine(sample * 9) * ClientSine(slot.process); } - const float maximum = slot.unknown_0008 * ClientSine(sample * 9); + const float maximum = slot.maxHeight * ClientSine(sample * 9); - if (slot.values[sample] > maximum) { - slot.values[sample] = maximum; - slot.finished[sample] = 1; + if (slot.height[sample] > maximum) { + slot.height[sample] = maximum; + slot.flag1[sample] = 1; } - if (slot.values[sample] < 0.0f) { - slot.values[sample] = 0.0f; + if (slot.height[sample] < 0.0f) { + slot.height[sample] = 0.0f; } } } } else { for (int sample = 0; sample < 21; ++sample) { - if (slot.finished[sample] != 0) { - const float base = slot.unknown_0008 * ClientSine(sample * 9) * 0.55f; - slot.values[sample] = base; - slot.values[sample] = slot.unknown_0008 * ClientSine(sample * 9) * 0.45f * ClientSine(slot.frame % 360) + base; + if (slot.flag1[sample] != 0) { + const float base = slot.maxHeight * ClientSine(sample * 9) * 0.55f; + slot.height[sample] = base; + slot.height[sample] = slot.maxHeight * ClientSine(sample * 9) * 0.45f * ClientSine(slot.process % 360) + base; } else { float value; - if (slot.frame > 90) { - slot.finished[sample] = 1; - value = slot.values[sample]; + if (slot.process > 90) { + slot.flag1[sample] = 1; + value = slot.height[sample]; } else { - value = slot.unknown_0008 * ClientSine(sample * 9) * ClientSine(slot.frame); - slot.values[sample] = value; + value = slot.maxHeight * ClientSine(sample * 9) * ClientSine(slot.process); + slot.height[sample] = value; } - if (value > slot.unknown_0008 * ClientSine(sample * 9)) { - slot.finished[sample] = 1; + if (value > slot.maxHeight * ClientSine(sample * 9)) { + slot.flag1[sample] = 1; } - if (slot.values[sample] < 0.0f) { - slot.values[sample] = 0.0f; + if (slot.height[sample] < 0.0f) { + slot.height[sample] = 0.0f; } } } @@ -682,10 +710,10 @@ bool EffectParticle::UpdateFireWall() { // OFFSET: 0x00BB63C0 // FUNCTION: fn_19775 bool EffectParticle::UpdateStoneCurse() { - const RagEffectState* parent = this->state.parent; - this->render.position[0] = std::bit_cast(parent->unknown_013C[3]); - this->render.position[1] = std::bit_cast(parent->unknown_013C[4]); - this->render.position[2] = std::bit_cast(parent->unknown_013C[5]); + const AttachedEffect* master = this->state.master; + this->render.position[0] = master->render.position[0]; + this->render.position[1] = master->render.position[1]; + this->render.position[2] = master->render.position[2]; for (int index = 0; index < 4; ++index) { if (this->state.scale == 3.0f && index < 3) { @@ -694,59 +722,59 @@ bool EffectParticle::UpdateStoneCurse() { EffectPrimitiveSlot& slot = this->state.slots[index]; - if (slot.active != 1) { + if (slot.life != 1) { continue; } - ++slot.frame; + ++slot.process; if (index < 3) { - slot.angle = static_cast(slot.angle + index + 3); + slot.rotStart = static_cast(slot.rotStart + index + 3); } - if (slot.angle >= 360) { - slot.angle = static_cast(slot.angle - 360); + if (slot.rotStart >= 360) { + slot.rotStart = static_cast(slot.rotStart - 360); } - if (slot.frame < this->state.lifetime - 40) { - if (slot.frame < 20 && slot.unknown_0004 != 1) { + if (slot.process < this->state.lifetime - 40) { + if (slot.process < 20 && slot.alphaT != 1) { if (index == 3) { - this->state.slots[3].unknown_0002 = static_cast(this->state.slots[3].unknown_0002 + 8); + this->state.slots[3].alphaB = static_cast(this->state.slots[3].alphaB + 8); - if (this->state.slots[3].unknown_0002 > 60) { - this->state.slots[3].unknown_0002 = 60; + if (this->state.slots[3].alphaB > 60) { + this->state.slots[3].alphaB = 60; } } else { - slot.unknown_0002 = static_cast(slot.unknown_0002 + 10); + slot.alphaB = static_cast(slot.alphaB + 10); - if (slot.unknown_0002 > 180) { - slot.unknown_0002 = 180; + if (slot.alphaB > 180) { + slot.alphaB = 180; } } } } else { - slot.unknown_0002 = static_cast(slot.unknown_0002 - (index >= 3 ? 2 : 5)); + slot.alphaB = static_cast(slot.alphaB - (index >= 3 ? 2 : 5)); - if (slot.unknown_0002 < 0) { - slot.unknown_0002 = 0; + if (slot.alphaB < 0) { + slot.alphaB = 0; } } for (int sample = 0; sample < 21; ++sample) { - if (slot.finished[sample] == 0) { - if (slot.frame <= 90) { - slot.values[sample] = slot.unknown_0008 * ClientSine(sample * 9) * ClientSine(slot.frame); + if (slot.flag1[sample] == 0) { + if (slot.process <= 90) { + slot.height[sample] = slot.maxHeight * ClientSine(sample * 9) * ClientSine(slot.process); } - const float maximum = slot.unknown_0008 * ClientSine(sample * 9); + const float maximum = slot.maxHeight * ClientSine(sample * 9); - if (slot.values[sample] > maximum) { - slot.values[sample] = maximum; - slot.finished[sample] = 1; + if (slot.height[sample] > maximum) { + slot.height[sample] = maximum; + slot.flag1[sample] = 1; } - if (slot.values[sample] < 0.0f) { - slot.values[sample] = 0.0f; + if (slot.height[sample] < 0.0f) { + slot.height[sample] = 0.0f; } } } @@ -758,10 +786,10 @@ bool EffectParticle::UpdateStoneCurse() { // OFFSET: 0x00BB65C0 // FUNCTION: fn_19776 void EffectParticle::UpdateBeginSpell() { - const RagEffectState* parent = this->state.parent; - this->state.origin.x = std::bit_cast(parent->unknown_013C[3]) + this->state.velocity.x; - this->state.origin.y = std::bit_cast(parent->unknown_013C[4]) + this->state.velocity.y; - this->state.origin.z = std::bit_cast(parent->unknown_013C[5]) + this->state.velocity.z; + const AttachedEffect* master = this->state.master; + this->state.origin.x = master->render.position[0] + this->state.velocity.x; + this->state.origin.y = master->render.position[1] + this->state.velocity.y; + this->state.origin.z = master->render.position[2] + this->state.velocity.z; float base; @@ -815,41 +843,218 @@ void EffectParticle::UpdateBeginSpell() { // OFFSET: 0x00BBAB10 // FUNCTION: fn_19790 void EffectParticle::PrimBottom2() { - const RagEffectState* parent = this->state.parent; - this->render.position[0] = std::bit_cast(parent->unknown_013C[3]); - this->render.position[1] = std::bit_cast(parent->unknown_013C[4]); - this->render.position[2] = std::bit_cast(parent->unknown_013C[5]); + const AttachedEffect* master = this->state.master; + this->render.position[0] = master->render.position[0]; + this->render.position[1] = master->render.position[1]; + this->render.position[2] = master->render.position[2]; for (int index = 0; index < 4; ++index) { EffectPrimitiveSlot& slot = this->state.slots[index]; - if (slot.active != 1) { + if (slot.life != 1) { continue; } - ++slot.frame; + ++slot.process; - if (slot.values[0] == 1.0f) { - slot.displayAngle += 4; + if (slot.height[0] == 1.0f) { + slot.fullDisplayAngle += 4; } else { - slot.displayAngle += 2; + slot.fullDisplayAngle += 2; } - if (slot.displayAngle >= 360) { - slot.displayAngle -= 360; + if (slot.fullDisplayAngle >= 360) { + slot.fullDisplayAngle -= 360; } - slot.unknown_0008 = 20.0f + 10.0f * ClientSine(slot.displayAngle); + slot.maxHeight = 20.0f + 10.0f * ClientSine(slot.fullDisplayAngle); + } +} + +// OFFSET: 0x00BBABC0 +// FUNCTION: fn_19791 +void EffectParticle::PrimBottom() { + EffectPrimitiveSlot* slots = this->state.slots; + FaceCamera(slots); + + const AttachedEffect* master = this->state.master; + this->render.position[0] = master->render.position[0]; + this->render.position[1] = master->render.position[1]; + this->render.position[2] = master->render.position[2]; + + EffectPrimitiveSlot& slot = slots[0]; + + if (slot.life != 1) { + return; + } + + ++slot.process; + + if (slot.process % 6 == 0) { + for (int index = 3; index > 0; --index) { + EffectPrimitiveSlot& trail = slots[index]; + const EffectPrimitiveSlot& previous = slots[index - 1]; + + if (trail.life > 0) { + trail.fullDisplayAngle = previous.fullDisplayAngle; + trail.distance = previous.distance + 0.5f; + trail.height[8] = previous.height[8]; + trail.alphaB = previous.alphaB - 50; + + if (trail.alphaB < 0) { + trail.alphaB = 0; + } + + trail.vecBNow = previous.vecBNow; + trail.fullDisplayAngle = previous.fullDisplayAngle; + trail.flag1[0] = previous.flag1[0]; + trail.flag1[2] = 2; + trail.height[7] = 0; + } + } + } + + slot.height[8] += 5.0f; + + if (slot.height[8] >= 360.0f) { + slot.height[8] -= 360.0f; + } + + ++slot.rotStart; + + if (slot.rotStart >= 360) { + slot.rotStart -= 360; + } + + slot.vecBNow.x = this->render.position[0]; + slot.vecBNow.z = this->render.position[2]; + slot.vecBPre.y = this->render.position[1] - 6.0f; + slot.vecBNow.y = slot.vecBPre.y + 3.0f * ClientSine(slot.rotStart); + + if (slot.flag1[2] == 7) { + slot.fullDisplayAngle += 10; + + if (slot.fullDisplayAngle >= 360) { + slot.fullDisplayAngle -= 360; + } + } +} + +// OFFSET: 0x00BBAF30 +// FUNCTION: fn_19792 +void EffectParticle::PrimBottomOut() { + FaceCamera(this->state.slots); + + const AttachedEffect* master = this->state.master; + this->render.position[0] = master->render.position[0]; + this->render.position[1] = master->render.position[1]; + this->render.position[2] = master->render.position[2]; + + for (int index = 0; index < 4; ++index) { + EffectPrimitiveSlot& slot = this->state.slots[index]; + + if (slot.life != 1) { + continue; + } + + ++slot.process; + + if (slot.process <= 10) { + slot.alphaB += 25; + } else { + slot.alphaB -= 5; + + if (slot.alphaB <= 0) { + slot.alphaB = 0; + slot.process = 0; + slot.vecBNow.y = slot.flag1[1] == 0 ? this->render.position[1] : this->render.position[1] - 5.0f; + } + + slot.vecBNow.y -= 0.2f; + } + + slot.height[8] += 5.0f; + + if (slot.height[8] >= 360.0f) { + slot.height[8] -= 360.0f; + } + + if (slot.flag1[1] == 1) { + slot.fullDisplayAngle += 5; + + if (slot.fullDisplayAngle >= 360) { + slot.fullDisplayAngle -= 360; + } + } + } +} + +// OFFSET: 0x00BBB120 +// FUNCTION: fn_19793 +void EffectParticle::PrimBubbleDrop() { + EffectPrimitiveSlot* slots = this->state.slots; + FaceCamera(slots); + + const AttachedEffect* master = this->state.master; + this->render.position[0] = master->render.position[0]; + this->render.position[1] = master->render.position[1]; + this->render.position[2] = master->render.position[2]; + + EffectPrimitiveSlot& slot = slots[0]; + + if (slot.life != 1) { + return; + } + + ++slot.process; + + if (slot.process <= 0) { + return; + } + + if (slot.process > 3) { + FollowTrail(slots[3], slots[2], 25); + } + + if (slot.process > 2) { + FollowTrail(slots[2], slots[1], 25); + } + + if (slot.process > 1) { + FollowTrail(slots[1], slot, 100); + } + + if (slot.process <= 10) { + slot.alphaB += 20; + } + + if (slot.process < slot.flag1[4]) { + slot.fullDisplayAngle -= 15; + + if (slot.fullDisplayAngle < 0) { + slot.fullDisplayAngle += 360; + } + + slot.vecBPre.y += slot.height[1]; + slot.vecBNow = { + this->render.position[0] + slot.vecBPre.x, this->render.position[1] + slot.vecBPre.y, this->render.position[2] + slot.vecBPre.z + }; + } else { + slot.alphaB -= 10; + + if (slot.alphaB < 0) { + slot.alphaB = 0; + } } } // OFFSET: 0x00BB6F30 // FUNCTION: fn_19779 bool EffectParticle::UpdateHit6() { - const RagEffectState* parent = this->state.parent; - this->state.origin.x = std::bit_cast(parent->unknown_013C[3]) + this->state.velocity.x; - this->state.origin.y = std::bit_cast(parent->unknown_013C[4]) + this->state.velocity.y; - this->state.origin.z = std::bit_cast(parent->unknown_013C[5]) + this->state.velocity.z; + const AttachedEffect* master = this->state.master; + this->state.origin.x = master->render.position[0] + this->state.velocity.x; + this->state.origin.y = master->render.position[1] + this->state.velocity.y; + this->state.origin.z = master->render.position[2] + this->state.velocity.z; const int frame = this->state.unknown_01E0; @@ -930,7 +1135,7 @@ bool EffectParticle::UpdateHit6() { this->render.position[2] = this->state.offset.z + this->state.origin.z; if ((this->state.flags & 0x100u) != 0) { - if (3.0f >= this->HorizontalDistance(this->state.parent->offset[0], this->state.parent->offset[2])) { + if (3.0f >= this->HorizontalDistance(this->state.master->state.offset[0], this->state.master->state.offset[2])) { return false; } } @@ -948,10 +1153,10 @@ bool EffectParticle::UpdateHit6() { // FUNCTION: fn_19780 bool EffectParticle::UpdateCoin() { CGameMode* mode = static_cast(g_world.ActiveGameMode()); - const RagEffectState* parent = this->state.parent; - this->state.origin.z = std::bit_cast(parent->unknown_013C[5]) + this->state.velocity.z; - this->state.origin.y = std::bit_cast(parent->unknown_013C[4]) + this->state.velocity.y; - this->state.origin.x = std::bit_cast(parent->unknown_013C[3]) + this->state.velocity.x; + const AttachedEffect* master = this->state.master; + this->state.origin.z = master->render.position[2] + this->state.velocity.z; + this->state.origin.y = master->render.position[1] + this->state.velocity.y; + this->state.origin.x = master->render.position[0] + this->state.velocity.x; this->state.unknown_02EC = this->state.unknown_02F0 + this->state.unknown_02EC; const float speed = this->state.unknown_02C0 + this->state.unknown_02BC; @@ -1082,8 +1287,8 @@ bool EffectParticle::UpdateDetoxication() { // OFFSET: 0x00BB7EC0 // FUNCTION: fn_19782 bool EffectParticle::UpdateExit() { - const RagEffectState* parent = this->state.parent; - this->state.origin = {std::bit_cast(parent->unknown_013C[3]), std::bit_cast(parent->unknown_013C[4]), std::bit_cast(parent->unknown_013C[5])}; + const AttachedEffect* master = this->state.master; + this->state.origin = {master->render.position[0], master->render.position[1], master->render.position[2]}; const float lateral = this->state.unknown_033C + this->state.unknown_0330; float vertical = this->state.unknown_02EC + this->state.unknown_02F0; @@ -1146,10 +1351,10 @@ bool EffectParticle::UpdateExit() { // OFFSET: 0x00BB8180 // FUNCTION: fn_19783 bool EffectParticle::UpdateWarp() { - const RagEffectState* parent = this->state.parent; + const AttachedEffect* master = this->state.master; this->state.origin = { - std::bit_cast(parent->unknown_013C[3]) + this->state.velocity.x, std::bit_cast(parent->unknown_013C[4]) + this->state.velocity.y, - std::bit_cast(parent->unknown_013C[5]) + this->state.velocity.z + master->render.position[0] + this->state.velocity.x, master->render.position[1] + this->state.velocity.y, + master->render.position[2] + this->state.velocity.z }; const int angle = static_cast(this->state.unknown_0314); diff --git a/Source/Contents/Effects/EffectPrimitiveRendering.cpp b/Source/Contents/Effects/EffectPrimitiveRendering.cpp index b70edb2..4f20145 100644 --- a/Source/Contents/Effects/EffectPrimitiveRendering.cpp +++ b/Source/Contents/Effects/EffectPrimitiveRendering.cpp @@ -1,4 +1,5 @@ #include "Contents/Effects/EffectPrimitiveRendering.h" +#include "Contents/Effects/AttachedEffect.h" #include "Contents/Effects/EffectPrimitiveState.h" #include "Contents/Effects/EffectVisibility.h" #include "Contents/Actors/RenderObjectState.h" @@ -86,7 +87,7 @@ void DrawEffectPrimitive15(EffectPrimitiveState& particle, const RenderObjectSta void RenderEffectPrimitive15( EffectPrimitiveState& particle, const RenderObjectState& render, EffectPrimitiveRenderContext& context, const EffectVisibilityContext& visibility ) { - if (particle.parent && PartyEffectVisible(*particle.parent, visibility)) { + if (particle.master && PartyEffectVisible(particle.master->state, visibility)) { DrawEffectPrimitive15(particle, render, context); } } diff --git a/Source/Contents/Effects/EffectPrimitiveState.cpp b/Source/Contents/Effects/EffectPrimitiveState.cpp index d8f412c..311704a 100644 --- a/Source/Contents/Effects/EffectPrimitiveState.cpp +++ b/Source/Contents/Effects/EffectPrimitiveState.cpp @@ -1,5 +1,5 @@ #include "Contents/Effects/EffectPrimitiveState.h" -#include "Contents/Effects/RagEffectState.h" +#include "Contents/Effects/AttachedEffect.h" #include "Contents/Actors/RenderObjectState.h" #include "Utility/Math/AffineMatrix.h" @@ -10,7 +10,7 @@ // FUNCTION: fn_19762 EffectPrimitiveSlot::EffectPrimitiveSlot() { for (int i = 0; i < 4; ++i) { - this->unknown_0084[i] = {0.0f, 0.0f, 0.0f}; + this->vectors[i] = {0.0f, 0.0f, 0.0f}; } } @@ -29,7 +29,7 @@ EffectPrimitiveState::EffectPrimitiveState(RenderObjectState& render) { this->unknown_0158 = 0; this->unknown_015C = 0; this->unknown_0138 = 0; - this->parent = nullptr; + this->master = nullptr; this->unknown_01D4 = 0; this->unknown_01D8 = 0; this->unknown_01E0 = 0; @@ -146,8 +146,8 @@ EffectPrimitiveState::EffectPrimitiveState(RenderObjectState& render) { // INLINED EffectPrimitiveState::~EffectPrimitiveState() { - if (this->parent) { - auto& related = this->parent->related; + if (this->master) { + auto& related = this->master->state.related; for (auto entry = related.begin(); entry != related.end(); ++entry) { if (*entry == this) { @@ -165,16 +165,16 @@ EffectPrimitiveState::~EffectPrimitiveState() { // OFFSET: 0x00BAE460 // FUNCTION: fn_19765 void EffectPrimitiveState::GetColorOverride(unsigned char* red, unsigned char* green, unsigned char* blue) { - if (this->parent->unknown_11C88[0] != -1) { - *red = static_cast(this->parent->unknown_11C88[0]); + if (this->master->state.unknown_11C88[0] != -1) { + *red = static_cast(this->master->state.unknown_11C88[0]); } - if (this->parent->unknown_11C88[1] != -1) { - *green = static_cast(this->parent->unknown_11C88[1]); + if (this->master->state.unknown_11C88[1] != -1) { + *green = static_cast(this->master->state.unknown_11C88[1]); } - if (this->parent->unknown_11C88[2] != -1) { - *blue = static_cast(this->parent->unknown_11C88[2]); + if (this->master->state.unknown_11C88[2] != -1) { + *blue = static_cast(this->master->state.unknown_11C88[2]); } } @@ -198,15 +198,15 @@ void __stdcall CalculateVectorAngle(float x, float y, std::int16_t* angle) { // OFFSET: 0x00BAE5D0 // FUNCTION: fn_19767 void EffectPrimitiveState::Initialize( - RenderObjectState& render, RagEffectState& parent, const RenderObjectState& parentRender, int type, float x, float y, float z + RenderObjectState& render, AttachedEffect& master, const RenderObjectState& masterRender, int type, float x, float y, float z ) { const float first = x + this->offset.x; - this->parent = &parent; + this->master = &master; this->type = type; this->offset.x = first; this->offset.y = y + this->offset.y; this->offset.z = z + this->offset.z; - this->origin = {parentRender.position[0], parentRender.position[1], parentRender.position[2]}; + this->origin = {masterRender.position[0], masterRender.position[1], masterRender.position[2]}; render.position[0] = this->origin.x; render.position[1] = this->origin.y; render.position[2] = this->origin.z; diff --git a/Source/Contents/Effects/EffectPrimitiveState.h b/Source/Contents/Effects/EffectPrimitiveState.h index d41ed98..662232e 100644 --- a/Source/Contents/Effects/EffectPrimitiveState.h +++ b/Source/Contents/Effects/EffectPrimitiveState.h @@ -8,7 +8,7 @@ #include #include "Contents/Actors/RenderObjectState.h" -struct RagEffectState; +struct AttachedEffect; class CTexture; struct EffectVector3 { @@ -33,34 +33,42 @@ struct EffectPrimitiveRecord { struct EffectPrimitiveSlot { EffectPrimitiveSlot(); - /* 0000 */ unsigned char active; + /* 0000 */ unsigned char life; /* 0001 */ unsigned char unknown_0001; - /* 0002 */ std::int16_t unknown_0002; - /* 0004 */ std::int16_t unknown_0004; - /* 0006 */ std::int16_t displayAngle; - /* 0008 */ float unknown_0008; - /* 000C */ int frame; - /* 0010 */ std::int16_t angle; + /* 0002 */ std::int16_t alphaB; + /* 0004 */ std::int16_t alphaT; + /* 0006 */ std::int16_t fullDisplayAngle; + /* 0008 */ float maxHeight; + /* 000C */ int process; + /* 0010 */ std::int16_t rotStart; /* 0012 */ unsigned char unknown_0012[2]; - /* 0014 */ float values[21]; - /* 0068 */ unsigned char finished[21]; + /* 0014 */ float height[21]; + /* 0068 */ unsigned char flag1[21]; /* 007D */ unsigned char unknown_007D; - /* 007E */ std::int16_t unknown_007E; - /* 0080 */ float unknown_0080; - /* 0084 */ EffectVector3 unknown_0084[4]; + /* 007E */ std::int16_t riseAngle; + /* 0080 */ float distance; + union { + /* 0084 */ EffectVector3 vectors[4]; + struct { + /* 0084 */ EffectVector3 vecBNow; + /* 0090 */ EffectVector3 vecTNow; + /* 009C */ EffectVector3 vecBPre; + /* 00A8 */ EffectVector3 vecTPre; + }; + }; }; struct EffectPrimitiveState { explicit EffectPrimitiveState(RenderObjectState& render); ~EffectPrimitiveState(); - void Initialize(RenderObjectState& render, RagEffectState& parent, const RenderObjectState& parentRender, int type, float x, float y, float z); + void Initialize(RenderObjectState& render, AttachedEffect& master, const RenderObjectState& masterRender, int type, float x, float y, float z); void UpdateColor(); void InitializeRecords(const RenderObjectState& render); void GetColorOverride(unsigned char* red, unsigned char* green, unsigned char* blue); /* 0138 */ std::uint32_t unknown_0138; /* 013C */ std::uint32_t unknown_013C; - /* 0140 */ RagEffectState* parent; + /* 0140 */ AttachedEffect* master; /* 0144 */ CTexture** textures; /* 0148 */ std::uint32_t unknown_0148; /* 014C */ EffectVector3 unknown_014C; @@ -185,9 +193,9 @@ static_assert(offsetof(EffectVector3, z) == 0x8); static_assert(offsetof(EffectPrimitiveRecord, corners) == 0xC); static_assert(offsetof(EffectPrimitiveRecord, matrix) == 0x48); static_assert(offsetof(EffectPrimitiveRecord, color) == 0x88); -static_assert(offsetof(EffectPrimitiveSlot, values) == 0x14); -static_assert(offsetof(EffectPrimitiveSlot, finished) == 0x68); -static_assert(offsetof(EffectPrimitiveSlot, unknown_0084) == 0x84); +static_assert(offsetof(EffectPrimitiveSlot, height) == 0x14); +static_assert(offsetof(EffectPrimitiveSlot, flag1) == 0x68); +static_assert(offsetof(EffectPrimitiveSlot, vecBNow) == 0x84); static_assert(sizeof(EffectPrimitiveState) == 0xF8E0); #endif diff --git a/Source/Contents/Effects/PartyEffect.cpp b/Source/Contents/Effects/PartyEffect.cpp index db6090e..5ffd50d 100644 --- a/Source/Contents/Effects/PartyEffect.cpp +++ b/Source/Contents/Effects/PartyEffect.cpp @@ -1,7 +1,7 @@ +#include "Contents/Effects/AttachedEffect.h" #include "Contents/Effects/EffectActor.h" #include "Contents/Effects/EffectParticle.h" #include "Contents/Effects/PartyEffect.h" -#include "Contents/Effects/RagEffectState.h" #include "Contents/Ground/TerrainHeight.h" #include "Render/Textures/TextureCache.h" @@ -10,9 +10,12 @@ // OFFSET: 0x00C2CF20 // FUNCTION: fn_20088 bool UpdatePartyEffect( - RagEffectState& effect, RenderObjectState& render, ResourceMgr& resources, JobNames& names, std::list& worldObjects, TextureCache& textures, - const TerrainHeight& ground, int updateTick + AttachedEffect& self, ResourceMgr& resources, JobNames& names, std::list& worldObjects, TextureCache& textures, const TerrainHeight& ground, + int updateTick ) { + RagEffectState& effect = self.state; + RenderObjectState& render = self.render; + if (!effect.parent) { return false; } @@ -36,7 +39,7 @@ bool UpdatePartyEffect( } if (effect.unknown_0178 == 0) { - EffectParticle* particle = CreateEffectParticle(effect, render, 15, 0, 0, 0, resources, names, worldObjects); + EffectParticle* particle = CreateEffectParticle(self, render, 15, 0, 0, 0, resources, names, worldObjects); effect.unknown_11C84 = &particle->state; EffectPrimitiveState& state = *effect.unknown_11C84; state.lifetime = 9999; diff --git a/Source/Contents/Effects/PartyEffect.h b/Source/Contents/Effects/PartyEffect.h index 17e9a15..53f4aa4 100644 --- a/Source/Contents/Effects/PartyEffect.h +++ b/Source/Contents/Effects/PartyEffect.h @@ -3,8 +3,7 @@ #include -struct RagEffectState; -struct RenderObjectState; +struct AttachedEffect; struct EffectParticle; struct JobNames; struct TerrainHeight; @@ -13,8 +12,8 @@ class TextureCache; class CRenderObject; bool UpdatePartyEffect( - RagEffectState& effect, RenderObjectState& render, ResourceMgr& resources, JobNames& names, std::list& worldObjects, TextureCache& textures, - const TerrainHeight& ground, int updateTick + AttachedEffect& self, ResourceMgr& resources, JobNames& names, std::list& worldObjects, TextureCache& textures, const TerrainHeight& ground, + int updateTick ); #endif diff --git a/Source/Contents/NavigationManager.h b/Source/Contents/NavigationManager.h index 7c0fcc0..c7515d5 100644 --- a/Source/Contents/NavigationManager.h +++ b/Source/Contents/NavigationManager.h @@ -20,6 +20,8 @@ class GroundGeometry; struct NaviCell { /* 0000 */ int x; /* 0004 */ int y; + + int GetY() const; }; struct NaviLink { @@ -34,6 +36,12 @@ struct NaviDataEntry { }; struct NaviRouteLine { + // OFFSET: 0x00B92F10 + // FUNCTION: fn_19558 + ~NaviRouteLine() { + this->text.clear(); + } + /* 0000 */ int kind; /* 0004 */ int x; /* 0008 */ int y; @@ -79,6 +87,9 @@ public: virtual const char* GetTitle() const; virtual NaviCell* GetCell(NaviCell* out) const; virtual CNaviInfo* GetInfo() const; + + bool HasId(const unsigned int& id) const; + bool IsNamed(const char* name) const; }; // VTABLE: vt_01778 @@ -105,6 +116,17 @@ public: CNaviInfo* GetInfo() const override; }; +#pragma pack(push, 1) +struct NaviObjectRecord { + /* 0000 */ int type; + /* 0004 */ int index; + /* 0008 */ int cellX; + /* 000C */ int cellY; + /* 0010 */ std::int16_t nameLength; + /* 0012 */ char name[1]; +}; +#pragma pack(pop) + class CNaviInfo { public: ~CNaviInfo(); @@ -118,10 +140,12 @@ public: std::vector::iterator LinksEnd(); int GetCellY() const; int GetIndex() const; + int GetCellX() const; CNavi_Link* FindLink(unsigned int key, bool byId); CNavi_Object* FindNpc(unsigned int key, bool byId); CNavi_Object* FindNpcAt(int cellX, int cellY); std::vector* Npcs(); + CNavi_Link* FindMatchingLink(CNavi_Link link); /* 0000 */ int index; /* 0004 */ int type; @@ -137,12 +161,22 @@ public: struct NavigationTarget { NavigationTarget() : type(-1), object(nullptr), x(-1), y(-1) {} + + // INLINED + NavigationTarget(int type, void* object, int x, int y, std::string name) { + this->type = type; + this->object = object; + this->x = x; + this->y = y; + this->name = name; + } + ~NavigationTarget(); /* 0000 */ int type; /* 0004 */ void* object; - /* 0008 */ int x; - /* 000C */ int y; + /* 0008 */ int x = 0; + /* 000C */ int y = 0; /* 0010 */ std::string name; }; @@ -155,6 +189,8 @@ public: CNaviInfo* GetInfo(); NaviCell* GetCell(NaviCell* out); void Clear(); + void ClearAll(); + CNavi_Link* FindLink(unsigned int key, bool byId); int FindRoute(std::vector* route, bool direct, bool allowGates, bool includeObjects, bool allowWarps, int side); void ResetLinkDepths(); @@ -190,6 +226,9 @@ public: std::string DestinationTitle(); float FitToSquare(int* offsetX, int* offsetY, int width, int height, float size) const; void LoadSettings(); + bool BuildRoute(int startX, int startY, int destX, int destY, bool collectCells); + NaviCell* MapCellToView(NaviCell* out, const char* mapName, int cellX, int cellY, float size); + std::string RouteText(); void Reset(bool clearAll); void ClearRouteNodes(); void ClearNodePaths(); @@ -197,6 +236,17 @@ public: bool UpdateGround(); bool PlayerCell(int* column, int* row); bool LoadStates(std::vector packet); + bool AddState(const std::vector& tokens); + bool ParseFlags(const char* text, bool* first, bool* second, bool* third); + NaviCell ParseCell(const char* text); + std::vector WriteTarget(NavigationTarget target); + std::vector WriteObject(CNavi_Object* object); + void ClearSavedTarget(); + void ClearCurrentObject(); + void ClearStates(); + void ClearNodes(); + int FindNodePath(unsigned int index); + void SelectNodePath(std::string name); /* 0000 */ CNaviData data; /* 004C */ CPathFinder pathFinder; @@ -254,6 +304,7 @@ static_assert(offsetof(CNaviInfo, visited) == 0x40); static_assert(offsetof(NaviDataEntry, cost) == 0x08); static_assert(offsetof(CNavi_Link, linkedLinks) == 0x8C); static_assert(sizeof(NaviCell) == 0x08); +static_assert(offsetof(NaviObjectRecord, name) == 0x12); static_assert(sizeof(NaviDataEntry) == 0x0C); static_assert(sizeof(NaviRouteLine) == 0x2C); static_assert(offsetof(NaviRouteLine, text) == 0x0C); diff --git a/Source/Contents/NavigationRequest.cpp b/Source/Contents/NavigationRequest.cpp index 99456f1..4ed22a1 100644 --- a/Source/Contents/NavigationRequest.cpp +++ b/Source/Contents/NavigationRequest.cpp @@ -3,6 +3,8 @@ #include "Client/Mode/CGameMode.h" #include "Contents/World/WorldScene.h" +#include + // OFFSET: 0x00B8DF10 // FUNCTION: fn_19461 std::uint16_t CNavi_Object::GetFlag() const { @@ -180,3 +182,18 @@ CNavi_Object* CNaviInfo::FindNpcAt(int cellX, int cellY) { std::vector* CNaviInfo::Npcs() { return &this->npcs; } + +// OFFSET: 0x00B8E010 +// FUNCTION: fn_19465 +CNavi_Link* CNaviInfo::FindMatchingLink(CNavi_Link link) { + const CNavi_Link target = link; + for (CNavi_Link* candidate : this->links) { + if (candidate->cellX == target.cellX && candidate->cellY == target.cellY && candidate->unknown_0080 == target.unknown_0080 && + candidate->unknown_0084 == target.unknown_0084 && candidate->info != nullptr && + std::strcmp(candidate->info->name.c_str(), target.info->name.c_str()) == 0 && candidate->linkedInfo != nullptr && + std::strcmp(candidate->linkedInfo->name.c_str(), target.linkedInfo->name.c_str()) == 0) { + return candidate; + } + } + return nullptr; +} diff --git a/Source/Contents/NavigationRoute.cpp b/Source/Contents/NavigationRoute.cpp index a3f6ab7..8734e9b 100644 --- a/Source/Contents/NavigationRoute.cpp +++ b/Source/Contents/NavigationRoute.cpp @@ -1,6 +1,12 @@ #include "Contents/NavigationManager.h" +#include "Contents/MessageTable.h" +#include "Utility/Text/FormatString.h" #include "Client/Registry.h" +#include "Client/Session.h" +#include "Resource/ResourceMgr.h" +#include "UI/Window/UIWindow.h" +#include "UI/Window/UIWindowMgr.h" #include "Utility/RegistryKey.h" #include @@ -17,6 +23,170 @@ CNaviInfo* CNavigationManager::FindObject(const char* name) const { return nullptr; } +// OFFSET: 0x00B88840 +// FUNCTION: fn_19377 +CNavi_Link* CNaviData::FindLink(unsigned int key, bool byId) { + if (byId) { + for (CNavi_Link* object : this->objects) { + if (object->HasId(key)) { + return object; + } + } + return nullptr; + } + if (this->objects.empty() || key >= this->objects.size()) { + return nullptr; + } + return this->objects[key]; +} + +// OFFSET: 0x00B88910 +// FUNCTION: fn_19379 +int NaviCell::GetY() const { + return this->y; +} + +// OFFSET: 0x00B88930 +// FUNCTION: fn_19381 +void CNaviData::ClearAll() { + this->Clear(); + + for (CNavi_Link*& object : this->objects) { + if (object != nullptr) { + delete object; + } + object = nullptr; + } + this->objects.clear(); + + for (CNaviInfo*& info : this->destinations) { + if (info != nullptr) { + delete info; + } + info = nullptr; + } + this->destinations.clear(); +} + +// OFFSET: 0x00B8CF70 +// FUNCTION: fn_19432 +bool CNavi_Object::HasId(const unsigned int& id) const { + return static_cast(this->id) == id; +} + +// OFFSET: 0x00B8D0D0 +// FUNCTION: fn_19433 +bool CNavi_Object::IsNamed(const char* name) const { + if (name == nullptr) { + return false; + } + return this->name == std::string(name); +} + +// OFFSET: 0x00B8E8E0 +// FUNCTION: fn_19496 +int CNaviInfo::GetCellX() const { + return this->cellX; +} + +// OFFSET: 0x00B97750 +// FUNCTION: fn_19600 +bool CNavigationManager::BuildRoute(int startX, int startY, int destX, int destY, bool collectCells) { + this->movementRoute.points.clear(); + const bool found = this->pathFinder.FindPath(0, startX, startY, 8, 8, destX, destY, 1, this->movementRoute, 15); + this->pathFinder.Reset(); + if (!collectCells) { + return found; + } + + this->cells.clear(); + if (!found) { + return found; + } + + CNaviInfo* info = this->FindObject(g_session.m_navigationPlayerMap); + if (info == nullptr) { + const std::string resolved = ResourceMgr::Instance().aliases.ResolveMapName(g_session.m_navigationPlayerMap); + info = this->FindObject(resolved.c_str()); + if (info == nullptr) { + return found; + } + } + + const int height = info->GetCellY(); + const int width = info->GetCellX(); + int offsetX; + int offsetY; + const float scale = this->FitToSquare(&offsetX, &offsetY, width, height, 126.0f); + for (const MovementRoutePoint& point : this->movementRoute.points) { + NaviCell cell; + cell.x = static_cast(point.x * scale + offsetX); + cell.y = static_cast(126.0f - (point.y * scale + offsetY)); + + bool exists = false; + for (const NaviCell& other : this->cells) { + if (other.x == cell.x && other.y == cell.y) { + exists = true; + break; + } + } + if (!exists) { + this->cells.push_back(cell); + } + } + + if (g_windowMgr.window_4F174 != nullptr) { + g_windowMgr.window_4F174->Invalidate(); + } + return found; +} + +// OFFSET: 0x00B97E90 +// FUNCTION: fn_19603 +NaviCell* CNavigationManager::MapCellToView(NaviCell* out, const char* mapName, int cellX, int cellY, float size) { + CNaviInfo* info = this->FindObject(mapName); + if (info == nullptr) { + const std::string resolved = ResourceMgr::Instance().aliases.ResolveMapName(mapName); + info = this->FindObject(resolved.c_str()); + } + + out->x = -1; + out->y = -1; + if (info != nullptr) { + const int height = info->GetCellY(); + const int width = info->GetCellX(); + int offsetX; + int offsetY; + const float scale = this->FitToSquare(&offsetX, &offsetY, width, height, size); + out->x = static_cast(cellX * scale + offsetX); + out->y = static_cast(static_cast(size) - (cellY * scale + offsetY)); + } + return out; +} + +// OFFSET: 0x00B98630 +// FUNCTION: fn_19608 +std::string CNavigationManager::RouteText() { + char buffer[0x32]; + std::memset(buffer, 0, sizeof(buffer)); + + CNaviInfo* info = this->data.GetInfo(); + if (info != nullptr) { + NaviCell cell; + if (this->data.GetCell(&cell)->x > 0) { + NaviCell cellY; + NaviCell cellX; + const int y = this->data.GetCell(&cellY)->y; + const int x = this->data.GetCell(&cellX)->x; + StringFormat(buffer, sizeof(buffer), static_cast(-1), "[%s] -> %s %d %d", LookupMessage(3585), info->GetName(), x, y); + } else { + StringFormat(buffer, sizeof(buffer), static_cast(-1), "[%s] %s", LookupMessage(3585), info->GetName()); + } + } + + this->routeText.assign(buffer, std::strlen(buffer)); + return this->routeText; +} // OFFSET: 0x00B8D270 // FUNCTION: fn_19434 bool CNaviInfo::HasName(const char* name) const { diff --git a/Source/Contents/NavigationSource.cpp b/Source/Contents/NavigationSource.cpp index 0a8490c..a6feb81 100644 --- a/Source/Contents/NavigationSource.cpp +++ b/Source/Contents/NavigationSource.cpp @@ -2,7 +2,9 @@ #include "Client/Session.h" #include "Resource/ResourceMgr.h" +#include #include +#include #include // GLOBAL: gl_24627 @@ -270,3 +272,282 @@ void CNavigationManager::Reset(bool clearAll) { this->unknown_12A4 = nullptr; this->ClearRouteNodes(); } + +// OFFSET: 0x00B93110 +// FUNCTION: fn_19562 +bool CNavigationManager::AddState(const std::vector& tokens) { + if (tokens.size() < 2) { + return false; + } + + CNaviInfo* info = this->FindObject(tokens[0].c_str()); + if (info == nullptr) { + return false; + } + + const NaviCell cell = this->ParseCell(tokens[1].c_str()); + if (cell.x == -1 || cell.y == -1) { + return false; + } + + std::string name; + for (std::size_t i = 2; i < tokens.size(); ++i) { + name.append(" ", 1); + name.append(tokens[i]); + } + + NavigationTarget target(0, info, cell.x, cell.y, name); + this->states.push_back(target); + return true; +} + +// OFFSET: 0x00B93670 +// FUNCTION: fn_19563 +bool CNavigationManager::ParseFlags(const char* text, bool* first, bool* second, bool* third) { + const char* firstSlash = std::strstr(text, "/"); + if (firstSlash == nullptr) { + return false; + } + + const char* rest = firstSlash + 1; + const char* secondSlash = std::strstr(rest, "/"); + if (secondSlash == nullptr) { + return false; + } + + std::string firstText(text, firstSlash); + if (firstText.empty()) { + return false; + } + + std::string secondText(rest, secondSlash); + if (secondText.empty()) { + return false; + } + + std::string thirdText(secondSlash + 1, text + std::strlen(text)); + if (thirdText.empty()) { + return false; + } + + const int firstValue = std::atoi(firstText.c_str()); + const int secondValue = std::atoi(secondText.c_str()); + const int thirdValue = std::atoi(thirdText.c_str()); + if ((firstValue != 0 && firstValue != 1) || (secondValue != 0 && secondValue != 1) || (thirdValue != 0 && thirdValue != 1)) { + return false; + } + + *first = firstValue == 1; + *second = secondValue == 1; + *third = thirdValue == 1; + return true; +} + +// OFFSET: 0x00B938F0 +// FUNCTION: fn_19564 +NaviCell CNavigationManager::ParseCell(const char* text) { + NaviCell cell; + const char* slash; + if (text == nullptr || (slash = std::strstr(text, "/")) == nullptr) { + cell.x = -1; + cell.y = -1; + return cell; + } + + std::string xText(text, slash); + if (xText.empty()) { + cell.x = -1; + cell.y = -1; + return cell; + } + + std::string yText(slash + 1, text + std::strlen(text)); + if (yText.empty()) { + cell.x = -1; + cell.y = -1; + return cell; + } + + const int x = std::atoi(xText.c_str()); + const int y = std::atoi(yText.c_str()); + if (x >= 0 && x <= 500 && y >= 0 && y <= 500) { + cell.x = x; + cell.y = y; + } else { + cell.x = -1; + cell.y = -1; + } + return cell; +} + +// OFFSET: 0x00B94890 +// FUNCTION: fn_19568 +std::vector CNavigationManager::WriteTarget(NavigationTarget target) { + std::vector buffer; + if (target.object == nullptr) { + return buffer; + } + + buffer.resize(sizeof(int), '\0'); + *reinterpret_cast(buffer.data()) = target.type; + + if (target.type == 1 || target.type == 0) { + std::string name = static_cast(target.object)->GetName(); + buffer.insert(buffer.end(), name.begin(), name.end()); + } else if (target.type == 3) { + std::vector object = this->WriteObject(static_cast(target.object)); + buffer.insert(buffer.end(), object.begin(), object.end()); + } else if (target.type == 2) { + std::vector object = this->WriteObject(static_cast(target.object)); + buffer.insert(buffer.end(), object.begin(), object.end()); + } + + return buffer; +} + +// OFFSET: 0x00B94B50 +// FUNCTION: fn_19569 +std::vector CNavigationManager::WriteObject(CNavi_Object* object) { + std::vector buffer; + if (object != nullptr) { + const int type = object->GetType(); + const int index = object->GetIndex(); + NaviCell cell; + object->GetCell(&cell); + std::string name = object->GetName(); + std::string title = object->GetTitle(); + std::string map = object->GetInfo()->GetName(); + + buffer.resize(map.size() + 0x16 + title.size() + name.size(), '\0'); + + NaviObjectRecord* record = reinterpret_cast(buffer.data()); + record->type = type; + record->index = index; + record->cellX = cell.x; + record->cellY = cell.y; + + const std::size_t nameLength = static_cast(name.size()); + reinterpret_cast(buffer.data())->nameLength = static_cast(name.size()); + std::memcpy(buffer.data() + 0x12, name.c_str(), nameLength); + + const std::size_t titleLength = static_cast(title.size()); + *reinterpret_cast(buffer.data() + nameLength + 0x12) = static_cast(title.size()); + std::memcpy(buffer.data() + nameLength + 0x14, title.c_str(), titleLength); + + const std::size_t offset = nameLength + 0x14 + titleLength; + *reinterpret_cast(buffer.data() + offset) = static_cast(map.size()); + std::memcpy(buffer.data() + offset + 2, map.c_str(), static_cast(map.size())); + } + + return buffer; +} + +// OFFSET: 0x00B950B0 +// FUNCTION: fn_19571 +void CNavigationManager::ClearSavedTarget() { + this->savedTarget = NavigationTarget(); + this->unknown_115C = false; + this->routeResult = 0; + this->nodes.clear(); + this->selectedNode = -1; + this->movementRoute.segment = 0; + this->unknown_117C = false; + this->ground = nullptr; + this->cells.clear(); + this->unknown_1258 = 1; +} + +// OFFSET: 0x00B95260 +// FUNCTION: fn_19572 +void CNavigationManager::ClearCurrentObject() { + this->currentTarget.type = 0; + this->currentTarget.object = nullptr; +} + +// OFFSET: 0x00B95AD0 +// FUNCTION: fn_19576 +void CNavigationManager::ClearStates() { + this->states.clear(); +} + +// OFFSET: 0x00B95AF0 +// FUNCTION: fn_19577 +void CNavigationManager::ClearNodes() { + this->nodes.clear(); +} + +// OFFSET: 0x00B95DC0 +// FUNCTION: fn_19579 +int CNavigationManager::FindNodePath(unsigned int index) { + if (this->states[index].type == 1 || this->states[index].type == 0) { + return -1; + } + + for (unsigned int i = 0; i < this->nodePaths.size(); ++i) { + if (this->nodePaths[i].empty()) { + return -1; + } + + const unsigned int first = this->nodePaths[i][0]; + if (first >= this->states.size()) { + return -1; + } + + NavigationTarget& target = this->states[index]; + if (target.type == 3 && target.x == -1000 && target.y == -1000) { + if (this->states[first].type != 1) { + continue; + } + + CNaviInfo* info = static_cast(this->states[first].object); + if (info == nullptr) { + return -1; + } + + CNavi_Object* object = static_cast(target.object); + if (object == nullptr || object->GetInfo() == nullptr) { + return -1; + } + + if (std::strcmp(info->GetName(), object->GetInfo()->GetName()) == 0) { + return i; + } + } + + NavigationTarget& other = this->states[first]; + if (other.type == this->states[index].type) { + CNavi_Object* otherObject = static_cast(other.object); + if (otherObject == nullptr) { + return -1; + } + + CNavi_Object* object = static_cast(this->states[index].object); + if (object == nullptr) { + return -1; + } + + if (std::strcmp(otherObject->GetName(), object->GetName()) == 0) { + return i; + } + } + } + + return -1; +} + +// OFFSET: 0x00B95FB0 +// FUNCTION: fn_19580 +void CNavigationManager::SelectNodePath(std::string name) { + if (name.empty() || this->nodePaths.size() < 2) { + return; + } + + std::vector first = this->nodePaths[0]; + for (unsigned int i = 0; i < this->nodePaths.size(); ++i) { + if (this->states[this->nodePaths[i][0]].name == name) { + this->nodePaths[0] = this->nodePaths[i]; + this->nodePaths[i] = first; + break; + } + } +} diff --git a/Source/Contents/Skills/SkillInfo.cpp b/Source/Contents/Skills/SkillInfo.cpp index f45499a..cfbe6b6 100644 --- a/Source/Contents/Skills/SkillInfo.cpp +++ b/Source/Contents/Skills/SkillInfo.cpp @@ -1,10 +1,82 @@ +#include "Client/Mode/ActiveGameMode.h" +#include "Client/Mode/CGameMode.h" +#include "Contents/Actors/GameActor.h" +#include "Contents/Configuration/ServiceOptions.h" +#include "Contents/Effects/EffectActor.h" +#include "Contents/Effects/EffectLists.h" +#include "Contents/Effects/EffectPrimitiveState.h" +#include "Contents/MessageTable.h" #include "Contents/Skills/SkillId.h" #include "Contents/Skills/SkillInfo.h" +#include "Contents/Sound/SoundMgr.h" #include "Contents/UserData/NormalItemSlots.h" +#include "Contents/World/WorldScene.h" +#include "Resource/World/TerrainResource.h" #include "Script/LuaScript.h" + #include +class CEfstAction_ChangeSize : public CEfstAction { +public: + void Slot01(int efstId, int argument2, int argument3, int argument4, CRenderObject* actor) override; + void Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) override; +}; + +class CEfstAction_GenderChange : public CEfstAction { +public: + void Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) override; +}; + +class CEfstAction_ShowEffect : public CEfstAction { +public: + void Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) override; +}; + +class CSkillAction_RA_UNLIMIT : public CSkillAction { +public: + void Slot08(int level, CGameActor* source) override; +}; + +class CEfstAction_UNLIMIT : public CEfstAction { +public: + void Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) override; +}; + +class CEfstAction_SHADOW_WEAPON : public CEfstAction { +public: + EfstActionMessage Slot03() override; + EfstActionMessage Slot04() override; +}; + +class CEfstAction_DANCING_KNIFE : public CEfstAction { +public: + void Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) override; +}; + +class CEfstAction_SHADOW_EXCEED : public CEfstAction { +public: + void Slot01(int efstId, int argument2, int argument3, int argument4, CRenderObject* actor) override; + void Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) override; + EfstActionMessage Slot03() override; + EfstActionMessage Slot04() override; +}; + +class CSkillAction_ABC_CHASING_BREAK : public CSkillAction { +public: + bool Slot07() override; +}; + +class CSkillAction_ABC_CHASING_SHOT : public CSkillAction { +public: + bool Slot07() override; +}; + +class CSkillAction_ABC_ABYSS_FLAME_ATK : public CSkillAction { +public: + bool Slot03(int argument) override; +}; + // GLOBAL: gl_24640 CSkillActionMgr* g_skillActionMgr; @@ -404,3 +476,202 @@ bool ShowsCastEffect(int skillId) { return true; } } + +// OFFSET: 0x007747B0 +// FUNCTION: fn_08327 +void CEfstAction_ChangeSize::Slot01(int efstId, int argument2, int argument3, int argument4, CRenderObject* actor) { + if (actor) { + g_effectMgr->Retire(actor->render.accountId, 2374); + } +} + +// OFFSET: 0x007747E0 +// FUNCTION: fn_08328 +void CEfstAction_ChangeSize::Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) { + this->Slot00(efstId, value, 0, 0, actor, false); +} + +// OFFSET: 0x00774E20 +// FUNCTION: fn_08331 +void CEfstAction_GenderChange::Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) { + this->Slot00(efstId, value, 0, 0, actor, false); +} + +// OFFSET: 0x00774F30 +// FUNCTION: fn_08334 +void CEfstAction_ShowEffect::Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) { + this->Slot00(efstId, value, 0, 0, actor, false); +} + +// OFFSET: 0x00774FB0 +// FUNCTION: fn_08335 +void CSkillAction_RA_UNLIMIT::Slot08(int level, CGameActor* source) { + g_soundMgr->PlayWave("effect\\EF_BeginSpell.wav", 0.0f, 0.0f, 0.0f, 250, 40, 1.0f, 0); +} + +// OFFSET: 0x007756D0 +// FUNCTION: fn_08339 +void CEfstAction_UNLIMIT::Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) { + this->Slot00(efstId, value, 0, 0, actor, false); +} + +// OFFSET: 0x007764E0 +// FUNCTION: fn_08346 +EfstActionMessage CEfstAction_SHADOW_WEAPON::Slot03() { + return {LookupMessage(3766), 0xFFFF00}; +} + +// OFFSET: 0x00776510 +// FUNCTION: fn_08347 +EfstActionMessage CEfstAction_SHADOW_WEAPON::Slot04() { + return {LookupMessage(3767), 0xFF}; +} + +// OFFSET: 0x00777910 +// FUNCTION: fn_08359 +void CEfstAction_DANCING_KNIFE::Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) { + this->Slot00(efstId, value, 0, 0, actor, false); +} + +// OFFSET: 0x00777E80 +// FUNCTION: fn_08366 +void CEfstAction_SHADOW_EXCEED::Slot01(int efstId, int argument2, int argument3, int argument4, CRenderObject* actor) { + if (!actor) { + return; + } + + auto* effectActor = static_cast(actor); + while (effectActor->state.Attachments().HasId(1004)) { + actor->SendObjectMessage(nullptr, 109, 0, 1004, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } +} + +// OFFSET: 0x00777EE0 +// FUNCTION: fn_08367 +void CEfstAction_SHADOW_EXCEED::Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) { + this->Slot00(efstId, value, 0, 0, actor, true); +} + +// OFFSET: 0x00777F00 +// FUNCTION: fn_08368 +EfstActionMessage CEfstAction_SHADOW_EXCEED::Slot03() { + return {LookupMessage(3762), 0xFFFF00}; +} + +// OFFSET: 0x00777F30 +// FUNCTION: fn_08369 +EfstActionMessage CEfstAction_SHADOW_EXCEED::Slot04() { + return {LookupMessage(3763), 0xFF}; +} + +// OFFSET: 0x00778140 +// FUNCTION: fn_08371 +bool CSkillAction_ABC_CHASING_BREAK::Slot07() { + return true; +} + +// OFFSET: 0x00778580 +// FUNCTION: fn_08375 +bool CSkillAction_ABC_CHASING_SHOT::Slot07() { + return true; +} + +// OFFSET: 0x007794D0 +// FUNCTION: fn_08385 +bool CSkillAction_ABC_ABYSS_FLAME_ATK::Slot03(int argument) { + return true; +} + +// OFFSET: 0x0077ACC0 +// FUNCTION: fn_08404 +std::pair CSkillActionMgr::ActionSlot01(int skillId) { + const auto found = this->m_skillActions.find(skillId); + std::unique_ptr action(found != this->m_skillActions.end() ? found->second() : nullptr); + if (!action) { + return {false, 8}; + } + + return {true, action->Slot01()}; +} + +// OFFSET: 0x0077AD90 +// FUNCTION: fn_08405 +EffectVector3 CellWorldPosition(int column, int row) { + if (!g_world.ActiveGameMode() || !static_cast(g_world.ActiveGameMode())->scene || + !static_cast(g_world.ActiveGameMode())->scene->terrain) { + return EffectVector3{0.0f, 0.0f, 0.0f}; + } + + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + static_cast(g_world.ActiveGameMode())->scene->terrain->Grid().CellOrigin(column, row, x, z); + y = static_cast(g_world.ActiveGameMode())->scene->terrain->Grid().Height(x, z); + return EffectVector3{x + 2.5f, y + 0.0f, z + 2.5f}; +} + +// OFFSET: 0x0077AEA0 +// FUNCTION: fn_08406 +EfstActionMessage CSkillActionMgr::EfstSlot03(int efstId) { + const auto found = this->m_efstActions.find(efstId); + std::unique_ptr action(found != this->m_efstActions.end() ? found->second() : nullptr); + if (!action) { + return {nullptr, 0xFFFF00}; + } + + return action->Slot03(); +} + +// OFFSET: 0x0077AF80 +// FUNCTION: fn_08407 +EfstActionMessage CSkillActionMgr::EfstSlot04(int efstId) { + const auto found = this->m_efstActions.find(efstId); + std::unique_ptr action(found != this->m_efstActions.end() ? found->second() : nullptr); + if (!action) { + return {nullptr, 0xFF}; + } + + return action->Slot04(); +} + +// OFFSET: 0x0077B060 +// FUNCTION: fn_08408 +int CSkillActionMgr::ActionSlot14(int skillId) { + const auto found = this->m_skillActions.find(skillId); + std::unique_ptr action(found != this->m_skillActions.end() ? found->second() : nullptr); + return action ? action->Slot14() : 0; +} + +// OFFSET: 0x0077B210 +// FUNCTION: fn_08409 +std::pair CSkillActionMgr::ActionSlot02(int skillId) { + const auto found = this->m_skillActions.find(skillId); + std::unique_ptr action(found != this->m_skillActions.end() ? found->second() : nullptr); + if (!action) { + return {false, 7}; + } + + return {true, action->Slot02()}; +} + +// OFFSET: 0x0077B2E0 +// FUNCTION: fn_08410 +bool CSkillActionMgr::ActionSlot03(int skillId, int argument) { + const auto found = this->m_skillActions.find(skillId); + std::unique_ptr action(found != this->m_skillActions.end() ? found->second() : nullptr); + return action ? action->Slot03(argument) : false; +} + +// OFFSET: 0x0077B3A0 +// FUNCTION: fn_08411 +bool CSkillActionMgr::ActionSlot04(int skillId) { + const auto found = this->m_skillActions.find(skillId); + std::unique_ptr action(found != this->m_skillActions.end() ? found->second() : nullptr); + return action ? action->Slot04() : false; +} + +// OFFSET: 0x0077B460 +// FUNCTION: fn_08412 +bool CSkillActionMgr::HasEfstAction(int efstId) { + return this->m_efstActions.find(efstId) != this->m_efstActions.end(); +} diff --git a/Source/Contents/Skills/SkillInfo.h b/Source/Contents/Skills/SkillInfo.h index 31733dd..f2b4236 100644 --- a/Source/Contents/Skills/SkillInfo.h +++ b/Source/Contents/Skills/SkillInfo.h @@ -6,16 +6,83 @@ #include #include #include +#include #include -class CEfstAction; +class CGameActor; +class CRenderObject; class CSkillObjectAction; class LuaScript; +struct EffectVector3; struct NormalItemSlot; +struct EfstActionMessage { + /* 0000 */ const char* text; + /* 0004 */ std::uint32_t color; +}; + class CSkillAction { public: - virtual bool Slot00() = 0; + virtual bool Slot00() { + return false; + } + virtual int Slot01() { + return 8; + } + virtual int Slot02() { + return 7; + } + virtual bool Slot03(int argument) { + return false; + } + virtual bool Slot04() { + return false; + } + virtual bool Slot05() { + return false; + } + virtual bool Slot06() { + return false; + } + virtual bool Slot07() { + return false; + } + virtual void Slot08(int level, CGameActor* source) { + } + virtual void Slot09(int argument1, int argument2, int argument3) { + } + virtual void Slot10(int argument1, int argument2, int argument3, int argument4, int argument5) { + } + virtual void Slot11(int level, CGameActor* source, CGameActor* target) { + } + virtual void Slot12(int argument1, int argument2, int argument3, int argument4, int argument5) { + } + virtual bool Slot13() { + return false; + } + virtual int Slot14() { + return 0; + } + virtual void Slot15(int level, CGameActor* source, CGameActor* target) { + } + virtual void Slot16(int level, CGameActor* source, CGameActor* target) { + } +}; + +class CEfstAction { +public: + virtual void Slot00(int efstId, int value, int argument3, int argument4, CRenderObject* actor, bool argument6) { + } + virtual void Slot01(int efstId, int argument2, int argument3, int argument4, CRenderObject* actor) { + } + virtual void Slot02(int efstId, int value, int argument3, int argument4, CRenderObject* actor) { + } + virtual EfstActionMessage Slot03() { + return {nullptr, 0xFFFF00}; + } + virtual EfstActionMessage Slot04() { + return {nullptr, 0xFF}; + } }; struct SkillInfoEntry { @@ -68,6 +135,14 @@ public: static CSkillActionMgr* GetInstance(); bool IsSupportAction(int skillId); + std::pair ActionSlot01(int skillId); + std::pair ActionSlot02(int skillId); + int ActionSlot14(int skillId); + bool ActionSlot03(int skillId, int argument); + bool ActionSlot04(int skillId); + EfstActionMessage EfstSlot03(int efstId); + EfstActionMessage EfstSlot04(int efstId); + bool HasEfstAction(int efstId); }; extern CSkillActionMgr* g_skillActionMgr; @@ -85,6 +160,7 @@ bool IsOptionalSelfTargetSkill(int skillId); bool HidesUseFailureMessage(int skillId); bool IsSupportSkill(int skillId); bool ShowsCastEffect(int skillId); +EffectVector3 CellWorldPosition(int column, int row); static_assert(sizeof(SkillInfoEntry) == 8); #if defined(_WIN32) && !defined(_WIN64) static_assert(offsetof(SkillInfoEntry, unknown_0004) == 0x4); @@ -93,6 +169,8 @@ static_assert(offsetof(CSkillActionMgr, m_efstActions) == 0x0C); static_assert(offsetof(CSkillActionMgr, m_skillObjectActions) == 0x14); static_assert(sizeof(CSkillActionMgr) == 0x1C); static_assert(sizeof(CSkillAction) == 4); +static_assert(sizeof(CEfstAction) == 4); +static_assert(sizeof(EfstActionMessage) == 8); #endif #endif diff --git a/Source/UI/Buttons/UIBmpRadioBtn.h b/Source/UI/Buttons/UIBmpRadioBtn.h new file mode 100644 index 0000000..4a8fb75 --- /dev/null +++ b/Source/UI/Buttons/UIBmpRadioBtn.h @@ -0,0 +1,33 @@ +#ifndef RAGNAROK_SOURCE_UI_BUTTONS_UIBMPRADIOBTN_H +#define RAGNAROK_SOURCE_UI_BUTTONS_UIBMPRADIOBTN_H + +#include "UI/Window/UIWindow.h" + +#include + +struct UIButtonContext; + +class UIBmpRadioBtn : public UIWindow { +public: + void Slot28(int x, int y) override; + +protected: + /* 007C */ unsigned char unknown_007C[0x64]; + /* 00E0 */ std::string m_tooltip; + UIButtonContext* m_controls; +}; + +class UIBmpRadioBtn2 : public UIBmpRadioBtn { +}; + +class UICaptchaImage : public UIWindow { +public: + void Slot28(int x, int y) override; + +protected: + /* 007C */ unsigned char unknown_007C[0x124]; + /* 01A0 */ std::string m_tooltip; + UIButtonContext* m_controls; +}; + +#endif diff --git a/Source/UI/Buttons/UITextButton.cpp b/Source/UI/Buttons/UITextButton.cpp index 69720a5..c6146e1 100644 --- a/Source/UI/Buttons/UITextButton.cpp +++ b/Source/UI/Buttons/UITextButton.cpp @@ -1,4 +1,6 @@ #include "Render/DeviceContext/DeviceContext.h" +#include "UI/UIBarGraph.h" +#include "UI/Buttons/UIBmpRadioBtn.h" #include "Render/Text/TextContext.h" #include "UI/Window/UIWindowMgr.h" #include "UI/Buttons/UIItemTagButton.h" @@ -6,6 +8,7 @@ #include "UI/Lists/UITreeView.h" #include "UI/UIStaticItem.h" +#include #include #include @@ -200,3 +203,76 @@ void UIItemTagButton::Slot28(int x, int y) { // FUNCTION: fn_11459 void UIStaticItem_Illust::Slot28(int, int) { } + +// OFFSET: 0x00891810 +// FUNCTION: fn_11450 +void UIBmpRadioBtn::Slot28(int, int) { + if (!this->m_tooltip.empty()) { + int screenX = 0, screenY = -20; + + for (const UIWindow* window = this; window; window = window->parent) { + screenX = (screenX + window->geometry.x); + screenY = (screenY + window->geometry.y); + } + + this->m_controls->services.Tooltip(this->m_tooltip.c_str(), screenX, screenY, -1, false, true); + } +} + +// OFFSET: 0x00891870 +// FUNCTION: fn_11451 +void UICaptchaImage::Slot28(int, int) { + if (!this->m_tooltip.empty()) { + int screenX = 0, screenY = -20; + + for (const UIWindow* window = this; window; window = window->parent) { + screenX = (screenX + window->geometry.x); + screenY = (screenY + window->geometry.y); + } + + this->m_controls->services.Tooltip(this->m_tooltip.c_str(), screenX, screenY, -1, false, true); + } +} + +// OFFSET: 0x00891240 +// FUNCTION: fn_11447 +void UIBarGraph4::Slot28(int, int) { + if (!this->m_current || !this->m_maximum || *this->m_maximum < *this->m_current || *this->m_maximum == 0) { + return; + } + + int screenX = 0, screenY = -16; + + for (const UIWindow* window = this; window; window = window->parent) { + screenX = (screenX + window->geometry.x); + screenY = (screenY + window->geometry.y); + } + + char text[260]; + std::memset(text, 0, sizeof(text)); + const int permille = static_cast(static_cast(*this->m_current) / static_cast(*this->m_maximum) * 1000.0f); + std::sprintf(text, "%.1f %%", static_cast(permille) * 0.1f); + this->m_controls->services.Tooltip(text, screenX, screenY, -1, false, true); +} + +// OFFSET: 0x008918D0 +// FUNCTION: fn_11452 +void UIINT64BarGraph::Slot28(int, int) { + if (this->m_maximum < this->m_current || this->m_maximum == 0 || this->m_maximum == 99999999) { + return; + } + + int screenX = 0, screenY = -16; + + for (const UIWindow* window = this; window; window = window->parent) { + screenX = (screenX + window->geometry.x); + screenY = (screenY + window->geometry.y); + } + + char text[260]; + std::memset(text, 0, sizeof(text)); + const int permille = static_cast(static_cast(this->m_current) / static_cast(this->m_maximum) * 1000.0); + std::sprintf(text, "%.1f %%", static_cast(permille) * 0.1f); + this->m_controls->services.Tooltip(text, screenX, screenY, -1, false, true); +} + diff --git a/Source/UI/Chat/ChatSuggestions.h b/Source/UI/Chat/ChatSuggestions.h index 22b0258..6738103 100644 --- a/Source/UI/Chat/ChatSuggestions.h +++ b/Source/UI/Chat/ChatSuggestions.h @@ -14,6 +14,7 @@ struct ItemTagRetirement; struct ChatSuggestions { UIWindow& chat; UIWindow* const& window; + UIEditCtrl* const& input; bool Visible() const; diff --git a/Source/UI/Chat/UINewChatWnd.h b/Source/UI/Chat/UINewChatWnd.h index 92996a7..502c13d 100644 --- a/Source/UI/Chat/UINewChatWnd.h +++ b/Source/UI/Chat/UINewChatWnd.h @@ -3,16 +3,21 @@ #include "UI/Window/UIFrameWnd.h" +#include + class UINewChatWnd : public UIFrameWnd { public: void Move(int x, int y) override; + void Show(int visible) override; private: /* 00BC */ int unknown_00BC; /* 00C0 */ int unknown_00C0; /* 00C4 */ int m_inputX; /* 00C8 */ int m_inputY; - /* 00CC */ unsigned char unknown_00CC[0x50]; + /* 00CC */ unsigned char unknown_00CC[0x28]; + /* 00F4 */ std::vector m_tabs; + /* 0100 */ unsigned char unknown_0100[0x1C]; /* 011C */ int m_inputOffsetY; }; diff --git a/Source/UI/Chat/UISubChatMiniWnd.h b/Source/UI/Chat/UISubChatMiniWnd.h new file mode 100644 index 0000000..c408c2f --- /dev/null +++ b/Source/UI/Chat/UISubChatMiniWnd.h @@ -0,0 +1,16 @@ +#ifndef RAGNAROK_SOURCE_UI_CHAT_UISUBCHATMINIWND_H +#define RAGNAROK_SOURCE_UI_CHAT_UISUBCHATMINIWND_H + +#include "UI/Window/UIFrameWnd.h" + +class UISubChatMiniWnd : public UIFrameWnd { +public: + int SendMsg(UIWindow* sender, int message, std::intptr_t a, std::intptr_t b, std::intptr_t c, std::intptr_t d) override; + +private: + /* 00B4 */ int unknown_00B4; + /* 00B8 */ int m_rowHeight; + /* 00BC */ int m_offsetY; +}; + +#endif diff --git a/Source/UI/Dialogs/UIMemorialDunWnd.h b/Source/UI/Dialogs/UIMemorialDunWnd.h new file mode 100644 index 0000000..1b71374 --- /dev/null +++ b/Source/UI/Dialogs/UIMemorialDunWnd.h @@ -0,0 +1,14 @@ +#ifndef RAGNAROK_SOURCE_UI_DIALOGS_UIMEMORIALDUNWND_H +#define RAGNAROK_SOURCE_UI_DIALOGS_UIMEMORIALDUNWND_H + +#include "UI/Window/UIFrameWnd.h" + +extern int g_memorialDunWndX; +extern int g_memorialDunWndY; + +class UIMemorialDunWnd : public UIFrameWnd { +public: + void Move(int x, int y) override; +}; + +#endif diff --git a/Source/UI/Lists/UIChatHisBox.h b/Source/UI/Lists/UIChatHisBox.h new file mode 100644 index 0000000..9fc9071 --- /dev/null +++ b/Source/UI/Lists/UIChatHisBox.h @@ -0,0 +1,17 @@ +#ifndef RAGNAROK_SOURCE_UI_LISTS_UICHATHISBOX_H +#define RAGNAROK_SOURCE_UI_LISTS_UICHATHISBOX_H + +#include "UI/Lists/UIListBox.h" + +#include +#include + +class UIChatHisBox : public UIListBox { +public: + const char* HistoryText(unsigned int index); + + /* 00DC */ unsigned char unknown_00DC[0x18]; + /* 00F4 */ std::vector history; +}; + +#endif diff --git a/Source/UI/Lists/UIImgMenu.h b/Source/UI/Lists/UIImgMenu.h new file mode 100644 index 0000000..ef2b5ed --- /dev/null +++ b/Source/UI/Lists/UIImgMenu.h @@ -0,0 +1,16 @@ +#ifndef RAGNAROK_SOURCE_UI_LISTS_UIIMGMENU_H +#define RAGNAROK_SOURCE_UI_LISTS_UIIMGMENU_H + +#include "UI/Window/UIWindow.h" + +class UIImgMenu : public UIWindow { +public: + int ItemAt(int x, int y); + + /* 0080 */ int count; + /* 0084 */ int itemWidth; + /* 0088 */ int itemHeight; + /* 008C */ int horizontal; +}; + +#endif diff --git a/Source/UI/Lists/UIListBoxEx.h b/Source/UI/Lists/UIListBoxEx.h index 00a719e..20a722b 100644 --- a/Source/UI/Lists/UIListBoxEx.h +++ b/Source/UI/Lists/UIListBoxEx.h @@ -29,6 +29,7 @@ public: virtual void AddLine(const char* text, std::uint32_t color); virtual void Clear(); virtual void Refresh(); + int FindLine(const std::string& text); /* 007C */ int backgroundRed; /* 0080 */ int backgroundGreen; diff --git a/Source/UI/Lists/UIListBoxTemplate.h b/Source/UI/Lists/UIListBoxTemplate.h new file mode 100644 index 0000000..c005d62 --- /dev/null +++ b/Source/UI/Lists/UIListBoxTemplate.h @@ -0,0 +1,144 @@ +#ifndef RAGNAROK_SOURCE_UI_LISTS_UILISTBOXTEMPLATE_H +#define RAGNAROK_SOURCE_UI_LISTS_UILISTBOXTEMPLATE_H + +#include "UI/Lists/UIVScrollBar.h" +#include "UI/Window/UIControl.h" + +#include +#include +#include +#include +#include + +class CUIListItem : public CUIControl { +public: + virtual void SetSelected(bool selected); + + /* 0084 */ unsigned char unknown_0084[0x04]; + /* 0088 */ int selected; + /* 008C */ unsigned char unknown_008C[0x04]; + /* 0090 */ unsigned char frozen; + /* 0094 */ int index; +}; + +namespace AdventureGuide { +class CUISubTab : public CUIListItem {}; +class CUITab : public CUIListItem {}; +class CUIItemInfo : public CUIListItem {}; +class CUIMonsterInfo : public CUIListItem {}; +class CUIQuestInfo : public CUIListItem {}; +class CUITabInfo : public CUIListItem {}; +class CItemData {}; +class CMonsterData {}; +class CQuestInfo {}; +} + +namespace Guide_Intro { +struct Contents {}; +} + +template class CUIListBox : public CUIControl { +public: + // OFFSET: 0x009D3FD0 + // OFFSET: 0x009D4040 + // FUNCTION: fn_14037 + // FUNCTION: fn_14038 + void Resize(int width, int height) override { + UIWindow::Resize(width, height); + this->scrollBar->Resize(this->scrollBar->geometry.width, height); + int total = 0; + for (T* item : this->items) { + total += item->geometry.height; + } + this->scrollBar->SetTotal(total); + this->scrollBar->SetVisible(this->geometry.height); + this->scrollBar->SetPosition(this->scrollBar->position); + } + + /* 0084 */ unsigned char unknown_0084[0x2C]; + /* 00B0 */ CUIVScrollBar* scrollBar; + /* 00B4 */ unsigned char unknown_00B4[0x10]; + /* 00C4 */ std::list items; +}; + +template class CUIListBoxEx : public CUIControl { +public: + // OFFSET: 0x009D43D0 + // OFFSET: 0x009D45D0 + // OFFSET: 0x009D47D0 + // OFFSET: 0x009D49D0 + // FUNCTION: fn_14043 + // FUNCTION: fn_14044 + // FUNCTION: fn_14045 + // FUNCTION: fn_14046 + void OnScroll(int position) { + auto it = this->items.begin(); + if (it == this->items.end()) { + return; + } + if (this->data.size() == 0) { + for (; it != this->items.end(); ++it) { + (*it)->index = -1; + (*it)->Show(0); + } + return; + } + + const int first = (*it)->index; + unsigned int row = position / this->rowHeight; + if (first != -1) { + if (first < static_cast(row)) { + if (row - first < static_cast(this->items.size())) { + std::rotate(this->items.begin(), this->items.begin() + (row - first), this->items.end()); + } + } else if (first > static_cast(row)) { + if (first - row < static_cast(this->items.size())) { + std::rotate(this->items.begin(), this->items.end() - (first - row), this->items.end()); + } + } + } + + it = this->items.begin(); + for (int y = this->itemY - position % this->rowHeight; y < this->geometry.height; y += this->rowHeight, ++it, ++row) { + if (it == this->items.end() || row >= this->data.size()) { + break; + } + T* item = *it; + if (item) { + if (this->rebind || row != static_cast(item->index)) { + item->index = row; + if (this->binder) { + auto node = this->data.begin(); + std::advance(node, static_cast(row)); + this->binder(item, *node); + } + } + item->SetSelected(row == this->selected); + item->Move(this->itemX, y); + item->Show(1); + } + } + + for (; it != this->items.end(); ++it) { + (*it)->index = -1; + (*it)->Show(0); + } + } + + /* 0084 */ unsigned char unknown_0084[0x2C]; + /* 00B0 */ CUIVScrollBar* scrollBar; + /* 00B4 */ int itemWidth; + /* 00B8 */ int rowHeight; + /* 00BC */ int itemX; + /* 00C0 */ int itemY; + /* 00C4 */ int unknown_00C4; + /* 00C8 */ std::vector items; + /* 00D4 */ std::list data; + /* 00DC */ unsigned int selected; + /* 00E0 */ unsigned char unknown_00E0; + /* 00E1 */ unsigned char rebind; + /* 00E2 */ unsigned char unknown_00E2[0x2E]; + /* 0110 */ std::function binder; +}; + +#endif diff --git a/Source/UI/Lists/UIScrollBar.cpp b/Source/UI/Lists/UIScrollBar.cpp index 284bc5a..551f8ef 100644 --- a/Source/UI/Lists/UIScrollBar.cpp +++ b/Source/UI/Lists/UIScrollBar.cpp @@ -4,6 +4,10 @@ #include "Resource/ResourceMgr.h" #include "UI/Window/UIWindowMgr.h" #include "UI/Lists/UIScrollBar.h" +#include "UI/Lists/UIChatHisBox.h" +#include "UI/Lists/UIImgMenu.h" +#include "UI/Lists/UIListBoxEx.h" +#include "UI/Lists/UITabControl.h" #include @@ -315,3 +319,74 @@ void UIScrollBar::Configure(int x, int y, unsigned char vertical, int length) { this->Slot15(width, height); this->Slot22(width, height); } + +// OFFSET: 0x00880800 +// FUNCTION: fn_11279 +int UIListBoxEx::FindLine(const std::string& text) { + const std::string key = text; + auto it = this->lines.begin(); + for (; it != this->lines.end(); ++it) { + if (key == it->second.text) { + break; + } + } + return it == this->lines.end() ? -1 : it->first; +} + +// OFFSET: 0x00880920 +// FUNCTION: fn_11280 +int UIImgMenu::ItemAt(int x, int y) { + const int width = this->geometry.width - 3; + if (x < 1 || x >= width || y < 1) { + return -1; + } + const int height = this->geometry.height - 3; + if (y >= height) { + return -1; + } + + int size; + int offset; + if (this->horizontal != 0) { + size = width / this->count; + this->itemWidth = size; + offset = x; + } else { + size = height / this->count; + this->itemHeight = size; + offset = y; + } + return std::min(offset / size, this->count - 1); +} + +// OFFSET: 0x00880B20 +// FUNCTION: fn_11283 +int UITabControl::TabCount() { + return static_cast(this->labels.size()); +} + +// OFFSET: 0x00880B40 +// FUNCTION: fn_11284 +int UITabControl::TabAt(int x, int y) { + int offset = x; + if (this->layout == 0 || this->layout == 2) { + offset = y; + } + const int count = this->TabCount(); + for (int index = 0; index < count; ++index) { + offset -= this->widths[index]; + if (offset < 0) { + return index; + } + } + return -1; +} + +// OFFSET: 0x00880BB0 +// FUNCTION: fn_11285 +const char* UIChatHisBox::HistoryText(unsigned int index) { + if (index < this->history.size() && !this->history[index].empty()) { + return this->history[index].c_str(); + } + return nullptr; +} diff --git a/Source/UI/Lists/UITabControl.h b/Source/UI/Lists/UITabControl.h new file mode 100644 index 0000000..919e4cd --- /dev/null +++ b/Source/UI/Lists/UITabControl.h @@ -0,0 +1,21 @@ +#ifndef RAGNAROK_SOURCE_UI_LISTS_UITABCONTROL_H +#define RAGNAROK_SOURCE_UI_LISTS_UITABCONTROL_H + +#include "UI/Window/UIWindow.h" + +#include +#include + +class UITabControl : public UIWindow { +public: + int TabCount(); + int TabAt(int x, int y); + + /* 0080 */ int layout; + /* 0084 */ unsigned char unknown_0084[0x30]; + /* 00B4 */ std::vector labels; + /* 00C0 */ unsigned char unknown_00C0[0xC]; + /* 00CC */ std::vector widths; +}; + +#endif diff --git a/Source/UI/Lists/UIVScrollBar.h b/Source/UI/Lists/UIVScrollBar.h new file mode 100644 index 0000000..ce4d316 --- /dev/null +++ b/Source/UI/Lists/UIVScrollBar.h @@ -0,0 +1,37 @@ +#ifndef RAGNAROK_SOURCE_UI_LISTS_UIVSCROLLBAR_H +#define RAGNAROK_SOURCE_UI_LISTS_UIVSCROLLBAR_H + +#include "UI/Window/UIControl.h" + +#include +#include + +class CUIVScrollBar : public CUIControl { +public: + virtual void Slot54(); + + void SetTotal(int value); + void SetVisible(int value); + void SetPosition(int value); + + /* 0084 */ UIWindow* upButton; + /* 0088 */ CUIControl* thumb; + /* 008C */ int unknown_008C; + /* 0090 */ int total; + /* 0094 */ int visible; + /* 0098 */ int unknown_0098; + /* 009C */ int position; + /* 00A0 */ int maximum; + /* 00A4 */ int trackLength; + /* 00A8 */ unsigned char dragging; + /* 00A9 */ unsigned char unknown_00A9[0x1F]; + /* 00C8 */ std::function onChange; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(offsetof(CUIVScrollBar, total) == 0x90); +static_assert(offsetof(CUIVScrollBar, dragging) == 0xA8); +static_assert(offsetof(CUIVScrollBar, onChange) == 0xC8); +#endif + +#endif diff --git a/Source/UI/Shortcuts/ShortcutCooldowns.cpp b/Source/UI/Shortcuts/ShortcutCooldowns.cpp index 82a4354..694ea00 100644 --- a/Source/UI/Shortcuts/ShortcutCooldowns.cpp +++ b/Source/UI/Shortcuts/ShortcutCooldowns.cpp @@ -1,4 +1,11 @@ #include "Client/Session.h" +#include "Resource/Bitmap/BitmapResource.h" +#include "Resource/ResourceMgr.h" +#include "Resource/SkinPalette.h" +#include "Resource/TextureNames.h" +#include "UI/Buttons/UIButton.h" +#include "UI/Window/UIRebirthWnd.h" +#include "UI/Window/UISeekPartyMBWnd.h" #include "UI/Shortcuts/ShortcutCooldowns.h" #include @@ -204,3 +211,32 @@ void ShortcutCooldowns::Paint( ++entry; } } + +// OFFSET: 0x0092DDE0 +// FUNCTION: fn_12673 +void UIRebirthWnd::Paint() { + auto* background = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\win_msgbox1.bmp"))); + this->DrawBitmap(0, 0, &background->image, false); +} + +// OFFSET: 0x0092E230 +// FUNCTION: fn_12676 +void UISeekPartyMBWnd::Paint() { + auto& controls = *this->m_controls; + this->DrawTitleBar(controls.fonts, controls.locale, true, "", 0); + if (this->m_minimized) { + return; + } + + unsigned red; + unsigned green; + unsigned blue; + SkinPaletteChannels(ResourceMgr::Instance(), 2, 2, red, green, blue); + this->FillRegion(0, 17, this->geometry.width, this->geometry.height - 38, RGB(red, green, blue)); + + const RECT outer = {50, 95, 310, 293}; + const RECT inner = {51, 96, 309, 292}; + this->FillSurface(0, &outer); + this->FillSurface(0xF0F0F0, &inner); + this->DrawButtonBar(2, 0, 1); +} diff --git a/Source/UI/Shortcuts/UIShortCutWnd.cpp b/Source/UI/Shortcuts/UIShortCutWnd.cpp index d630c01..be6093f 100644 --- a/Source/UI/Shortcuts/UIShortCutWnd.cpp +++ b/Source/UI/Shortcuts/UIShortCutWnd.cpp @@ -15,18 +15,29 @@ #include "UI/Shortcuts/ShortcutVisibility.h" #include "UI/Shortcuts/UIShortCutWnd.h" #include "UI/Chat/UINewChatWnd.h" +#include "UI/Chat/UISubChatMiniWnd.h" +#include "UI/Dialogs/UIMemorialDunWnd.h" #include "UI/Buttons/UIBitmapButton.h" #include "UI/Buttons/UIButton.h" #include "UI/Buttons/UICheckBox.h" #include "UI/Layout/WindowSnapping.h" #include "UI/Window/UIWindow.h" #include "UI/Window/UIWindowMgr.h" +#include "UI/Window/UIWindowCallBtnWnd.h" +#include "UI/Window/WindowManagerWindows.h" +#include "Render/Renderer.h" #include #include namespace {} +// GLOBAL: gl_24737 +int g_memorialDunWndX; + +// GLOBAL: gl_24738 +int g_memorialDunWndY; + // OFFSET: 0x009122B0 // FUNCTION: fn_12528 UIShortCutWnd::UIShortCutWnd(UIWindowContext& windows, UIFrameContext& frames, ShortcutWindowContext& shortcuts) @@ -429,3 +440,62 @@ void UINewChatWnd::Move(int x, int y) { window->SendMsg(this, 6, 197, this->geometry.x + x, this->geometry.y + y, 0); } } + +// OFFSET: 0x0093D090 +// FUNCTION: fn_12765 +void UINewChatWnd::Show(int visible) { + this->geometry.visible = visible; + + if (visible != 0) { + this->Invalidate(); + this->m_frameContext->snapping.Store(this); + } else if (this->m_frameContext->snapping.enabled) { + this->m_frameContext->snapping.rectangles.erase(this); + } + + for (UIWindow* tab : this->m_tabs) { + tab->Show(this->geometry.visible); + } + + for (unsigned int i = 0; i < g_windowMgr.retainedWindows.size(); ++i) { + g_windowMgr.retainedWindows[i]->Show(this->geometry.visible); + } +} +// OFFSET: 0x0091E960 +// FUNCTION: fn_12630 +void UIMemorialDunWnd::Move(int x, int y) { + const int left = x <= g_renderer->viewport.width - this->geometry.width ? x : 0; + const int top = y <= g_renderer->viewport.height - this->geometry.height ? y : 310; + this->UIFrameWnd::Move(left, top); + + if (g_memorialDunWndX != left) { + g_memorialDunWndX = left; + } + if (g_memorialDunWndY != top) { + g_memorialDunWndY = top; + } +} + +// OFFSET: 0x0093CA50 +// FUNCTION: fn_12762 +int UISubChatMiniWnd::SendMsg(UIWindow* sender, int message, std::intptr_t a, std::intptr_t b, std::intptr_t c, std::intptr_t d) { + if (message != 6) { + return this->UIFrameWnd::SendMsg(sender, message, a, b, c, d); + } + + if (a == 197) { + this->Move(b, c - this->m_offsetY); + } else if (a == 202) { + this->Slot25(this->m_rowHeight * b + 1, 0); + } + return 0; +} + +// OFFSET: 0x0093CAB0 +// FUNCTION: fn_12763 +int UIWindowCallBtnWnd::SendMsg(UIWindow*, int message, std::intptr_t a, std::intptr_t, std::intptr_t, std::intptr_t) { + if (message == 6) { + this->m_frameContext->windows.EnsureWindow(a); + } + return 0; +} diff --git a/Source/UI/UIBarGraph.h b/Source/UI/UIBarGraph.h new file mode 100644 index 0000000..031d098 --- /dev/null +++ b/Source/UI/UIBarGraph.h @@ -0,0 +1,34 @@ +#ifndef RAGNAROK_SOURCE_UI_UIBARGRAPH_H +#define RAGNAROK_SOURCE_UI_UIBARGRAPH_H + +#include "UI/Window/UIWindow.h" + +#include + +struct UIButtonContext; + +class UIBarGraph3 : public UIWindow { +}; + +class UIBarGraph4 : public UIBarGraph3 { +public: + void Slot28(int x, int y) override; + +protected: + /* 007C */ const int* m_current; + /* 0080 */ const int* m_maximum; + UIButtonContext* m_controls; +}; + +class UIINT64BarGraph : public UIWindow { +public: + void Slot28(int x, int y) override; + +protected: + /* 007C */ int unknown_007C; + /* 0080 */ std::int64_t m_current; + /* 0088 */ std::int64_t m_maximum; + UIButtonContext* m_controls; +}; + +#endif diff --git a/Source/UI/UILapineScrollBar.cpp b/Source/UI/UILapineScrollBar.cpp index eb46b8a..26cfa3d 100644 --- a/Source/UI/UILapineScrollBar.cpp +++ b/Source/UI/UILapineScrollBar.cpp @@ -81,3 +81,9 @@ void UILapineScrollBar::Paint() { this->minimumThumb = 0; this->parent->Invalidate(); } + +// OFFSET: 0x00600260 +// FUNCTION: fn_04157 +void UILapineBox_IngredientListWnd::Paint() { + this->ClearSurface(0xFFF7F7); +} diff --git a/Source/UI/UILapineScrollBar.h b/Source/UI/UILapineScrollBar.h index 8d6338e..bd7d319 100644 --- a/Source/UI/UILapineScrollBar.h +++ b/Source/UI/UILapineScrollBar.h @@ -8,4 +8,12 @@ public: void Paint() override; }; +class UIScrollWnd : public UIWindow { +}; + +class UILapineBox_IngredientListWnd : public UIScrollWnd { +public: + void Paint() override; +}; + #endif diff --git a/Source/UI/Window/UIControl.cpp b/Source/UI/Window/UIControl.cpp index d63cec1..a278594 100644 --- a/Source/UI/Window/UIControl.cpp +++ b/Source/UI/Window/UIControl.cpp @@ -1,4 +1,5 @@ #include "UI/Window/UIControl.h" +#include "UI/Lists/UIListBoxTemplate.h" #include "Resource/Bitmap/BitmapResource.h" #include "Resource/ResourceMgr.h" #include "Resource/TextureNames.h" @@ -150,3 +151,80 @@ void CUIControl::Slot52(int value) { // FUNCTION: fn_14036 void CUIControl::Slot53(int) { } + +// OFFSET: 0x009F2B70 +// FUNCTION: fn_14726 +void CUIVScrollBar::SetTotal(int value) { + const int count = value > 1 ? value : 1; + this->total = count; + const int shown = this->visible <= count ? this->visible : count; + this->visible = shown; + this->maximum = count - shown; + this->Invalidate(); +} + +// OFFSET: 0x009F2E80 +// FUNCTION: fn_14732 +void CUIVScrollBar::SetVisible(int value) { + const int count = this->total; + const int shown = count < value ? count : value; + this->visible = shown; + this->maximum = count - shown; + this->Invalidate(); +} + +// OFFSET: 0x009F2530 +// FUNCTION: fn_14716 +void CUIVScrollBar::SetPosition(int value) { + int zero = 0; + const int* clamped = &this->maximum; + if (value <= this->maximum) { + clamped = value >= 0 ? &value : &zero; + } + if (!this->dragging) { + this->position = *clamped; + } + this->Show(this->total > this->visible); + this->Slot54(); + if (this->onChange) { + this->onChange(this->position); + } + this->Invalidate(); +} + +// OFFSET: 0x009F25C0 +// FUNCTION: fn_14717 +void CUIVScrollBar::Slot54() { + CUIControl* bar = this->thumb; + float offset; + if (bar->hitEnabled) { + const int length = static_cast((static_cast(this->visible) / static_cast(this->total)) * static_cast(this->trackLength)); + bar->Resize(this->geometry.width, length > 1 ? length : 1); + offset = (static_cast(this->trackLength) / static_cast(this->total)) * static_cast(this->position); + } else { + if (this->total - this->visible <= 0) { + bar->Move(0, this->upButton->geometry.height); + return; + } + offset = static_cast(this->position * (this->trackLength - bar->geometry.height)) / static_cast(this->total - this->visible); + } + this->thumb->Move(0, static_cast(offset + static_cast(this->upButton->geometry.height))); +} + +// OFFSET: 0x009F0290 +// FUNCTION: fn_14661 +void CUIListItem::SetSelected(bool selected) { + if (this->selected != selected) { + this->selected = selected; + if (!this->frozen) { + this->Invalidate(); + } + } +} + +template void CUIListBox::Resize(int, int); +template void CUIListBox::Resize(int, int); +template void CUIListBoxEx::OnScroll(int); +template void CUIListBoxEx::OnScroll(int); +template void CUIListBoxEx::OnScroll(int); +template void CUIListBoxEx::OnScroll(int); diff --git a/Source/UI/Window/UIRebirthWnd.h b/Source/UI/Window/UIRebirthWnd.h new file mode 100644 index 0000000..e4ba4fd --- /dev/null +++ b/Source/UI/Window/UIRebirthWnd.h @@ -0,0 +1,11 @@ +#ifndef RAGNAROK_SOURCE_UI_WINDOW_UIREBIRTHWND_H +#define RAGNAROK_SOURCE_UI_WINDOW_UIREBIRTHWND_H + +#include "UI/Window/UIFrameWnd.h" + +class UIRebirthWnd : public UIFrameWnd { +public: + void Paint() override; +}; + +#endif diff --git a/Source/UI/Window/UISeekPartyMBWnd.h b/Source/UI/Window/UISeekPartyMBWnd.h new file mode 100644 index 0000000..660b899 --- /dev/null +++ b/Source/UI/Window/UISeekPartyMBWnd.h @@ -0,0 +1,18 @@ +#ifndef RAGNAROK_SOURCE_UI_WINDOW_UISEEKPARTYMBWND_H +#define RAGNAROK_SOURCE_UI_WINDOW_UISEEKPARTYMBWND_H + +#include "UI/Window/UIFrameWnd.h" + +struct UIButtonContext; + +class UISeekPartyMBWnd : public UIFrameWnd { +public: + void Paint() override; + +protected: + /* 00BC */ unsigned char unknown_00BC[0xAC]; + /* 0168 */ unsigned char m_minimized; + UIButtonContext* m_controls; +}; + +#endif diff --git a/Source/UI/Window/UIWindowCallBtnWnd.h b/Source/UI/Window/UIWindowCallBtnWnd.h new file mode 100644 index 0000000..7e33661 --- /dev/null +++ b/Source/UI/Window/UIWindowCallBtnWnd.h @@ -0,0 +1,11 @@ +#ifndef RAGNAROK_SOURCE_UI_WINDOW_UIWINDOWCALLBTNWND_H +#define RAGNAROK_SOURCE_UI_WINDOW_UIWINDOWCALLBTNWND_H + +#include "UI/Window/UIFrameWnd.h" + +class UIWindowCallBtnWnd : public UIFrameWnd { +public: + int SendMsg(UIWindow* sender, int message, std::intptr_t a, std::intptr_t b, std::intptr_t c, std::intptr_t d) override; +}; + +#endif diff --git a/Source/Utility/Timestamp.h b/Source/Utility/Timestamp.h index 8eb2f15..20bf56f 100644 --- a/Source/Utility/Timestamp.h +++ b/Source/Utility/Timestamp.h @@ -7,7 +7,9 @@ class Timestamp { public: + Timestamp(__time32_t value); ~Timestamp(); + void Set(__time32_t value); TIMESTAMP_STRUCT ToSqlTimestamp(); int IsBefore(__time32_t other) const; int IsBeforeNow() const;