diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 79a0a09..7ecaedf 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -226,6 +226,8 @@ extern Point point; extern Point target; extern MenuState menuState; extern HelpState helpState; +extern int helpScrollOffset; +extern int upgradeListScrollOffset; extern PlayerStats classicStats; extern PlayerStats rogueStats; extern UpgradeUiState upgradeUiState; diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index 6c9832d..9561cc4 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -29,6 +29,43 @@ static bool FileExists(const std::wstring& path); static void StopBackgroundMusic(); static void StartBackgroundMusic(); +/** + * @brief 将指定滚动偏移按步长调整,并限制在非负范围内。 + */ +static void AdjustScrollOffset(int& scrollOffset, int delta) +{ + scrollOffset += delta; + if (scrollOffset < 0) + { + scrollOffset = 0; + } + if (scrollOffset > 2400) + { + scrollOffset = 2400; + } +} + +/** + * @brief 按当前窗口缩放返回一次滚动操作的像素距离。 + */ +static int GetScrollStep(HWND hWnd, int baseStep) +{ + 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; + } + + return MulDiv(baseStep, scale, 1000); +} + struct LayoutMetrics { int scale; @@ -886,6 +923,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { helpState.selectedIndex = i; helpState.currentPage = i + 1; + helpScrollOffset = 0; InvalidateRect(hWnd, nullptr, FALSE); break; } @@ -899,6 +937,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) else if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY)) { helpState.currentPage = 0; + helpScrollOffset = 0; InvalidateRect(hWnd, nullptr, FALSE); } break; @@ -1013,6 +1052,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, message, wParam, lParam); } + case WM_MOUSEWHEEL: + { + int wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam); + int direction = (wheelDelta > 0) ? -1 : 1; + int scrollStep = GetScrollStep(hWnd, 64); + if (currentScreen == SCREEN_RULES && helpState.currentPage != 0) + { + AdjustScrollOffset(helpScrollOffset, direction * scrollStep); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + if (currentScreen == SCREEN_PLAYING && currentMode == MODE_ROGUE) + { + AdjustScrollOffset(upgradeListScrollOffset, direction * scrollStep); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + break; + } case WM_KEYDOWN: if (currentScreen == SCREEN_MENU) { @@ -1103,6 +1161,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (helpState.currentPage == 0) { helpState.currentPage = helpState.selectedIndex + 1; + helpScrollOffset = 0; InvalidateRect(hWnd, nullptr, FALSE); } break; @@ -1116,6 +1175,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) else { helpState.currentPage = 0; + helpScrollOffset = 0; } InvalidateRect(hWnd, nullptr, FALSE); break; @@ -1280,6 +1340,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; } + if (currentMode == MODE_ROGUE && (wParam == 'J' || wParam == 'K')) + { + int direction = (wParam == 'J') ? 1 : -1; + AdjustScrollOffset(upgradeListScrollOffset, direction * GetScrollStep(hWnd, 52)); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + switch (wParam) { case VK_LEFT: diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 161913f..9c1966c 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -15,6 +15,8 @@ Point point = { 0, 0 }; Point target = { 0, 0 }; MenuState menuState = { 0, 2 }; HelpState helpState = { 0, 3, 0 }; +int helpScrollOffset = 0; +int upgradeListScrollOffset = 0; PlayerStats classicStats = { 0, 1, 0, 0, 0 }; PlayerStats rogueStats = { 0, 1, 0, 30, 0, 100, 100, 0 }; UpgradeUiState upgradeUiState = { 0, 0, 0, 0, 0, 0, {}, {} }; diff --git a/src/source/TetrisLogicInnovation.cpp b/src/source/TetrisLogicInnovation.cpp index e8fddf9..0ba321c 100644 --- a/src/source/TetrisLogicInnovation.cpp +++ b/src/source/TetrisLogicInnovation.cpp @@ -506,6 +506,7 @@ void StartGameWithMode(int mode) { currentMode = mode; currentScreen = SCREEN_PLAYING; + upgradeListScrollOffset = 0; Restart(); currentFallInterval = (currentMode == MODE_ROGUE) ? GetRogueFallInterval() : 500; tScore = (currentMode == MODE_CLASSIC) ? classicStats.score : rogueStats.score; @@ -521,6 +522,8 @@ void ReturnToMainMenu() gameOverFlag = false; ResetVisualEffects(); ResetPendingRogueVisualEvents(); + helpScrollOffset = 0; + upgradeListScrollOffset = 0; pendingLineClearEffectTicks = 0; pendingLineClearEffectRowCount = 0; pendingLineClearEffectLineCount = 0; @@ -545,4 +548,5 @@ void OpenRulesScreen() helpState.selectedIndex = 0; helpState.optionCount = 3; helpState.currentPage = 0; + helpScrollOffset = 0; } diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index bd8e51d..f0f586d 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -695,68 +695,152 @@ void TDrawScreen(HDC hdc, HWND hWnd) }; const TCHAR* categoryTexts[8] = { - _T("赏金纹章:得分+20%,可叠加\r\n成长印记:EXP+25%,可叠加\r\n缓坠羽翼:自然下落变慢\r\n连击律动:连续消行追加奖励\r\n先见之眼:下一方块预览+1"), - _T("最后一搏:首次濒死清底3行\r\n备用仓:C / Shift 暂存方块\r\n完美旋转:旋转受阻时左右修正\r\n时间缓流:高堆叠自动减速\r\n空中换形:V 将当前方块变 I"), - _T("卸压清场:立刻清最高占用行\r\n底线清道夫:消行充能后清底\r\n清屏炸弹:X 清底5行,消行充能\r\n黑洞奇点:Z 吞噬数量最多的方块"), - _T("爆破核心:橙红边框,落地清 3x3\r\n棱镜激光:青色边框,按落地中心清整列\r\n十字方块:绿色边框,按落地中心清行列\r\n彩虹方块:清中心行主色,覆盖行染为场上主色\r\n方块改造:提高 I 块生成概率"), - _T("连锁火花:消行后追加随机破坏\r\n连环炸弹:爆破扩大为 5x5\r\n雷霆四消:三消/四消额外轰击\r\n雷霆棱镜:三消/四消追加激光"), - _T("狂热节拍:累计12行进入狂热\r\n怒火连段:连击越高倍率越高\r\n无尽狂热:狂热中消行延时\r\n高压悬赏:更快但收益更高\r\n豪赌四消 / 极限玩家:偏向四消爆发"), - _T("双重抉择:升级可多选1个\r\n命运轮盘:6选2但含诅咒\r\n升级冲击波:升级后清底2行\r\n进化冲击:升级后清底3行\r\n成长核心:永久得分/EXP +15%"), - _T("操控大师:Hold 后减速并预览+1\r\n方块风暴:接下来5个全 I 块\r\n稳定结构:概率填局部小洞\r\n虚空核心:黑洞生成彩虹,彩虹生效追加小黑洞\r\n赌徒契约:强化20%翻倍/20%落空,收益波动") + _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底线清道夫:通过消行充能,充满后自动清理底部行。\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 columnGap = SS(18); - int rowGap = SS(14); - int columnWidth = (contentRect.right - contentRect.left - columnGap) / 2; - int rowHeight = (contentRect.bottom - contentRect.top - rowGap * 3) / 4; + 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) { - int column = i % 2; - int row = i / 2; + 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 + column * (columnWidth + columnGap), - contentRect.top + row * (rowHeight + rowGap), - contentRect.left + column * (columnWidth + columnGap) + columnWidth, - contentRect.top + row * (rowHeight + rowGap) + rowHeight + contentRect.left, + sectionTop, + contentRect.right, + sectionTop + titleHeight + bodyHeight + SS(12) }; - HPEN categoryPen = CreatePen(PS_SOLID, 1, RGB(232, 202, 215)); - HBRUSH categoryBrush = CreateSolidBrush(RGB(255, 252, 250)); - oldPen = (HPEN)SelectObject(hdc, categoryPen); - oldBrush = (HBRUSH)SelectObject(hdc, categoryBrush); - RoundRect(hdc, categoryRect.left, categoryRect.top, categoryRect.right, categoryRect.bottom, SS(16), SS(16)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(categoryBrush); - DeleteObject(categoryPen); + if (categoryRect.bottom < contentRect.top || categoryRect.top > contentRect.bottom) + { + sectionTop += titleHeight + bodyHeight + sectionGap; + continue; + } SetTextColor(hdc, titleColor); - SelectObject(hdc, bodyFont); + SelectObject(hdc, sectionFont); RECT categoryTitleRect = { - categoryRect.left + SS(16), - categoryRect.top + SS(10), - categoryRect.right - SS(16), - categoryRect.top + SS(34) + 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, smallFont); + SelectObject(hdc, bodyFont); RECT categoryBodyRect = { - categoryRect.left + SS(16), - categoryRect.top + SS(40), - categoryRect.right - SS(16), - categoryRect.bottom - SS(10) + 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); } if (helpState.currentPage != 3) { - DrawText(hdc, pageText, -1, &contentRect, pageFlags); + 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); } } @@ -771,7 +855,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) }; const TCHAR* helpHint = helpState.currentPage == 0 ? _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter / Space \u786e\u8ba4\uff0cEsc / M \u8fd4\u56de\u4e3b\u83dc\u5355") - : _T("Esc / 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); DrawBackButton(); @@ -1317,6 +1401,17 @@ void TDrawScreen(HDC hdc, HWND hWnd) 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 = @@ -1495,7 +1590,44 @@ void TDrawScreen(HDC hdc, HWND hWnd) _stprintf_s(upgradeSummary, _T("尚未掌握强化。\r\n下一次升级将开启构筑。")); } - DrawText(hdc, upgradeSummary, -1, &upgradeBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + 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); } @@ -2150,7 +2282,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) cardRect.left + SS(20), cardRect.top + SS(18), cardRect.right - SS(116), - cardRect.top + SS(44) + cardRect.top + SS(40) }; DrawText(hdc, synthesisPath, -1, &synthesisRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } @@ -2160,30 +2292,30 @@ void TDrawScreen(HDC hdc, HWND hWnd) RECT nameRect = { cardRect.left + SS(20), - cardRect.top + (hasSynthesisPath ? SS(54) : SS(44)), - cardRect.right - SS(96), - cardRect.top + (hasSynthesisPath ? SS(106) : SS(98)) + 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_TOP | DT_WORDBREAK); + DrawText(hdc, upgradeUiState.options[i].name, -1, &nameRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - SelectObject(hdc, bodyFont); + SelectObject(hdc, smallFont); SetTextColor(hdc, descColor); RECT descRect = { cardRect.left + SS(20), - cardRect.top + (hasSynthesisPath ? SS(124) : SS(116)), + cardRect.top + (hasSynthesisPath ? SS(84) : SS(78)), cardRect.right - SS(20), - cardRect.bottom - SS(64) + cardRect.bottom - SS(48) }; - DrawText(hdc, upgradeUiState.options[i].description, -1, &descRect, DT_LEFT | DT_WORDBREAK); + 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(42), + cardRect.bottom - SS(34), cardRect.right - SS(20), - cardRect.bottom - SS(14) + cardRect.bottom - SS(10) }; SetTextColor(hdc, footerColor); DrawText( diff --git a/src/source/TetrisRogue.cpp b/src/source/TetrisRogue.cpp index c50e6fe..3cbfbca 100644 --- a/src/source/TetrisRogue.cpp +++ b/src/source/TetrisRogue.cpp @@ -57,17 +57,17 @@ static const UpgradeEntry kUpgradePool[] = { { UPGRADE_SCORE_MULTIPLIER, -1, 100, true, _T("赏金纹章"), _T("得分"), _T("所有得分提高 20%,每次选择都会继续叠加。") }, { UPGRADE_COMBO_BONUS, -1, 95, true, _T("连击律动"), _T("节奏"), _T("连续消行会追加连击奖励,节奏越稳收益越高。") }, - { UPGRADE_SLOW_FALL, -1, 90, true, _T("缓坠羽翼"), _T("操作"), _T("自然下落延缓 80ms,为摆放和补洞争取更多时间。") }, - { UPGRADE_PREVIEW_PLUS_ONE, 3, 85, false, _T("先见之眼"), _T("视野"), _T("下一个方块预览 +1,最多可同时看见 3 个。") }, + { UPGRADE_SLOW_FALL, 4, 90, true, _T("缓坠羽翼"), _T("操作"), _T("自然下落延缓 80ms,最多叠加 4 次。") }, + { UPGRADE_PREVIEW_PLUS_ONE, 1, 85, false, _T("先见之眼"), _T("视野"), _T("下一个方块预览 +1;第三个预览由操控大师解锁。") }, { UPGRADE_EXP_MULTIPLIER, -1, 100, true, _T("成长印记"), _T("成长"), _T("消行获得的 EXP 提高 25%,更快迎来下一次强化。") }, { UPGRADE_LAST_CHANCE, 1, 72, false, _T("最后一搏"), _T("保命"), _T("首次濒临失败时清除底部 3 行,让本局继续战斗。") }, { UPGRADE_HOLD_UNLOCK, 1, 78, false, _T("备用仓"), _T("特殊"), _T("解锁 Hold。按 C 或 Shift 暂存下落方块,每个方块落地前限用一次。") }, { UPGRADE_PRESSURE_RELIEF, -1, 82, true, _T("卸压清场"), _T("特殊"), _T("立刻清除最高占用行,为棋盘腾出一段喘息空间。") }, { UPGRADE_SWEEPER, -1, 74, true, _T("底线清道夫"), _T("特殊"), _T("消行会为清道夫充能,充满后自动清除底部 1 行。") }, - { UPGRADE_EXPLOSIVE_PIECE, -1, 86, true, _T("爆破核心"), _T("特殊"), _T("大幅提高爆破方块出现率。爆破方块落地时清除 3x3 区域。") }, + { UPGRADE_EXPLOSIVE_PIECE, 1, 86, false, _T("爆破核心"), _T("特殊"), _T("解锁爆破方块生成。爆破方块落地时清除 3x3 区域。") }, { UPGRADE_CHAIN_BLAST, 1, 92, false, _T("连锁火花"), _T("进阶"), _T("每次消行后,在被清除行附近追加随机破坏。") }, { UPGRADE_CHAIN_BOMB, 1, 110, false, _T("连环炸弹"), _T("进化"), _T("爆破范围扩大为 5x5;若引发消行,再追加一次小爆炸。") }, - { UPGRADE_LASER_PIECE, -1, 84, true, _T("棱镜激光"), _T("特殊"), _T("大幅提高激光方块出现率。激光方块落地后清除所在整列。") }, + { UPGRADE_LASER_PIECE, 1, 84, false, _T("棱镜激光"), _T("特殊"), _T("解锁激光方块生成。激光方块落地后清除所在整列。") }, { UPGRADE_THUNDER_TETRIS, 1, 94, false, _T("雷霆四消"), _T("进阶"), _T("完成三消或四消时,额外轰击随机 2 行。") }, { UPGRADE_THUNDER_LASER, 1, 112, false, _T("雷霆棱镜"), _T("进化"), _T("三消或四消时额外发射 2 道激光,随机清除 2 列并获得 EXP。") }, { UPGRADE_FEVER_MODE, 1, 92, false, _T("狂热节拍"), _T("进阶"), _T("累计消行 12 行后进入 12 秒狂热:得分与 EXP 翻倍。") }, @@ -86,15 +86,15 @@ static const UpgradeEntry kUpgradePool[] = { UPGRADE_EVOLUTION_IMPACT, 1, 118, false, _T("进化冲击"), _T("进化"), _T("升级时清除底部 3 行,并获得 10 秒双倍 EXP。") }, { UPGRADE_CONTROL_MASTER, 1, 112, false, _T("操控大师"), _T("进化"), _T("使用备用仓后短暂降低下落速度,并额外增加 1 个预览方块。") }, { UPGRADE_BLOCK_STORM, 1, 82, false, _T("方块风暴"), _T("爆发"), _T("接下来 5 个方块全部变为 I 块,快速制造四消机会。") }, - { UPGRADE_CROSS_PIECE, -1, 84, true, _T("十字方块"), _T("爆发"), _T("大幅提高十字方块出现率。十字方块落地后清除所在行与所在列。") }, + { UPGRADE_CROSS_PIECE, 1, 84, false, _T("十字方块"), _T("爆发"), _T("解锁十字方块生成。十字方块落地后清除所在行与所在列。") }, { UPGRADE_BLACK_HOLE, 1, 88, false, _T("黑洞奇点"), _T("特殊"), _T("获得 2 次黑洞。按 Z 吞噬棋盘上数量最多的一种固定方块。") }, { UPGRADE_AIR_RESHAPE, 1, 82, false, _T("空中换形"), _T("操作"), _T("获得 2 次换形。按 V 将正在下落的方块重塑为 I 块。") }, - { UPGRADE_RAINBOW_PIECE, 1, 84, false, _T("彩虹方块"), _T("爆发"), _T("更高概率生成彩虹方块。落地后清除自身中心行最多的颜色,并把覆盖行染成场上最多的颜色。") }, + { UPGRADE_RAINBOW_PIECE, 1, 84, false, _T("彩虹方块"), _T("爆发"), _T("解锁彩虹方块生成。落地后清中心行主色,并把覆盖行染成场上主色。") }, { UPGRADE_VOID_CORE, 1, 112, false, _T("虚空核心"), _T("进化"), _T("黑洞后额外生成 1 个彩虹方块;彩虹生效后追加一次小型黑洞。") }, { UPGRADE_STABLE_STRUCTURE, -1, 72, true, _T("稳定结构"), _T("特殊"), _T("落地后有小概率填补邻近空洞,让阵型更加稳固。") }, { UPGRADE_DOUBLE_GROWTH, 1, 84, false, _T("成长核心"), _T("成长"), _T("永久获得 +15% 得分与 +15% EXP;每局只能选择一次。") }, { UPGRADE_PIECE_TUNING, -1, 64, true, _T("方块改造"), _T("特殊"), _T("固定提高 I 方块的生成概率。") }, - { UPGRADE_GAMBLER, -1, 64, true, _T("赌徒契约"), _T("风险"), _T("后续强化 20% 翻倍 / 20% 落空,每级+5%;消行收益随机波动。") } + { UPGRADE_GAMBLER, 4, 64, true, _T("赌徒契约"), _T("风险"), _T("后续强化 20% 翻倍 / 20% 落空,每级+5%,最多 4 级;消行收益随机波动。") } }; static constexpr int kUpgradePoolSize = sizeof(kUpgradePool) / sizeof(kUpgradePool[0]); @@ -613,14 +613,9 @@ static bool IsUpgradeSelectable(const UpgradeEntry& entry) rogueStats.evolutionImpactLevel == 0; } - if (entry.repeatable) - { - return true; - } - if (entry.maxLevel <= 0) { - return GetUpgradeCurrentLevel(entry.id) == 0; + return entry.repeatable || GetUpgradeCurrentLevel(entry.id) == 0; } return GetUpgradeCurrentLevel(entry.id) < entry.maxLevel; @@ -1616,15 +1611,16 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) break; case UPGRADE_SLOW_FALL: rogueStats.slowFallStacks += applyCount; + if (rogueStats.slowFallStacks > 4) + { + rogueStats.slowFallStacks = 4; + } currentFallInterval = GetRogueFallInterval(); break; case UPGRADE_PREVIEW_PLUS_ONE: - for (int i = 0; i < applyCount; i++) + if (rogueStats.previewCount < 2) { - if (rogueStats.previewCount < 3) - { - rogueStats.previewCount++; - } + rogueStats.previewCount = 2; } rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; break; @@ -1653,7 +1649,7 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) rogueStats.sweeperLevel += applyCount; break; case UPGRADE_EXPLOSIVE_PIECE: - rogueStats.explosiveLevel += applyCount; + rogueStats.explosiveLevel = 1; break; case UPGRADE_CHAIN_BLAST: rogueStats.chainBlastLevel = 1; @@ -1662,7 +1658,7 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) rogueStats.chainBombLevel = 1; break; case UPGRADE_LASER_PIECE: - rogueStats.laserLevel += applyCount; + rogueStats.laserLevel = 1; break; case UPGRADE_THUNDER_TETRIS: rogueStats.thunderTetrisLevel = 1; @@ -1741,7 +1737,7 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) nextTypes[2] = 0; break; case UPGRADE_CROSS_PIECE: - rogueStats.crossPieceLevel += applyCount; + rogueStats.crossPieceLevel = 1; break; case UPGRADE_BLACK_HOLE: rogueStats.blackHoleLevel = 1; @@ -1770,6 +1766,10 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) break; case UPGRADE_GAMBLER: rogueStats.gamblerLevel += applyCount; + if (rogueStats.gamblerLevel > 4) + { + rogueStats.gamblerLevel = 4; + } break; default: break;