diff --git a/Source/Contents/NavigationManager.h b/Source/Contents/NavigationManager.h index b8aa5422..5dff9dcd 100644 --- a/Source/Contents/NavigationManager.h +++ b/Source/Contents/NavigationManager.h @@ -349,6 +349,12 @@ public: bool GetRouteFlag() const; int GetSelectedNode() const; CNavi_Link* GetNode(int index) const; + int AddWrappedRouteLines(int kind, int x, int y, const char* text); + bool SelectState(int index); + bool SelectNodeState(); + bool SetDestinationMap(std::string mapName, int x, int y); + bool LoadTargetPreview(); + bool BuildTargetRouteLines(); /* 0000 */ CNaviData data; /* 004C */ CPathFinder pathFinder; diff --git a/Source/Contents/NavigationRoute.cpp b/Source/Contents/NavigationRoute.cpp index 3eadf158..1d4c9bce 100644 --- a/Source/Contents/NavigationRoute.cpp +++ b/Source/Contents/NavigationRoute.cpp @@ -4,10 +4,17 @@ #include "Utility/Text/FormatString.h" #include "Client/Registry.h" #include "Client/Replay.h" +#include "Client/ReplayPacketRecorder.h" #include "Client/Session.h" #include "Resource/ResourceMgr.h" +#include "Network/Packets/PacketLengths.h" +#include "Render/Textures/Texture.h" +#include "Render/Textures/TextureCache.h" +#include "Resource/Sprites/ActionResource.h" +#include "Resource/Sprites/SpriteResource.h" #include "UI/Window/UIWindow.h" #include "UI/Navigation/UINavigationV4Wnd.h" +#include "UI/Window/UINavigationWnd.h" #include "UI/Window/UIWindowMgr.h" #include "UI/Window/WindowDialogs.h" #include "Utility/RegistryKey.h" @@ -16,6 +23,7 @@ #include #include #include +#include // OFFSET: 0x00B88800 // FUNCTION: fn_19376 @@ -849,3 +857,397 @@ void CNavigationManager::RefreshSearch() { static_cast(g_windowMgr.window_4F174)->FillRouteGroups(); } } + +// OFFSET: 0x00B9C740 +// FUNCTION: fn_19623 +int CNavigationManager::AddWrappedRouteLines(int kind, int x, int y, const char* text) { + std::string bracketed = "["; + bracketed += text; + + wchar_t wide[100]; + std::memset(wide, 0, sizeof(wide)); + MultiByteToWideChar(CP_ACP, 0, bracketed.c_str(), -1, wide, static_cast(bracketed.size())); + const int length = static_cast(wcsnlen(wide, 100)); + + char line[40] = {}; + const int count = length / 12; + for (int index = 0; index <= count; ++index) { + const int limit = 11 * (index + 1); + const int end = length <= limit ? length : limit; + std::wstring part(wide + 11 * index, wide + end); + WideCharToMultiByte(CP_ACP, 0, part.c_str(), -1, line, 40, nullptr, nullptr); + NaviRouteLine routeLine(kind, x, y + 14 * index, line, 0xDCDCDC, 0xCB6B54); + this->routeLines.push_back(routeLine); + } + + this->routeLines.back().text += "]"; + return count + 1; +} + +// OFFSET: 0x00B9DB50 +// FUNCTION: fn_19624 +bool CNavigationManager::SelectState(int index) { + if (this->states.empty() || this->states[index].object == nullptr) { + return false; + } + + this->currentTarget = this->states[index]; + return this->LoadTargetPreview(); +} + +// OFFSET: 0x00B9DBD0 +// FUNCTION: fn_19625 +bool CNavigationManager::SelectNodeState() { + const int path = this->unknown_12C4; + const int node = this->unknown_12C8; + if (path >= 0 && static_cast(path) < this->nodePaths.size() && node >= 0 && + static_cast(node) < this->nodePaths[path].size() && this->nodePaths[path][node] != -1) { + return this->SelectState(this->nodePaths[path][node]); + } + + this->currentTarget = NavigationTarget(); + this->LoadTargetPreview(); + this->ClearRouteNodes(); + if (this->data.GetInfo() && !this->data.GetInfo()->IsSearchable(this->data.GetInfo()->GetName())) { + g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4195)), 0x0A0AFF, 0, 0, 0); + } + return false; +} + +#pragma pack(push, 1) +struct NavigationDestinationRecord { + /* 0000 */ std::uint32_t header; + /* 0004 */ std::int16_t length; + /* 0006 */ char mapName[30]; + /* 0024 */ int x; + /* 0028 */ int y; +}; +#pragma pack(pop) + +static_assert(sizeof(NavigationDestinationRecord) == 0x2C); + +// OFFSET: 0x00B9E290 +// FUNCTION: fn_19628 +bool CNavigationManager::SetDestinationMap(std::string mapName, int x, int y) { + CNaviInfo* info = this->FindObject(mapName.c_str()); + if (!info) { + return false; + } + + { + std::string name = mapName; + if (Replay::Instance().state == 2) { + char packet[100] = {}; + NavigationDestinationRecord record; + record.header = 0x039008AE; + record.length = sizeof(record); + std::memset(record.mapName, 0, sizeof(record.mapName)); + std::memcpy(record.mapName, name.c_str(), name.size()); + record.x = x; + record.y = y; + std::memcpy(packet, &record, sizeof(record)); + + Replay& replay = Replay::Instance(); + ReplayPacketRecorder recorder = { + g_packetLengths, + replay.baseFrame, + replay.started, + replay.frame, + replay.header.key0, + replay.header.key1, + replay.header.options.recordRestricted, + replay.file, + replay.fileOffset, + replay.records, + replay.cipherIndex, + replay.cipherBase, + replay.cipherKey, + }; + recorder.Record(static_cast(record.length), packet, 0x29, 0); + } + } + + char text[50]; + std::memset(text, 0, sizeof(text)); + this->currentTarget.object = info; + if (x == 1000) { + this->currentTarget.type = 1; + this->currentTarget.x = -1; + this->currentTarget.y = -1; + std::sprintf(text, "%s", info->GetName()); + } else { + this->currentTarget.x = x; + this->currentTarget.type = 0; + this->currentTarget.y = y; + std::sprintf(text, "%s %d,%d", info->GetName(), x, y); + } + this->currentTarget.name = text; + + this->unknown_125C = 0; + this->unknown_1254 = false; + this->unknown_1248 = false; + if (this->unknown_1258 != 2) { + this->unknown_1258 = 1; + } + + this->LoadTargetPreview(); + g_windowMgr.EnsureWindow(203); + if (g_windowMgr.window_4F174) { + UIWindow* window = g_windowMgr.window_4F174; + window->geometry.targetAlpha = 255; + window->geometry.alphaStart = timeGetTime(); + static_cast(g_windowMgr.window_4F174)->RefreshLayout(); + static_cast(g_windowMgr.window_4F174)->LayoutWithDetails(); + g_windowMgr.window_4F174->Invalidate(); + } + + if (g_windowMgr.modal) { + reinterpret_cast(g_windowMgr.modal)->RefreshWorldMapSelection(); + reinterpret_cast(g_windowMgr.modal)->RefreshWorldMapLevels(); + } + return true; +} + +// OFFSET: 0x00B9FBB0 +// FUNCTION: fn_19632 +bool CNavigationManager::LoadTargetPreview() { + this->unknown_128C = -1; + this->unknown_1290 = -1; + this->unknown_12A4 = nullptr; + this->unknown_12A0 = nullptr; + this->unknown_1288 = nullptr; + if (!this->currentTarget.object || !this->BuildTargetRouteLines()) { + return false; + } + + const int type = this->currentTarget.type; + void* object = this->currentTarget.object; + this->unknown_1284 = 1; + + int mapType; + const char* mapName; + if (type == 0 || type == 1) { + if (!object) { + return false; + } + CNaviInfo* info = static_cast(object); + mapType = info->GetType(); + mapName = info->GetName(); + } else if (type == 6) { + if (!object) { + return false; + } + CNavi_Object* target = static_cast(object); + if (target->GetType() == 400) { + mapName = target->GetName(); + mapType = 5004; + } else { + mapType = target->GetInfo()->GetType(); + mapName = target->GetInfo()->GetName(); + } + } else { + this->unknown_12A4 = nullptr; + this->unknown_12A0 = nullptr; + if (!object) { + return false; + } + CNavi_Object* target = static_cast(object); + mapType = target->GetInfo()->GetType(); + mapName = target->GetInfo()->GetName(); + if (target->GetIndex() >= 0) { + char spriteName[128]; + char actionName[128]; + std::memset(spriteName, 0, sizeof(spriteName)); + std::memset(actionName, 0, sizeof(actionName)); + bool found = false; + if (this->currentTarget.type == 2) { + std::sprintf(spriteName, "npc\\%s.spr", g_session.GetJobName(target->GetIndex(), -1)); + std::sprintf(actionName, "npc\\%s.act", g_session.GetJobName(target->GetIndex(), -1)); + if (ResourceMgr::Instance().Exists(spriteName) && ResourceMgr::Instance().Exists(actionName)) { + found = true; + } else { + std::sprintf(spriteName, "몬스터\\%s.spr", g_session.GetJobName(target->GetIndex(), -1)); + std::sprintf(actionName, "몬스터\\%s.act", g_session.GetJobName(target->GetIndex(), -1)); + found = ResourceMgr::Instance().Exists(spriteName) && ResourceMgr::Instance().Exists(actionName); + } + } else if (this->currentTarget.type == 3) { + std::sprintf(spriteName, "몬스터\\%s.spr", g_session.GetJobName(target->GetIndex(), -1)); + std::sprintf(actionName, "몬스터\\%s.act", g_session.GetJobName(target->GetIndex(), -1)); + if (ResourceMgr::Instance().Exists(spriteName) && ResourceMgr::Instance().Exists(actionName)) { + found = true; + } else { + std::sprintf(spriteName, "npc\\%s.spr", g_session.GetJobName(target->GetIndex(), -1)); + std::sprintf(actionName, "npc\\%s.act", g_session.GetJobName(target->GetIndex(), -1)); + found = ResourceMgr::Instance().Exists(spriteName) && ResourceMgr::Instance().Exists(actionName); + } + } + if (found) { + this->unknown_12A4 = dynamic_cast(ResourceMgr::Instance().Load(spriteName)); + this->unknown_12A0 = dynamic_cast(ResourceMgr::Instance().Load(actionName)); + } + } + this->unknown_1284 = 2; + } + + this->unknown_1288 = nullptr; + if (mapType == 5004) { + this->unknown_1288 = g_texMgr.Load("유저인터페이스\\collection\\불의주문서.bmp", 0, false, true, false); + } else if (mapType != 5001 && mapType != 5005) { + this->unknown_1288 = g_texMgr.Load("유저인터페이스\\navigation_interface\\Interior.bmp", 0, false, true, false); + } else { + char path[60]; + std::memset(path, 0, sizeof(path)); + std::sprintf(path, "유저인터페이스\\map\\%s.bmp", mapName); + this->unknown_1288 = g_texMgr.Load(path, 0, false, true, false); + if (this->unknown_1288) { + const int targetType = this->currentTarget.type; + if (targetType == 2 || targetType == 6) { + CNavi_Object* target = static_cast(this->currentTarget.object); + NaviCell cellY; + NaviCell cellX; + const int cellRow = target->GetCell(&cellY)->y; + const int cellColumn = target->GetCell(&cellX)->x; + CNaviInfo* info = target->GetInfo(); + int viewX = -1; + int viewY = -1; + if (info) { + int offsetX; + int offsetY; + const float scale = this->FitToSquare(&offsetX, &offsetY, info->GetCellX(), info->GetCellY(), 126.0f); + viewX = static_cast(cellColumn * scale + offsetX); + viewY = static_cast(126.0f - (cellRow * scale + offsetY)); + } + this->unknown_1290 = viewY; + this->unknown_128C = viewX; + } else if (targetType == 0) { + int cellColumn = this->currentTarget.x; + int cellRow; + CNaviInfo* info; + if (cellColumn < 0) { + NaviCell cellY; + NaviCell cellX; + cellRow = this->data.GetCell(&cellY)->y; + cellColumn = this->data.GetCell(&cellX)->x; + info = this->data.GetInfo(); + } else { + cellRow = this->currentTarget.y; + info = static_cast(this->currentTarget.object); + } + int viewX = -1; + int viewY = -1; + if (info) { + int offsetX; + int offsetY; + const float scale = this->FitToSquare(&offsetX, &offsetY, info->GetCellX(), info->GetCellY(), 126.0f); + viewX = static_cast(cellColumn * scale + offsetX); + viewY = static_cast(126.0f - (cellRow * scale + offsetY)); + } + this->unknown_1290 = viewY; + this->unknown_128C = viewX; + } + } + } + + if (!this->unknown_1288 || this->unknown_1288->Width() == 1) { + this->unknown_1288 = g_texMgr.Load("유저인터페이스\\navigation_interface\\NoImage.bmp", 0, false, true, false); + } + return true; +} + +// OFFSET: 0x00BA02F0 +// FUNCTION: fn_19633 +bool CNavigationManager::BuildTargetRouteLines() { + this->ClearRouteNodes(); + void* object = this->currentTarget.object; + if (!object) { + return false; + } + + char text[50]; + std::memset(text, 0, sizeof(text)); + const int type = this->currentTarget.type; + if (type == 1) { + this->AddWrappedRouteLines(1, 141, 46, static_cast(object)->GetTitle()); + } else if (type == 2) { + CNavi_Object* npc = static_cast(object); + this->AddWrappedRouteLines(2, 141, 46, npc->GetName()); + NaviCell cellY; + NaviCell cellX; + std::sprintf(text, "( %d %d )", npc->GetCell(&cellX)->x, npc->GetCell(&cellY)->y); + this->routeLines.push_back(NaviRouteLine(2, 142, 149, text, 0xDCDCDC, 0xCB6B54)); + } else if (type == 3) { + CNavi_Object* mob = static_cast(object); + this->AddWrappedRouteLines(2, 141, 46, mob->GetName()); + NaviCell cell; + std::sprintf(text, "Lv. %d", mob->GetCell(&cell)->x); + this->routeLines.push_back(NaviRouteLine(2, 142, 77, text, 0xDCDCDC, 0xCB6B54)); + + const int attributes = mob->GetCell(&cell)->y; + const unsigned int property = static_cast(attributes >> 16); + const unsigned int propertyLevel = property / 20; + const unsigned int element = property % 10; + const unsigned int size = static_cast(attributes); + + int message; + if (element == 1) { + message = 2263; + } else if (element == 2) { + message = 2264; + } else if (element == 3) { + message = 2265; + } else if (element == 4) { + message = 2266; + } else if (element == 5) { + message = 2267; + } else if (element == 6) { + message = 2268; + } else if (element == 7) { + message = 2269; + } else if (element == 8) { + message = 2270; + } else { + message = element == 9 ? 2271 : 2272; + } + std::sprintf(text, LookupMessage(message), propertyLevel); + this->routeLines.push_back(NaviRouteLine(2, 142, 93, text, 0xDCDCDC, 0xCB6B54)); + + if (size == 1) { + message = 2276; + } else if (size == 2) { + message = 2277; + } else if (size == 3) { + message = 2278; + } else if (size == 4) { + message = 2279; + } else if (size == 5) { + message = 2280; + } else if (size == 6) { + message = 2281; + } else if (size == 7) { + message = 2282; + } else if (size == 8) { + message = 2283; + } else { + message = size == 9 ? 2284 : 2285; + } + std::sprintf(text, LookupMessage(message)); + this->routeLines.push_back(NaviRouteLine(2, 142, 109, text, 0xDCDCDC, 0xCB6B54)); + + if ((attributes & 0xFF00) == 0x100) { + message = 2273; + } else { + message = (attributes & 0xFF00) == 0x200 ? 2274 : 2275; + } + std::sprintf(text, LookupMessage(message)); + this->routeLines.push_back(NaviRouteLine(2, 142, 125, text, 0xDCDCDC, 0xCB6B54)); + + if (mob->GetType() == 301) { + this->routeLines.push_back(NaviRouteLine(2, 142, 149, LookupMessage(2230), 0xC8C8C8, 0xFF)); + } + } else if (type == 0) { + this->AddWrappedRouteLines(1, 141, 46, static_cast(object)->GetName()); + std::sprintf(text, "( %d %d )", this->currentTarget.x, this->currentTarget.y); + this->routeLines.push_back(NaviRouteLine(1, 142, 149, text, 0xDCDCDC, 0xCB6B54)); + } + return true; +} diff --git a/Source/UI/Navigation/UINavigationV4Wnd.h b/Source/UI/Navigation/UINavigationV4Wnd.h index 1e310263..0c3d1a2d 100644 --- a/Source/UI/Navigation/UINavigationV4Wnd.h +++ b/Source/UI/Navigation/UINavigationV4Wnd.h @@ -31,6 +31,8 @@ public: bool ClearRouteTexts(); void LayoutWithoutDetails(); void LayoutWithDetails(); + void RefreshLayout(); + void SelectRouteNode(int node); bool IsListShown() const; void DrawMapTexture(int x, int y, int size); std::string ObjectName(int type, void* object); diff --git a/Source/UI/Window/UIWindow.cpp b/Source/UI/Window/UIWindow.cpp index 3c815781..ec9927cf 100644 --- a/Source/UI/Window/UIWindow.cpp +++ b/Source/UI/Window/UIWindow.cpp @@ -2482,3 +2482,70 @@ void UINavigationV4Wnd::InsertNavigationLink(std::string name, int x, int y) { g_windowMgr.GetHoverWindow()->SendMsg(nullptr, 153, reinterpret_cast(link.c_str()), 0, 0, 0); } + +// OFFSET: 0x005C4830 +// FUNCTION: fn_03576 +void UINavigationV4Wnd::RefreshLayout() { + if (g_navigationManager.unknown_1254) { + this->Resize(134, 61); + this->unknown_00B8->Move((this->geometry.width - 15), 2); + this->unknown_00C4->Move((this->geometry.width - 32), 2); + this->unknown_00C0->Move(-1000, -1000); + this->unknown_00B4->Move(-1000, -1000); + this->unknown_00BC->Move(-1000, -1000); + this->unknown_00DC->Move(-1000, -1000); + this->unknown_00E0->Move(-1000, -1000); + this->unknown_00E4->Move(-1000, -1000); + this->unknown_00CC->Move(-1000, -1000); + this->unknown_00D0->Move(-1000, -1000); + this->unknown_00C8->Move(-1000, -1000); + this->unknown_00D4->Move(-1000, -1000); + this->unknown_00E8->Move(-1000, -1000); + this->unknown_00EC->Move(-1000, -1000); + this->unknown_00F0->Move(-1000, -1000); + this->unknown_00F4->Move(-1000, -1000); + this->unknown_00F8->Move(-1000, -1000); + this->unknown_00FC->Move(-1000, -1000); + this->unknown_00D8->Move(-1000, -1000); + this->unknown_0100->Move(-1000, -1000); + } else { + this->Resize(272, 338); + if (g_navigationManager.unknown_125C == 0) { + this->LayoutWithDetails(); + } else { + this->LayoutWithoutDetails(); + } + } + + this->Show(!g_navigationManager.unknown_1248); + this->FillRouteGroups(); + this->SelectRoute(g_navigationManager.unknown_12C4); + g_navigationManager.LoadTargetPreview(); + g_navigationManager.DestinationTitle(); + g_navigationManager.DestinationText(); + g_navigationManager.RouteText(); + g_navigationManager.SelectedNodeText(); + g_navigationManager.SubDestinationText(); + this->Invalidate(); +} + +// OFFSET: 0x005C9FE0 +// FUNCTION: fn_03637 +void UINavigationV4Wnd::SelectRouteNode(int node) { + if (node == -1) { + return; + } + + g_navigationManager.unknown_12C8 = node; + if (!g_navigationManager.SelectNodeState()) { + g_navigationManager.unknown_12C8 = 0; + g_navigationManager.SelectNodeState(); + } + + this->unknown_00F0->selected = g_navigationManager.unknown_12C8; + this->unknown_00F0->Invalidate(); + if (g_windowMgr.modal) { + reinterpret_cast(g_windowMgr.modal)->RefreshWorldMapSelection(); + } + this->Invalidate(); +}