#include "Contents/Jobs/JobId.h" #include "Client/Mode/ModeMessage.h" #include "UI/Window/UIEnums.h" #include "UI/Window/WindowId.h" #include "UI/Window/UIEnums.h" #include "Render/DeviceContext/DCSurface.h" #include "UI/Window/UINewSkillListWnd.h" #include "Client/Mode/CGameMode.h" #include "Client/Mode/ModeMgr.h" #include "Client/Replay.h" #include "Client/Session.h" #include "Contents/Jobs/JobNames.h" #include "Contents/MessageTable.h" #include "Contents/Skills/AutomaticSkills.h" #include "Contents/Hotkeys/HotkeyInfo.h" #include "Contents/Skills/SkillId.h" #include "Contents/UserData/NormalItemSlots.h" #include "Render/Renderer.h" #include "Resource/Bitmap/BitmapResource.h" #include "Resource/ResourceMgr.h" #include "Resource/SkinPalette.h" #include "Resource/TextureNames.h" #include "Script/LuaScript.h" #include "UI/Buttons/UIBitmapButton.h" #include "UI/Window/UIResizer.h" #include "UI/Lists/UIScrollBar.h" #include "UI/Lists/UITabControl.h" #include "UI/Chat/UINewChatWnd.h" #include "UI/Window/InputFocus.h" #include "UI/Skill/UISkillDescribeWnd.h" #include "UI/Window/UIWindowMgr.h" #include "UI/Window/WindowBorder.h" #include "UI/Window/WindowManagerWindows.h" #include "Utility/Cipher/XorBytes.h" #include "Utility/Text/FormatString.h" #include "Contents/Actors/Pc.h" #include "Contents/Configuration/ServiceConfiguration.h" #include "Contents/SpriteRidingOffset.h" #include "Render/DeviceContext/DeviceContext.h" #include "Render/Textures/SpriteAtlas.h" #include "Render/Textures/SpritePixels.h" #include "Render/Textures/Texture.h" #include "Resource/PaletteResource.h" #include "Resource/Sprites/ActionResource.h" #include "Resource/Sprites/SpriteResource.h" #include "Client/Mode/CMode.h" #include "Resource/MSGSTRINGID.h" #include #include #include #include #include #include namespace { template class EncryptedName { public: explicit EncryptedName(const std::uint8_t (&source)[Length]) : m_encrypted(true) { for (std::uint64_t index = 0; index < Length; ++index) { this->m_text[index] = source[index]; } } ~EncryptedName() { for (std::uint64_t index = 0; index < Length; ++index) { this->m_text[index] = 0; } } const char* Text() { if (this->m_encrypted) { utility::XorBytes(this->m_text, Length, Key); this->m_encrypted = false; } return reinterpret_cast(this->m_text); } private: std::uint8_t m_text[Length]; bool m_encrypted; }; } // INLINED static int SkillTabOf(int skillId) { if (skillId >= NV_BASIC && skillId <= ALL_RESURRECTION) { return 0; } if (skillId >= NV_FIRSTAID && skillId <= MG_ENERGYCOAT) { return 0; } if (skillId >= GS_GLITTERING && skillId <= NJ_ISSEN) { return 0; } if (skillId >= THIRDJOB_BEGIN && skillId <= THIRDJOB_END) { return 2; } if (skillId >= TK_RUN && skillId <= SG_FUSION) { return 0; } return 1; } // OFFSET: 0x009B3E70 // FUNCTION: fn_13826 void UINewSkillListWnd::StoreInfo() { if (this->m_saved) { this->m_saved->x = this->geometry.x; this->m_saved->y = this->geometry.y; this->m_saved->width = this->geometry.width; this->m_saved->height = this->geometry.height; } } // OFFSET: 0x009B2C60 // FUNCTION: fn_13821 void UINewSkillListWnd::OnWheel(int delta) { UIWindow* window = g_windowMgr.LookupWindow(WID_SKILLDESCRIBEWND); if (window && window->geometry.visible) { static_cast(window)->m_scrollBar->OnWheel(delta); return; } if (this->m_scrollable) { int step = this->m_visibleRows - 1; if (step > 3) { step = 3; } if (delta < 0) { this->SendMsg(this->m_scrollBar, UM_SCROLLVERT, step, 0, 0, 0); } else { this->SendMsg(this->m_scrollBar, UM_SCROLLVERT, -step, 0, 0, 0); } } } // OFFSET: 0x009AE730 // FUNCTION: fn_13800 void UINewSkillListWnd::ApplySkillPoints() { if (g_session.m_simplifiedSkillList == 0) { for (auto it = this->m_plannedSkillLevels.begin(); it != this->m_plannedSkillLevels.end(); ++it) { const int id = it->id; const int value = it->value; CSkillInfo* skill = g_session.m_skillBundle.FindGrouped(id); if (skill != nullptr && skill->available != 0) { for (int level = skill->learnedLevel + 1; level <= value; ++level) { g_modeMgr.gameMode->SendMsg(MM_UPGRADE_SKILLLEVEL, id, 0, 0, 0); } } } this->m_plannedSkillLevels.clear(); } else { std::list& live = this->SkillList(this->m_tab, true); std::list& pending = this->SkillList(this->m_tab, false); auto current = live.begin(); for (auto it = pending.begin(); it != pending.end(); ++it, ++current) { if (current->available != 0 && current->id == it->id && current->level < it->level) { for (int i = 0; i < it->level - current->level; ++i) { g_modeMgr.gameMode->SendMsg(MM_UPGRADE_SKILLLEVEL, current->id, 0, 0, 0); } } } } this->m_locked = 0; this->LayoutButtons(); } // GLOBAL: gl_25138 int g_skillListHeadOffsetX; // GLOBAL: gl_25139 int g_skillListHeadOffsetY; // OFFSET: 0x009AE930 // FUNCTION: fn_13801 void UINewSkillListWnd::DrawCharacter() { std::string bodyImf; std::string actions[2]; std::string sprites[2]; std::string bodyPal; std::string headPal; SpriteAtlasRecord record{}; const int job = g_session.m_job; const int sex = g_session.GetCurrentSex(); int head = g_session.m_head; const int bodyPalette = g_session.m_bodyStyle; const int headPalette = g_session.m_headPalette; actions[0].assign(static_cast(GetBodyActionResourceName(job, sex, g_session.m_bodyCostume, 0))); sprites[0].assign(static_cast(g_session.GetBodySprName(job, sex, g_session.m_bodyCostume, 0))); actions[1].assign(static_cast(g_session.GetHeadActName(job, sex, &head))); sprites[1].assign(static_cast(g_session.GetHeadSprName(job, sex, &head))); if (bodyPalette != 0) { bodyPal.assign(static_cast(g_session.GetBodyPalName(job, sex, bodyPalette, g_session.m_bodyCostume, 0, 0))); } if (headPalette != 0) { headPal.assign(static_cast(g_session.GetHeadPalName(head, job, sex, headPalette))); } bodyImf = GetBodyImfName(job, sex, head, g_session.m_bodyCostume); for (int layer = 0; layer < 2; ++layer) { ResourceMgr::Instance().Load(bodyImf.c_str()); if (actions[layer].size() == 0 || sprites[layer].size() == 0) { continue; } CActRes* action = static_cast(ResourceMgr::Instance().Load(actions[layer].c_str())); CSprRes* sprite = static_cast(ResourceMgr::Instance().Load(sprites[layer].c_str())); const int frameCount = static_cast(action->FrameCount(23)); int frame = static_cast(static_cast(timeGetTime() - this->m_lastTick) / 300.0f) / frameCount; if (frame >= 3) { frame = 0; this->m_lastTick = timeGetTime(); } const ActionFrame& actionFrame = action->Frame(23, frame); int offsetX = 0; int offsetY = 0; if (g_session.IsDoramJob(job) == true) { int view = 0; if (layer == 4) { view = *reinterpret_cast(&g_session.accessoryAppearance[0]); } else if (layer == 3) { view = *reinterpret_cast(&g_session.accessoryAppearance[4]); } else if (layer == 2) { view = *reinterpret_cast(&g_session.accessoryAppearance[8]); } int x = 0; int y = 0; g_session.m_hotkeyScript->Call("OffsetItemPos_GetOffsetForDoram", "ddd>dd", view, 23, g_session.GetCurrentSex(), &x, &y); offsetX = x; offsetY = y; } const std::uint32_t* palette = sprite->palette; if (layer == 1) { CActRes* bodyAction = static_cast(ResourceMgr::Instance().Load(actions[0].c_str())); const ActionFrame& bodyFrame = bodyAction->Frame(23, frame); if (actionFrame.anchorCount != 0 && bodyFrame.anchorCount != 0 && actionFrame.anchors[0].attachmentId == bodyFrame.anchors[0].attachmentId) { offsetX += bodyFrame.anchors[0].x - actionFrame.anchors[0].x; offsetY += bodyFrame.anchors[0].y - actionFrame.anchors[0].y; g_skillListHeadOffsetX = offsetX; g_skillListHeadOffsetY = offsetY; } } if (layer == 0) { if (bodyPalette != 0) { palette = static_cast(ResourceMgr::Instance().Load(bodyPal.c_str()))->palette; } } else if (layer == 1) { if (headPalette != 0) { palette = static_cast(ResourceMgr::Instance().Load(headPal.c_str()))->palette; } } const SpriteFrameCell& cell = actionFrame.Cell(layer > 1 ? 0 : layer); if (cell.image == -1) { continue; } const SpriteRleImage* image = sprite->images[cell.imageType][cell.image]; CTexture* texture; if (cell.imageType == 0) { texture = g_renderer->atlas.Find(image, palette, record); if (!texture) { texture = g_renderer->atlas.Insert(*GetRenderSystemHolder()->system, image, palette, record, g_serviceType, g_resourceReuseDelay); } } else { texture = static_cast(const_cast(image->surface)); } const int y = offsetY - 25 + cell.y + this->geometry.height; const int clipY = y < 0 ? y : 0; this->geometry.surface->DrawSurface( cell.x + offsetX + 48, y < 0 ? 0 : y, texture, static_cast(record.u0 * 256.0f), static_cast(record.v0 * 256.0f - clipY), image->width, image->height + clipY, cell.mirrored != 0, image->doubleWidth + 1, image->doubleHeight + 1, 0, 0 ); } } // OFFSET: 0x009AF300 // FUNCTION: fn_13802 void UINewSkillListWnd::DrawSkillList() { LuaScript& script = *g_session.m_hotkeyScript; int drawn = 0; int index = 0; for (const CSkillInfo& skill : this->SkillList(this->m_tab, false)) { if (index >= this->m_firstRow * this->m_columns) { const int level = skill.level; const int y = drawn / this->m_columns * 32; const int x = drawn % this->m_columns * 32; if (this->m_selectedSkill == index) { unsigned red, green, blue; SkinPaletteChannels(ResourceMgr::Instance(), 6, 2, red, green, blue); if (skill.level == 0) { SkinPaletteChannels(ResourceMgr::Instance(), 10, 2, red, green, blue); } else if (skill.spCost == 0) { SkinPaletteChannels(ResourceMgr::Instance(), 14, 2, red, green, blue); } this->FillRegion(x + 104, y + 32, 164, 28, RGB(red, green, blue)); } int shade = 0; char text[80]; g_session.ItemBitmapPath(skill.name, text); CBitmapRes* bitmap = static_cast(ResourceMgr::Instance().Load(Resolve(text))); if (!bitmap) { std::sprintf(text, "유저인터페이스\\item\\am_berserkpitcher.bmp"); bitmap = static_cast(ResourceMgr::Instance().Load(Resolve(text))); } if (bitmap) { if (level == 0) { shade = 120; this->DrawAuxiliary(1, x + 44, y + 34, &bitmap->image, true, false); } else { this->DrawBitmap(x + 44, y + 34, &bitmap->image, true); } } const int maxLevel = this->SkillAt(index, true).level; const int red = level > maxLevel ? 255 : shade; const int green = level > maxLevel ? 0 : shade; const int textX = x + 112; const int textY = y + 47; const bool usesLevel = CSkillInfo::UsesLevel(script, static_cast(skill.id)); if (level != 0) { std::sprintf(text, g_serviceType == 12 ? "Nv :" : "Lv :"); const int labelX = this->m_locked != 0 || !usesLevel ? textX + 1 : textX + 12; const std::uint32_t gray = RGB(shade, shade, shade); this->DrawTextAt(labelX, textY, text, 0, 0, 12, gray, 0, 0); int levelX; if (usesLevel) { int useLevel = g_session.UseLevel(static_cast(skill.id)); if (useLevel <= 0 || useLevel > skill.level) { useLevel = skill.level; } levelX = (this->m_locked == 0 ? 37 : 25) + textX; std::sprintf(text, "%2d", useLevel); this->DrawTextAt(levelX, textY, text, 0, 0, 12, useLevel == skill.level ? gray : 255, 0, 0); levelX += 7 + this->TextWidth(text, 0, 0, 12, 0, 0); this->DrawTextAt(levelX, textY, "/ ", 0, 0, 12, gray, 0, 0); levelX += this->TextWidth("/ ", 0, 0, 12, 0, 0); std::sprintf(text, "%2d", skill.level); } else { std::sprintf(text, "%d", skill.level); levelX = textX + 25; } this->DrawTextAt(levelX, textY, text, 0, 0, 12, RGB(green, green, red), 0, 0); if (skill.type == 0) { std::sprintf(text, LookupMessage(MSI_PASSIVE)); } else { int sp = static_cast(skill.spCost); const int useLevel = g_session.UseLevel(static_cast(skill.id)); bool found = false; if (usesLevel) { const std::uint32_t id = skill.id; for (const CSkillInfo& live : this->SkillList(this->m_tab, true)) { if (live.id == id && live.level != 0) { const int amount = skill.UseSpAmount(script, useLevel); if (amount != 0 && useLevel <= skill.level) { sp = amount; } found = true; break; } } } if (!found) { sp = skill.UseSpAmount(script, skill.level); } const int ap = skill.UseApAmount(script, useLevel > 0 ? useLevel : skill.level); if (SkillUsesPercentSp(static_cast(skill.id))) { if (ap != 0) { std::sprintf(text, "Sp : %d%%, Ap : %d", sp, ap); } else { std::sprintf(text, "Sp : %d%%", sp); } } else if (ap != 0) { std::sprintf(text, "Sp : %d, Ap : %d", sp, ap); } else { std::sprintf(text, "Sp : %d", sp); } } this->DrawTextAt(textX + 100, textY, text, 0, 0, 12, gray, 0, 0); } this->DrawTextAt(textX, y + 35, skill.DisplayName(script), 0, 0, 12, RGB(shade, shade, shade), 0, 0); if (++drawn == this->m_visibleRows * this->m_columns) { break; } } ++index; } } // OFFSET: 0x009B0370 // FUNCTION: fn_13805 std::string UINewSkillListWnd::ValueText(int id) { for (const NewSkillListValue& entry : this->m_values) { if (entry.id == id) { if (entry.value > 0) { char text[12]; _itoa(entry.value, text, 10); return text; } break; } } return std::string(""); } // OFFSET: 0x009B0460 // FUNCTION: fn_13806 CSkillInfo UINewSkillListWnd::SkillAt(int index, bool live) { int sequentialIndex = 0; CSkillInfo result(0); std::list& list = this->SkillList(this->m_tab, live); std::list::iterator it = list.begin(); while (it != list.end()) { int key = it->treePosition; if (g_session.m_simplifiedSkillList != 0) { key = sequentialIndex; } if (key == index) { result = *it; break; } ++it; if (g_session.m_simplifiedSkillList != 0) { ++sequentialIndex; } } return result; } // OFFSET: 0x009B0680 // FUNCTION: fn_13807 CSkillInfo UINewSkillListWnd::VisibleSkill(int row) { std::list& list = this->SkillList(this->m_tab, false); int index = 0; for (std::list::iterator it = list.begin(); it != list.end(); ++it) { if (row + this->m_firstRow == index) { return *it; } ++index; } return CSkillInfo(0); } // OFFSET: 0x009B0750 // FUNCTION: fn_13808 std::optional UINewSkillListWnd::SkillPtr(int index, bool live) { std::list& list = this->SkillList(this->m_tab, live); if (g_session.m_simplifiedSkillList != 0) { if (index >= 0 && static_cast(index) < list.size()) { auto it = list.begin(); std::advance(it, index); return &*it; } return std::nullopt; } for (auto it = list.begin(); it != list.end(); ++it) { if (it->treePosition == index) { return &*it; } } return std::nullopt; } // OFFSET: 0x009B07C0 // FUNCTION: fn_13809 std::list& UINewSkillListWnd::SkillList(int tab, bool live) { if (tab >= 0 && static_cast(this->m_tabs->labels.size()) - 1 == tab) { return g_session.m_skillBundle.extra; } if (live) { return g_session.m_skillBundle.Group(tab); } return this->m_lists[tab]; } // OFFSET: 0x009B0820 // FUNCTION: fn_13810 void UINewSkillListWnd::InitTabs() { const int initialTabCount = this->m_tabs->labels.size(); const std::uint8_t source[23] = { 0xEF, 0xF2, 0x0F, 0xAA, 0x08, 0x4E, 0x1B, 0x6F, 0xF1, 0xFC, 0x0F, 0xA6, 0x24, 0x42, 0x03, 0x57, 0xC4, 0xFF, 0x23, 0x98, 0x0E, 0x42, 0x77 }; thread_local EncryptedName funcName(source); const char* func = funcName.Text(); const char* defaultTabs[4] = {}; const char* jobTabs[4] = {}; g_session.m_hotkeyScript->Call(func, "d>ssss", 0, &defaultTabs[0], &defaultTabs[1], &defaultTabs[2], &defaultTabs[3]); const int baseJob = JobNames::BaseJob(g_session.GetDisplayJob()); int jobKey = baseJob; switch (baseJob) { case JT_NOVICE: case JT_NOVICE_H: case JT_NOVICE_B: jobKey = 0; break; case JT_SWORDMAN: case JT_SWORDMAN_H: case JT_SWORDMAN_B: jobKey = 1; break; case JT_MAGICIAN: case JT_MAGICIAN_H: case JT_MAGICIAN_B: jobKey = 2; break; case JT_ARCHER: case JT_ARCHER_H: case JT_ARCHER_B: jobKey = 3; break; case JT_ACOLYTE: case JT_ACOLYTE_H: case JT_ACOLYTE_B: jobKey = 4; break; case JT_MERCHANT: case JT_MERCHANT_H: case JT_MERCHANT_B: jobKey = 5; break; case JT_THIEF: case JT_THIEF_H: case JT_THIEF_B: jobKey = 6; break; case JT_KNIGHT: case JT_KNIGHT_H: case JT_KNIGHT_B: case JT_RUNE_KNIGHT: case JT_RUNE_KNIGHT_H: case JT_RUNE_KNIGHT_B: case JT_DRAGON_KNIGHT: jobKey = 7; break; case JT_PRIEST: case JT_PRIEST_H: case JT_PRIEST_B: case JT_ARCHBISHOP: case JT_ARCHBISHOP_H: case JT_ARCHBISHOP_B: case JT_CARDINAL: jobKey = 8; break; case JT_WIZARD: case JT_WIZARD_H: case JT_WIZARD_B: case JT_WARLOCK: case JT_WARLOCK_H: case JT_WARLOCK_B: case JT_ARCH_MAGE: jobKey = 9; break; case JT_BLACKSMITH: case JT_BLACKSMITH_H: case JT_BLACKSMITH_B: case JT_MECHANIC: case JT_MECHANIC_H: case JT_MECHANIC_B: case JT_MEISTER: jobKey = 10; break; case JT_HUNTER: case JT_HUNTER_H: case JT_HUNTER_B: case JT_RANGER: case JT_RANGER_H: case JT_RANGER_B: case JT_WINDHAWK: jobKey = 11; break; case JT_ASSASSIN: case JT_ASSASSIN_H: case JT_ASSASSIN_B: case JT_GUILLOTINE_CROSS: case JT_GUILLOTINE_CROSS_H: case JT_GUILLOTINE_CROSS_B: case JT_SHADOW_CROSS: jobKey = 12; break; case JT_CRUSADER: case JT_CRUSADER_H: case JT_CRUSADER_B: case JT_ROYAL_GUARD: case JT_ROYAL_GUARD_H: case JT_ROYAL_GUARD_B: case JT_IMPERIAL_GUARD: jobKey = 14; break; case JT_MONK: case JT_MONK_H: case JT_MONK_B: case JT_SURA: case JT_SURA_H: case JT_SURA_B: case JT_INQUISITOR: jobKey = 15; break; case JT_SAGE: case JT_SAGE_H: case JT_SAGE_B: case JT_SORCERER: case JT_SORCERER_H: case JT_SORCERER_B: case JT_ELEMENTAL_MASTER: jobKey = 16; break; case JT_ROGUE: case JT_ROGUE_H: case JT_ROGUE_B: case JT_SHADOW_CHASER: case JT_SHADOW_CHASER_H: case JT_SHADOW_CHASER_B: case JT_ABYSS_CHASER: jobKey = 17; break; case JT_ALCHEMIST: case JT_ALCHEMIST_H: case JT_ALCHEMIST_B: case JT_GENETIC: case JT_GENETIC_H: case JT_GENETIC_B: case JT_BIOLO: jobKey = 18; break; case JT_BARD: case JT_BARD_H: case JT_BARD_B: case JT_MINSTREL: case JT_MINSTREL_H: case JT_MINSTREL_B: case JT_TROUBADOUR: jobKey = 19; break; case JT_DANCER: case JT_DANCER_H: case JT_DANCER_B: case JT_WANDERER: case JT_WANDERER_H: case JT_WANDERER_B: case JT_TROUVERE: jobKey = 20; break; case JT_SUPERNOVICE: case JT_SUPERNOVICE_B: case JT_SUPERNOVICE2: case JT_SUPERNOVICE2_B: case JT_HYPER_NOVICE: jobKey = 23; break; case JT_GUNSLINGER: case JT_REBELLION: case JT_GUNSLINGER_B: case JT_REBELLION_B: case JT_NIGHT_WATCH: jobKey = 24; break; case JT_NINJA: case JT_KAGEROU: case JT_OBORO: case JT_NINJA_B: case JT_KAGEROU_B: case JT_OBORO_B: case JT_SHINKIRO: case JT_SHIRANUI: jobKey = 25; break; case JT_TAEKWON: case JT_TAEKWON_B: jobKey = 4046; break; case JT_STAR: case JT_STAR_B: case JT_STAR_EMPEROR: case JT_STAR_EMPEROR_B: case JT_SKY_EMPEROR: jobKey = 4047; break; case JT_LINKER: case JT_LINKER_B: case JT_SOUL_REAPER: case JT_SOUL_REAPER_B: case JT_SOUL_ASCETIC: jobKey = 4049; break; case JT_DO_SUMMONER: case JT_DO_SUMMONER_B: case JT_SPIRIT_HANDLER: jobKey = 4218; break; case JT_DRUID: case JT_DRUID_B: case JT_KARNOS: case JT_KARNOS_B: case JT_ALITEA: jobKey = 4351; break; default: break; } g_session.m_hotkeyScript->Call(func, "d>ssss", jobKey, &jobTabs[0], &jobTabs[1], &jobTabs[2], &jobTabs[3]); for (int i = this->m_tabs->labels.size(); i < 4; ++i) { if (g_session.m_skillBundle.GroupSize(i) > 0) { const char* name = jobTabs[i]; if (!name) { name = defaultTabs[i]; } this->m_tabs->AddTab(name, ""); this->m_tab = i; } } if (initialTabCount == 0) { this->m_tabs->AddTab(LookupMessage(MSI_ETC), ""); } this->m_tabs->activeTabIndex = this->m_tab; } // OFFSET: 0x009B0E40 // FUNCTION: fn_13811 void UINewSkillListWnd::ResetLists() { this->m_locked = 0; this->m_skillPoints = g_session.m_skillPoint; for (unsigned i = 0; i < 4; ++i) { this->m_lists[i] = g_session.m_skillBundle.Group(i); } this->m_plannedSkillLevels.clear(); g_session.m_skillBundle.MarkLevelChanges(); } // OFFSET: 0x009B0EE0 // FUNCTION: fn_13812 bool UINewSkillListWnd::IsExtraTab(int tab) { return tab >= 0 && static_cast(this->m_tabs->labels.size()) - 1 == tab; } // OFFSET: 0x009B0FA0 // FUNCTION: fn_13813 bool UINewSkillListWnd::IsSkillArea(int x, int y) { int minX = 76; int rowHeight = 56; int yOffset = 40; bool inside = true; if (g_session.m_simplifiedSkillList != 0) { rowHeight = 32; minX = 40; yOffset = 30; } if (y < this->m_topMargin || y >= this->m_visibleRows * rowHeight + 4 + yOffset) { inside = false; } if (x < minX) { inside = false; } if (g_session.m_simplifiedSkillList == 0 && x > 504) { inside = false; } return inside; } // OFFSET: 0x009B1050 // FUNCTION: fn_13814 void UINewSkillListWnd::OnCreate(int width, int height) { this->m_tabs = new UITabControl(); this->m_tabs->Configure(0, 17, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0); this->m_tabs->Move(0, 6); this->AddChild(this->m_tabs); { std::string minimizeTooltip(""); this->m_btnHelp = this->AddBaseButton(); this->m_btnMinimize = this->AddMinimizeButton(minimizeTooltip.c_str(), 0); this->m_btnClose = this->AddCloseButton("Alt+S", 0); int titleButtonX[3] = {22, this->geometry.width - 30, this->geometry.width - 16}; UIBitmapButton** titleButtons = &this->m_btnHelp; for (int i = 0; i < 3; ++i) { titleButtons[i]->Move(titleButtonX[i], 5); } } this->m_scrollBar = new UIScrollBar(); this->m_scrollBar->Configure(this->geometry.width - 15, 20, 1, this->geometry.height - this->m_topMargin - this->m_bottomMargin); this->m_scrollBar->Move(this->geometry.width - 15, 20); this->m_scrollBar->Resize(this->m_scrollBar->geometry.width, this->geometry.height - this->m_topMargin - this->m_bottomMargin); this->AddChild(this->m_scrollBar); { std::string normal = "유저인터페이스\\basic_interface\\skill_up_a.bmp"; std::string hover = "유저인터페이스\\basic_interface\\skill_up_b.bmp"; std::string pressed = "유저인터페이스\\basic_interface\\skill_up_c.bmp"; for (int i = 0; i < 10; ++i) { this->m_btnLevelUp[i] = new UIBitmapButton(); this->m_btnLevelUp[i]->SetBitmap(normal.c_str(), 0, 0); this->m_btnLevelUp[i]->SetBitmap(hover.c_str(), 1, 0); this->m_btnLevelUp[i]->SetBitmap(pressed.c_str(), 2, 0); this->m_btnLevelUp[i]->SetSize(this->m_btnLevelUp[i]->BitmapWidth(), this->m_btnLevelUp[i]->BitmapHeight()); this->m_btnLevelUp[i]->Move(78, 21 + i * 32); this->m_btnLevelUp[i]->SetId(i + 239); this->AddChild(this->m_btnLevelUp[i]); } } { std::string left = "유저인터페이스\\basic_interface\\arw_left.bmp"; for (int i = 0; i < 42; ++i) { this->m_btnSkills[i] = new UIBitmapButton(); this->m_btnSkills[i]->SetBitmap(left.c_str(), 0, 0); this->m_btnSkills[i]->SetBitmap(left.c_str(), 1, 0); this->m_btnSkills[i]->SetBitmap(left.c_str(), 2, 0); this->m_btnSkills[i]->SetSize(this->m_btnSkills[i]->BitmapWidth(), this->m_btnSkills[i]->BitmapHeight()); this->m_btnSkills[i]->Move(106, 34 + i * 32); this->m_btnSkills[i]->SetId(i); this->AddChild(this->m_btnSkills[i]); } } { std::string right = "유저인터페이스\\basic_interface\\arw_right.bmp"; for (int i = 0; i < 42; ++i) { this->m_btnSkillUps[i] = new UIBitmapButton(); this->m_btnSkillUps[i]->SetBitmap(right.c_str(), 0, 0); this->m_btnSkillUps[i]->SetBitmap(right.c_str(), 1, 0); this->m_btnSkillUps[i]->SetBitmap(right.c_str(), 2, 0); this->m_btnSkillUps[i]->SetSize(this->m_btnSkillUps[i]->BitmapWidth(), this->m_btnSkillUps[i]->BitmapHeight()); this->m_btnSkillUps[i]->Move(198, 34 + i * 32); this->m_btnSkillUps[i]->SetId(i + 42); this->AddChild(this->m_btnSkillUps[i]); } } { std::string baseDir("유저인터페이스\\"); const char* buttonNames[2][3] = { {"btn_apply", "btn_apply_a", "btn_apply_b"}, {"btn_reset", "btn_reset_a", "btn_reset_b"}, }; POINT buttonPos[2] = { {this->geometry.width - 100, this->geometry.height - 27}, {this->geometry.width - 50, this->geometry.height - 27}, }; int buttonIds[2] = {271, 363}; UIBitmapButton** buttons = &this->m_btnApply; for (int i = 0; i < 2; ++i) { buttons[i] = new UIBitmapButton(); buttons[i]->SetBitmap((baseDir + buttonNames[i][0] + ".bmp").c_str(), 0, 0); buttons[i]->SetBitmap((baseDir + buttonNames[i][1] + ".bmp").c_str(), 1, 0); buttons[i]->SetBitmap((baseDir + buttonNames[i][2] + ".bmp").c_str(), 2, 0); buttons[i]->SetSize(buttons[i]->BitmapWidth(), buttons[i]->BitmapHeight()); buttons[i]->Move(buttonPos[i].x, buttonPos[i].y); buttons[i]->SetId(buttonIds[i]); buttons[i]->SetDisabled(1); buttons[i]->Invalidate(); this->AddChild(buttons[i]); } } this->m_btnDetail = new UIResizer(); this->m_btnDetail->SetSize(16, 16); this->m_btnDetail->SetDirection(3); this->m_btnDetail->SetBitmapName("유저인터페이스\\btn_resize.bmp"); this->m_btnDetail->Move(this->geometry.width - 15, this->geometry.height - 15); this->m_btnDetail->SetColors(0xFFFF00FF, 0xFFFF00FF); this->AddChild(this->m_btnDetail); this->InitTabs(); if (!g_session.m_skillBundle.extraIds.empty()) { this->m_tabs->SetFlag(static_cast(this->m_tabs->labels.size()) - 1, true); } this->SendMsg(nullptr, UM_REFRESHITEM, 0, 0, 0, 0); } // OFFSET: 0x009B2160 // FUNCTION: fn_13815 void UINewSkillListWnd::Paint() { const int left = this->m_tabs->geometry.width; this->ClearSurface(0xFFFF00FF); this->FillRegion(left, this->m_topMargin, this->geometry.width - left, this->geometry.height - this->m_bottomMargin - this->m_topMargin, 0xFFFFFF); DrawRoundedBorder(*this, left, 0, this->geometry.width - left, this->geometry.height, 0xC3C3C3); DrawRoundedBorder(*this, left + 2, 2, this->geometry.width - left - 4, this->geometry.height - 4, 0xC3C3C3); CBitmapRes* bitmap = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\skill_upbar_left.bmp"))); this->DrawBitmap(left, 0, bitmap, false); bitmap = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\skill_upbar_right.bmp"))); this->DrawBitmap(this->geometry.width - bitmap->image.width, 0, bitmap, false); int span = this->geometry.width - left - 24; int count = span / 5 + (span % 5 != 0); for (int i = 0, x = left + 12; i < count; ++i, x += 5) { bitmap = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\skill_upbar_mid.bmp"))); this->DrawBitmap(x, 0, bitmap, false); } bitmap = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\skill_botbar_left.bmp"))); this->DrawBitmap(left, this->geometry.height - bitmap->image.height, bitmap, false); bitmap = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\skill_botbar_right.bmp"))); this->DrawBitmap(this->geometry.width - bitmap->image.width, this->geometry.height - bitmap->image.height, bitmap, false); bitmap = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\skill_botbar_mid.bmp"))); span = this->geometry.width - left - 24; count = span / bitmap->image.width + (span % bitmap->image.width != 0); for (int i = 0, x = left + 12; i < count; ++i, x += 5) { this->DrawBitmap(x, this->geometry.height - bitmap->image.height, bitmap, false); } this->DrawTextAt(41, 6, LookupMessage(MSI_SKILLLIST), 0, 0, 12, 0xFFFFFF, 0, 0); this->DrawTextAt(40, 5, LookupMessage(MSI_SKILLLIST), 0, 0, 12, 0, 0, 0); const int points = this->m_skillPoints; char text[80]; std::memset(text, 0, sizeof(text)); if (g_session.m_simplifiedSkillList != 0) { std::sprintf(text, LookupMessage(MSI_SKILLPOINT), points); this->TextWidth(text, 0, 0, 12, 0, 0); this->DrawTextAt(30, this->geometry.height - 23, text, 0, 0, 12, 0, 0, 0); this->DrawSkillList(); } else { const int total = g_session.m_skillPoint; if (points < total) { StringFormat(text, sizeof(text), static_cast(-1), "%s^0000ff%d^000000(^ff0000%d^000000) / %d", LookupMessage(MSI_REFUSE_CHANGE_PASSWD_SITE), points, total - points, total); } else { StringFormat(text, sizeof(text), static_cast(-1), "%s%d", LookupMessage(MSI_REFUSE_CHANGE_PASSWD_SITE), total); } this->DrawWrappedFormattedText(80, this->geometry.height - 23, std::string(text), 0, 0, 0, 0, 12, 0, 0); this->DrawSkillTree(); this->DrawCharacter(); } this->m_needsRedraw = 0; } // OFFSET: 0x009B2590 // FUNCTION: fn_13816 void UINewSkillListWnd::OnLBtnDblClk(int x, int y) { if (g_session.GetOption(5) > 0) { return; } if (y < this->m_topMargin) { UIFrameWnd::OnLBtnDown(x, y); return; } if (this->m_selectedSkill == -1) { return; } CSkillInfo skill = this->SkillAt(this->m_selectedSkill, false); if (skill.available != 0 && skill.level != 0) { AutomaticSkills& automatic = g_session.m_automaticSkills; if (automatic.Contains(skill.id)) { g_session.SetOption(automatic.CancelOption(skill.id), 1, 0); } else { automatic.DisableAll(); } this->SendMsg(nullptr, UM_USESKILL, reinterpret_cast(&skill), 0, 0, 0); } } // OFFSET: 0x009B26D0 // FUNCTION: fn_13817 void UINewSkillListWnd::OnLBtnDown(int x, int y) { if (y < this->m_topMargin) { UIFrameWnd::OnLBtnDown(x, y); return; } if (this->m_showDetail) { this->m_showDetail = false; this->UpdateRequiredSkills(); } int oldSelected = this->m_selectedSkill; this->m_selectedSkill = this->SkillIndexAt(x, y); if (oldSelected != this->m_selectedSkill) { this->Invalidate(); } if (this->IsSkillArea(x, y) && !this->m_locked && this->m_selectedSkill != -1 && g_replayAnimationScaling == 0) { CSkillInfo skill = this->SkillAt(this->m_selectedSkill, false); if (skill.available != 0 && skill.level != 0 && skill.type != 0) { CGameMode* gameMode = static_cast(g_modeMgr.GetGameMode()); if (gameMode) { int level = g_session.UseLevel(skill.id); if (level == 0) { level = skill.level; } gameMode->drag.BeginSkill(g_windowMgr, ResourceMgr::Instance(), 8, skill.id, skill.name, level, -1); } } } } // OFFSET: 0x009B2810 // FUNCTION: fn_13818 void UINewSkillListWnd::OnLBtnUp(int x, int y) { if (y < this->m_topMargin) { UIFrameWnd::OnLBtnUp(x, y); this->StoreInfo(); return; } const int gate = g_session.m_simplifiedSkillList; if (gate == 0 && this->IsSkillArea(x, y)) { CSkillInfo skill = this->SkillAt(this->m_selectedSkill, false); if (skill.canPlanLevel != 0 && skill.available != 0) { if (this->IncreaseSkillLevel(skill.id)) { this->m_needsRedraw = 1; this->m_locked = 1; this->LayoutButtons(); this->Invalidate(); } } } } // OFFSET: 0x009B28F0 // FUNCTION: fn_13819 void UINewSkillListWnd::OnMouseHover(int x, int y) { const int gate = g_session.m_simplifiedSkillList; if (!this->IsSkillArea(x, y) || (gate != 0 && x > 96)) { return; } const int previous = this->m_selectedSkill; this->m_selectedSkill = this->SkillIndexAt(x, y); if (this->m_selectedSkill != -1) { std::optional skill = this->SkillPtr(this->m_selectedSkill, false); if (skill.has_value() && (*skill)->available != 0) { int tooltipX = -10; int tooltipY = -20; this->ToScreen(tooltipX, tooltipY); const char* name = (*skill)->DisplayName(*g_session.m_hotkeyScript); g_modeMgr.gameMode->ShowTooltip(name, tooltipX + x, tooltipY + y, -1, 0, 1); } } if (previous != this->m_selectedSkill) { if (!this->m_showDetail) { this->UpdateRequiredSkills(); } this->m_needsRedraw = 1; this->Invalidate(); } } // OFFSET: 0x009B29E0 // FUNCTION: fn_13820 void UINewSkillListWnd::OnRBtnUp(int x, int y) { this->m_selectedSkill = this->SkillIndexAt(x, y); if (this->m_selectedSkill == -1) { this->m_showDetail = false; this->m_values.clear(); for (int tab = 0; tab < static_cast(this->m_tabs->labels.size()); ++tab) { UITabControl* tabs = this->m_tabs; tabs->colors[tab] = 0xFFFFFFFF; tabs->Invalidate(); } } else if (this->m_detailId != this->m_selectedSkill) { this->m_showDetail = true; this->UpdateRequiredSkills(); } else { this->m_showDetail = !this->m_showDetail; } if (this->m_showDetail) { this->m_detailId = this->m_selectedSkill; } this->m_needsRedraw = 1; this->Invalidate(); const std::optional selected = this->SkillPtr(this->m_selectedSkill, false); if (!selected.has_value()) { return; } const CSkillInfo& skill = **selected; UISkillDescribeWnd* describe = static_cast(g_windowMgr.LookupWindow(WID_SKILLDESCRIBEWND)); if (describe && (describe->m_skillId == static_cast(skill.id) || !skill.available)) { g_windowMgr.CloseWindow(WID_SKILLDESCRIBEWND); return; } UIWindow* window = g_windowMgr.EnsureWindow(WID_SKILLDESCRIBEWND); if (!window) { return; } window->SendMsg(nullptr, UM_SKILLDESCRIBE, skill.id, skill.upgradable, skill.level, 1); int originX = 0; int originY = 0; this->ToScreen(originX, originY); int left = x + originX + 24; const int bottom = y + originY; if (left + window->geometry.width > g_renderer->viewport.width) { left -= window->geometry.width + 48; } int top = bottom + window->geometry.height > g_renderer->viewport.height ? bottom - window->geometry.height : bottom; if (top < 0) { top = 0; } window->Move(left, top); g_windowMgr.ShowWindowById(WID_SKILLDESCRIBEWND, 1); } // OFFSET: 0x009B2CD0 // FUNCTION: fn_13822 void UINewSkillListWnd::UpdateRequiredSkills() { this->m_values.clear(); for (int i = 0; i < static_cast(this->m_tabs->labels.size()); ++i) { UITabControl* tabs = this->m_tabs; tabs->colors[i] = 0xFFFFFFFF; tabs->Invalidate(); } for (CSkillInfo& skill : this->SkillList(this->m_tab, false)) { const int key = static_cast(skill.treePosition); if (key != -1 && this->m_firstRow * this->m_columns <= key && key == this->m_selectedSkill) { this->m_values.push_back({static_cast(skill.id), 0}); for (std::size_t i = 0; i < skill.entries.size(); ++i) { const int id = static_cast(skill.entries[i].skillId); const int tab = SkillTabOf(id); if (tab != this->m_tab) { UITabControl* tabs = this->m_tabs; if (tab < static_cast(tabs->labels.size())) { tabs->colors[tab] = 0xE1E7FC; tabs->Invalidate(); } } int level = static_cast(skill.entries[i].requiredLevel); for (auto it = this->m_values.begin(); it != this->m_values.end(); ++it) { if (it->id == id) { level = (std::max)(level, it->value); this->m_values.erase(it); break; } } skill.entries[i].requiredLevel = level; const SkillInfoEntry& entry = skill.entries[i]; this->m_values.push_back({static_cast(entry.skillId), static_cast(entry.requiredLevel)}); } return; } } } // OFFSET: 0x009B3040 // FUNCTION: fn_13823 void UINewSkillListWnd::UpdateScrollBar() { if (g_session.m_simplifiedSkillList != 0) { this->m_columns = 1; this->m_visibleRows = (this->geometry.height - this->m_bottomMargin - 30) / 32; } else { this->m_columns = 7; this->m_visibleRows = 6; } if (!this->m_scrollBar) { return; } this->m_scrollBar->Move(this->geometry.width - 15, 20); const int visibleRows = this->m_visibleRows; const std::list& list = this->SkillList(this->m_tab, false); int total = 0; if (g_session.m_simplifiedSkillList != 0) { total = list.size() / this->m_columns + (list.size() % this->m_columns != 0); } else { int maxKey = 0; for (const CSkillInfo& skill : list) { const int key = static_cast(skill.treePosition); if (key > maxKey) { maxKey = key; } } const int count = maxKey + 1; total = count / this->m_columns + (count % this->m_columns != 0); } int maxScroll = total - visibleRows; if (maxScroll < 0) { maxScroll = 0; } this->m_scrollable = maxScroll != 0; if (maxScroll != 0) { this->m_scrollBar->SetRange(maxScroll, visibleRows, total); if (this->m_firstRow < 0) { this->m_firstRow = 0; } if (this->m_firstRow > total - this->m_visibleRows) { this->m_firstRow = total - this->m_visibleRows; } this->m_scrollBar->SetPosition(this->m_firstRow); } else { this->m_firstRow = 0; this->m_scrollBar->SetRange(0, visibleRows, total); } this->m_scrollBar->Invalidate(); this->m_needsRedraw = 1; } // OFFSET: 0x009B31C0 // FUNCTION: fn_13824 void UINewSkillListWnd::LayoutButtons() { int inset = 0; if (g_session.m_simplifiedSkillList != 0) { this->m_selectedSkill = -1; inset = 15; this->m_btnDetail->Move(this->geometry.width - inset, this->geometry.height - inset); } else { this->m_btnDetail->Move(-100, -100); } this->m_btnMinimize->Move(this->geometry.width - 30, 5); this->m_btnClose->Move(this->geometry.width - 16, 5); this->m_btnApply->Move(this->geometry.width - inset - 100, this->geometry.height - 27); this->m_btnCancel->Move(this->geometry.width - inset - 50, this->geometry.height - 27); this->m_btnApply->SetDisabled(!this->m_locked); this->m_btnApply->Invalidate(); this->m_btnCancel->SetDisabled(!this->m_locked); this->m_btnCancel->Invalidate(); if (!this->m_locked) { this->m_btnApply->SetState(0); this->m_btnCancel->SetState(0); } for (int i = 0; i < 10; ++i) { this->m_btnLevelUp[i]->Move(-200, -200); } for (int i = 0; i < 42; ++i) { this->m_btnSkills[i]->Move(-200, -200); this->m_btnSkillUps[i]->Move(-200, -200); } std::list& list = this->SkillList(this->m_tab, false); if (g_session.m_simplifiedSkillList != 0) { for (int row = 0; row < 10; ++row) { const int y = row * 32 + 48; CSkillInfo skill = this->VisibleSkill(row); if (skill.level < static_cast(skill.maxLevel) && row < this->m_visibleRows && static_cast(this->m_firstRow + row) < list.size() && this->m_skillPoints != 0 && skill.available != 0 && skill.upgradable != 0) { this->m_btnLevelUp[row]->Move(78, y - 14); } if (!this->m_locked && row < this->m_visibleRows && static_cast(this->m_firstRow + row) < list.size() && skill.available != 0 && skill.level != 0 && CSkillInfo::UsesLevel(*g_session.m_hotkeyScript, skill.id)) { this->m_btnSkills[row]->Move(112, y); this->m_btnSkillUps[row]->Move(192, y); } } } else if (!this->m_locked) { for (std::list::iterator it = list.begin(); it != list.end(); ++it) { const unsigned int slot = it->treePosition - this->m_columns * this->m_firstRow; if (slot < 42 && it->available != 0 && it->treePosition != 0xFFFFFFFF && it->level != 0 && CSkillInfo::UsesLevel(*g_session.m_hotkeyScript, it->id)) { const int y = static_cast(slot) / this->m_columns * 56 + 70; const int x = static_cast(slot) % this->m_columns * 68 + 56; this->m_btnSkillUps[slot]->Move(x + 55, y); this->m_btnSkills[slot]->Move(x, y); } } } } // OFFSET: 0x009B3580 // FUNCTION: fn_13825 int UINewSkillListWnd::SendMsg(UIWindow* sender, int message, std::intptr_t a, std::intptr_t b, std::intptr_t c, std::intptr_t d) { int& gate = g_session.m_simplifiedSkillList; CMode* gameMode = g_modeMgr.gameMode; switch (message) { case UM_SETINFO: this->m_saved = reinterpret_cast(a); if (this->m_saved->IsValid() == 0) { this->m_saved->Reset(); } if (gate != 0) { this->SendMsg(nullptr, UM_RESIZE, 3, 330, this->m_saved->height, 0); } else { this->SendMsg(nullptr, UM_RESIZE, 3, 560, 400, 0); } return 0; case UM_RESIZE: if (a == 3) { if (gate != 0) { const int minimum = this->m_tabs->geometry.height + 12; if (c < minimum) { c = minimum; } if (c > 400) { c = 400; } c = (c - 400) / 32 * 32 + 400; } if (this->geometry.width != b || c != this->geometry.height) { this->Resize(b, c); this->m_scrollBar->Resize(this->m_scrollBar->geometry.width, this->geometry.height - this->m_bottomMargin - this->m_topMargin); this->UpdateScrollBar(); this->LayoutButtons(); this->Invalidate(); } this->Move(this->m_saved->x, this->m_saved->y); g_windowMgr.StoreSnapRectangle(this); return 0; } break; case UM_COMMAND: switch (a) { case ID_CLOSE: g_windowMgr.CloseWindow(WID_SKILLLISTWND); return 0; case ID_APPLY: if (g_windowMgr.Confirm(LookupMessage(MSI_APPLY_SKILL_UP), 2, false, false, 0, "Message", 280, 120, 0) == 187) { this->ApplySkillPoints(); this->SendMsg(nullptr, UM_REFRESHITEM, 0, 0, 0, 0); if (ChatWindow()) { g_windowMgr.SetFocus(ChatWindow()->m_suggestions); return 0; } } break; case ID_RESET: this->ResetLists(); this->SendMsg(nullptr, UM_REFRESHITEM, 0, 0, 0, 0); return 0; case ID_MINI: this->StoreInfo(); this->m_needsRedraw = 1; gate = gate == 0; if (gate != 0) { this->SendMsg(nullptr, UM_RESIZE, 3, 330, this->m_saved->height, 0); } else { this->SendMsg(nullptr, UM_RESIZE, 3, 560, 400, 0); } return 0; case ID_CHECK: { int& flag = g_session.m_showSkillDescription; flag = flag == 0; return 0; } case ID_LVLUP1: case ID_LVLUP2: case ID_LVLUP3: case ID_LVLUP4: case ID_LVLUP5: case ID_LVLUP6: case ID_LVLUP7: case 246: case 247: case 248: if (this->m_skillPoints != 0) { CSkillInfo skill = this->SkillAt(this->m_columns * this->m_firstRow + a - 239, false); if (skill.available != 0 && skill.upgradable != 0 && this->IncreaseSkillLevel(skill.id)) { this->m_needsRedraw = 1; this->m_locked = 1; this->LayoutButtons(); this->Invalidate(); } return 0; } break; case ID_PLUS: { const int tab = this->m_tab; int next = 0; if (tab + 1 != this->m_tabs->TabCount()) { next = tab + 1; } this->m_tabs->SelectTab(next); return 0; } default: { g_session.SetOption(7, 1, 0); const unsigned int offset = static_cast(a) - 42; const unsigned int slot = offset > 41 ? static_cast(a) : offset; CSkillInfo skill = this->SkillAt(this->m_columns * this->m_firstRow + slot, false); if (skill.available != 0) { const int current = g_session.UseLevel(skill.id); int level = offset > 41 ? current - 1 : current + 1; if (level <= 0) { level = 1; } if (level > skill.level) { level = skill.level; } g_session.m_skillBundle.SetSkillValue(skill.id, level); this->m_needsRedraw = 1; this->Invalidate(); } return 0; } } break; case UM_TAB: { std::list& list = this->SkillList(a, false); if (!this->IsExtraTab(a) && list.size() == 0) { this->m_tabs->activeTabIndex = this->m_tab; return 0; } if (this->m_tab != a) { this->m_selectedSkill = -1; this->m_tab = a; this->m_needsRedraw = 1; if (this->IsExtraTab(a) == true) { this->m_tabs->SetFlag(a, false); g_session.m_skillBundle.extraIds.clear(); } } this->UpdateScrollBar(); this->LayoutButtons(); this->Invalidate(); return 0; } case UM_REFRESHITEM: this->ResetLists(); g_session.m_skillBundle.NumberExtra(); this->UpdateScrollBar(); this->LayoutButtons(); this->Invalidate(); return 0; case UM_REFRESH: this->InitTabs(); return 0; case UM_SCROLLVERT: case UM_SCROLLVERTPAGEUP: case UM_SCROLLVERTPAGEDOWN: { const int previous = this->m_firstRow; int next; if (message == UM_SCROLLVERTPAGEUP) { next = previous - this->m_visibleRows + 1; } else { if (message == UM_SCROLLVERTPAGEDOWN) { a = this->m_visibleRows - 1; } next = a + previous; } this->m_firstRow = next; if (previous != next) { this->UpdateScrollBar(); this->LayoutButtons(); this->Invalidate(); next = this->m_firstRow; } return next - previous; } case UM_USESKILL: { const CSkillInfo* skill = reinterpret_cast(a); if (skill->level != 0) { gameMode->SendMsg(MM_USING_SKILL, a, g_session.UseLevel(skill->id), 0, 0); return 0; } break; } case UM_REFRESH_EXTRA_SKILL_TAB: if (!this->IsExtraTab(this->m_tab)) { this->m_tabs->SetFlag(this->m_tabs->TabCount() - 1, !g_session.m_skillBundle.extraIds.empty()); this->m_tabs->Invalidate(); } break; default: return UIFrameWnd::SendMsg(sender, message, a, b, c, d); } return 0; } // INLINED static bool HasSkillValue(const std::list& values, int id) { for (const NewSkillListValue& entry : values) { if (entry.id == id) { return true; } } return false; } // INLINED static int SkillValueOf(const std::list& values, int id) { for (const NewSkillListValue& entry : values) { if (entry.id == id) { return entry.value; } } return 0; } // OFFSET: 0x009B3EB0 // FUNCTION: fn_13827 bool UINewSkillListWnd::IncreaseSkillLevel(int skillId) { if (this->m_skillPoints <= 0 || this->m_showDetail) { return false; } const int gate = g_session.m_simplifiedSkillList; if (gate == 0) { for (auto it = this->m_values.begin(); it != this->m_values.end(); ++it) { if (this->m_skillPoints <= 0) { break; } const int id = it->id; CSkillInfo* info = g_session.m_skillBundle.FindGrouped(id); if (info == nullptr || info->available == 0) { continue; } int target; if (id == skillId) { target = (std::max)(static_cast(info->learnedLevel), SkillValueOf(this->m_plannedSkillLevels, id)) + 1; if (static_cast(info->learnedLevel) >= static_cast(info->maxLevel)) { return false; } } else { target = it->value; } if (target > static_cast(info->maxLevel)) { continue; } const auto planned = std::find_if(this->m_plannedSkillLevels.begin(), this->m_plannedSkillLevels.end(), [id](const NewSkillListValue& value) { return value.id == id; }); const int current = (std::max)(static_cast(info->learnedLevel), planned != this->m_plannedSkillLevels.end() ? planned->value : 0); if (current >= target) { continue; } if (static_cast(info->canPlanLevel) <= 0) { const char* name = info->DisplayName(*g_session.m_hotkeyScript); g_windowMgr.SendNotice(UIM_PUSHINTOCHATHISTORY, reinterpret_cast(FormatString(LookupMessage(MSI_REFUSE_LOGIN_AUTH), name)), 0xFF, 0, 0, 0); return false; } const int added = (std::min)(this->m_skillPoints, target - current); this->m_skillPoints -= added; const int level = current + added; bool found = false; for (NewSkillListValue& value : this->m_plannedSkillLevels) { if (value.id == id) { value.value = level; found = true; break; } } if (!found) { this->m_plannedSkillLevels.push_back(NewSkillListValue{id, level}); } } return true; } std::list& list = this->SkillList(this->m_tab, false); for (CSkillInfo& skill : list) { if (static_cast(skill.id) == skillId) { if (skill.level >= static_cast(skill.maxLevel)) { return false; } ++skill.level; --this->m_skillPoints; return true; } } return false; } // OFFSET: 0x009AF960 // FUNCTION: fn_13803 void UINewSkillListWnd::DrawSkillTree() { LuaScript& script = *g_session.m_hotkeyScript; CBitmapRes* emptySlot = static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\no_skill.bmp"))); for (int slot = 0; slot < 42; ++slot) { this->DrawBitmap(slot % this->m_columns * 68 + 76, slot / this->m_columns * 56 + 44, emptySlot, true); } for (const CSkillInfo& skill : this->SkillList(this->m_tab, false)) { const int position = static_cast(skill.treePosition); if (position == -1) { continue; } const int first = this->m_firstRow * this->m_columns; if (position < first) { continue; } const int slot = position - first; if (slot >= 42) { continue; } const int level = skill.level; const int x = slot % this->m_columns * 68 + 72; const int y = slot / this->m_columns * 56; if (!this->IsExtraTab(this->m_tab) && HasSkillValue(this->m_values, static_cast(skill.id))) { this->FillRegion(x + 3, y + 43, 26, 26, 0xCCC0FF); DrawRoundedBorder(*this, x + 2, y + 42, 28, 28, 0xC1B6FF); } else if (this->m_skillPoints != 0 && skill.available != 0 && skill.upgradable != 0) { this->FillRegion(x + 3, y + 43, 26, 26, 0xF0C4AF); DrawRoundedBorder(*this, x + 2, y + 42, 28, 28, 0xF0C4AF); } else { this->FillRegion(x + 3, y + 43, 26, 26, 0xFFFFFF); } int shade = 102; char text[80]; g_session.ItemBitmapPath(skill.name, text); CBitmapRes* bitmap = static_cast(ResourceMgr::Instance().Load(Resolve(text))); if (!bitmap) { std::sprintf(text, "유저인터페이스\\item\\am_berserkpitcher.bmp"); bitmap = static_cast(ResourceMgr::Instance().Load(Resolve(text))); } if (bitmap) { if (!this->IsExtraTab(this->m_tab) && HasSkillValue(this->m_values, static_cast(skill.id))) { this->DrawAuxiliary(2, x + 4, y + 44, &bitmap->image, true, true); const std::string value = this->ValueText(static_cast(skill.id)); if (value.size() != 0) { DrawOutlinedText(*this, x + 30, y + 58, value.c_str(), 0, 0xFFFFFF, 0, 0, 11, 1, 0); } } else if (this->m_skillPoints != 0 && skill.available != 0 && skill.upgradable != 0) { this->DrawAuxiliary(4, x + 4, y + 44, &bitmap->image, true, true); } else if (level == 0) { shade = 120; this->DrawAuxiliary(1, x + 4, y + 44, &bitmap->image, true, true); } else { this->DrawBitmap(x + 4, y + 44, bitmap, true); } const int planned = SkillValueOf(this->m_plannedSkillLevels, static_cast(skill.id)); const CSkillInfo* current = g_session.m_skillBundle.FindGrouped(skill.id); if (current) { int added = planned - current->learnedLevel; if (added < 0) { added = 0; } if (added > 0) { this->DrawTextAt(x + 30, y + 44, FormatString("+%d", added), 0, 0, 11, 0xFF, 0, 0); } } } const int maxLevel = this->SkillAt(static_cast(skill.treePosition), true).level; int red = shade; int green = shade; int blue = shade; if (level > maxLevel) { red = 127; green = 60; blue = 255; } const int textY = y + 70; const std::uint32_t gray = (shade << 8 | shade) << 8 | shade; const bool usesLevel = CSkillInfo::UsesLevel(script, static_cast(skill.id)); if (level != 0) { if (!usesLevel) { std::sprintf(text, "%d", skill.level); const int width = this->TextWidth(text, 0, 0, 11, 1, 0); DrawOutlinedText(*this, (24 - width) / 2 + x + 4, textY, text, 0, (blue << 8 | green) << 8 | red, 0xFFFFFF, 0, 11, 1, 0); } else { int useLevel = g_session.UseLevel(static_cast(skill.id)); if (useLevel <= 0 || useLevel > skill.level) { useLevel = skill.level; } std::sprintf(text, "%d / %d", useLevel, skill.level); int textX = (24 - this->TextWidth(text, 0, 0, 11, 1, 0)) / 2 + x + 4; std::sprintf(text, "%d", useLevel); DrawOutlinedText(*this, textX, textY, text, 0, useLevel == skill.level ? gray : 0x7F3CFF, 0xFFFFFF, 0, 11, 1, 0); textX += this->TextWidth(text, 0, 0, 11, 1, 0); DrawOutlinedText(*this, textX, textY, " / ", 0, gray, 0xFFFFFF, 0, 11, 1, 0); textX += this->TextWidth(" / ", 0, 0, 11, 1, 0); const std::uint32_t id = skill.id; int target = SkillValueOf(this->m_plannedSkillLevels, static_cast(id)); const int grouped = g_session.m_skillBundle.GroupedLevel(id); if (target < grouped) { target = grouped; } const CSkillInfo* extra = g_session.m_skillBundle.FindExtra(id); if (extra && target < extra->level) { target = extra->level; } StringFormat(text, sizeof(text), static_cast(-1), "%d", target); DrawOutlinedText(*this, textX, textY, text, 0, (blue << 8 | green) << 8 | red, 0xFFFFFF, 0, 11, 1, 0); } } const int nameWidth = this->TextWidth(skill.DisplayName(script), 0, 0, 11, 0, 0); int nameOffset = 0; if (nameWidth < 48) { nameOffset = (48 - nameWidth) / 2; } this->DrawTextAt(x - 8 + nameOffset, y + 30, this->ClipTextToWidth(skill.DisplayName(script), 48, 0, 11, 0), 0, 0, 11, gray, 0, 0 ); if (g_session.m_skillBundle.HasHigherLevel(skill.id) == true) { this->DrawBitmap( x + 8, y + 58, &static_cast(ResourceMgr::Instance().Load(Resolve("유저인터페이스\\basic_interface\\item_skill.bmp")))->image, true ); } } } // OFFSET: 0x009AE260 // FUNCTION: fn_13797 UINewSkillListWnd::UINewSkillListWnd() : UIFrameWnd(0) { this->m_tab = 0; this->m_needsRedraw = 1; this->m_selectedSkill = -1; this->m_lastTick = timeGetTime(); this->m_topMargin = 20; this->m_bottomMargin = 32; this->m_saved = nullptr; this->m_columns = 1; this->m_visibleRows = 1; this->m_showDetail = false; this->m_detailId = -1; } // OFFSET: 0x009AE530 // FUNCTION: fn_13798 UINewSkillListWnd::~UINewSkillListWnd() { }