diff --git a/assets/images/qhy.jpg b/assets/images/qhy.jpg new file mode 100644 index 0000000..765764b Binary files /dev/null and b/assets/images/qhy.jpg differ diff --git a/assets/images/wyk.jpg b/assets/images/wyk.jpg index a34fd59..13c5bda 100644 Binary files a/assets/images/wyk.jpg and b/assets/images/wyk.jpg differ diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 1655d77..5cd1d96 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -191,6 +191,16 @@ struct CellFlashEffect COLORREF color; }; +struct GravityFallEffect +{ + int ticks; + int totalTicks; + int x; + int fromY; + int toY; + int cellValue; +}; + enum ScreenState { SCREEN_MENU = 0, @@ -240,6 +250,7 @@ extern ClearEffectState clearEffectState; extern FloatingTextEffect floatingTextEffects[8]; extern ParticleEffect particleEffects[96]; extern CellFlashEffect cellFlashEffects[64]; +extern GravityFallEffect gravityFallEffects[80]; extern int currentScreen; extern int currentMode; extern int currentFallInterval; @@ -270,11 +281,17 @@ void StartGameWithMode(int mode); void ReturnToMainMenu(); void ReviveAfterVideo(); void StartRogueSkillDemo(); +void StartRogueSkillDemoAt(int demoIndex); bool IsRogueSkillDemoMode(); bool TickRogueSkillDemo(); void AdvanceRogueSkillDemo(); +int GetRogueSkillDemoCount(); +const TCHAR* GetRogueSkillDemoName(int demoIndex); +const TCHAR* GetRogueSkillDemoDetail(int demoIndex); +const TCHAR* GetCurrentRogueSkillDemoName(); void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks); void OpenRulesScreen(); +void OpenSkillDemoScreen(); void OpenCreditScreen(); void ChangeCreditPage(int direction); void OpenUpgradeMenu(); @@ -292,6 +309,7 @@ void TriggerLineClearEffect(const int* rows, int rowCount, int linesCleared); void PlayPendingLineClearEffect(); void TriggerCellClearEffect(const Point* cells, int cellCount, bool strongBurst); void TriggerColoredCellClearEffect(const Point* cells, int cellCount, COLORREF flashColor, bool strongBurst); +void TriggerGravityFallEffect(int x, int fromY, int toY, int cellValue); void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain, bool allowLevelProgress); void CheckRogueLevelProgress(); void ApplyBoardGravity(); diff --git a/src/include/TetrisLogicInternal.h b/src/include/TetrisLogicInternal.h index 7276b33..d4f0879 100644 --- a/src/include/TetrisLogicInternal.h +++ b/src/include/TetrisLogicInternal.h @@ -84,6 +84,11 @@ void RollCurrentPieceSpecialFlags(bool allowRandomSpecials); */ void QueueLineClearEffect(const int* rows, int rowCount, int linesCleared); +/** + * @brief 记录固定方块受重力下落的轨迹,用于播放纵向残影特效。 + */ +void TriggerGravityFallEffect(int x, int fromY, int toY, int cellValue); + /** * @brief 尝试把旋转后的方块横向偏移指定格数后放置。 */ diff --git a/src/include/resource.h b/src/include/resource.h index 6eef328..5ad2731 100644 --- a/src/include/resource.h +++ b/src/include/resource.h @@ -12,6 +12,7 @@ #define IDD_ABOUTBOX 103 #define IDM_ABOUT 104 #define IDM_EXIT 105 +#define IDM_SKILL_DEMO 106 #define IDI_TETRIS 107 #define IDI_SMALL 108 #define IDC_TETRIS 109 diff --git a/src/resources/Tetris.rc b/src/resources/Tetris.rc index 056a7c1..304d2a6 100644 Binary files a/src/resources/Tetris.rc and b/src/resources/Tetris.rc differ diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index c28a21d..b14dd74 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -197,6 +197,30 @@ static RECT GetHelpOptionRect(HWND hWnd, int index) return rect; } +static RECT GetHelpSkillDemoItemRect(HWND hWnd, int index) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT rulesCard = GetRulesCardRect(hWnd); + RECT contentRect = + { + rulesCard.left + ScaleValue(metrics, 36), + rulesCard.top + ScaleValue(metrics, 126), + rulesCard.right - ScaleValue(metrics, 36), + rulesCard.bottom - ScaleValue(metrics, 86) + }; + int itemHeight = ScaleValue(metrics, 58); + int itemGap = ScaleValue(metrics, 10); + int itemTop = contentRect.top + ScaleValue(metrics, 8) - helpScrollOffset; + RECT rect = + { + contentRect.left, + itemTop + index * (itemHeight + itemGap), + contentRect.right, + itemTop + index * (itemHeight + itemGap) + itemHeight + }; + return rect; +} + static RECT GetHelpBackHintRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -744,6 +768,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) switch (wmId) { + case IDM_SKILL_DEMO: + OpenSkillDemoScreen(); + InvalidateRect(hWnd, nullptr, FALSE); + break; case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; @@ -1005,8 +1033,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) helpState.selectedIndex = i; if (i == 3) { - StartRogueSkillDemo(); - ResetGameTimer(hWnd); + helpState.currentPage = 5; + helpState.selectedIndex = 0; + helpScrollOffset = 0; } else { @@ -1023,6 +1052,31 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) InvalidateRect(hWnd, nullptr, FALSE); } } + else if (helpState.currentPage == 5) + { + if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY)) + { + helpState.currentPage = 0; + helpState.selectedIndex = 3; + helpScrollOffset = 0; + InvalidateRect(hWnd, nullptr, FALSE); + } + else + { + int demoCount = GetRogueSkillDemoCount(); + for (int i = 0; i < demoCount; i++) + { + if (IsPointInRect(GetHelpSkillDemoItemRect(hWnd, i), mouseX, mouseY)) + { + helpState.selectedIndex = i; + StartRogueSkillDemoAt(i); + ResetGameTimer(hWnd); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + } + } + } else if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY)) { if (helpState.currentPage == 4) @@ -1256,6 +1310,19 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ChangeCreditPage(-1); InvalidateRect(hWnd, nullptr, FALSE); } + else if (helpState.currentPage == 5) + { + helpState.selectedIndex--; + if (helpState.selectedIndex < 0) + { + helpState.selectedIndex = GetRogueSkillDemoCount() - 1; + } + if (helpState.selectedIndex * 68 < helpScrollOffset) + { + helpScrollOffset = helpState.selectedIndex * 68; + } + InvalidateRect(hWnd, nullptr, FALSE); + } break; case VK_DOWN: case VK_RIGHT: @@ -1275,6 +1342,20 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ChangeCreditPage(1); InvalidateRect(hWnd, nullptr, FALSE); } + else if (helpState.currentPage == 5) + { + helpState.selectedIndex++; + if (helpState.selectedIndex >= GetRogueSkillDemoCount()) + { + helpState.selectedIndex = 0; + helpScrollOffset = 0; + } + else if (helpState.selectedIndex * 68 > helpScrollOffset + 360) + { + helpScrollOffset = helpState.selectedIndex * 68 - 360; + } + InvalidateRect(hWnd, nullptr, FALSE); + } break; case VK_RETURN: case VK_SPACE: @@ -1282,8 +1363,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { if (helpState.selectedIndex == 3) { - StartRogueSkillDemo(); - ResetGameTimer(hWnd); + helpState.currentPage = 5; + helpState.selectedIndex = 0; + helpScrollOffset = 0; } else { @@ -1292,6 +1374,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } InvalidateRect(hWnd, nullptr, FALSE); } + else if (helpState.currentPage == 5) + { + StartRogueSkillDemoAt(helpState.selectedIndex); + ResetGameTimer(hWnd); + InvalidateRect(hWnd, nullptr, FALSE); + } break; case VK_ESCAPE: case VK_BACK: @@ -1304,6 +1392,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { ReturnToMainMenu(); } + else if (helpState.currentPage == 5) + { + helpState.currentPage = 0; + helpState.selectedIndex = 3; + helpScrollOffset = 0; + } else { helpState.currentPage = 0; diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 97b210e..06193e4 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -15,7 +15,7 @@ int workRegion[20][10] = { 0 }; Point point = { 0, 0 }; Point target = { 0, 0 }; MenuState menuState = { 0, 4 }; -HelpState helpState = { 0, 3, 0 }; +HelpState helpState = { 0, 4, 0 }; int helpScrollOffset = 0; int creditPageIndex = 0; int creditAnimationTicks = 0; @@ -29,6 +29,7 @@ ClearEffectState clearEffectState = { 0, 0, 0, {} }; FloatingTextEffect floatingTextEffects[8] = {}; ParticleEffect particleEffects[96] = {}; CellFlashEffect cellFlashEffects[64] = {}; +GravityFallEffect gravityFallEffects[80] = {}; int currentScreen = SCREEN_MENU; int currentMode = MODE_CLASSIC; int currentFallInterval = 500; diff --git a/src/source/TetrisLogicInnovation.cpp b/src/source/TetrisLogicInnovation.cpp index bf26c0d..0816a73 100644 --- a/src/source/TetrisLogicInnovation.cpp +++ b/src/source/TetrisLogicInnovation.cpp @@ -116,6 +116,11 @@ void ResetVisualEffects() { cellFlashEffects[i].ticks = 0; } + + for (int i = 0; i < 80; i++) + { + gravityFallEffects[i].ticks = 0; + } } /** @@ -158,6 +163,15 @@ bool TickVisualEffects() } } + for (int i = 0; i < 80; i++) + { + if (gravityFallEffects[i].ticks > 0) + { + gravityFallEffects[i].ticks--; + active = true; + } + } + return active; } @@ -429,6 +443,51 @@ void TriggerColoredCellClearEffect(const Point* cells, int cellCount, COLORREF f } } +/** + * @brief 为一个受重力下落的固定方块记录纵向残影和落点粒子。 + */ +void TriggerGravityFallEffect(int x, int fromY, int toY, int cellValue) +{ + if (x < 0 || x >= nGameWidth || fromY < 0 || fromY >= nGameHeight || + toY < 0 || toY >= nGameHeight || toY <= fromY || cellValue == 0) + { + return; + } + + int effectIndex = -1; + for (int i = 0; i < 80; i++) + { + if (gravityFallEffects[i].ticks <= 0) + { + effectIndex = i; + break; + } + } + + if (effectIndex < 0) + { + return; + } + + int totalTicks = 12 + (toY - fromY) * 2; + if (totalTicks > 26) + { + totalTicks = 26; + } + + gravityFallEffects[effectIndex].ticks = totalTicks; + gravityFallEffects[effectIndex].totalTicks = totalTicks; + gravityFallEffects[effectIndex].x = x; + gravityFallEffects[effectIndex].fromY = fromY; + gravityFallEffects[effectIndex].toY = toY; + gravityFallEffects[effectIndex].cellValue = cellValue; + + COLORREF particleColor = BrickColor[(cellValue - 1) % 7]; + AddParticle(x * 100 + 50, toY * 100 + 18, -2 - rand() % 5, -12 - rand() % 7, 4, particleColor); + AddParticle(x * 100 + 50, toY * 100 + 18, 2 + rand() % 5, -12 - rand() % 7, 4, particleColor); + AddCellFlash(x, toY, RGB(210, 245, 255), true); +} + /** * @brief 判断指定方块、旋转状态和位置是否可以合法放置。 */ @@ -575,6 +634,20 @@ void OpenRulesScreen() /** * @brief 打开致谢界面并重置致谢页切换状态。 */ +void OpenSkillDemoScreen() +{ + rogueDemoMode = false; + currentScreen = SCREEN_RULES; + suspendFlag = false; + helpState.selectedIndex = 0; + helpState.optionCount = 4; + helpState.currentPage = 5; + helpScrollOffset = 0; + creditPageIndex = 0; + creditAnimationTicks = 0; + creditAnimationDirection = 0; +} + void OpenCreditScreen() { rogueDemoMode = false; @@ -594,7 +667,7 @@ void OpenCreditScreen() */ void ChangeCreditPage(int direction) { - constexpr int creditPageCount = 3; + constexpr int creditPageCount = 4; if (direction == 0) { return; diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index f05eef0..f50daf8 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -131,7 +131,7 @@ static Bitmap* LoadBackgroundImage() */ static Bitmap* LoadCreditImage(int index) { - constexpr int creditPageCount = 3; + constexpr int creditPageCount = 4; static ULONG_PTR gdiplusToken = 0; static Bitmap* creditImages[creditPageCount] = {}; static bool attempted[creditPageCount] = {}; @@ -152,16 +152,25 @@ static Bitmap* LoadCreditImage(int index) { L"assets\\images\\qls.jpg", L"assets\\images\\wyk.jpg", - L"assets\\images\\swj.jpg" + L"assets\\images\\swj.jpg", + L"assets\\images\\qhy.jpg" }; - const std::wstring candidates[] = + const std::wstring creditExtraCandidates[] = { BuildAssetPath(imageNames[index]), - BuildWorkingDirAssetPath(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 (const std::wstring& candidate : candidates) + for (int i = 0; i < candidateCount; i++) { + const std::wstring& candidate = creditExtraCandidates[i]; if (candidate.empty()) { continue; @@ -639,6 +648,10 @@ void TDrawScreen(HDC hdc, HWND hWnd) { 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); @@ -880,21 +893,108 @@ void TDrawScreen(HDC hdc, HWND hWnd) 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 = 3; + constexpr int creditPageCount = 4; const TCHAR* creditNames[creditPageCount] = { _T("qls"), _T("wyk"), - _T("juju") + _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\u8c22\u8bfe\u524d\u95f2\u91cc\u5077\u5fd9\u7684juju"), + _T("\u611f\u8c22qhy\u7684\u5929\u624d\u6784\u60f3") }; int currentCredit = creditPageIndex; @@ -946,9 +1046,11 @@ void TDrawScreen(HDC hdc, HWND hWnd) previousOffset = currentOffset - creditAnimationDirection * slideDistance; } - Bitmap* preloadedCreditImageA = LoadCreditImage(0); - Bitmap* preloadedCreditImageB = LoadCreditImage(1); - Bitmap* preloadedCreditImageC = LoadCreditImage(2); + Bitmap* preloadedCreditImages[creditPageCount] = {}; + for (int i = 0; i < creditPageCount; i++) + { + preloadedCreditImages[i] = LoadCreditImage(i); + } Graphics creditGraphics(hdc); creditGraphics.SetInterpolationMode(InterpolationModeHighQualityBilinear); @@ -959,14 +1061,10 @@ void TDrawScreen(HDC hdc, HWND hWnd) OffsetRect(&shiftedImageArea, offset, 0); OffsetRect(&shiftedTextArea, offset, 0); - Bitmap* creditImage = preloadedCreditImageA; - if (cardIndex == 1) + Bitmap* creditImage = nullptr; + if (cardIndex >= 0 && cardIndex < creditPageCount) { - creditImage = preloadedCreditImageB; - } - else if (cardIndex == 2) - { - creditImage = preloadedCreditImageC; + creditImage = preloadedCreditImages[cardIndex]; } if (creditImage != nullptr) { @@ -1083,7 +1181,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) SelectObject(hdc, oldPen); DeleteObject(chevronPen); } - if (helpState.currentPage != 3 && helpState.currentPage != 4) + 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); @@ -1133,7 +1231,9 @@ void TDrawScreen(HDC hdc, HWND hWnd) ? _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") - : _T("\u9f20\u6807\u6eda\u8f6e\u4e0a\u4e0b\u7ffb\u52a8\uff0cEsc / Backspace / M \u8fd4\u56de\u5e2e\u52a9")); + : (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); @@ -1373,6 +1473,72 @@ void TDrawScreen(HDC hdc, HWND hWnd) } } + 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; @@ -1561,12 +1727,50 @@ void TDrawScreen(HDC hdc, HWND hWnd) 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(270), + leftPanelRect.top + SS(progressTop), leftPanelRect.right - SS(20), - leftPanelRect.top + SS(488) + leftPanelRect.top + SS(progressBottom) }; DrawPanelCardAlpha(progressRect, RGB(255, 248, 251), RGB(233, 191, 208), 24, panelAlpha); @@ -1670,7 +1874,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) RECT upgradeListRect = { leftPanelRect.left + SS(20), - leftPanelRect.top + SS(510), + leftPanelRect.top + SS(upgradeTop), leftPanelRect.right - SS(20), leftPanelRect.bottom - SS(20) }; diff --git a/src/source/TetrisRogue.cpp b/src/source/TetrisRogue.cpp index fbdddac..c7aaf77 100644 --- a/src/source/TetrisRogue.cpp +++ b/src/source/TetrisRogue.cpp @@ -111,6 +111,7 @@ static int pendingUpgradeShockwaveRows = 0; static bool pendingEvolutionImpactShockwave = false; static int rogueDemoStepIndex = 0; static int rogueDemoTicks = 0; +static bool rogueDemoAutoAdvance = true; enum RogueDemoKind { @@ -194,6 +195,7 @@ static void ShowRogueDemoFloatingName(const TCHAR* name); static void FillRogueDemoCell(int y, int x, int value); static void FillRogueDemoRows(int firstRow, int lastRow, int baseValue); static void SetRogueDemoCurrentPiece(int pieceType, int pieceState, int x, int y); +static void StartRogueSkillDemoInternal(int demoIndex, bool autoAdvance); /** * @brief 限制 Rogue 模式的下一方块预览数量。 @@ -1966,6 +1968,10 @@ void ApplyBoardGravity() int cell = workRegion[y][x]; workRegion[y][x] = 0; workRegion[writeY][x] = cell; + if (writeY != y) + { + TriggerGravityFallEffect(x, y, writeY, cell); + } writeY--; } } @@ -2772,16 +2778,89 @@ bool IsRogueSkillDemoMode() return rogueDemoMode; } +/** + * @brief 返回 Rogue 技能演示条目数量,供帮助页逐行绘制可点击名单。 + */ +int GetRogueSkillDemoCount() +{ + return kRogueDemoStepCount; +} + +/** + * @brief 按序号返回 Rogue 技能演示名称。 + */ +const TCHAR* GetRogueSkillDemoName(int demoIndex) +{ + if (demoIndex < 0 || demoIndex >= kRogueDemoStepCount) + { + return _T(""); + } + + return kRogueDemoSteps[demoIndex].name; +} + +/** + * @brief 按序号返回 Rogue 技能演示说明。 + */ +const TCHAR* GetRogueSkillDemoDetail(int demoIndex) +{ + if (demoIndex < 0 || demoIndex >= kRogueDemoStepCount) + { + return _T(""); + } + + return kRogueDemoSteps[demoIndex].detail; +} + +/** + * @brief 返回当前正在演示的 Rogue 技能名称。 + */ +const TCHAR* GetCurrentRogueSkillDemoName() +{ + if (!rogueDemoMode) + { + return _T(""); + } + + return GetRogueSkillDemoName(rogueDemoStepIndex); +} + /** * @brief 从帮助页进入 Rogue 技能演示,并播放第一段技能展示。 */ void StartRogueSkillDemo() { + StartRogueSkillDemoInternal(0, true); +} + +/** + * @brief 从帮助页的技能名单进入指定 Rogue 技能演示。 + */ +void StartRogueSkillDemoAt(int demoIndex) +{ + StartRogueSkillDemoInternal(demoIndex, false); +} + +/** + * @brief 进入 Rogue 技能演示并配置起始条目和是否自动轮播。 + */ +static void StartRogueSkillDemoInternal(int demoIndex, bool autoAdvance) +{ + if (demoIndex < 0) + { + demoIndex = 0; + } + if (demoIndex >= kRogueDemoStepCount) + { + demoIndex = kRogueDemoStepCount - 1; + } + currentMode = MODE_ROGUE; currentScreen = SCREEN_PLAYING; rogueDemoMode = true; - rogueDemoStepIndex = 0; + rogueDemoStepIndex = demoIndex; rogueDemoTicks = 0; + rogueDemoAutoAdvance = autoAdvance; Restart(); rogueDemoMode = true; @@ -2805,10 +2884,13 @@ bool TickRogueSkillDemo() return false; } - rogueDemoTicks++; - if (rogueDemoTicks >= kRogueDemoStepTicks) + if (rogueDemoAutoAdvance) { - AdvanceRogueSkillDemo(); + rogueDemoTicks++; + if (rogueDemoTicks >= kRogueDemoStepTicks) + { + AdvanceRogueSkillDemo(); + } } return true; @@ -2876,12 +2958,7 @@ static void ResetRogueDemoBoard() */ static void ShowRogueDemoFloatingName(const TCHAR* name) { - floatingTextEffects[0].ticks = 56; - floatingTextEffects[0].totalTicks = 56; - floatingTextEffects[0].boardX = 500; - floatingTextEffects[0].boardY = 980; - floatingTextEffects[0].color = RGB(255, 248, 250); - lstrcpyn(floatingTextEffects[0].text, name, sizeof(floatingTextEffects[0].text) / sizeof(TCHAR)); + (void)name; } /** diff --git a/微信图片_20260428171006_303_11.jpg b/微信图片_20260428171006_303_11.jpg new file mode 100644 index 0000000..765764b Binary files /dev/null and b/微信图片_20260428171006_303_11.jpg differ