#include "stdafx.h" #include "Tetris.h" #include #include #include #pragma comment(lib, "gdiplus.lib") using namespace Gdiplus; static HBRUSH GetCachedParticleBrush(COLORREF color) { static COLORREF cachedColors[16] = {}; static HBRUSH cachedBrushes[16] = {}; for (int i = 0; i < 16; i++) { if (cachedBrushes[i] != nullptr && cachedColors[i] == color) { return cachedBrushes[i]; } } for (int i = 0; i < 16; i++) { if (cachedBrushes[i] == nullptr) { cachedColors[i] = color; cachedBrushes[i] = CreateSolidBrush(color); return cachedBrushes[i]; } } return CreateSolidBrush(color); } static std::wstring BuildAssetPath(const wchar_t* relativePath) { wchar_t modulePath[MAX_PATH] = {}; GetModuleFileNameW(nullptr, modulePath, MAX_PATH); std::wstring basePath(modulePath); size_t lastSlash = basePath.find_last_of(L"\\/"); if (lastSlash != std::wstring::npos) { basePath.resize(lastSlash); } std::wstring projectRelative = basePath + L"\\..\\..\\" + relativePath; wchar_t fullPath[MAX_PATH] = {}; DWORD result = GetFullPathNameW(projectRelative.c_str(), MAX_PATH, fullPath, nullptr); if (result > 0 && result < MAX_PATH) { return fullPath; } return projectRelative; } static std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath) { wchar_t currentDirectory[MAX_PATH] = {}; DWORD length = GetCurrentDirectoryW(MAX_PATH, currentDirectory); if (length == 0 || length >= MAX_PATH) { return L""; } std::wstring candidate = std::wstring(currentDirectory) + L"\\" + relativePath; wchar_t fullPath[MAX_PATH] = {}; DWORD result = GetFullPathNameW(candidate.c_str(), MAX_PATH, fullPath, nullptr); if (result > 0 && result < MAX_PATH) { return fullPath; } return candidate; } static Bitmap* LoadBackgroundImage() { static ULONG_PTR gdiplusToken = 0; static Bitmap* backgroundImage = nullptr; static bool attempted = false; if (!attempted) { attempted = true; GdiplusStartupInput startupInput; if (GdiplusStartup(&gdiplusToken, &startupInput, nullptr) == Ok) { const std::wstring candidates[] = { BuildAssetPath(L"assets\\images\\background.png"), BuildWorkingDirAssetPath(L"assets\\images\\background.png"), BuildAssetPath(L"assets\\images\\background.bmp"), BuildWorkingDirAssetPath(L"assets\\images\\background.bmp") }; for (const std::wstring& candidate : candidates) { if (candidate.empty()) { continue; } DWORD attributes = GetFileAttributesW(candidate.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { continue; } Bitmap* loadedImage = Bitmap::FromFile(candidate.c_str(), FALSE); if (loadedImage != nullptr && loadedImage->GetLastStatus() == Ok) { backgroundImage = loadedImage; break; } delete loadedImage; } } } return backgroundImage; } /** * @brief 按序号加载致谢页图片资源。 */ static Bitmap* LoadCreditImage(int index) { constexpr int creditPageCount = 4; static ULONG_PTR gdiplusToken = 0; static Bitmap* creditImages[creditPageCount] = {}; static bool attempted[creditPageCount] = {}; if (index < 0 || index >= creditPageCount) { return nullptr; } if (!attempted[index]) { attempted[index] = true; GdiplusStartupInput startupInput; if (GdiplusStartup(&gdiplusToken, &startupInput, nullptr) == Ok) { const wchar_t* imageNames[creditPageCount] = { L"assets\\images\\qls.jpg", L"assets\\images\\wyk.jpg", L"assets\\images\\swj.jpg", L"assets\\images\\qhy.jpg" }; const std::wstring creditExtraCandidates[] = { BuildAssetPath(imageNames[index]), BuildWorkingDirAssetPath(imageNames[index]), BuildAssetPath(L"assets\\images\\qhy.png"), BuildWorkingDirAssetPath(L"assets\\images\\qhy.png"), BuildAssetPath(L"assets\\images\\qhy.jpeg"), BuildWorkingDirAssetPath(L"assets\\images\\qhy.jpeg"), BuildAssetPath(L"assets\\images\\qhy.bmp"), BuildWorkingDirAssetPath(L"assets\\images\\qhy.bmp") }; int candidateCount = (index == 3) ? 8 : 2; for (int i = 0; i < candidateCount; i++) { const std::wstring& candidate = creditExtraCandidates[i]; if (candidate.empty()) { continue; } DWORD attributes = GetFileAttributesW(candidate.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { continue; } Bitmap* loadedImage = Bitmap::FromFile(candidate.c_str(), FALSE); if (loadedImage != nullptr && loadedImage->GetLastStatus() == Ok) { creditImages[index] = loadedImage; break; } delete loadedImage; } } } return creditImages[index]; } void TDrawScreen(HDC hdc, HWND hWnd) { RECT clientRect; GetClientRect(hWnd, &clientRect); int clientWidth = clientRect.right - clientRect.left; int clientHeight = clientRect.bottom - clientRect.top; int scaleX = MulDiv(clientWidth, 1000, WINDOW_CLIENT_WIDTH); int scaleY = MulDiv(clientHeight, 1000, WINDOW_CLIENT_HEIGHT); int scale = (scaleX < scaleY) ? scaleX : scaleY; if (scale < 500) { scale = 500; } int layoutWidth = MulDiv(WINDOW_CLIENT_WIDTH, scale, 1000); int layoutHeight = MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000); int offsetX = (clientWidth - layoutWidth) / 2; int offsetY = 0; auto SX = [offsetX, scale](int value) -> int { return offsetX + MulDiv(value, scale, 1000); }; auto SY = [offsetY, scale](int value) -> int { return offsetY + MulDiv(value, scale, 1000); }; auto SS = [scale](int value) -> int { int result = MulDiv(value, scale, 1000); return result < 1 ? 1 : result; }; int grid = SS(GRID); int previewMiniCell = SS(16); int panelGap = SS(SIDE_PANEL_GAP); int panelWidth = SS(SIDE_PANEL_WIDTH); int panelHeight = SS(SIDE_PANEL_HEIGHT - WINDOW_PADDING * 2); int boardWidth = grid * nGameWidth; int boardHeight = grid * nGameHeight; const RECT leftPanelRect = { SX(WINDOW_PADDING), SY(WINDOW_PADDING), SX(WINDOW_PADDING) + panelWidth, SY(WINDOW_PADDING) + panelHeight }; const RECT gameRect = { leftPanelRect.right + panelGap, SY(WINDOW_PADDING), leftPanelRect.right + panelGap + boardWidth, SY(WINDOW_PADDING) + boardHeight }; const RECT rightPanelRect = { gameRect.right + panelGap, SY(WINDOW_PADDING), gameRect.right + panelGap + panelWidth, SY(WINDOW_PADDING) + panelHeight }; const COLORREF pageColor = RGB(255, 244, 248); const COLORREF blobColorA = RGB(255, 230, 238); const COLORREF blobColorB = RGB(250, 221, 233); const COLORREF cardColor = RGB(255, 252, 253); const COLORREF boardColor = RGB(70, 57, 78); const COLORREF boardInnerColor = RGB(54, 44, 62); const COLORREF lineColor = RGB(104, 90, 116); const COLORREF frameColor = RGB(212, 168, 188); const COLORREF titleColor = RGB(104, 62, 84); const COLORREF textColor = RGB(92, 73, 88); const COLORREF accentColor = RGB(228, 145, 182); const BYTE shellAlpha = 142; const BYTE panelAlpha = 150; const BYTE panelNestedAlpha = 128; const BYTE panelStrongAlpha = 168; Bitmap* backgroundImage = LoadBackgroundImage(); if (backgroundImage != nullptr) { Graphics graphics(hdc); graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); graphics.DrawImage( backgroundImage, Rect(0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top)); SolidBrush washBrush(Color(76, 255, 250, 252)); graphics.FillRectangle(&washBrush, 0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); } else { HBRUSH pageBrush = CreateSolidBrush(pageColor); FillRect(hdc, &clientRect, pageBrush); DeleteObject(pageBrush); } HBRUSH oldBrush = nullptr; HPEN oldPen = nullptr; if (backgroundImage == nullptr) { HBRUSH blobBrushA = CreateSolidBrush(blobColorA); HBRUSH blobBrushB = CreateSolidBrush(blobColorB); oldBrush = (HBRUSH)SelectObject(hdc, blobBrushA); oldPen = (HPEN)SelectObject(hdc, GetStockObject(NULL_PEN)); Ellipse(hdc, clientRect.right - 260, -20, clientRect.right + 80, 260); Ellipse(hdc, -80, clientRect.bottom - 200, 220, clientRect.bottom + 70); SelectObject(hdc, blobBrushB); Ellipse(hdc, clientRect.right - 180, clientRect.bottom - 220, clientRect.right + 120, clientRect.bottom + 40); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(blobBrushA); DeleteObject(blobBrushB); } HFONT titleFont = CreateFont( -SS(36), 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, VARIABLE_PITCH, _T("Microsoft YaHei UI")); HFONT sectionFont = CreateFont( -SS(22), 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, VARIABLE_PITCH, _T("Microsoft YaHei UI")); HFONT bodyFont = CreateFont( -SS(18), 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, VARIABLE_PITCH, _T("Microsoft YaHei UI")); HFONT smallFont = CreateFont( -SS(15), 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, VARIABLE_PITCH, _T("Microsoft YaHei UI")); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, textColor); auto DrawPanelCard = [&](const RECT& rect, COLORREF fillColor, COLORREF borderColor, int radius) { HBRUSH cardBrush = CreateSolidBrush(fillColor); HPEN cardPen = CreatePen(PS_SOLID, 1, borderColor); HGDIOBJ savedPen = SelectObject(hdc, cardPen); HGDIOBJ savedBrush = SelectObject(hdc, cardBrush); RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, SS(radius), SS(radius)); SelectObject(hdc, savedBrush); SelectObject(hdc, savedPen); DeleteObject(cardBrush); DeleteObject(cardPen); }; auto DrawPanelCardAlpha = [&](const RECT& rect, COLORREF fillColor, COLORREF borderColor, int radius, BYTE alpha) { Graphics graphics(hdc); graphics.SetSmoothingMode(SmoothingModeAntiAlias); GraphicsPath path; INT left = static_cast(rect.left); INT top = static_cast(rect.top); INT width = static_cast(rect.right - rect.left); INT height = static_cast(rect.bottom - rect.top); INT corner = static_cast(SS(radius)); path.AddArc(left, top, corner, corner, 180.0f, 90.0f); path.AddArc(left + width - corner, top, corner, corner, 270.0f, 90.0f); path.AddArc(left + width - corner, top + height - corner, corner, corner, 0.0f, 90.0f); path.AddArc(left, top + height - corner, corner, corner, 90.0f, 90.0f); path.CloseFigure(); SolidBrush brush(Color(alpha, GetRValue(fillColor), GetGValue(fillColor), GetBValue(fillColor))); Pen pen(Color(232, GetRValue(borderColor), GetGValue(borderColor), GetBValue(borderColor)), 1.0f); graphics.FillPath(&brush, &path); graphics.DrawPath(&pen, &path); }; auto DrawPanelHeader = [&](const RECT& panelRect, const TCHAR* title, int lineWidth) { SelectObject(hdc, titleFont); SetTextColor(hdc, titleColor); RECT titleRect = { panelRect.left + SS(24), panelRect.top + SS(20), panelRect.right - SS(24), panelRect.top + SS(62) }; DrawText(hdc, title, -1, &titleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); HPEN localAccentPen = CreatePen(PS_SOLID, SS(3), accentColor); HPEN savedPen = (HPEN)SelectObject(hdc, localAccentPen); MoveToEx(hdc, panelRect.left + SS(24), panelRect.top + SS(78), nullptr); LineTo(hdc, panelRect.left + SS(24) + SS(lineWidth), panelRect.top + SS(78)); SelectObject(hdc, savedPen); DeleteObject(localAccentPen); }; auto DrawMusicButton = [&]() { RECT musicButtonRect = { SX(WINDOW_CLIENT_WIDTH - 40), SY(WINDOW_CLIENT_HEIGHT - 40), SX(WINDOW_CLIENT_WIDTH - 12), SY(WINDOW_CLIENT_HEIGHT - 12) }; DrawPanelCardAlpha( musicButtonRect, bgmEnabled ? RGB(255, 238, 246) : RGB(244, 236, 240), bgmEnabled ? RGB(222, 130, 166) : RGB(170, 148, 158), 12, bgmEnabled ? 218 : 190); HPEN musicPen = CreatePen(PS_SOLID, SS(3), bgmEnabled ? RGB(128, 70, 100) : RGB(128, 112, 120)); HPEN oldMusicPen = (HPEN)SelectObject(hdc, musicPen); HBRUSH oldMusicBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); int noteStemX = musicButtonRect.left + SS(17); int noteTop = musicButtonRect.top + SS(7); int noteBottom = musicButtonRect.bottom - SS(9); MoveToEx(hdc, noteStemX, noteTop, nullptr); LineTo(hdc, noteStemX, noteBottom); MoveToEx(hdc, noteStemX, noteTop, nullptr); LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(4)); LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(10)); Ellipse( hdc, musicButtonRect.left + SS(7), musicButtonRect.bottom - SS(13), musicButtonRect.left + SS(19), musicButtonRect.bottom - SS(4)); if (!bgmEnabled) { MoveToEx(hdc, musicButtonRect.left + SS(7), musicButtonRect.bottom - SS(7), nullptr); LineTo(hdc, musicButtonRect.right - SS(6), musicButtonRect.top + SS(6)); } SelectObject(hdc, oldMusicBrush); SelectObject(hdc, oldMusicPen); DeleteObject(musicPen); }; auto DrawBackButton = [&]() { RECT backButtonRect = { SX(6), SY(6), SX(34), SY(34) }; HBRUSH backBrush = CreateSolidBrush(RGB(255, 242, 247)); HPEN backFramePen = CreatePen(PS_SOLID, 1, RGB(222, 130, 166)); HBRUSH oldBackBrush = (HBRUSH)SelectObject(hdc, backBrush); HPEN oldBackFramePen = (HPEN)SelectObject(hdc, backFramePen); RoundRect(hdc, backButtonRect.left, backButtonRect.top, backButtonRect.right, backButtonRect.bottom, SS(12), SS(12)); SelectObject(hdc, oldBackBrush); SelectObject(hdc, oldBackFramePen); DeleteObject(backFramePen); DeleteObject(backBrush); HPEN backPen = CreatePen(PS_SOLID, SS(3), RGB(128, 70, 100)); HPEN oldBackPen = (HPEN)SelectObject(hdc, backPen); MoveToEx(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(8), nullptr); LineTo(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14)); LineTo(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(20)); MoveToEx(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14), nullptr); LineTo(hdc, backButtonRect.right - SS(8), backButtonRect.top + SS(14)); SelectObject(hdc, oldBackPen); DeleteObject(backPen); }; if (currentScreen == SCREEN_MENU) { RECT menuCard = { SX(110), SY(70), SX(WINDOW_CLIENT_WIDTH - 110), SY(WINDOW_CLIENT_HEIGHT - 70) }; DrawPanelCardAlpha(menuCard, cardColor, frameColor, 34, 214); HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); SetTextColor(hdc, titleColor); RECT titleRect = { menuCard.left, menuCard.top + SS(24), menuCard.right, menuCard.top + SS(70) }; DrawText(hdc, _T("Rogue Tetris"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); RECT subtitleRect = { menuCard.left, menuCard.top + SS(72), menuCard.right, menuCard.top + SS(110) }; SelectObject(hdc, bodyFont); SetTextColor(hdc, textColor); DrawText(hdc, _T("选择你的战局"), -1, &subtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); const TCHAR* modeNames[4] = { _T("\u7ecf\u5178\u6a21\u5f0f"), _T("Rogue \u6a21\u5f0f"), _T("\u5e2e\u52a9"), _T("\u81f4\u8c22") }; const TCHAR* modeDescriptions[4] = { _T("纯粹方块挑战,专注消行、堆叠与生存。"), _T("在不断升级的棋盘中收集强化,构筑本局专属流派。"), _T("查看操作、模式规则与全部强化效果。"), _T("感谢程序测试者与代码贡献者。") }; for (int i = 0; i < menuState.optionCount; i++) { bool isSelected = (i == menuState.selectedIndex); int top = menuCard.top + SS(140) + i * SS(130); RECT optionRect = { menuCard.left + SS(36), top, menuCard.right - SS(36), top + SS(104) }; DrawPanelCardAlpha( optionRect, isSelected ? RGB(255, 232, 240) : RGB(255, 247, 250), isSelected ? accentColor : RGB(226, 198, 210), 24, isSelected ? 208 : 172); if (isSelected) { HPEN optionPen = CreatePen(PS_SOLID, SS(3), accentColor); oldPen = (HPEN)SelectObject(hdc, optionPen); oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); RoundRect(hdc, optionRect.left, optionRect.top, optionRect.right, optionRect.bottom, SS(24), SS(24)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(optionPen); } RECT modeNameRect = { optionRect.left + SS(24), optionRect.top + SS(14), optionRect.right - SS(24), optionRect.top + SS(44) }; RECT modeDescriptionRect = { optionRect.left + SS(24), optionRect.top + SS(46), optionRect.right - SS(24), optionRect.bottom - SS(14) }; SelectObject(hdc, sectionFont); SetTextColor(hdc, isSelected ? titleColor : textColor); DrawText(hdc, modeNames[i], -1, &modeNameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(118, 96, 110)); DrawText(hdc, modeDescriptions[i], -1, &modeDescriptionRect, DT_LEFT | DT_WORDBREAK); } RECT hintRect = { menuCard.left + SS(36), menuCard.bottom - SS(92), menuCard.right - SS(36), menuCard.bottom - SS(36) }; SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); DrawText(hdc, _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4\uff0cEsc \u9000\u51fa"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); DrawMusicButton(); SelectObject(hdc, oldFont); DeleteObject(titleFont); DeleteObject(sectionFont); DeleteObject(bodyFont); DeleteObject(smallFont); return; } if (currentScreen == SCREEN_RULES) { RECT rulesCard = { SX(76), SY(54), SX(WINDOW_CLIENT_WIDTH - 76), SY(WINDOW_CLIENT_HEIGHT - 54) }; HPEN rulesPen = CreatePen(PS_SOLID, 1, frameColor); HBRUSH rulesBrush = CreateSolidBrush(cardColor); HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); oldPen = (HPEN)SelectObject(hdc, rulesPen); oldBrush = (HBRUSH)SelectObject(hdc, rulesBrush); RoundRect(hdc, rulesCard.left, rulesCard.top, rulesCard.right, rulesCard.bottom, SS(34), SS(34)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(rulesBrush); DeleteObject(rulesPen); SetTextColor(hdc, titleColor); RECT rulesTitleRect = { rulesCard.left + SS(36), rulesCard.top + SS(26), rulesCard.right - SS(36), rulesCard.top + SS(78) }; const TCHAR* helpTitle = _T("\u5e2e\u52a9"); if (helpState.currentPage == 1) { helpTitle = _T("\u6e38\u620f\u4ecb\u7ecd"); } else if (helpState.currentPage == 2) { helpTitle = _T("\u64cd\u4f5c\u8bf4\u660e"); } else if (helpState.currentPage == 3) { helpTitle = _T("\u5f3a\u5316\u56fe\u9274"); } else if (helpState.currentPage == 4) { helpTitle = _T("\u81f4\u8c22"); } else if (helpState.currentPage == 5) { helpTitle = _T("\u6280\u80fd\u6f14\u793a"); } DrawText(hdc, helpTitle, -1, &rulesTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); HPEN rulesAccentPen = CreatePen(PS_SOLID, SS(3), accentColor); oldPen = (HPEN)SelectObject(hdc, rulesAccentPen); MoveToEx(hdc, rulesCard.left + SS(38), rulesCard.top + SS(92), nullptr); LineTo(hdc, rulesCard.left + SS(186), rulesCard.top + SS(92)); SelectObject(hdc, oldPen); DeleteObject(rulesAccentPen); RECT contentRect = { rulesCard.left + SS(36), rulesCard.top + SS(126), rulesCard.right - SS(36), rulesCard.bottom - SS(86) }; if (helpState.currentPage == 0) { const TCHAR* optionTitles[4] = { _T("\u6e38\u620f\u4ecb\u7ecd"), _T("\u64cd\u4f5c\u8bf4\u660e"), _T("\u5f3a\u5316\u56fe\u9274"), _T("\u6280\u80fd\u6f14\u793a") }; const TCHAR* optionDetails[4] = { _T("\u7ecf\u5178\u6a21\u5f0f\u3001Rogue \u6a21\u5f0f\u548c\u590d\u6d3b\u89c4\u5219\u6982\u89c8\u3002"), _T("\u79fb\u52a8\u3001\u65cb\u8f6c\u3001\u786c\u964d\u3001Hold \u4e0e\u6280\u80fd\u5feb\u6377\u952e\u3002"), _T("\u67e5\u770b Rogue \u6a21\u5f0f\u5168\u90e8\u5f3a\u5316\u7684\u7b80\u8981\u6548\u679c\u3002"), _T("\u8fdb\u5165\u81ea\u52a8\u8f6e\u64ad\u6f14\u793a\uff0c\u4f9d\u6b21\u5c55\u793a Rogue \u4e3b\u8981\u6280\u80fd\u548c\u68cb\u76d8\u6548\u679c\u3002") }; int optionHeight = SS(100); int optionGap = SS(22); int optionTop = contentRect.top + SS(18); for (int i = 0; i < helpState.optionCount; ++i) { RECT optionRect = { contentRect.left, optionTop + i * (optionHeight + optionGap), contentRect.right, optionTop + i * (optionHeight + optionGap) + optionHeight }; bool selected = (i == helpState.selectedIndex); COLORREF optionFill = selected ? RGB(255, 245, 249) : RGB(255, 252, 250); COLORREF optionFrame = selected ? accentColor : frameColor; HPEN optionPen = CreatePen(PS_SOLID, selected ? SS(2) : SS(1), optionFrame); HBRUSH optionBrush = CreateSolidBrush(optionFill); oldPen = (HPEN)SelectObject(hdc, optionPen); oldBrush = (HBRUSH)SelectObject(hdc, optionBrush); RoundRect(hdc, optionRect.left, optionRect.top, optionRect.right, optionRect.bottom, SS(24), SS(24)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(optionBrush); DeleteObject(optionPen); SetTextColor(hdc, selected ? titleColor : textColor); SelectObject(hdc, sectionFont); RECT optionTitleRect = { optionRect.left + SS(28), optionRect.top + SS(16), optionRect.right - SS(28), optionRect.top + SS(48) }; DrawText(hdc, optionTitles[i], -1, &optionTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SetTextColor(hdc, RGB(112, 91, 104)); SelectObject(hdc, bodyFont); RECT optionDetailRect = { optionRect.left + SS(28), optionRect.top + SS(52), optionRect.right - SS(28), optionRect.bottom - SS(14) }; DrawText(hdc, optionDetails[i], -1, &optionDetailRect, DT_LEFT | DT_TOP | DT_WORDBREAK); } } else { SetTextColor(hdc, textColor); SelectObject(hdc, bodyFont); const TCHAR* pageText = _T(""); UINT pageFlags = DT_LEFT | DT_TOP | DT_WORDBREAK; if (helpState.currentPage == 1) { pageText = _T("经典模式:考验堆叠、消行和生存。消行后悬空方块会继续受重力下落。\r\n\r\n") _T("Rogue:消行得分和 EXP,升级后选择强化,构筑会改变方块、清场、得分和保命方式。\r\n\r\n") _T("特殊方块用粗色边框提示:橙红=爆破,青色=激光,绿色=十字,紫色=彩虹。\r\n\r\n") _T("战斗越久危险越高,底部封锁区会压缩空间。\r\n\r\n") _T("死亡后可以按 V 看视频复活一次,经典模式和 Rogue 模式都支持。"); } else if (helpState.currentPage == 2) { pageText = _T("\u2190 / A\uff1a\u5411\u5de6\u79fb\u52a8\r\n") _T("\u2192 / D\uff1a\u5411\u53f3\u79fb\u52a8\r\n") _T("\u2191 / W\uff1a\u65cb\u8f6c\u65b9\u5757\r\n") _T("\u2193 / S\uff1a\u8f6f\u964d\r\n") _T("Space\uff1a\u786c\u964d\r\n") _T("C / Shift\uff1a备用仓\uff08获得后\uff09\r\n") _T("Z\uff1a黑洞奇点\uff08获得后\uff09\r\n") _T("X\uff1a清屏炸弹\uff08获得后\uff09\r\n") _T("V\uff1a空中换形\uff08获得后\uff09\r\n") _T("P\uff1a\u6682\u505c / \u7ee7\u7eed\r\n") _T("R\uff1a\u91cd\u5f00\u5f53\u524d\u5bf9\u5c40\r\n") _T("M\uff1a\u8fd4\u56de\u4e3b\u83dc\u5355\r\n") _T("死亡后 V:看视频复活一次"); } else if (helpState.currentPage == 3) { const TCHAR* categoryNames[8] = { _T("基础"), _T("保命 / 操作"), _T("清场"), _T("特殊方块"), _T("进阶"), _T("狂热 / 风险"), _T("升级系"), _T("构筑") }; const TCHAR* categoryTexts[8] = { _T("赏金纹章:所有得分收益提高 20%,可重复叠加,是最直接的分数成长。\r\n成长印记:所有 EXP 收益提高 25%,可重复叠加,用来更快进入后续构筑。\r\n缓坠羽翼:降低自然下落速度,最多叠加 4 次。\r\n连击律动:连续消行时追加得分和 EXP,断连后重新累计。\r\n先见之眼:额外显示 1 个后续方块;第三个预览由操控大师解锁。"), _T("最后一搏:首次濒死时自动清理底部 3 行,并保留本局继续机会。\r\n备用仓:解锁 C / Shift,将当前方块放入 Hold 仓或取出备用方块。\r\n完美旋转:旋转被阻挡时尝试左右修正,提高贴墙和缝隙旋转成功率。\r\n时间缓流:堆叠过高时自动减速,给危险局面留出处理时间。\r\n空中换形:可重复补充次数,按 V 将当前下落方块变成 I 块。"), _T("卸压清场:获得时立刻清除最高的一条占用行,直接降低顶部压力。\r\n底线清道夫:通过消行充能自动清底;每级降低需求,最多 4 级。\r\n清屏炸弹:可重复补充数量,按 X 主动清理底部 5 行,消行也会继续充能。\r\n黑洞奇点:可重复补充次数,按 Z 吞噬当前场上数量最多的一种颜色方块。"), _T("爆破核心:一次性解锁橙红边框方块,落地后以落点为中心清除 3x3 区域。\r\n棱镜激光:一次性解锁青色边框方块,按落地中心列清除整列固定方块。\r\n十字方块:一次性解锁绿色边框方块,按落地中心同时清除一行一列。\r\n彩虹方块:一次性解锁紫色边框方块,清中心行主色并把覆盖行染成场上主色。\r\n方块改造:提高指定方块出现概率,目前主要强化 I 块生成。"), _T("连锁火花:完成消行后追加随机破坏,适合配合稳定堆叠扩大收益。\r\n连环炸弹:强化爆破核心,将爆破范围从 3x3 扩大为 5x5。\r\n雷霆四消:三消或四消后追加雷击,额外清除局部方块。\r\n雷霆棱镜:三消或四消后追加激光清列,强化四消后的清场能力。"), _T("狂热节拍:累计清除 12 行进入狂热,狂热期间收益更高。\r\n怒火连段:连击越高倍率越高,适合连续小消和稳定节奏。\r\n无尽狂热:狂热期间继续消行会延长狂热时间。\r\n高压悬赏:游戏速度更快,但高压下收益也更高。\r\n豪赌四消:四消收益更高,但非四消表现更不稳定。\r\n极限玩家:危险高度下获得更高收益,同时承担更高操作压力。\r\n赌徒契约:后续强化有概率翻倍或落空,每级提高概率,最多 4 级。"), _T("双重抉择:升级时可额外选择 1 个强化,加快构筑成型。\r\n命运轮盘:升级变为 6 选 2,但选项中可能混入诅咒。\r\n升级冲击波:每次升级后清理底部 2 行。\r\n进化冲击:升级后清理底部 3 行,是升级冲击波的进阶路线。\r\n成长核心:永久提高得分和 EXP 收益 15%。"), _T("操控大师:使用 Hold 后短暂减速,并额外增加预览能力。\r\n方块风暴:接下来 5 个方块都变为 I 块,适合冲四消或紧急清场。\r\n稳定结构:落地后概率填补局部小洞,让场地更平整。\r\n虚空核心:黑洞生效后生成彩虹方块,彩虹生效时会追加小型黑洞。") }; int sectionGap = SS(18); int titleHeight = SS(34); int bodyPadding = SS(16); int contentHeight = contentRect.bottom - contentRect.top; int virtualHeight = 0; SelectObject(hdc, bodyFont); for (int i = 0; i < 8; ++i) { RECT calculateBodyRect = { contentRect.left + bodyPadding, 0, contentRect.right - bodyPadding, 0 }; DrawText(hdc, categoryTexts[i], -1, &calculateBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); virtualHeight += titleHeight + (calculateBodyRect.bottom - calculateBodyRect.top) + sectionGap; } int maxHelpScroll = virtualHeight - contentHeight; if (maxHelpScroll < 0) { maxHelpScroll = 0; } if (helpScrollOffset > maxHelpScroll) { helpScrollOffset = maxHelpScroll; } HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); SelectClipRgn(hdc, contentClipRegion); int sectionTop = contentRect.top - helpScrollOffset; for (int i = 0; i < 8; ++i) { RECT bodyCalculateRect = { contentRect.left + bodyPadding, 0, contentRect.right - bodyPadding, 0 }; SelectObject(hdc, bodyFont); DrawText(hdc, categoryTexts[i], -1, &bodyCalculateRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); int bodyHeight = bodyCalculateRect.bottom - bodyCalculateRect.top; RECT categoryRect = { contentRect.left, sectionTop, contentRect.right, sectionTop + titleHeight + bodyHeight + SS(12) }; if (categoryRect.bottom < contentRect.top || categoryRect.top > contentRect.bottom) { sectionTop += titleHeight + bodyHeight + sectionGap; continue; } SetTextColor(hdc, titleColor); SelectObject(hdc, sectionFont); RECT categoryTitleRect = { categoryRect.left, categoryRect.top, categoryRect.right, categoryRect.top + titleHeight }; DrawText(hdc, categoryNames[i], -1, &categoryTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); HPEN categoryLinePen = CreatePen(PS_SOLID, 1, RGB(232, 202, 215)); oldPen = (HPEN)SelectObject(hdc, categoryLinePen); MoveToEx(hdc, categoryRect.left, categoryTitleRect.bottom, nullptr); LineTo(hdc, categoryRect.right, categoryTitleRect.bottom); SelectObject(hdc, oldPen); DeleteObject(categoryLinePen); SetTextColor(hdc, RGB(86, 66, 80)); SelectObject(hdc, bodyFont); RECT categoryBodyRect = { categoryRect.left + bodyPadding, categoryTitleRect.bottom + SS(10), categoryRect.right - bodyPadding, categoryTitleRect.bottom + SS(10) + bodyHeight }; DrawText(hdc, categoryTexts[i], -1, &categoryBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); sectionTop += titleHeight + bodyHeight + sectionGap; } if (hasOldClipRegion == 1) { SelectClipRgn(hdc, oldClipRegion); } else { SelectClipRgn(hdc, nullptr); } DeleteObject(contentClipRegion); DeleteObject(oldClipRegion); } else if (helpState.currentPage == 5) { int demoCount = GetRogueSkillDemoCount(); int itemHeight = SS(58); int itemGap = SS(10); int contentHeight = contentRect.bottom - contentRect.top; int virtualHeight = SS(8) + demoCount * (itemHeight + itemGap); int maxHelpScroll = virtualHeight - contentHeight; if (maxHelpScroll < 0) { maxHelpScroll = 0; } if (helpScrollOffset > maxHelpScroll) { helpScrollOffset = maxHelpScroll; } HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); SelectClipRgn(hdc, contentClipRegion); int itemTop = contentRect.top + SS(8) - helpScrollOffset; for (int i = 0; i < demoCount; i++) { RECT itemRect = { contentRect.left, itemTop + i * (itemHeight + itemGap), contentRect.right, itemTop + i * (itemHeight + itemGap) + itemHeight }; if (itemRect.bottom < contentRect.top || itemRect.top > contentRect.bottom) { continue; } bool selected = (i == helpState.selectedIndex); COLORREF itemFill = selected ? RGB(255, 245, 249) : RGB(255, 252, 250); COLORREF itemFrame = selected ? accentColor : frameColor; HPEN itemPen = CreatePen(PS_SOLID, selected ? SS(2) : SS(1), itemFrame); HBRUSH itemBrush = CreateSolidBrush(itemFill); oldPen = (HPEN)SelectObject(hdc, itemPen); oldBrush = (HBRUSH)SelectObject(hdc, itemBrush); RoundRect(hdc, itemRect.left, itemRect.top, itemRect.right, itemRect.bottom, SS(18), SS(18)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(itemBrush); DeleteObject(itemPen); SetTextColor(hdc, selected ? titleColor : textColor); SelectObject(hdc, sectionFont); RECT nameRect = { itemRect.left + SS(22), itemRect.top + SS(8), itemRect.left + SS(210), itemRect.bottom - SS(8) }; DrawText(hdc, GetRogueSkillDemoName(i), -1, &nameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SetTextColor(hdc, RGB(112, 91, 104)); SelectObject(hdc, bodyFont); RECT detailRect = { itemRect.left + SS(220), itemRect.top + SS(8), itemRect.right - SS(22), itemRect.bottom - SS(8) }; DrawText(hdc, GetRogueSkillDemoDetail(i), -1, &detailRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); } if (hasOldClipRegion == 1) { SelectClipRgn(hdc, oldClipRegion); } else { SelectClipRgn(hdc, nullptr); } DeleteObject(contentClipRegion); DeleteObject(oldClipRegion); } else if (helpState.currentPage == 4) { const int creditAnimationTotalTicks = 60; constexpr int creditPageCount = 4; const TCHAR* creditNames[creditPageCount] = { _T("qls"), _T("wyk"), _T("juju"), _T("qhy") }; const TCHAR* creditTexts[creditPageCount] = { _T("\u611f\u8c22\u6fc0\u60c5\u6295\u8eab\u4e8e\u6d4b\u8bd5\u4e4b\u4e2d\u7684Lisa"), _T("\u611f\u8c22\u70ed\u5ff1coding\u7684\u5c0f\u86cb\u7cd5"), _T("\u611f\u8c22\u8bfe\u524d\u95f2\u91cc\u5077\u5fd9\u7684juju"), _T("\u611f\u8c22qhy\u7684\u5929\u624d\u6784\u60f3") }; int currentCredit = creditPageIndex; if (currentCredit < 0) { currentCredit = 0; } if (currentCredit >= creditPageCount) { currentCredit = creditPageCount - 1; } int previousCredit = currentCredit - creditAnimationDirection; if (previousCredit < 0) { previousCredit = creditPageCount - 1; } if (previousCredit >= creditPageCount) { previousCredit = 0; } RECT imageArea = { contentRect.left + SS(92), contentRect.top + SS(10), contentRect.right - SS(92), contentRect.bottom - SS(112) }; RECT textArea = { contentRect.left + SS(72), contentRect.bottom - SS(94), contentRect.right - SS(72), contentRect.bottom - SS(18) }; HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); SelectClipRgn(hdc, contentClipRegion); int slideDistance = contentRect.right - contentRect.left; int currentOffset = 0; int previousOffset = 0; if (creditAnimationTicks > 0 && creditAnimationDirection != 0) { currentOffset = creditAnimationDirection * slideDistance * creditAnimationTicks / creditAnimationTotalTicks; previousOffset = currentOffset - creditAnimationDirection * slideDistance; } Bitmap* preloadedCreditImages[creditPageCount] = {}; for (int i = 0; i < creditPageCount; i++) { preloadedCreditImages[i] = LoadCreditImage(i); } Graphics creditGraphics(hdc); creditGraphics.SetInterpolationMode(InterpolationModeHighQualityBilinear); auto DrawCreditCard = [&](int cardIndex, int offset) { RECT shiftedImageArea = imageArea; RECT shiftedTextArea = textArea; OffsetRect(&shiftedImageArea, offset, 0); OffsetRect(&shiftedTextArea, offset, 0); Bitmap* creditImage = nullptr; if (cardIndex >= 0 && cardIndex < creditPageCount) { creditImage = preloadedCreditImages[cardIndex]; } if (creditImage != nullptr) { int imageWidth = static_cast(creditImage->GetWidth()); int imageHeight = static_cast(creditImage->GetHeight()); int areaWidth = shiftedImageArea.right - shiftedImageArea.left; int areaHeight = shiftedImageArea.bottom - shiftedImageArea.top; int drawWidth = areaWidth; int drawHeight = imageHeight * drawWidth / imageWidth; if (drawHeight > areaHeight) { drawHeight = areaHeight; drawWidth = imageWidth * drawHeight / imageHeight; } int drawLeft = shiftedImageArea.left + (areaWidth - drawWidth) / 2; int drawTop = shiftedImageArea.top + (areaHeight - drawHeight) / 2; creditGraphics.DrawImage(creditImage, Rect(drawLeft, drawTop, drawWidth, drawHeight)); } else { HBRUSH missingBrush = CreateSolidBrush(RGB(255, 245, 249)); HPEN missingPen = CreatePen(PS_DASH, SS(2), frameColor); oldBrush = (HBRUSH)SelectObject(hdc, missingBrush); oldPen = (HPEN)SelectObject(hdc, missingPen); RoundRect(hdc, shiftedImageArea.left, shiftedImageArea.top, shiftedImageArea.right, shiftedImageArea.bottom, SS(24), SS(24)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(missingPen); DeleteObject(missingBrush); SetTextColor(hdc, RGB(128, 104, 118)); SelectObject(hdc, bodyFont); DrawText(hdc, _T("\u56fe\u7247\u8d44\u6e90\u672a\u627e\u5230"), -1, &shiftedImageArea, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } if (creditAnimationTicks <= 0) { SetTextColor(hdc, titleColor); SelectObject(hdc, sectionFont); RECT nameRect = { shiftedTextArea.left, shiftedTextArea.top, shiftedTextArea.right, shiftedTextArea.top + SS(30) }; DrawText(hdc, creditNames[cardIndex], -1, &nameRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SetTextColor(hdc, textColor); SelectObject(hdc, bodyFont); RECT detailRect = { shiftedTextArea.left, shiftedTextArea.top + SS(34), shiftedTextArea.right, shiftedTextArea.bottom }; DrawText(hdc, creditTexts[cardIndex], -1, &detailRect, DT_CENTER | DT_TOP | DT_WORDBREAK); } }; if (creditAnimationTicks > 0 && creditAnimationDirection != 0) { DrawCreditCard(previousCredit, previousOffset); } DrawCreditCard(currentCredit, currentOffset); if (hasOldClipRegion == 1) { SelectClipRgn(hdc, oldClipRegion); } else { SelectClipRgn(hdc, nullptr); } DeleteObject(contentClipRegion); DeleteObject(oldClipRegion); RECT leftArrow = { rulesCard.left + SS(52), (rulesCard.top + rulesCard.bottom) / 2 - SS(27), rulesCard.left + SS(106), (rulesCard.top + rulesCard.bottom) / 2 + SS(27) }; RECT rightArrow = { rulesCard.right - SS(106), (rulesCard.top + rulesCard.bottom) / 2 - SS(27), rulesCard.right - SS(52), (rulesCard.top + rulesCard.bottom) / 2 + SS(27) }; HBRUSH arrowBrush = CreateSolidBrush(RGB(255, 245, 249)); HPEN arrowPen = CreatePen(PS_SOLID, SS(2), accentColor); oldBrush = (HBRUSH)SelectObject(hdc, arrowBrush); oldPen = (HPEN)SelectObject(hdc, arrowPen); RoundRect(hdc, leftArrow.left, leftArrow.top, leftArrow.right, leftArrow.bottom, SS(18), SS(18)); RoundRect(hdc, rightArrow.left, rightArrow.top, rightArrow.right, rightArrow.bottom, SS(18), SS(18)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(arrowPen); DeleteObject(arrowBrush); HPEN chevronPen = CreatePen(PS_SOLID, SS(4), titleColor); oldPen = (HPEN)SelectObject(hdc, chevronPen); MoveToEx(hdc, leftArrow.left + SS(32), leftArrow.top + SS(16), nullptr); LineTo(hdc, leftArrow.left + SS(22), leftArrow.top + SS(27)); LineTo(hdc, leftArrow.left + SS(32), leftArrow.bottom - SS(16)); MoveToEx(hdc, rightArrow.right - SS(32), rightArrow.top + SS(16), nullptr); LineTo(hdc, rightArrow.right - SS(22), rightArrow.top + SS(27)); LineTo(hdc, rightArrow.right - SS(32), rightArrow.bottom - SS(16)); SelectObject(hdc, oldPen); DeleteObject(chevronPen); } if (helpState.currentPage != 3 && helpState.currentPage != 4 && helpState.currentPage != 5) { RECT calculateRect = { contentRect.left, contentRect.top, contentRect.right, contentRect.top }; DrawText(hdc, pageText, -1, &calculateRect, pageFlags | DT_CALCRECT); int maxHelpScroll = (calculateRect.bottom - calculateRect.top) - (contentRect.bottom - contentRect.top); if (maxHelpScroll < 0) { maxHelpScroll = 0; } if (helpScrollOffset > maxHelpScroll) { helpScrollOffset = maxHelpScroll; } HRGN oldClipRegion = CreateRectRgn(0, 0, 0, 0); int hasOldClipRegion = GetClipRgn(hdc, oldClipRegion); HRGN contentClipRegion = CreateRectRgn(contentRect.left, contentRect.top, contentRect.right, contentRect.bottom); SelectClipRgn(hdc, contentClipRegion); RECT scrolledContentRect = contentRect; scrolledContentRect.top -= helpScrollOffset; scrolledContentRect.bottom += maxHelpScroll; DrawText(hdc, pageText, -1, &scrolledContentRect, pageFlags); if (hasOldClipRegion == 1) { SelectClipRgn(hdc, oldClipRegion); } else { SelectClipRgn(hdc, nullptr); } DeleteObject(contentClipRegion); DeleteObject(oldClipRegion); } } SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT backHintRect = { rulesCard.left + SS(36), rulesCard.bottom - SS(58), rulesCard.right - SS(36), rulesCard.bottom - SS(24) }; const TCHAR* helpHint = helpState.currentPage == 0 ? _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter / Space \u786e\u8ba4\uff0cEsc / M \u8fd4\u56de\u4e3b\u83dc\u5355") : (helpState.currentPage == 4 ? _T("\u5de6\u53f3\u65b9\u5411\u952e / A D \u5207\u6362\uff0cEsc / Backspace / M \u8fd4\u56de\u4e3b\u83dc\u5355") : (helpState.currentPage == 5 ? _T("\u70b9\u51fb\u6280\u80fd\u540d\u79f0\u6216 Enter \u6f14\u793a\uff0c\u6eda\u8f6e\u7ffb\u52a8\uff0cEsc / Backspace / M \u8fd4\u56de\u5e2e\u52a9") : _T("\u9f20\u6807\u6eda\u8f6e\u4e0a\u4e0b\u7ffb\u52a8\uff0cEsc / Backspace / M \u8fd4\u56de\u5e2e\u52a9"))); DrawText(hdc, helpHint, -1, &backHintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectClipRgn(hdc, nullptr); DrawBackButton(); DrawMusicButton(); SelectObject(hdc, oldFont); DeleteObject(titleFont); DeleteObject(sectionFont); DeleteObject(bodyFont); DeleteObject(smallFont); return; } DrawPanelCardAlpha( { gameRect.left - SS(10), gameRect.top - SS(10), gameRect.right + SS(10), gameRect.bottom + SS(10) }, cardColor, frameColor, 28, shellAlpha); DrawPanelCardAlpha(leftPanelRect, cardColor, frameColor, 30, shellAlpha); DrawPanelCardAlpha(rightPanelRect, cardColor, frameColor, 30, shellAlpha); RECT innerRect = { gameRect.left + SS(6), gameRect.top + SS(6), gameRect.right - SS(6), gameRect.bottom - SS(6) }; { Graphics boardGraphics(hdc); SolidBrush boardBrush(Color(164, GetRValue(boardColor), GetGValue(boardColor), GetBValue(boardColor))); SolidBrush innerBrush(Color(176, GetRValue(boardInnerColor), GetGValue(boardInnerColor), GetBValue(boardInnerColor))); boardGraphics.FillRectangle( &boardBrush, static_cast(gameRect.left), static_cast(gameRect.top), static_cast(gameRect.right - gameRect.left), static_cast(gameRect.bottom - gameRect.top)); boardGraphics.FillRectangle( &innerBrush, static_cast(innerRect.left), static_cast(innerRect.top), static_cast(innerRect.right - innerRect.left), static_cast(innerRect.bottom - innerRect.top)); } HPEN borderPen = CreatePen(PS_SOLID, SS(2), RGB(132, 108, 146)); oldPen = (HPEN)SelectObject(hdc, borderPen); oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); RoundRect(hdc, gameRect.left, gameRect.top, gameRect.right, gameRect.bottom, SS(18), SS(18)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(borderPen); HPEN gridPen = CreatePen(PS_SOLID, 1, lineColor); oldPen = (HPEN)SelectObject(hdc, gridPen); for (int i = 1; i < nGameWidth; i++) { int x = gameRect.left + i * grid; MoveToEx(hdc, x, gameRect.top, nullptr); LineTo(hdc, x, gameRect.bottom); } for (int i = 1; i < nGameHeight; i++) { int y = gameRect.top + i * grid; MoveToEx(hdc, gameRect.left, y, nullptr); LineTo(hdc, gameRect.right, y); } SelectObject(hdc, oldPen); DeleteObject(gridPen); int lockedRows = GetRogueLockedRows(); if (lockedRows > 0) { RECT lockedRect = { gameRect.left, gameRect.top + (nGameHeight - lockedRows) * grid, gameRect.right, gameRect.bottom }; Graphics lockedGraphics(hdc); SolidBrush lockedBrush(Color(150, 58, 48, 68)); Pen lockedPen(Color(210, 232, 170, 194), 1.0f); lockedGraphics.FillRectangle( &lockedBrush, static_cast(lockedRect.left), static_cast(lockedRect.top), static_cast(lockedRect.right - lockedRect.left), static_cast(lockedRect.bottom - lockedRect.top)); lockedGraphics.DrawRectangle( &lockedPen, static_cast(lockedRect.left), static_cast(lockedRect.top), static_cast(lockedRect.right - lockedRect.left), static_cast(lockedRect.bottom - lockedRect.top)); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(238, 204, 216)); RECT lockedTextRect = { lockedRect.left + SS(12), lockedRect.top + SS(8), lockedRect.right - SS(12), lockedRect.bottom - SS(8) }; DrawText(hdc, _T("封锁区"), -1, &lockedTextRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } for (int i = 0; i < nGameHeight; i++) { for (int j = 0; j < nGameWidth; j++) { if (workRegion[i][j] != 0) { int cellValue = workRegion[i][j]; bool isRainbowCell = (cellValue == 8); int colorIndex = isRainbowCell ? 0 : (cellValue - 1); RECT brickRect = { gameRect.left + j * grid + SS(2), gameRect.top + i * grid + SS(2), gameRect.left + (j + 1) * grid - SS(2), gameRect.top + (i + 1) * grid - SS(2) }; COLORREF fillColor = isRainbowCell ? RGB(186, 154, 255) : BrickColor[colorIndex]; COLORREF borderColor = isRainbowCell ? RGB(250, 238, 255) : RGB(255, 248, 250); HBRUSH brickBrush = CreateSolidBrush(fillColor); HPEN brickPen = CreatePen(PS_SOLID, isRainbowCell ? SS(2) : 1, borderColor); oldPen = (HPEN)SelectObject(hdc, brickPen); oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(10), SS(10)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(brickBrush); DeleteObject(brickPen); } } } if (targetFlag && !gameOverFlag) { HPEN targetPen = CreatePen(PS_DOT, SS(2), RGB(255, 240, 245)); oldPen = (HPEN)SelectObject(hdc, targetPen); oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[type][state][i][j] != 0) { int drawY = target.y + i; int drawX = target.x + j; if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) { RoundRect( hdc, gameRect.left + drawX * grid + SS(5), gameRect.top + drawY * grid + SS(5), gameRect.left + (drawX + 1) * grid - SS(5), gameRect.top + (drawY + 1) * grid - SS(5), SS(8), SS(8)); } } } } SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(targetPen); } if (!gameOverFlag) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[type][state][i][j] != 0) { int drawY = point.y + i; int drawX = point.x + j; if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) { RECT brickRect = { gameRect.left + drawX * grid + SS(2), gameRect.top + drawY * grid + SS(2), gameRect.left + (drawX + 1) * grid - SS(2), gameRect.top + (drawY + 1) * grid - SS(2) }; HBRUSH brickBrush = CreateSolidBrush(BrickColor[type]); COLORREF activeOutlineColor = RGB(255, 250, 252); int activeOutlineWidth = 1; if (currentPieceIsExplosive) { activeOutlineColor = RGB(255, 116, 78); activeOutlineWidth = SS(3); } else if (currentPieceIsLaser) { activeOutlineColor = RGB(120, 232, 255); activeOutlineWidth = SS(3); } else if (currentPieceIsCross) { activeOutlineColor = RGB(196, 255, 132); activeOutlineWidth = SS(3); } else if (currentPieceIsRainbow) { activeOutlineColor = RGB(186, 126, 255); activeOutlineWidth = SS(3); } HPEN brickPen = CreatePen(PS_SOLID, activeOutlineWidth, activeOutlineColor); oldPen = (HPEN)SelectObject(hdc, brickPen); oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(12), SS(12)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(brickBrush); DeleteObject(brickPen); } } } } } for (int i = 0; i < 80; i++) { if (gravityFallEffects[i].ticks <= 0 || gravityFallEffects[i].totalTicks <= 0) { continue; } if (gravityFallEffects[i].x < 0 || gravityFallEffects[i].x >= nGameWidth || gravityFallEffects[i].fromY < 0 || gravityFallEffects[i].fromY >= nGameHeight || gravityFallEffects[i].toY < 0 || gravityFallEffects[i].toY >= nGameHeight) { continue; } int elapsed = gravityFallEffects[i].totalTicks - gravityFallEffects[i].ticks; int travel = gravityFallEffects[i].toY - gravityFallEffects[i].fromY; int offsetY = travel * grid * elapsed / gravityFallEffects[i].totalTicks; int drawX = gameRect.left + gravityFallEffects[i].x * grid; int fromPixelY = gameRect.top + gravityFallEffects[i].fromY * grid; int toPixelY = gameRect.top + gravityFallEffects[i].toY * grid; int currentPixelY = fromPixelY + offsetY; int colorIndex = gravityFallEffects[i].cellValue - 1; if (colorIndex < 0 || colorIndex >= 7) { colorIndex = (gravityFallEffects[i].x + gravityFallEffects[i].toY) % 7; } COLORREF fallColor = BrickColor[colorIndex]; int alpha = 56 + gravityFallEffects[i].ticks * 150 / gravityFallEffects[i].totalTicks; int trailTop = fromPixelY + grid / 2; int trailBottom = currentPixelY + grid / 2; if (trailBottom < trailTop + SS(8)) { trailBottom = trailTop + SS(8); } if (trailBottom > toPixelY + grid / 2) { trailBottom = toPixelY + grid / 2; } Graphics fallGraphics(hdc); fallGraphics.SetSmoothingMode(SmoothingModeAntiAlias); SolidBrush trailBrush(Color(alpha / 2, GetRValue(fallColor), GetGValue(fallColor), GetBValue(fallColor))); fallGraphics.FillRectangle( &trailBrush, static_cast(drawX + SS(12)), static_cast(trailTop), static_cast(grid - SS(24)), static_cast(trailBottom - trailTop)); SolidBrush ghostBrush(Color(alpha, GetRValue(fallColor), GetGValue(fallColor), GetBValue(fallColor))); Pen ghostPen(Color(230, 255, 255, 255), static_cast(SS(2))); fallGraphics.FillRectangle( &ghostBrush, static_cast(drawX + SS(3)), static_cast(currentPixelY + SS(3)), static_cast(grid - SS(6)), static_cast(grid - SS(6))); fallGraphics.DrawRectangle( &ghostPen, static_cast(drawX + SS(3)), static_cast(currentPixelY + SS(3)), static_cast(grid - SS(6)), static_cast(grid - SS(6))); } if (clearEffectState.ticks > 0 && clearEffectState.totalTicks > 0) { int elapsed = clearEffectState.totalTicks - clearEffectState.ticks; int alpha = 42 + clearEffectState.ticks * 150 / clearEffectState.totalTicks; int inset = SS(elapsed * 2); Graphics flashGraphics(hdc); for (int i = 0; i < clearEffectState.rowCount; i++) { int row = clearEffectState.rows[i]; if (row < 0 || row >= nGameHeight) { continue; } int top = gameRect.top + row * grid + inset; int height = grid - inset * 2; if (height < SS(4)) { height = SS(4); } SolidBrush flashBrush(Color(alpha, 255, 248, 174)); flashGraphics.FillRectangle( &flashBrush, static_cast(gameRect.left + SS(2)), static_cast(top), static_cast(gameRect.right - gameRect.left - SS(4)), static_cast(height)); } } for (int i = 0; i < 64; i++) { if (cellFlashEffects[i].ticks <= 0 || cellFlashEffects[i].totalTicks <= 0) { continue; } if (cellFlashEffects[i].x < 0 || cellFlashEffects[i].x >= nGameWidth || cellFlashEffects[i].y < 0 || cellFlashEffects[i].y >= nGameHeight) { continue; } int alpha = 48 + cellFlashEffects[i].ticks * 168 / cellFlashEffects[i].totalTicks; int pulseInset = SS(2 + (cellFlashEffects[i].totalTicks - cellFlashEffects[i].ticks) % 3); RECT flashRect = { gameRect.left + cellFlashEffects[i].x * grid + pulseInset, gameRect.top + cellFlashEffects[i].y * grid + pulseInset, gameRect.left + (cellFlashEffects[i].x + 1) * grid - pulseInset, gameRect.top + (cellFlashEffects[i].y + 1) * grid - pulseInset }; Graphics cellGraphics(hdc); cellGraphics.SetSmoothingMode(SmoothingModeAntiAlias); SolidBrush cellBrush(Color(alpha, GetRValue(cellFlashEffects[i].color), GetGValue(cellFlashEffects[i].color), GetBValue(cellFlashEffects[i].color))); Pen cellPen(Color(230, GetRValue(cellFlashEffects[i].color), GetGValue(cellFlashEffects[i].color), GetBValue(cellFlashEffects[i].color)), static_cast(SS(2))); cellGraphics.FillRectangle( &cellBrush, static_cast(flashRect.left), static_cast(flashRect.top), static_cast(flashRect.right - flashRect.left), static_cast(flashRect.bottom - flashRect.top)); cellGraphics.DrawRectangle( &cellPen, static_cast(flashRect.left), static_cast(flashRect.top), static_cast(flashRect.right - flashRect.left), static_cast(flashRect.bottom - flashRect.top)); } for (int i = 0; i < 96; i++) { if (particleEffects[i].ticks <= 0 || particleEffects[i].totalTicks <= 0) { continue; } int elapsed = particleEffects[i].totalTicks - particleEffects[i].ticks; int particleX = gameRect.left + particleEffects[i].boardX * grid / 100 + SS(particleEffects[i].velocityX * elapsed); int particleY = gameRect.top + particleEffects[i].boardY * grid / 100 + SS(particleEffects[i].velocityY * elapsed + elapsed * elapsed / 4); int particleSize = particleEffects[i].size * particleEffects[i].ticks / particleEffects[i].totalTicks; if (particleSize < 2) { particleSize = 2; } if (particleSize > 6) { particleSize = 6; } RECT particleRect = { particleX - particleSize, particleY - particleSize, particleX + particleSize, particleY + particleSize }; HBRUSH particleBrush = GetCachedParticleBrush(particleEffects[i].color); FillRect(hdc, &particleRect, particleBrush); } for (int i = 0; i < 8; i++) { if (floatingTextEffects[i].ticks <= 0 || floatingTextEffects[i].totalTicks <= 0) { continue; } int elapsed = floatingTextEffects[i].totalTicks - floatingTextEffects[i].ticks; int textX = gameRect.left + floatingTextEffects[i].boardX * grid / 100; int textY = gameRect.top + floatingTextEffects[i].boardY * grid / 100 - SS(elapsed * 4); HFONT oldFloatFont = (HFONT)SelectObject(hdc, sectionFont); SetTextColor(hdc, floatingTextEffects[i].color); RECT floatRect = { textX - SS(160), textY - SS(24), textX + SS(160), textY + SS(28) }; DrawText(hdc, floatingTextEffects[i].text, -1, &floatRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, oldFloatFont); } HFONT oldFont = (HFONT)SelectObject(hdc, titleFont); DrawPanelHeader(leftPanelRect, _T("战局信息"), 120); DrawPanelHeader(rightPanelRect, _T("预览与战术"), 148); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); RECT overviewRect = { leftPanelRect.left + SS(20), leftPanelRect.top + SS(98), leftPanelRect.right - SS(20), leftPanelRect.top + SS(252) }; DrawPanelCardAlpha(overviewRect, RGB(255, 247, 250), RGB(233, 191, 208), 24, panelAlpha); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); RECT overviewTitleRect = { overviewRect.left + SS(18), overviewRect.top + SS(12), overviewRect.right - SS(18), overviewRect.top + SS(42) }; DrawText(hdc, _T("战场状态"), -1, &overviewTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, bodyFont); TCHAR overviewText[160]; int totalLines = (currentMode == MODE_CLASSIC) ? classicStats.totalLinesCleared : rogueStats.totalLinesCleared; if (currentMode == MODE_ROGUE) { _stprintf_s( overviewText, _T("得分 %d\r\n模式 Rogue\r\n消行 %d\r\n危险 Lv.%d 封锁 %d"), tScore, totalLines, rogueStats.difficultyLevel, GetRogueLockedRows()); } else { _stprintf_s( overviewText, _T("得分 %d\r\n模式 经典\r\n消行 %d"), tScore, totalLines); } RECT overviewBodyRect = { overviewRect.left + SS(22), overviewRect.top + SS(62), overviewRect.right - SS(22), overviewRect.bottom - SS(22) }; DrawText(hdc, overviewText, -1, &overviewBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); if (currentMode == MODE_ROGUE) { int progressTop = IsRogueSkillDemoMode() ? 350 : 270; int progressBottom = IsRogueSkillDemoMode() ? 568 : 488; int upgradeTop = IsRogueSkillDemoMode() ? 590 : 510; if (IsRogueSkillDemoMode()) { RECT demoSkillRect = { leftPanelRect.left + SS(20), leftPanelRect.top + SS(270), leftPanelRect.right - SS(20), leftPanelRect.top + SS(332) }; DrawPanelCardAlpha(demoSkillRect, RGB(255, 244, 248), RGB(232, 184, 202), 20, panelNestedAlpha); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(132, 102, 118)); RECT demoSkillLabelRect = { demoSkillRect.left + SS(18), demoSkillRect.top + SS(8), demoSkillRect.right - SS(18), demoSkillRect.top + SS(26) }; DrawText(hdc, _T("\u5f53\u524d\u6280\u80fd"), -1, &demoSkillLabelRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, sectionFont); SetTextColor(hdc, titleColor); RECT demoSkillNameRect = { demoSkillRect.left + SS(18), demoSkillRect.top + SS(26), demoSkillRect.right - SS(18), demoSkillRect.bottom - SS(8) }; DrawText(hdc, GetCurrentRogueSkillDemoName(), -1, &demoSkillNameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); } RECT progressRect = { leftPanelRect.left + SS(20), leftPanelRect.top + SS(progressTop), leftPanelRect.right - SS(20), leftPanelRect.top + SS(progressBottom) }; DrawPanelCardAlpha(progressRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); RECT progressTitleRect = { progressRect.left + SS(18), progressRect.top + SS(12), progressRect.right - SS(18), progressRect.top + SS(42) }; DrawText(hdc, _T("成长进度"), -1, &progressTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, bodyFont); TCHAR progressText[96]; _stprintf_s(progressText, _T("等级 %d\r\nEXP %d / %d"), rogueStats.level, rogueStats.exp, rogueStats.requiredExp); RECT progressTextRect = { progressRect.left + SS(22), progressRect.top + SS(56), progressRect.right - SS(22), progressRect.top + SS(120) }; DrawText(hdc, progressText, -1, &progressTextRect, DT_LEFT | DT_TOP | DT_WORDBREAK); RECT expBarRect = { progressRect.left + SS(22), progressRect.top + SS(122), progressRect.right - SS(22), progressRect.top + SS(148) }; HBRUSH expTrackBrush = CreateSolidBrush(RGB(240, 220, 229)); FillRect(hdc, &expBarRect, expTrackBrush); DeleteObject(expTrackBrush); RECT expFillRect = expBarRect; int expWidth = expBarRect.right - expBarRect.left; if (rogueStats.requiredExp > 0) { expFillRect.right = expBarRect.left + expWidth * rogueStats.exp / rogueStats.requiredExp; } if (expFillRect.right < expFillRect.left) { expFillRect.right = expFillRect.left; } if (expFillRect.right > expBarRect.right) { expFillRect.right = expBarRect.right; } HBRUSH expFillBrush = CreateSolidBrush(accentColor); FillRect(hdc, &expFillRect, expFillBrush); DeleteObject(expFillBrush); const TCHAR* statLabels[2] = { _T("\u5f97\u5206\u500d\u7387"), _T("EXP \u500d\u7387") }; TCHAR statValues[2][32]; _stprintf_s(statValues[0], _T("%d%%"), rogueStats.scoreMultiplierPercent); _stprintf_s(statValues[1], _T("%d%%"), rogueStats.expMultiplierPercent); int statGap = SS(8); int statWidth = (progressRect.right - progressRect.left - SS(44) - statGap) / 2; for (int statIndex = 0; statIndex < 2; statIndex++) { RECT statRect = { progressRect.left + SS(22) + statIndex * (statWidth + statGap), progressRect.top + SS(154), progressRect.left + SS(22) + statIndex * (statWidth + statGap) + statWidth, progressRect.top + SS(204) }; DrawPanelCardAlpha(statRect, RGB(255, 239, 245), RGB(232, 184, 202), 16, panelNestedAlpha); RECT statLabelRect = { statRect.left + SS(8), statRect.top + SS(4), statRect.right - SS(8), statRect.top + SS(22) }; RECT statValueRect = { statRect.left + SS(8), statRect.top + SS(21), statRect.right - SS(8), statRect.bottom - SS(2) }; SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(132, 102, 118)); DrawText(hdc, statLabels[statIndex], -1, &statLabelRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, bodyFont); SetTextColor(hdc, titleColor); DrawText(hdc, statValues[statIndex], -1, &statValueRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } RECT upgradeListRect = { leftPanelRect.left + SS(20), leftPanelRect.top + SS(upgradeTop), leftPanelRect.right - SS(20), leftPanelRect.bottom - SS(20) }; DrawPanelCardAlpha(upgradeListRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); TextOut(hdc, upgradeListRect.left + SS(18), upgradeListRect.top + SS(16), _T("已掌握强化"), lstrlen(_T("已掌握强化"))); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(148, 118, 132)); RECT upgradeScrollHintRect = { upgradeListRect.left + SS(116), upgradeListRect.top + SS(17), upgradeListRect.right - SS(18), upgradeListRect.top + SS(42) }; DrawText(hdc, _T("J/K / 滚轮"), -1, &upgradeScrollHintRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT upgradeBodyRect = { upgradeListRect.left + SS(22), upgradeListRect.top + SS(56), upgradeListRect.right - SS(22), upgradeListRect.bottom - SS(20) }; TCHAR upgradeSummary[512]; upgradeSummary[0] = _T('\0'); if (rogueStats.scoreUpgradeLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5206\u6570\u500d\u7387 Lv.%d\r\n"), rogueStats.scoreUpgradeLevel); } if (rogueStats.expUpgradeLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("EXP \u5f3a\u5316 Lv.%d\r\n"), rogueStats.expUpgradeLevel); } if (rogueStats.slowFallStacks > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6162\u901f\u4e0b\u843d Lv.%d\r\n"), rogueStats.slowFallStacks); } if (rogueStats.comboBonusStacks > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u51fb\u52a0\u6210 Lv.%d\r\n"), rogueStats.comboBonusStacks); } if (rogueStats.previewUpgradeLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u989d\u5916\u9884\u89c8 Lv.%d\r\n"), rogueStats.previewUpgradeLevel); } if (rogueStats.lastChanceUpgradeLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6700\u540e\u4e00\u640f Lv.1 \u5269\u4f59 %d \u6b21\r\n"), rogueStats.lastChanceCount); } if (rogueStats.pressureReliefLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u51cf\u538b Lv.%d\r\n"), rogueStats.pressureReliefLevel); } if (rogueStats.sweeperLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6e05\u626b\u8005 Lv.%d\r\n"), rogueStats.sweeperLevel); } if (rogueStats.explosiveLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7206\u7834\u65b9\u5757 Lv.%d\r\n"), rogueStats.explosiveLevel); } if (rogueStats.chainBlastLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u9501\u7206\u7834 Lv.1\r\n")); } if (rogueStats.chainBombLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fde\u73af\u70b8\u5f39 Lv.1\r\n")); } if (rogueStats.laserLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6fc0\u5149\u65b9\u5757 Lv.%d\r\n"), rogueStats.laserLevel); } if (rogueStats.thunderTetrisLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u96f7\u9706\u56db\u6d88 Lv.1\r\n")); } if (rogueStats.thunderLaserLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u96f7\u9706\u6fc0\u5149 Lv.1\r\n")); } if (rogueStats.feverLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u72c2\u70ed\u6a21\u5f0f Lv.1\r\n")); } if (rogueStats.rageStackLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u66b4\u8d70\u5806\u53e0 Lv.1\r\n")); } if (rogueStats.infiniteFeverLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65e0\u9650\u72c2\u70ed Lv.1\r\n")); } if (rogueStats.screenBombLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6e05\u5c4f\u70b8\u5f39 Lv.%d \u5269\u4f59 %d \u679a\r\n"), rogueStats.screenBombLevel, rogueStats.screenBombCount); } if (rogueStats.terminalClearLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7ec8\u672b\u6e05\u573a Lv.1\r\n")); } if (rogueStats.perfectRotateLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5b8c\u7f8e\u65cb\u8f6c Lv.1\r\n")); } if (rogueStats.timeDilationLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65f6\u95f4\u7f13\u6d41 Lv.1\r\n")); } if (rogueStats.highPressureLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u9ad8\u538b\u5956\u52b1 Lv.1\r\n")); } if (rogueStats.tetrisGambleLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8d4c\u547d\u56db\u6d88 Lv.1\r\n")); } if (rogueStats.extremePlayerLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6781\u9650\u73a9\u5bb6 Lv.1\r\n")); } if (rogueStats.upgradeShockwaveLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5347\u7ea7\u51b2\u51fb\u6ce2 Lv.1\r\n")); } if (rogueStats.evolutionImpactLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8fdb\u5316\u51b2\u51fb Lv.1\r\n")); } if (rogueStats.controlMasterLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u64cd\u63a7\u5927\u5e08 Lv.1\r\n")); } if (rogueStats.blockStormLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u65b9\u5757\u98ce\u66b4 Lv.1\r\n")); } if (rogueStats.crossPieceLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5341\u5b57\u65b9\u5757 Lv.%d\r\n"), rogueStats.crossPieceLevel); } if (rogueStats.blackHoleLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u9ed1\u6d1e Lv.%d \u5269\u4f59 %d \u6b21\r\n"), rogueStats.blackHoleLevel, rogueStats.blackHoleCharges); } if (rogueStats.reshapeLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a7a\u4e2d\u6362\u5f62 Lv.%d \u5269\u4f59 %d \u6b21\r\n"), rogueStats.reshapeLevel, rogueStats.reshapeCharges); } if (rogueStats.rainbowPieceLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5f69\u8679\u65b9\u5757 Lv.1\r\n")); } if (rogueStats.voidCoreLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u865a\u7a7a\u6838\u5fc3 Lv.1\r\n")); } if (rogueStats.stableStructureLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a33\u5b9a\u7ed3\u6784 Lv.%d\r\n"), rogueStats.stableStructureLevel); } if (rogueStats.doubleGrowthLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u6210\u957f\u6838\u5fc3 Lv.1\r\n")); } if (rogueStats.pieceTuningLevels[0] > 0) { _stprintf_s( upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("I \u65b9\u5757\u6539\u9020 Lv.%d\r\n"), rogueStats.pieceTuningLevels[0]); } if (rogueStats.gamblerLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8d4c\u5f92 Lv.%d\r\n"), rogueStats.gamblerLevel); } if (rogueStats.dualChoiceLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u53cc\u91cd\u9009\u62e9 Lv.1\r\n")); } if (rogueStats.destinyWheelLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u547d\u8fd0\u8f6e\u76d8 Lv.1\r\n")); } if (lstrlen(upgradeSummary) == 0) { _stprintf_s(upgradeSummary, _T("尚未掌握强化。\r\n下一次升级将开启构筑。")); } RECT upgradeCalculateRect = { upgradeBodyRect.left, upgradeBodyRect.top, upgradeBodyRect.right, upgradeBodyRect.top }; DrawText(hdc, upgradeSummary, -1, &upgradeCalculateRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_CALCRECT); int maxUpgradeScroll = (upgradeCalculateRect.bottom - upgradeCalculateRect.top) - (upgradeBodyRect.bottom - upgradeBodyRect.top); if (maxUpgradeScroll < 0) { maxUpgradeScroll = 0; } if (upgradeListScrollOffset > maxUpgradeScroll) { upgradeListScrollOffset = maxUpgradeScroll; } HRGN oldUpgradeClipRegion = CreateRectRgn(0, 0, 0, 0); int hasOldUpgradeClipRegion = GetClipRgn(hdc, oldUpgradeClipRegion); HRGN upgradeClipRegion = CreateRectRgn(upgradeBodyRect.left, upgradeBodyRect.top, upgradeBodyRect.right, upgradeBodyRect.bottom); SelectClipRgn(hdc, upgradeClipRegion); RECT scrolledUpgradeBodyRect = upgradeBodyRect; scrolledUpgradeBodyRect.top -= upgradeListScrollOffset; scrolledUpgradeBodyRect.bottom += maxUpgradeScroll; DrawText(hdc, upgradeSummary, -1, &scrolledUpgradeBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); if (hasOldUpgradeClipRegion == 1) { SelectClipRgn(hdc, oldUpgradeClipRegion); } else { SelectClipRgn(hdc, nullptr); } DeleteObject(upgradeClipRegion); DeleteObject(oldUpgradeClipRegion); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); } else { RECT classicInfoRect = { leftPanelRect.left + SS(20), leftPanelRect.top + SS(270), leftPanelRect.right - SS(20), leftPanelRect.top + SS(458) }; DrawPanelCardAlpha(classicInfoRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT classicBodyRect = { classicInfoRect.left + SS(22), classicInfoRect.top + SS(24), classicInfoRect.right - SS(22), classicInfoRect.bottom - SS(24) }; DrawText(hdc, _T("经典模式:没有成长、EXP 和强化,只保留纯粹的消行挑战。\r\n\r\n在有限空间中保持阵型,尽可能活得更久。"), -1, &classicBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); } RECT holdNextRect = { rightPanelRect.left + SS(20), rightPanelRect.top + SS(98), rightPanelRect.right - SS(20), rightPanelRect.top + SS(326) }; DrawPanelCardAlpha(holdNextRect, RGB(255, 247, 250), RGB(233, 191, 208), 24, panelAlpha); RECT holdCard = { holdNextRect.left + SS(88), holdNextRect.top + SS(24), holdNextRect.left + SS(150), holdNextRect.top + SS(86) }; RECT nextSectionRect = { holdNextRect.left + SS(18), holdNextRect.top + SS(146), holdNextRect.right - SS(18), holdNextRect.bottom - SS(22) }; SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); RECT holdTitleRect = { holdNextRect.left + SS(18), holdNextRect.top + SS(34), holdNextRect.left + SS(80), holdNextRect.top + SS(70) }; DrawText(hdc, _T("Hold"), -1, &holdTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); RECT nextTitleRect = { nextSectionRect.left, holdNextRect.top + SS(102), holdNextRect.right - SS(18), holdNextRect.top + SS(136) }; DrawText(hdc, _T("\u4e0b\u4e00\u4e2a"), -1, &nextTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); DrawPanelCardAlpha(holdCard, RGB(255, 238, 244), RGB(233, 191, 208), 22, panelNestedAlpha); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT holdHintRect = { holdCard.right + SS(14), holdCard.top + SS(2), holdNextRect.right - SS(18), holdCard.bottom - SS(2) }; DrawText( hdc, currentMode == MODE_ROGUE && rogueStats.holdUnlocked > 0 ? (holdUsedThisTurn ? _T("已使用\r\n落地后恢复") : _T("C / Shift\r\n暂存一次")) : _T("未开启\r\n需备用仓"), -1, &holdHintRect, DT_LEFT | DT_VCENTER | DT_WORDBREAK); if (currentMode == MODE_ROGUE) { if (rogueStats.holdUnlocked == 0) { SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT holdLockedRect = { holdCard.left + SS(10), holdCard.top + SS(10), holdCard.right - SS(10), holdCard.bottom - SS(10) }; DrawText(hdc, _T("封存"), -1, &holdLockedRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); } else if (holdType >= 0) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[holdType][0][i][j] != 0) { int holdAvailableWidth = holdCard.right - holdCard.left - SS(16); int holdAvailableHeight = holdCard.bottom - holdCard.top - SS(16); int holdMiniCell = holdAvailableWidth < holdAvailableHeight ? holdAvailableWidth / 4 : holdAvailableHeight / 4; if (holdMiniCell < SS(7)) { holdMiniCell = SS(7); } int holdOffsetX = holdCard.left + (holdCard.right - holdCard.left - holdMiniCell * 4) / 2; int holdOffsetY = holdCard.top + (holdCard.bottom - holdCard.top - holdMiniCell * 4) / 2; RECT brickRect = { holdOffsetX + j * holdMiniCell, holdOffsetY + i * holdMiniCell, holdOffsetX + (j + 1) * holdMiniCell - SS(1), holdOffsetY + (i + 1) * holdMiniCell - SS(1) }; HBRUSH brickBrush = CreateSolidBrush(BrickColor[holdType]); HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 248, 250)); oldPen = (HPEN)SelectObject(hdc, brickPen); oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(8), SS(8)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(brickBrush); DeleteObject(brickPen); } } } } else { SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT emptyHoldRect = { holdCard.left + SS(10), holdCard.top + SS(10), holdCard.right - SS(10), holdCard.bottom - SS(10) }; DrawText(hdc, _T("\u7a7a"), -1, &emptyHoldRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); } } else { SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT classicHoldRect = { holdCard.left + SS(10), holdCard.top + SS(10), holdCard.right - SS(10), holdCard.bottom - SS(10) }; DrawText(hdc, _T("\u5173"), -1, &classicHoldRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); } int previewCount = 1; if (currentMode == MODE_ROGUE) { previewCount = rogueStats.previewCount; if (previewCount < 1) { previewCount = 1; } if (previewCount > 3) { previewCount = 3; } } for (int previewIndex = 0; previewIndex < previewCount; previewIndex++) { int nextCardGap = SS(8); int nextCardWidth = (nextSectionRect.right - nextSectionRect.left - nextCardGap * 2) / 3; RECT nextCard = { nextSectionRect.left + previewIndex * (nextCardWidth + nextCardGap), nextSectionRect.top, nextSectionRect.left + previewIndex * (nextCardWidth + nextCardGap) + nextCardWidth, nextSectionRect.bottom }; DrawPanelCardAlpha(nextCard, RGB(255, 247, 250), RGB(233, 191, 208), 18, panelStrongAlpha); int previewType = nextTypes[previewIndex]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[previewType][0][i][j] != 0) { int nextAvailableWidth = nextCard.right - nextCard.left - SS(14); int nextAvailableHeight = nextCard.bottom - nextCard.top - SS(14); int nextMiniCell = nextAvailableWidth < nextAvailableHeight ? nextAvailableWidth / 4 : nextAvailableHeight / 4; if (nextMiniCell < SS(7)) { nextMiniCell = SS(7); } int previewOffsetX = nextCard.left + (nextCard.right - nextCard.left - nextMiniCell * 4) / 2; int previewOffsetY = nextCard.top + (nextCard.bottom - nextCard.top - nextMiniCell * 4) / 2; RECT brickRect = { previewOffsetX + j * nextMiniCell, previewOffsetY + i * nextMiniCell, previewOffsetX + (j + 1) * nextMiniCell - SS(1), previewOffsetY + (i + 1) * nextMiniCell - SS(1) }; HBRUSH brickBrush = CreateSolidBrush(BrickColor[previewType]); HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 248, 250)); oldPen = (HPEN)SelectObject(hdc, brickPen); oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(8), SS(8)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(brickBrush); DeleteObject(brickPen); } } } } RECT controlRect = { rightPanelRect.left + SS(20), rightPanelRect.top + SS(346), rightPanelRect.right - SS(20), rightPanelRect.bottom - SS(20) }; DrawPanelCardAlpha(controlRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); TextOut(hdc, controlRect.left + SS(18), controlRect.top + SS(16), _T("战斗日志"), lstrlen(_T("战斗日志"))); RECT feedbackPanelRect = { controlRect.left + SS(18), controlRect.top + SS(56), controlRect.right - SS(18), controlRect.top + SS(172) }; DrawPanelCardAlpha(feedbackPanelRect, RGB(255, 244, 248), RGB(232, 184, 202), 20, panelNestedAlpha); SelectObject(hdc, bodyFont); SetTextColor(hdc, titleColor); RECT feedbackTitleRect = { feedbackPanelRect.left + SS(16), feedbackPanelRect.top + SS(14), feedbackPanelRect.right - SS(16), feedbackPanelRect.top + SS(42) }; DrawText( hdc, feedbackState.visibleTicks > 0 ? feedbackState.title : _T("局势平稳"), -1, &feedbackTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(116, 90, 104)); RECT feedbackBodyRect = { feedbackPanelRect.left + SS(16), feedbackPanelRect.top + SS(46), feedbackPanelRect.right - SS(16), feedbackPanelRect.bottom - SS(14) }; DrawText( hdc, feedbackState.visibleTicks > 0 ? feedbackState.detail : _T("连消、主动技能和特殊方块的战斗记录会出现在这里。"), -1, &feedbackBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); SelectObject(hdc, sectionFont); SetTextColor(hdc, textColor); RECT tipsTitleRect = { controlRect.left + SS(22), controlRect.top + SS(192), controlRect.right - SS(22), controlRect.top + SS(224) }; DrawText(hdc, _T("操作指令"), -1, &tipsTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, bodyFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT controlBodyRect = { controlRect.left + SS(22), controlRect.top + SS(232), controlRect.right - SS(22), controlRect.bottom - SS(22) }; if (currentMode == MODE_ROGUE) { DrawText( hdc, _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") _T("战局:P 暂停 R 重开 M 菜单\r\n") _T("技能:C 备用仓 Z 黑洞 X 炸弹 V 换形"), -1, &controlBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); } else { DrawText( hdc, _T("\u79fb\u52a8\uff1a\u2190/\u2192 \u6216 A/D\r\n") _T("\u65cb\u8f6c/\u4e0b\u843d\uff1a\u2191/W\u3001\u2193/S\u3001Space\r\n") _T("战局:P 暂停 R 重开 M 菜单"), -1, &controlBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); } if (suspendFlag || gameOverFlag) { RECT overlayRect = { gameRect.left + SS(28), gameRect.top + grid * 6 + SS(10), gameRect.right - SS(28), gameRect.top + grid * 10 + SS(30) }; HBRUSH overlayBrush = CreateSolidBrush(RGB(255, 241, 246)); HPEN overlayPen = CreatePen(PS_SOLID, 2, RGB(232, 170, 194)); oldPen = (HPEN)SelectObject(hdc, overlayPen); oldBrush = (HBRUSH)SelectObject(hdc, overlayBrush); RoundRect(hdc, overlayRect.left, overlayRect.top, overlayRect.right, overlayRect.bottom, SS(26), SS(26)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(overlayBrush); DeleteObject(overlayPen); RECT titleRect = { overlayRect.left + SS(24), overlayRect.top + SS(18), overlayRect.right - SS(24), overlayRect.top + SS(78) }; RECT tipRect = { overlayRect.left + SS(28), overlayRect.top + SS(86), overlayRect.right - SS(28), overlayRect.bottom - SS(20) }; SetTextColor(hdc, RGB(121, 76, 99)); SelectObject(hdc, titleFont); if (suspendFlag) { DrawText(hdc, _T("时间静止"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); int buttonGap = SS(18); int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; RECT continueRect = { overlayRect.left + SS(34), overlayRect.top + SS(94), overlayRect.left + SS(34) + buttonWidth, overlayRect.top + SS(138) }; RECT menuRect = { continueRect.right + buttonGap, continueRect.top, overlayRect.right - SS(34), continueRect.bottom }; DrawPanelCardAlpha(continueRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); SelectObject(hdc, bodyFont); DrawText(hdc, _T("继续 (P)"), -1, &continueRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } else { DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); if (reviveAvailable) { int buttonGap = SS(8); int buttonWidth = (overlayRect.right - overlayRect.left - SS(28) - buttonGap * 2) / 3; RECT reviveRect = { overlayRect.left + SS(14), overlayRect.top + SS(94), overlayRect.left + SS(14) + buttonWidth, overlayRect.top + SS(138) }; RECT restartRect = { reviveRect.right + buttonGap, reviveRect.top, reviveRect.right + buttonGap + buttonWidth, reviveRect.bottom }; RECT menuRect = { restartRect.right + buttonGap, reviveRect.top, overlayRect.right - SS(14), reviveRect.bottom }; DrawPanelCardAlpha(reviveRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); DrawPanelCardAlpha(restartRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); SelectObject(hdc, bodyFont); DrawText(hdc, _T("复活 (V)"), -1, &reviveRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } else { int buttonGap = SS(18); int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; RECT restartRect = { overlayRect.left + SS(34), overlayRect.top + SS(94), overlayRect.left + SS(34) + buttonWidth, overlayRect.top + SS(138) }; RECT menuRect = { restartRect.right + buttonGap, restartRect.top, overlayRect.right - SS(34), restartRect.bottom }; DrawPanelCardAlpha(restartRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); SelectObject(hdc, bodyFont); DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } } } if (currentScreen == SCREEN_UPGRADE) { RECT dimRect = { SX(20), SY(20), SX(WINDOW_CLIENT_WIDTH - 20), SY(WINDOW_CLIENT_HEIGHT - 20) }; Graphics dimGraphics(hdc); SolidBrush dimBrush(Color(214, 246, 232, 238)); dimGraphics.FillRectangle( &dimBrush, static_cast(dimRect.left), static_cast(dimRect.top), static_cast(dimRect.right - dimRect.left), static_cast(dimRect.bottom - dimRect.top)); RECT overlayRect = { SX(60), SY(80), SX(WINDOW_CLIENT_WIDTH - 60), SY(WINDOW_CLIENT_HEIGHT - 80) }; DrawPanelCardAlpha(overlayRect, RGB(255, 250, 252), RGB(232, 170, 194), 30, 238); SetTextColor(hdc, titleColor); SelectObject(hdc, titleFont); RECT overlayTitleRect = { overlayRect.left, overlayRect.top + SS(24), overlayRect.right, overlayRect.top + SS(68) }; DrawText(hdc, _T("选择强化"), -1, &overlayTitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, bodyFont); SetTextColor(hdc, textColor); RECT overlaySubtitleRect = { overlayRect.left, overlayRect.top + SS(70), overlayRect.right, overlayRect.top + SS(106) }; TCHAR overlaySubtitle[128]; if (upgradeUiState.picksRemaining > 1) { _stprintf_s( overlaySubtitle, _T("出现 %d 个强化,请标记 %d 个(已选 %d 个)"), upgradeUiState.optionCount, upgradeUiState.picksRemaining, upgradeUiState.markedCount); } else { _stprintf_s( overlaySubtitle, _T("出现 %d 个强化,还可选择 %d 个"), upgradeUiState.optionCount, upgradeUiState.picksRemaining); } DrawText(hdc, overlaySubtitle, -1, &overlaySubtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); int gap = SS(18); int horizontalPadding = SS(36); int verticalTop = overlayRect.top + SS(138); int columnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3; if (columnCount < 1) { columnCount = 1; } int rowCount = (upgradeUiState.optionCount + columnCount - 1) / columnCount; if (rowCount < 1) { rowCount = 1; } int cardWidth = (overlayRect.right - overlayRect.left - horizontalPadding * 2 - gap * (columnCount - 1)) / columnCount; int availableHeight = overlayRect.bottom - verticalTop - SS(72) - (rowCount - 1) * gap; int cardHeight = availableHeight / rowCount; for (int i = 0; i < upgradeUiState.optionCount; i++) { bool isSelected = (i == upgradeUiState.selectedIndex); bool isMarked = upgradeUiState.marked[i]; int column = i % columnCount; int row = i / columnCount; int left = overlayRect.left + horizontalPadding + column * (cardWidth + gap); int top = verticalTop + row * (cardHeight + gap); int right = left + cardWidth; RECT cardRect = { left, top, right, top + cardHeight }; COLORREF cardFill = RGB(255, 247, 250); COLORREF cardBorder = RGB(221, 197, 208); COLORREF descColor = RGB(108, 86, 99); COLORREF footerColor = RGB(128, 104, 118); const TCHAR* synthesisPath = GetUpgradeSynthesisPath(upgradeUiState.options[i].id); bool hasSynthesisPath = synthesisPath != nullptr && lstrlen(synthesisPath) > 0; const TCHAR* rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u666e\u901a"); if (upgradeUiState.options[i].cursed) { cardFill = isSelected ? RGB(238, 232, 238) : RGB(250, 245, 248); cardBorder = RGB(38, 34, 42); descColor = RGB(72, 62, 72); footerColor = RGB(48, 42, 50); rarityText = _T("\u8bc5\u5492\uff1a\u4e0b\u6b21\u5347\u7ea7\u6240\u9700 EXP +25%"); } else if (hasSynthesisPath) { cardBorder = RGB(208, 74, 78); footerColor = RGB(164, 58, 64); rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u8fdb\u9636"); } else if (upgradeUiState.options[i].rarity == UPGRADE_RARITY_RARE) { cardBorder = RGB(218, 172, 72); footerColor = RGB(150, 111, 40); rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u73cd\u7a00"); } else if (upgradeUiState.options[i].rarity == UPGRADE_RARITY_UNCOMMON) { cardBorder = RGB(92, 152, 218); footerColor = RGB(68, 112, 166); rarityText = _T("\u7a00\u6709\u5ea6\uff1a\u7f55\u89c1"); } if (isMarked && !upgradeUiState.options[i].cursed) { cardFill = RGB(255, 226, 236); } if (isSelected && !upgradeUiState.options[i].cursed) { cardFill = RGB(255, 235, 242); } DrawPanelCardAlpha(cardRect, cardFill, cardBorder, 24, isSelected ? 238 : 226); if (isSelected) { HPEN selectedPen = CreatePen(PS_SOLID, SS(3), cardBorder); oldPen = (HPEN)SelectObject(hdc, selectedPen); oldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); RoundRect(hdc, cardRect.left, cardRect.top, cardRect.right, cardRect.bottom, SS(24), SS(24)); SelectObject(hdc, oldBrush); SelectObject(hdc, oldPen); DeleteObject(selectedPen); } TCHAR levelText[32]; _stprintf_s(levelText, _T("Lv.%d"), upgradeUiState.options[i].currentLevel + 1); RECT levelRect = { cardRect.right - SS(88), cardRect.top + SS(20), cardRect.right - SS(20), cardRect.top + SS(48) }; SelectObject(hdc, smallFont); SetTextColor(hdc, footerColor); if (isMarked) { DrawText(hdc, _T("[已选]"), -1, &levelRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); } else { DrawText(hdc, levelText, -1, &levelRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); } if (hasSynthesisPath) { SelectObject(hdc, smallFont); SetTextColor(hdc, upgradeUiState.options[i].cursed ? RGB(142, 78, 78) : RGB(116, 82, 104)); RECT synthesisRect = { cardRect.left + SS(20), cardRect.top + SS(18), cardRect.right - SS(116), cardRect.top + SS(40) }; DrawText(hdc, synthesisPath, -1, &synthesisRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } SelectObject(hdc, sectionFont); SetTextColor(hdc, upgradeUiState.options[i].cursed ? RGB(130, 54, 54) : (isSelected ? titleColor : textColor)); RECT nameRect = { cardRect.left + SS(20), cardRect.top + (hasSynthesisPath ? SS(44) : SS(38)), cardRect.right - SS(20), cardRect.top + (hasSynthesisPath ? SS(76) : SS(70)) }; DrawText(hdc, upgradeUiState.options[i].name, -1, &nameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, bodyFont); SetTextColor(hdc, descColor); RECT descRect = { cardRect.left + SS(20), cardRect.top + (hasSynthesisPath ? SS(84) : SS(78)), cardRect.right - SS(20), cardRect.bottom - SS(48) }; DrawText(hdc, upgradeUiState.options[i].description, -1, &descRect, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_END_ELLIPSIS); SelectObject(hdc, smallFont); RECT footerRect = { cardRect.left + SS(20), cardRect.bottom - SS(34), cardRect.right - SS(20), cardRect.bottom - SS(10) }; SetTextColor(hdc, footerColor); DrawText( hdc, rarityText, -1, &footerRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); RECT hintRect = { overlayRect.left + SS(30), overlayRect.bottom - SS(52), overlayRect.right - SS(30), overlayRect.bottom - SS(18) }; DrawText( hdc, upgradeUiState.picksRemaining > 1 ? _T("W/A/S/D 或方向键切换,Space 标记,Enter 确认已选强化") : _T("W/A/S/D 或方向键切换,Enter 或 Space 确认"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } if (currentScreen != SCREEN_MENU) { DrawBackButton(); } DrawMusicButton(); SelectObject(hdc, oldFont); DeleteObject(titleFont); DeleteObject(sectionFont); DeleteObject(bodyFont); DeleteObject(smallFont); }