4 Commits

Author SHA1 Message Date
Qi-huanye be95bd25e1 清屏炸弹不可多选 2026-04-29 15:45:02 +08:00
Qi-huanye b98d2c9d59 调整致谢页并清理worktree 2026-04-29 15:27:06 +08:00
Qi-huanye a331162349 进一步拆分与注释补强 2026-04-29 15:21:14 +08:00
Qi-huanye 58ab400949 调整照片 2026-04-29 15:05:37 +08:00
10 changed files with 984 additions and 665 deletions
+1
View File
@@ -1,5 +1,6 @@
# Build outputs
/.vscode-build/
/.worktrees/
/build/
/bin/
/obj/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 KiB

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 KiB

After

Width:  |  Height:  |  Size: 84 KiB

+208 -128
View File
@@ -381,6 +381,129 @@ void DropDown()
}
}
/**
* @brief 收集当前方块将要固定到棋盘上的格子,并标记是否越过顶部。
* @param overflowTop 返回是否有方块格位于可视区域顶部之外。
* @param fixedCells 返回普通落地格,用于后续特殊效果定位。
* @param fixedCellCount 返回普通落地格数量。
* @param explosiveCells 返回爆破方块落地格。
* @param explosiveCellCount 返回爆破方块落地格数量。
*/
static void CollectAndWriteFixedCells(
bool& overflowTop,
Point fixedCells[],
int& fixedCellCount,
Point explosiveCells[],
int& explosiveCellCount)
{
overflowTop = false;
fixedCellCount = 0;
explosiveCellCount = 0;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (bricks[type][state][i][j] == 0)
{
continue;
}
int fixY = point.y + i;
int fixX = point.x + j;
// 顶部溢出只记录状态,真正的复活或结束逻辑在后续统一处理。
if (fixY < 0)
{
overflowTop = true;
}
if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth)
{
workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j];
if (fixedCellCount < 4)
{
fixedCells[fixedCellCount].x = fixX;
fixedCells[fixedCellCount].y = fixY;
fixedCellCount++;
}
if (currentPieceIsExplosive && explosiveCellCount < 4)
{
explosiveCells[explosiveCellCount].x = fixX;
explosiveCells[explosiveCellCount].y = fixY;
explosiveCellCount++;
}
}
}
}
}
/**
* @brief 处理方块固定时的顶部溢出、终末清场和最后一搏。
* @param overflowTop 是否出现顶部溢出。
* @return 溢出已被处理且游戏可以继续时返回 true;需要结束游戏时返回 false。
*/
static bool ResolveFixingOverflow(bool overflowTop)
{
if (!overflowTop)
{
return true;
}
if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0)
{
rogueStats.lastChanceCount--;
rogueStats.screenBombCount--;
int clearedByTerminal = TriggerScreenBomb();
rogueStats.feverTicks = 10;
currentFallInterval = GetRogueFallInterval();
TCHAR terminalDetail[128];
_stprintf_s(
terminalDetail,
_T("终末清场启动,清除 %d 格,并进入 10 秒狂热。"),
clearedByTerminal);
SetFeedbackMessage(_T("终末清场"), terminalDetail, 14);
return true;
}
if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0)
{
rogueStats.lastChanceCount--;
for (int i = 0; i < 3; i++)
{
DeleteOneLine(GetRoguePlayableHeight() - 1);
}
SetFeedbackMessage(
_T("最后一搏"),
_T("底部 3 行被清除,战局得以延续。"),
14);
return true;
}
gameOverFlag = true;
return false;
}
/**
* @brief 生成下一枚活动方块,并刷新 Hold、特殊方块和预测落点状态。
*/
static void SpawnNextFallingPiece()
{
// 消耗预览队列后重置本回合状态,确保 Hold 和特殊标记只影响新方块。
type = ConsumeNextType();
nType = nextTypes[0];
state = 0;
holdUsedThisTurn = false;
RollCurrentPieceSpecialFlags(true);
point = GetSpawnPoint(type);
target = point;
ComputeTarget();
}
/**
* @brief 将当前活动方块固定到工作区,并生成下一个活动方块。
*
@@ -400,81 +523,13 @@ void Fixing()
int explosiveCellCount = 0;
pendingChainBombFollowup = false;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (bricks[type][state][i][j] != 0)
{
int fixY = point.y + i;
int fixX = point.x + j;
// 只要当前方块任意非空单元仍超出顶部,就标记为结束
if (fixY < 0)
{
overflowTop = true;
}
// 将当前方块在可视区域内的部分写入工作区
if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth)
{
workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j];
if (fixedCellCount < 4)
{
fixedCells[fixedCellCount].x = fixX;
fixedCells[fixedCellCount].y = fixY;
fixedCellCount++;
}
if (currentPieceIsExplosive && explosiveCellCount < 4)
{
explosiveCells[explosiveCellCount].x = fixX;
explosiveCells[explosiveCellCount].y = fixY;
explosiveCellCount++;
}
}
}
}
}
CollectAndWriteFixedCells(overflowTop, fixedCells, fixedCellCount, explosiveCells, explosiveCellCount);
ApplyRainbowLandingEffect(overflowTop, fixedCells, fixedCellCount);
if (overflowTop)
if (!ResolveFixingOverflow(overflowTop))
{
if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0)
{
rogueStats.lastChanceCount--;
rogueStats.screenBombCount--;
int clearedByTerminal = TriggerScreenBomb();
rogueStats.feverTicks = 10;
currentFallInterval = GetRogueFallInterval();
TCHAR terminalDetail[128];
_stprintf_s(
terminalDetail,
_T("终末清场启动,清除 %d 格,并进入 10 秒狂热。"),
clearedByTerminal);
SetFeedbackMessage(_T("终末清场"), terminalDetail, 14);
}
else if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0)
{
rogueStats.lastChanceCount--;
for (int i = 0; i < 3; i++)
{
DeleteOneLine(GetRoguePlayableHeight() - 1);
}
SetFeedbackMessage(
_T("最后一搏"),
_T("底部 3 行被清除,战局得以延续。"),
14);
}
else
{
gameOverFlag = true;
return;
}
return;
}
ApplySpecialLandingEffects(fixedCells, fixedCellCount, explosiveCells, explosiveCellCount);
@@ -484,15 +539,7 @@ void Fixing()
currentFallInterval = GetRogueFallInterval();
}
// 生成下一个活动方块
type = ConsumeNextType();
nType = nextTypes[0];
state = 0;
holdUsedThisTurn = false;
RollCurrentPieceSpecialFlags(true);
point = GetSpawnPoint(type);
target = point;
ComputeTarget();
SpawnNextFallingPiece();
}
/**
@@ -521,20 +568,15 @@ void DeleteOneLine(int number)
}
/**
* @brief 检查并删除所有已满的行,同时更新当前得分
*
* 该函数会从底部向上遍历工作区,判断每一行是否被完全填满
* 如果某一行全部非 0,则调用 DeleteOneLine 删除该行,
* 并将该行上方的内容整体下移。为了避免连续满行被漏检,
* 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。
*
* @return 本次实际消除的行数。
* @brief 从底向上扫描满行并删除,记录本次消除的原始行号
* @param clearedRows 返回最多 8 个被消除行号,用于播放消行动画。
* @param clearedRowCount 返回记录的行号数量
* @return 本次标准消行数量。
*/
int DeleteLines()
static int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount)
{
int clearedLines = 0;
int clearedRows[8] = {};
int clearedRowCount = 0;
clearedRowCount = 0;
int playableHeight = GetRoguePlayableHeight();
for (int i = playableHeight - 1; i >= 0; i--)
@@ -564,8 +606,17 @@ int DeleteLines()
}
}
// 消行数量先进入玩法结算,再根据是否正在升级决定动画立即播放还是暂存。
ApplyLineClearResult(clearedLines);
return clearedLines;
}
/**
* @brief 根据当前界面状态立即播放或暂存消行动画。
* @param clearedRows 已消除行号数组。
* @param clearedRowCount 行号数量。
* @param clearedLines 本次消行数量。
*/
static void DispatchLineClearEffect(const int clearedRows[], int clearedRowCount, int clearedLines)
{
if (currentScreen == SCREEN_UPGRADE)
{
QueueLineClearEffect(clearedRows, clearedRowCount, clearedLines);
@@ -574,55 +625,84 @@ int DeleteLines()
{
TriggerLineClearEffect(clearedRows, clearedRowCount, clearedLines);
}
}
// 连环炸弹的追加爆破只在爆破方块导致后续消行时触发一次。
if (pendingChainBombFollowup && clearedLines > 0)
/**
* @brief 处理连环炸弹因消行触发的一次追加 3x3 爆破。
* @param clearedLines 本次标准消行数量。
*/
static void ResolveChainBombFollowup(int clearedLines)
{
if (!pendingChainBombFollowup || clearedLines <= 0)
{
pendingChainBombFollowup = false;
return;
}
int followupCleared = 0;
int centerY = pendingChainBombCenter.y;
int centerX = pendingChainBombCenter.x;
Point followupCells[9] = {};
pendingChainBombFollowup = false;
for (int y = centerY - 1; y <= centerY + 1; y++)
int followupCleared = 0;
int centerY = pendingChainBombCenter.y;
int centerX = pendingChainBombCenter.x;
Point followupCells[9] = {};
for (int y = centerY - 1; y <= centerY + 1; y++)
{
for (int x = centerX - 1; x <= centerX + 1; x++)
{
for (int x = centerX - 1; x <= centerX + 1; x++)
if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0)
{
if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0)
if (followupCleared < 9)
{
if (followupCleared < 9)
{
followupCells[followupCleared].x = x;
followupCells[followupCleared].y = y;
}
workRegion[y][x] = 0;
followupCleared++;
followupCells[followupCleared].x = x;
followupCells[followupCleared].y = y;
}
workRegion[y][x] = 0;
followupCleared++;
}
}
if (currentMode == MODE_ROGUE && followupCleared > 0)
{
TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true);
int followupScore = 0;
int followupExp = 0;
AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false);
TCHAR followupDetail[128];
_stprintf_s(
followupDetail,
_T("追加爆炸清除 %d 格 +%d 分 +%d EXP"),
followupCleared,
followupScore,
followupExp);
SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12);
}
}
else
if (currentMode == MODE_ROGUE && followupCleared > 0)
{
pendingChainBombFollowup = false;
TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true);
int followupScore = 0;
int followupExp = 0;
AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false);
TCHAR followupDetail[128];
_stprintf_s(
followupDetail,
_T("追加爆炸清除 %d 格 +%d 分 +%d EXP"),
followupCleared,
followupScore,
followupExp);
SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12);
}
}
/**
* @brief 检查并删除所有已满的行,同时更新当前得分。
*
* 该函数会从底部向上遍历工作区,判断每一行是否被完全填满。
* 如果某一行全部非 0,则调用 DeleteOneLine 删除该行,
* 并将该行上方的内容整体下移。为了避免连续满行被漏检,
* 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。
*
* @return 本次实际消除的行数。
*/
int DeleteLines()
{
int clearedRows[8] = {};
int clearedRowCount = 0;
int clearedLines = ScanAndDeleteFullLines(clearedRows, clearedRowCount);
// 消行数量先进入玩法结算,再根据是否正在升级决定动画立即播放还是暂存。
ApplyLineClearResult(clearedLines);
DispatchLineClearEffect(clearedRows, clearedRowCount, clearedLines);
// 连环炸弹的追加爆破只在爆破方块导致后续消行时触发一次。
ResolveChainBombFollowup(clearedLines);
return clearedLines;
}
+7 -5
View File
@@ -646,7 +646,7 @@ void TDrawScreen(HDC hdc, HWND hWnd)
{
_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("卸压清场:获得时立刻清除最高的一条占用行,直接降低顶部压力。\r\n底线清道夫:通过消行充能自动清底;每级降低需求,最多 4 级。\r\n清屏炸弹:一次性解锁,之后通过消行充能;满 16 行获得 1 枚,按 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 级。"),
@@ -844,20 +844,22 @@ void TDrawScreen(HDC hdc, HWND hWnd)
else if (helpState.currentPage == 4)
{
const int creditAnimationTotalTicks = 60;
constexpr int creditPageCount = 4;
constexpr int creditPageCount = 5;
const TCHAR* creditNames[creditPageCount] =
{
_T("qls"),
_T("wyk"),
_T("juju"),
_T("qhy")
_T("swj"),
_T("qhy"),
_T("syc")
};
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")
_T("\u611f\u8c22qhy\u7684\u5929\u624d\u6784\u60f3"),
_T("\u611f\u8c22syc\u63fd\u4e0b\u6240\u6709\u6742\u6d3b")
};
int currentCredit = creditPageIndex;
+305 -178
View File
@@ -417,6 +417,88 @@ static bool HandleMenuKey(HWND hWnd, WPARAM key)
return true;
}
/**
* @brief
* @param direction
*/
static void MoveHelpHomeSelection(int direction)
{
helpState.selectedIndex += direction;
if (helpState.selectedIndex < 0)
{
helpState.selectedIndex = helpState.optionCount - 1;
}
if (helpState.selectedIndex >= helpState.optionCount)
{
helpState.selectedIndex = 0;
}
}
/**
* @brief
* @param direction
*/
static void MoveSkillDemoSelection(int direction)
{
helpState.selectedIndex += direction;
if (helpState.selectedIndex < 0)
{
helpState.selectedIndex = GetRogueSkillDemoCount() - 1;
}
if (helpState.selectedIndex >= GetRogueSkillDemoCount())
{
helpState.selectedIndex = 0;
helpScrollOffset = 0;
}
else if (direction < 0 && helpState.selectedIndex * 68 < helpScrollOffset)
{
helpScrollOffset = helpState.selectedIndex * 68;
}
else if (direction > 0 && helpState.selectedIndex * 68 > helpScrollOffset + 360)
{
helpScrollOffset = helpState.selectedIndex * 68 - 360;
}
}
/**
* @brief
*/
static void ActivateHelpSelection()
{
if (helpState.selectedIndex == 3)
{
helpState.currentPage = 5;
helpState.selectedIndex = 0;
helpScrollOffset = 0;
}
else
{
helpState.currentPage = helpState.selectedIndex + 1;
helpScrollOffset = 0;
}
}
/**
* @brief
*/
static void LeaveRulesPage()
{
int previousPage = helpState.currentPage;
if (helpState.currentPage == 0)
{
ReturnToMainMenu();
}
else
{
helpState.currentPage = 0;
if (previousPage == 4 || previousPage == 5)
{
helpState.selectedIndex = 3;
}
helpScrollOffset = 0;
}
}
/**
* @brief
* @param hWnd
@@ -438,11 +520,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key)
case 'A':
if (helpState.currentPage == 0)
{
helpState.selectedIndex--;
if (helpState.selectedIndex < 0)
{
helpState.selectedIndex = helpState.optionCount - 1;
}
MoveHelpHomeSelection(-1);
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 4)
@@ -452,15 +530,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key)
}
else if (helpState.currentPage == 5)
{
helpState.selectedIndex--;
if (helpState.selectedIndex < 0)
{
helpState.selectedIndex = GetRogueSkillDemoCount() - 1;
}
if (helpState.selectedIndex * 68 < helpScrollOffset)
{
helpScrollOffset = helpState.selectedIndex * 68;
}
MoveSkillDemoSelection(-1);
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
@@ -470,11 +540,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key)
case 'D':
if (helpState.currentPage == 0)
{
helpState.selectedIndex++;
if (helpState.selectedIndex >= helpState.optionCount)
{
helpState.selectedIndex = 0;
}
MoveHelpHomeSelection(1);
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 4)
@@ -484,16 +550,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key)
}
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;
}
MoveSkillDemoSelection(1);
InvalidateRect(hWnd, nullptr, FALSE);
}
break;
@@ -501,17 +558,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key)
case VK_SPACE:
if (helpState.currentPage == 0)
{
if (helpState.selectedIndex == 3)
{
helpState.currentPage = 5;
helpState.selectedIndex = 0;
helpScrollOffset = 0;
}
else
{
helpState.currentPage = helpState.selectedIndex + 1;
helpScrollOffset = 0;
}
ActivateHelpSelection();
InvalidateRect(hWnd, nullptr, FALSE);
}
else if (helpState.currentPage == 5)
@@ -524,24 +571,9 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key)
case VK_ESCAPE:
case VK_BACK:
case 'M':
{
int previousPage = helpState.currentPage;
if (helpState.currentPage == 0)
{
ReturnToMainMenu();
}
else
{
helpState.currentPage = 0;
if (previousPage == 4 || previousPage == 5)
{
helpState.selectedIndex = 3;
}
helpScrollOffset = 0;
}
LeaveRulesPage();
InvalidateRect(hWnd, nullptr, FALSE);
break;
}
default:
break;
}
@@ -549,6 +581,92 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key)
return true;
}
/**
* @brief
* @return 使 1
*/
static int GetUpgradeColumnCount()
{
int upgradeColumnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3;
if (upgradeColumnCount < 1)
{
upgradeColumnCount = 1;
}
return upgradeColumnCount;
}
/**
* @brief
* @param direction
*/
static void MoveUpgradeSelectionHorizontal(int direction)
{
if (upgradeUiState.optionCount <= 1)
{
return;
}
int upgradeColumnCount = GetUpgradeColumnCount();
int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount);
int rowEnd = rowStart + upgradeColumnCount - 1;
if (rowEnd >= upgradeUiState.optionCount)
{
rowEnd = upgradeUiState.optionCount - 1;
}
if (direction < 0)
{
upgradeUiState.selectedIndex = (upgradeUiState.selectedIndex > rowStart) ? upgradeUiState.selectedIndex - 1 : rowEnd;
}
else
{
upgradeUiState.selectedIndex = (upgradeUiState.selectedIndex < rowEnd) ? upgradeUiState.selectedIndex + 1 : rowStart;
}
}
/**
* @brief
* @param direction
*/
static void MoveUpgradeSelectionVertical(int direction)
{
int upgradeColumnCount = GetUpgradeColumnCount();
if (direction < 0 && upgradeUiState.selectedIndex >= upgradeColumnCount)
{
upgradeUiState.selectedIndex -= upgradeColumnCount;
}
else if (direction > 0 && upgradeUiState.selectedIndex + upgradeColumnCount < upgradeUiState.optionCount)
{
upgradeUiState.selectedIndex += upgradeColumnCount;
}
}
/**
* @brief
*/
static void ToggleUpgradeMarkedSelection()
{
if (upgradeUiState.picksRemaining <= 1 || upgradeUiState.optionCount <= 0)
{
return;
}
bool currentlyMarked = upgradeUiState.marked[upgradeUiState.selectedIndex];
if (currentlyMarked)
{
upgradeUiState.marked[upgradeUiState.selectedIndex] = false;
if (upgradeUiState.markedCount > 0)
{
upgradeUiState.markedCount--;
}
}
else if (upgradeUiState.markedCount < upgradeUiState.picksRemaining)
{
upgradeUiState.marked[upgradeUiState.selectedIndex] = true;
upgradeUiState.markedCount++;
}
}
/**
* @brief
* @param hWnd
@@ -562,64 +680,26 @@ static bool HandleUpgradeKey(HWND hWnd, WPARAM key)
return false;
}
int upgradeColumnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3;
if (upgradeColumnCount < 1)
{
upgradeColumnCount = 1;
}
switch (key)
{
case VK_LEFT:
case 'A':
if (upgradeUiState.optionCount > 1)
{
int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount);
if (upgradeUiState.selectedIndex > rowStart)
{
upgradeUiState.selectedIndex--;
}
else
{
int rowEnd = rowStart + upgradeColumnCount - 1;
if (rowEnd >= upgradeUiState.optionCount)
{
rowEnd = upgradeUiState.optionCount - 1;
}
upgradeUiState.selectedIndex = rowEnd;
}
}
MoveUpgradeSelectionHorizontal(-1);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_RIGHT:
case 'D':
if (upgradeUiState.optionCount > 1)
{
int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount);
int rowEnd = rowStart + upgradeColumnCount - 1;
if (rowEnd >= upgradeUiState.optionCount)
{
rowEnd = upgradeUiState.optionCount - 1;
}
upgradeUiState.selectedIndex = (upgradeUiState.selectedIndex < rowEnd) ? upgradeUiState.selectedIndex + 1 : rowStart;
}
MoveUpgradeSelectionHorizontal(1);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_UP:
case 'W':
if (upgradeUiState.selectedIndex >= upgradeColumnCount)
{
upgradeUiState.selectedIndex -= upgradeColumnCount;
}
MoveUpgradeSelectionVertical(-1);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_DOWN:
case 'S':
if (upgradeUiState.selectedIndex + upgradeColumnCount < upgradeUiState.optionCount)
{
upgradeUiState.selectedIndex += upgradeColumnCount;
}
MoveUpgradeSelectionVertical(1);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case VK_RETURN:
@@ -630,20 +710,7 @@ static bool HandleUpgradeKey(HWND hWnd, WPARAM key)
case VK_SPACE:
if (upgradeUiState.picksRemaining > 1 && upgradeUiState.optionCount > 0)
{
bool currentlyMarked = upgradeUiState.marked[upgradeUiState.selectedIndex];
if (currentlyMarked)
{
upgradeUiState.marked[upgradeUiState.selectedIndex] = false;
if (upgradeUiState.markedCount > 0)
{
upgradeUiState.markedCount--;
}
}
else if (upgradeUiState.markedCount < upgradeUiState.picksRemaining)
{
upgradeUiState.marked[upgradeUiState.selectedIndex] = true;
upgradeUiState.markedCount++;
}
ToggleUpgradeMarkedSelection();
InvalidateRect(hWnd, nullptr, FALSE);
}
else
@@ -665,40 +732,54 @@ static bool HandleUpgradeKey(HWND hWnd, WPARAM key)
}
/**
* @brief
* @brief Rogue
* @param hWnd
* @param key
* @return true
*/
static void HandlePlayingKey(HWND hWnd, WPARAM key)
static bool HandleDemoPlayingKey(HWND hWnd, WPARAM key)
{
if (IsRogueSkillDemoMode())
if (!IsRogueSkillDemoMode())
{
if (key == 'N')
{
AdvanceRogueSkillDemo();
InvalidateRect(hWnd, nullptr, FALSE);
return;
}
if (key == 'R')
{
RestartCurrentRogueSkillDemo();
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
return;
}
if (key == VK_ESCAPE || key == VK_BACK || key == 'M')
{
OpenSkillDemoScreen();
InvalidateRect(hWnd, nullptr, FALSE);
return;
}
return false;
}
if (key == 'N')
{
AdvanceRogueSkillDemo();
InvalidateRect(hWnd, nullptr, FALSE);
return true;
}
if (key == 'R')
{
RestartCurrentRogueSkillDemo();
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
return true;
}
if (key == VK_ESCAPE || key == VK_BACK || key == 'M')
{
OpenSkillDemoScreen();
InvalidateRect(hWnd, nullptr, FALSE);
return true;
}
return false;
}
/**
* @brief
* @param hWnd
* @param key
* @return true
*/
static bool HandleBattleControlKey(HWND hWnd, WPARAM key)
{
if (!IsRogueSkillDemoMode() && key == 'M')
{
ReturnToMainMenu();
InvalidateRect(hWnd, nullptr, FALSE);
return;
return true;
}
if (!IsRogueSkillDemoMode() && key == 'R')
@@ -706,21 +787,21 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key)
StartGameWithMode(currentMode);
ResetGameTimer(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
return;
return true;
}
if (!IsRogueSkillDemoMode() && key == 'P')
{
suspendFlag = !suspendFlag;
InvalidateRect(hWnd, nullptr, FALSE);
return;
return true;
}
if (key == 'G')
{
targetFlag = !targetFlag;
InvalidateRect(hWnd, nullptr, FALSE);
return;
return true;
}
if (gameOverFlag && reviveAvailable && key == 'V')
@@ -735,23 +816,70 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key)
SetFeedbackMessage(_T("视频播放失败"), _T("无法打开复活视频,复活机会未消耗。"), 14);
}
InvalidateRect(hWnd, nullptr, FALSE);
return;
return true;
}
if (gameOverFlag || suspendFlag)
{
return;
}
return false;
}
/**
* @brief Rogue
* @param hWnd
* @param key
* @return true
*/
static bool HandleRogueSkillKey(HWND hWnd, WPARAM key)
{
if (currentMode == MODE_ROGUE && (key == 'J' || key == 'K'))
{
int direction = (key == 'J') ? 1 : -1;
AdjustScrollOffset(upgradeListScrollOffset, direction * GetScrollStep(hWnd, 52));
InvalidateRect(hWnd, nullptr, FALSE);
return;
return true;
}
// 正常游玩按键先改变方块或触发技能,再统一刷新预测落点和界面。
switch (key)
{
case 'C':
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
HoldCurrentPiece();
return true;
case 'Z':
UseBlackHole();
return true;
case 'X':
UseScreenBomb();
return true;
case 'V':
UseAirReshape();
return true;
default:
return false;
}
}
/**
* @brief Rogue
*/
static void FixPieceAndResolveLines()
{
Fixing();
if (!gameOverFlag)
{
DeleteLines();
CheckRogueLevelProgress();
}
}
/**
* @brief
* @param key
* @return true
*/
static bool HandlePieceMovementKey(WPARAM key)
{
switch (key)
{
case VK_LEFT:
@@ -760,14 +888,14 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key)
{
MoveLeft();
}
break;
return true;
case VK_RIGHT:
case 'D':
if (CanMoveRight())
{
MoveRight();
}
break;
return true;
case VK_DOWN:
case 'S':
if (CanMoveDown())
@@ -776,44 +904,43 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key)
}
else
{
Fixing();
if (!gameOverFlag)
{
DeleteLines();
CheckRogueLevelProgress();
}
FixPieceAndResolveLines();
}
break;
return true;
case VK_UP:
case 'W':
Rotate();
break;
return true;
case VK_SPACE:
DropDown();
Fixing();
if (!gameOverFlag)
{
DeleteLines();
CheckRogueLevelProgress();
}
break;
case 'C':
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
HoldCurrentPiece();
break;
case 'Z':
UseBlackHole();
break;
case 'X':
UseScreenBomb();
break;
case 'V':
UseAirReshape();
break;
FixPieceAndResolveLines();
return true;
default:
break;
return false;
}
}
/**
* @brief
* @param hWnd
* @param key
*/
static void HandlePlayingKey(HWND hWnd, WPARAM key)
{
if (HandleDemoPlayingKey(hWnd, key) || HandleBattleControlKey(hWnd, key))
{
return;
}
if (gameOverFlag || suspendFlag)
{
return;
}
// 正常游玩按键先改变方块或触发技能,再统一刷新预测落点和界面。
if (!HandlePieceMovementKey(key))
{
HandleRogueSkillKey(hWnd, key);
}
if (!gameOverFlag)
@@ -726,7 +726,7 @@ void OpenCreditScreen()
*/
void ChangeCreditPage(int direction)
{
constexpr int creditPageCount = 4;
constexpr int creditPageCount = 5;
if (direction == 0)
{
return;
+3 -2
View File
@@ -101,7 +101,7 @@ Bitmap* LoadBackgroundImage()
*/
Bitmap* LoadCreditImage(int index)
{
constexpr int creditPageCount = 4;
constexpr int creditPageCount = 5;
static Bitmap* creditImages[creditPageCount] = {};
static bool attempted[creditPageCount] = {};
@@ -121,7 +121,8 @@ Bitmap* LoadCreditImage(int index)
L"assets\\images\\qls.jpg",
L"assets\\images\\wyk.jpg",
L"assets\\images\\swj.jpg",
L"assets\\images\\qhy.jpg"
L"assets\\images\\qhy.jpg",
L"assets\\images\\syc.jpg"
};
const std::wstring creditExtraCandidates[] =
{
+459 -351
View File
@@ -78,7 +78,7 @@ static const UpgradeEntry kUpgradePool[] =
{ UPGRADE_FEVER_MODE, 1, 92, false, _T("狂热节拍"), _T("进阶"), _T("累计消行 12 行后进入 12 秒狂热:得分与 EXP 翻倍。") },
{ UPGRADE_RAGE_STACK, 1, 84, false, _T("怒火连段"), _T("进阶"), _T("连续消行越多,得分倍率追加越高。") },
{ UPGRADE_INFINITE_FEVER, 1, 110, false, _T("无尽狂热"), _T("进化"), _T("狂热期间消行可延长狂热时间;连击越高,倍率越凶。") },
{ UPGRADE_SCREEN_BOMB, -1, 90, true, _T("清屏炸弹"), _T("进阶"), _T("立刻获得 1 枚炸弹;可重复选择补充数量,之后累计消行 16 行获得 1 枚。按 X 清底 5 行。") },
{ UPGRADE_SCREEN_BOMB, 1, 90, false, _T("清屏炸弹"), _T("进阶"), _T("解锁清屏炸弹;之后累计消行 16 行获得 1 枚。按 X 清底 5 行。") },
{ UPGRADE_TERMINAL_CLEAR, 1, 108, false, _T("终末清场"), _T("进化"), _T("最后一搏启动时,自动引爆 1 枚清屏炸弹,并进入 10 秒狂热。") },
{ UPGRADE_DUAL_CHOICE, 1, 68, false, _T("双重抉择"), _T("进阶"), _T("每次升级可额外选择 1 个强化,但下一次升级所需 EXP +30%。") },
{ UPGRADE_DESTINY_WHEEL, 1, 104, false, _T("命运轮盘"), _T("进化"), _T("升级时出现 6 个选项,可选择 2 个;其中 1 个会携带诅咒。") },
@@ -581,11 +581,6 @@ static int GetUpgradeDynamicWeight(const UpgradeEntry& entry)
weight -= 20;
}
if (entry.id == UPGRADE_SCREEN_BOMB && rogueStats.screenBombCount > 0)
{
weight += 20;
}
if (entry.id == UPGRADE_BLACK_HOLE && rogueStats.blackHoleCharges > 0)
{
weight += 20;
@@ -1555,18 +1550,15 @@ static int TriggerUpgradeShockwave(int rowsToClear)
}
/**
* @brief
*
* UI
*
* @brief
* @param selectableIndexes
* @param selectableWeights
* @return
*/
static void FillUpgradeOptions()
static int CollectSelectableUpgrades(int selectableIndexes[], int selectableWeights[])
{
int selectableIndexes[kUpgradePoolSize] = { 0 };
int selectableWeights[kUpgradePoolSize] = { 0 };
int selectableCount = 0;
// 第一段:筛出当前真正可选的强化,并记录它们的动态权重。
for (int i = 0; i < kUpgradePoolSize; i++)
{
if (IsUpgradeSelectable(kUpgradePool[i]))
@@ -1577,7 +1569,16 @@ static void FillUpgradeOptions()
}
}
// 命运轮盘把候选扩到 6 个;双重抉择和命运轮盘都会允许本轮选 2 个。
return selectableCount;
}
/**
* @brief
* @param selectableCount
* @return
*/
static int PrepareUpgradeOptionState(int selectableCount)
{
int optionLimit = (rogueStats.destinyWheelLevel > 0) ? 6 : 3;
int optionCount = selectableCount < optionLimit ? selectableCount : optionLimit;
upgradeUiState.optionCount = optionCount;
@@ -1589,71 +1590,114 @@ static void FillUpgradeOptions()
upgradeUiState.marked[i] = false;
}
return optionCount;
}
/**
* @brief
* @param selectableWeights
* @param selectableCount
* @return
*/
static int PickUpgradeSlotByWeight(const int selectableWeights[], int selectableCount)
{
int totalWeight = 0;
for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++)
{
totalWeight += selectableWeights[weightIndex];
}
int pickSlot = 0;
if (totalWeight > 0)
{
int roll = rand() % totalWeight;
int accumulatedWeight = 0;
for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++)
{
accumulatedWeight += selectableWeights[weightIndex];
if (roll < accumulatedWeight)
{
pickSlot = weightIndex;
break;
}
}
}
return pickSlot;
}
/**
* @brief UI
* @param optionIndex UI
* @param pickedEntry
*/
static void FillUpgradeOptionFromEntry(int optionIndex, const UpgradeEntry& pickedEntry)
{
upgradeUiState.options[optionIndex].id = pickedEntry.id;
upgradeUiState.options[optionIndex].currentLevel = GetUpgradeCurrentLevel(pickedEntry.id);
upgradeUiState.options[optionIndex].targetPieceType = -1;
upgradeUiState.options[optionIndex].rarity = GetUpgradeBaseRarity(pickedEntry.id);
upgradeUiState.options[optionIndex].cursed = false;
upgradeUiState.options[optionIndex].name = pickedEntry.name;
upgradeUiState.options[optionIndex].category = pickedEntry.category;
upgradeUiState.options[optionIndex].description = pickedEntry.description;
if (pickedEntry.id == UPGRADE_PIECE_TUNING)
{
int targetPieceType = 0;
upgradeUiState.options[optionIndex].targetPieceType = targetPieceType;
upgradeUiState.options[optionIndex].currentLevel = rogueStats.pieceTuningLevels[0];
upgradeUiState.options[optionIndex].name = _T("方块改造");
static TCHAR tuningDescriptions[6][64];
_stprintf_s(
tuningDescriptions[optionIndex],
_T("%s 块的生成概率提高。"),
GetPieceShortName(0));
upgradeUiState.options[optionIndex].description = tuningDescriptions[optionIndex];
}
}
/**
* @brief
* @param optionCount
*/
static void MarkDestinyCursedOption(int optionCount)
{
if (rogueStats.destinyWheelLevel > 0 && optionCount > 0)
{
int cursedIndex = rand() % optionCount;
upgradeUiState.options[cursedIndex].cursed = true;
}
}
/**
* @brief
*
* UI
*
*/
static void FillUpgradeOptions()
{
int selectableIndexes[kUpgradePoolSize] = { 0 };
int selectableWeights[kUpgradePoolSize] = { 0 };
int selectableCount = CollectSelectableUpgrades(selectableIndexes, selectableWeights);
int optionCount = PrepareUpgradeOptionState(selectableCount);
// 第二段:按权重不放回抽取候选,抽中后用末尾元素覆盖当前槽位。
for (int i = 0; i < optionCount; i++)
{
int totalWeight = 0;
for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++)
{
totalWeight += selectableWeights[weightIndex];
}
int pickSlot = 0;
if (totalWeight > 0)
{
int roll = rand() % totalWeight;
int accumulatedWeight = 0;
for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++)
{
accumulatedWeight += selectableWeights[weightIndex];
if (roll < accumulatedWeight)
{
pickSlot = weightIndex;
break;
}
}
}
int pickSlot = PickUpgradeSlotByWeight(selectableWeights, selectableCount);
int pickedIndex = selectableIndexes[pickSlot];
const UpgradeEntry& pickedEntry = kUpgradePool[pickedIndex];
upgradeUiState.options[i].id = pickedEntry.id;
upgradeUiState.options[i].currentLevel = GetUpgradeCurrentLevel(pickedEntry.id);
upgradeUiState.options[i].targetPieceType = -1;
upgradeUiState.options[i].rarity = GetUpgradeBaseRarity(pickedEntry.id);
upgradeUiState.options[i].cursed = false;
upgradeUiState.options[i].name = pickedEntry.name;
upgradeUiState.options[i].category = pickedEntry.category;
upgradeUiState.options[i].description = pickedEntry.description;
// 方块改造目前固定展示 I 块概率提升,说明文字需要运行时拼接。
if (pickedEntry.id == UPGRADE_PIECE_TUNING)
{
int targetPieceType = 0;
upgradeUiState.options[i].targetPieceType = targetPieceType;
upgradeUiState.options[i].currentLevel = rogueStats.pieceTuningLevels[0];
upgradeUiState.options[i].name = _T("方块改造");
static TCHAR tuningDescriptions[6][64];
_stprintf_s(
tuningDescriptions[i],
_T("%s 块的生成概率提高。"),
GetPieceShortName(0));
upgradeUiState.options[i].description = tuningDescriptions[i];
}
FillUpgradeOptionFromEntry(i, kUpgradePool[pickedIndex]);
selectableIndexes[pickSlot] = selectableIndexes[selectableCount - 1];
selectableWeights[pickSlot] = selectableWeights[selectableCount - 1];
selectableCount--;
}
// 命运轮盘在候选中随机附加一个诅咒,确认时才提高下一次升级需求。
if (rogueStats.destinyWheelLevel > 0 && optionCount > 0)
{
int cursedIndex = rand() % optionCount;
upgradeUiState.options[cursedIndex].cursed = true;
}
MarkDestinyCursedOption(optionCount);
}
/**
@@ -1708,6 +1752,240 @@ int GetRogueFallInterval()
return baseInterval;
}
/**
* @brief
* @param upgradeId
* @param applyCount
* @return true
*/
static bool ApplyGrowthOrOperationUpgrade(int upgradeId, int applyCount)
{
switch (upgradeId)
{
case UPGRADE_SCORE_MULTIPLIER:
rogueStats.scoreMultiplierPercent += 20 * applyCount;
rogueStats.scoreUpgradeLevel += applyCount;
return true;
case UPGRADE_COMBO_BONUS:
rogueStats.comboBonusStacks += applyCount;
return true;
case UPGRADE_EXP_MULTIPLIER:
rogueStats.expMultiplierPercent += 25 * applyCount;
rogueStats.expUpgradeLevel += applyCount;
return true;
case UPGRADE_SLOW_FALL:
rogueStats.slowFallStacks += applyCount;
if (rogueStats.slowFallStacks > 4)
{
rogueStats.slowFallStacks = 4;
}
currentFallInterval = GetRogueFallInterval();
return true;
case UPGRADE_PREVIEW_PLUS_ONE:
if (rogueStats.previewCount < 2)
{
rogueStats.previewCount = 2;
}
rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1;
return true;
case UPGRADE_LAST_CHANCE:
rogueStats.lastChanceCount += applyCount;
rogueStats.lastChanceUpgradeLevel += applyCount;
return true;
case UPGRADE_HOLD_UNLOCK:
rogueStats.holdUnlocked = 1;
holdUsedThisTurn = false;
return true;
case UPGRADE_PERFECT_ROTATE:
rogueStats.perfectRotateLevel = 1;
return true;
case UPGRADE_TIME_DILATION:
rogueStats.timeDilationLevel = 1;
currentFallInterval = GetRogueFallInterval();
return true;
case UPGRADE_CONTROL_MASTER:
rogueStats.controlMasterLevel = 1;
if (rogueStats.previewCount < 3)
{
rogueStats.previewCount++;
}
rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1;
return true;
case UPGRADE_DOUBLE_GROWTH:
rogueStats.doubleGrowthLevel = 1;
rogueStats.scoreMultiplierPercent += 15;
rogueStats.expMultiplierPercent += 15;
return true;
default:
return false;
}
}
/**
* @brief
* @param upgradeId
* @param applyCount
* @return true
*/
static bool ApplySpecialPieceUpgrade(int upgradeId, int applyCount)
{
switch (upgradeId)
{
case UPGRADE_PRESSURE_RELIEF:
rogueStats.pressureReliefLevel += applyCount;
for (int i = 0; i < applyCount; i++)
{
int topOccupiedRow = GetTopOccupiedRow();
if (topOccupiedRow >= 0)
{
DeleteOneLine(topOccupiedRow);
}
}
return true;
case UPGRADE_SWEEPER:
rogueStats.sweeperLevel += applyCount;
if (rogueStats.sweeperLevel > 4)
{
rogueStats.sweeperLevel = 4;
}
return true;
case UPGRADE_EXPLOSIVE_PIECE:
rogueStats.explosiveLevel = 1;
return true;
case UPGRADE_CHAIN_BLAST:
rogueStats.chainBlastLevel = 1;
return true;
case UPGRADE_CHAIN_BOMB:
rogueStats.chainBombLevel = 1;
return true;
case UPGRADE_LASER_PIECE:
rogueStats.laserLevel = 1;
return true;
case UPGRADE_THUNDER_TETRIS:
rogueStats.thunderTetrisLevel = 1;
return true;
case UPGRADE_THUNDER_LASER:
rogueStats.thunderLaserLevel = 1;
return true;
case UPGRADE_CROSS_PIECE:
rogueStats.crossPieceLevel = 1;
return true;
case UPGRADE_RAINBOW_PIECE:
rogueStats.rainbowPieceLevel = 1;
return true;
case UPGRADE_STABLE_STRUCTURE:
rogueStats.stableStructureLevel += applyCount;
return true;
case UPGRADE_PIECE_TUNING:
rogueStats.pieceTuningLevels[0] += applyCount;
return true;
default:
return false;
}
}
/**
* @brief
* @param upgradeId
* @param applyCount
* @return true
*/
static bool ApplyActiveSkillUpgrade(int upgradeId, int applyCount)
{
switch (upgradeId)
{
case UPGRADE_FEVER_MODE:
rogueStats.feverLevel = 1;
return true;
case UPGRADE_RAGE_STACK:
rogueStats.rageStackLevel = 1;
return true;
case UPGRADE_INFINITE_FEVER:
rogueStats.infiniteFeverLevel = 1;
return true;
case UPGRADE_SCREEN_BOMB:
rogueStats.screenBombLevel = 1;
return true;
case UPGRADE_TERMINAL_CLEAR:
rogueStats.terminalClearLevel = 1;
return true;
case UPGRADE_BLOCK_STORM:
rogueStats.blockStormLevel = 1;
rogueStats.blockStormPiecesRemaining = 5;
nextTypes[0] = 0;
nextTypes[1] = 0;
nextTypes[2] = 0;
return true;
case UPGRADE_BLACK_HOLE:
rogueStats.blackHoleLevel = 1;
rogueStats.blackHoleCharges += 2 * applyCount;
return true;
case UPGRADE_AIR_RESHAPE:
rogueStats.reshapeLevel += applyCount;
rogueStats.reshapeCharges += 2 * applyCount;
return true;
case UPGRADE_VOID_CORE:
rogueStats.voidCoreLevel = 1;
return true;
default:
return false;
}
}
/**
* @brief
* @param upgradeId
* @param applyCount
* @return true
*/
static bool ApplyRiskOrEvolutionUpgrade(int upgradeId, int applyCount)
{
switch (upgradeId)
{
case UPGRADE_DUAL_CHOICE:
rogueStats.dualChoiceLevel = 1;
rogueStats.requiredExp = rogueStats.requiredExp * 130 / 100;
if (rogueStats.requiredExp < 10)
{
rogueStats.requiredExp = 10;
}
return true;
case UPGRADE_DESTINY_WHEEL:
rogueStats.destinyWheelLevel = 1;
return true;
case UPGRADE_HIGH_PRESSURE:
rogueStats.highPressureLevel = 1;
rogueStats.scoreMultiplierPercent += 50;
rogueStats.expMultiplierPercent += 50;
currentFallInterval = GetRogueFallInterval();
return true;
case UPGRADE_TETRIS_GAMBLE:
rogueStats.tetrisGambleLevel = 1;
return true;
case UPGRADE_EXTREME_PLAYER:
rogueStats.extremePlayerLevel = 1;
rogueStats.extremeDangerTicks = 30;
rogueStats.extremeDangerLevel = 0;
currentFallInterval = GetRogueFallInterval();
return true;
case UPGRADE_UPGRADE_SHOCKWAVE:
rogueStats.upgradeShockwaveLevel = 1;
return true;
case UPGRADE_EVOLUTION_IMPACT:
rogueStats.evolutionImpactLevel = 1;
return true;
case UPGRADE_GAMBLER:
rogueStats.gamblerLevel += applyCount;
if (rogueStats.gamblerLevel > 4)
{
rogueStats.gamblerLevel = 4;
}
return true;
default:
return false;
}
}
/**
* @brief Rogue
*
@@ -1727,191 +2005,10 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount)
return;
}
// 基础成长类强化直接叠加倍率或层数。
switch (upgradeId)
{
case UPGRADE_SCORE_MULTIPLIER:
rogueStats.scoreMultiplierPercent += 20 * applyCount;
rogueStats.scoreUpgradeLevel += applyCount;
break;
case UPGRADE_COMBO_BONUS:
rogueStats.comboBonusStacks += applyCount;
break;
case UPGRADE_EXP_MULTIPLIER:
rogueStats.expMultiplierPercent += 25 * applyCount;
rogueStats.expUpgradeLevel += applyCount;
break;
case UPGRADE_SLOW_FALL:
rogueStats.slowFallStacks += applyCount;
if (rogueStats.slowFallStacks > 4)
{
rogueStats.slowFallStacks = 4;
}
currentFallInterval = GetRogueFallInterval();
break;
case UPGRADE_PREVIEW_PLUS_ONE:
if (rogueStats.previewCount < 2)
{
rogueStats.previewCount = 2;
}
rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1;
break;
case UPGRADE_LAST_CHANCE:
rogueStats.lastChanceCount += applyCount;
rogueStats.lastChanceUpgradeLevel += applyCount;
break;
case UPGRADE_HOLD_UNLOCK:
rogueStats.holdUnlocked = 1;
holdUsedThisTurn = false;
break;
case UPGRADE_PRESSURE_RELIEF:
{
// 卸压清场立即从最高占用行开始删除,直接改善当前局面。
rogueStats.pressureReliefLevel += applyCount;
for (int i = 0; i < applyCount; i++)
{
int topOccupiedRow = GetTopOccupiedRow();
if (topOccupiedRow >= 0)
{
DeleteOneLine(topOccupiedRow);
}
}
break;
}
case UPGRADE_SWEEPER:
rogueStats.sweeperLevel += applyCount;
if (rogueStats.sweeperLevel > 4)
{
rogueStats.sweeperLevel = 4;
}
break;
case UPGRADE_EXPLOSIVE_PIECE:
rogueStats.explosiveLevel = 1;
break;
case UPGRADE_CHAIN_BLAST:
rogueStats.chainBlastLevel = 1;
break;
case UPGRADE_CHAIN_BOMB:
rogueStats.chainBombLevel = 1;
break;
case UPGRADE_LASER_PIECE:
rogueStats.laserLevel = 1;
break;
case UPGRADE_THUNDER_TETRIS:
rogueStats.thunderTetrisLevel = 1;
break;
case UPGRADE_THUNDER_LASER:
rogueStats.thunderLaserLevel = 1;
break;
case UPGRADE_FEVER_MODE:
rogueStats.feverLevel = 1;
break;
case UPGRADE_RAGE_STACK:
rogueStats.rageStackLevel = 1;
break;
case UPGRADE_INFINITE_FEVER:
rogueStats.infiniteFeverLevel = 1;
break;
case UPGRADE_SCREEN_BOMB:
rogueStats.screenBombLevel += applyCount;
rogueStats.screenBombCount += applyCount;
break;
case UPGRADE_TERMINAL_CLEAR:
rogueStats.terminalClearLevel = 1;
break;
case UPGRADE_DUAL_CHOICE:
rogueStats.dualChoiceLevel = 1;
rogueStats.requiredExp = rogueStats.requiredExp * 130 / 100;
if (rogueStats.requiredExp < 10)
{
rogueStats.requiredExp = 10;
}
break;
case UPGRADE_DESTINY_WHEEL:
rogueStats.destinyWheelLevel = 1;
break;
case UPGRADE_PERFECT_ROTATE:
rogueStats.perfectRotateLevel = 1;
break;
case UPGRADE_TIME_DILATION:
rogueStats.timeDilationLevel = 1;
currentFallInterval = GetRogueFallInterval();
break;
case UPGRADE_HIGH_PRESSURE:
rogueStats.highPressureLevel = 1;
rogueStats.scoreMultiplierPercent += 50;
rogueStats.expMultiplierPercent += 50;
currentFallInterval = GetRogueFallInterval();
break;
case UPGRADE_TETRIS_GAMBLE:
rogueStats.tetrisGambleLevel = 1;
break;
case UPGRADE_EXTREME_PLAYER:
rogueStats.extremePlayerLevel = 1;
rogueStats.extremeDangerTicks = 30;
rogueStats.extremeDangerLevel = 0;
currentFallInterval = GetRogueFallInterval();
break;
case UPGRADE_UPGRADE_SHOCKWAVE:
rogueStats.upgradeShockwaveLevel = 1;
break;
case UPGRADE_EVOLUTION_IMPACT:
rogueStats.evolutionImpactLevel = 1;
break;
case UPGRADE_CONTROL_MASTER:
rogueStats.controlMasterLevel = 1;
if (rogueStats.previewCount < 3)
{
rogueStats.previewCount++;
}
rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1;
break;
case UPGRADE_BLOCK_STORM:
// 方块风暴会同时改写预览队列,确保玩家马上看到连续 I 块。
rogueStats.blockStormLevel = 1;
rogueStats.blockStormPiecesRemaining = 5;
nextTypes[0] = 0;
nextTypes[1] = 0;
nextTypes[2] = 0;
break;
case UPGRADE_CROSS_PIECE:
rogueStats.crossPieceLevel = 1;
break;
case UPGRADE_BLACK_HOLE:
rogueStats.blackHoleLevel = 1;
rogueStats.blackHoleCharges += 2 * applyCount;
break;
case UPGRADE_AIR_RESHAPE:
rogueStats.reshapeLevel += applyCount;
rogueStats.reshapeCharges += 2 * applyCount;
break;
case UPGRADE_RAINBOW_PIECE:
rogueStats.rainbowPieceLevel = 1;
break;
case UPGRADE_VOID_CORE:
rogueStats.voidCoreLevel = 1;
break;
case UPGRADE_STABLE_STRUCTURE:
rogueStats.stableStructureLevel += applyCount;
break;
case UPGRADE_DOUBLE_GROWTH:
rogueStats.doubleGrowthLevel = 1;
rogueStats.scoreMultiplierPercent += 15;
rogueStats.expMultiplierPercent += 15;
break;
case UPGRADE_PIECE_TUNING:
rogueStats.pieceTuningLevels[0] += applyCount;
break;
case UPGRADE_GAMBLER:
rogueStats.gamblerLevel += applyCount;
if (rogueStats.gamblerLevel > 4)
{
rogueStats.gamblerLevel = 4;
}
break;
default:
break;
}
ApplyGrowthOrOperationUpgrade(upgradeId, applyCount) ||
ApplySpecialPieceUpgrade(upgradeId, applyCount) ||
ApplyActiveSkillUpgrade(upgradeId, applyCount) ||
ApplyRiskOrEvolutionUpgrade(upgradeId, applyCount);
}
/**
@@ -1959,16 +2056,6 @@ void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain,
rogueStats.exp += expGain;
tScore = rogueStats.score;
if (rogueStats.screenBombLevel > 0)
{
rogueStats.screenBombCharge += clearedCells;
while (rogueStats.screenBombCharge >= kScreenBombLineThreshold)
{
rogueStats.screenBombCharge -= kScreenBombLineThreshold;
rogueStats.screenBombCount++;
}
}
if (allowLevelProgress && !rogueDemoMode)
{
int levelUps = ApplyLevelProgress(rogueStats);
@@ -2473,6 +2560,109 @@ void OpenUpgradeMenu()
currentScreen = SCREEN_UPGRADE;
}
/**
* @brief
* @return true false
*/
static bool FinishUpgradeSelectionRound()
{
if (upgradeUiState.pendingCount > 0)
{
upgradeUiState.pendingCount--;
}
if (upgradeUiState.pendingCount > 0)
{
FillUpgradeOptions();
currentScreen = SCREEN_UPGRADE;
return true;
}
upgradeUiState.optionCount = 0;
upgradeUiState.picksRemaining = 0;
upgradeUiState.markedCount = 0;
currentScreen = SCREEN_PLAYING;
ResolvePendingUpgradeShockwave();
PlayPendingLineClearEffect();
return false;
}
/**
* @brief
* @param selectedOption
* @param gamblerSuffix
* @param feedbackDetail
* @param feedbackDetailCapacity
*/
static void BuildSingleUpgradeFeedback(const UpgradeOption& selectedOption, const TCHAR* gamblerSuffix, TCHAR feedbackDetail[], int feedbackDetailCapacity)
{
if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0)
{
_stprintf_s(
feedbackDetail,
feedbackDetailCapacity,
_T("%s 块的生成概率提高%s"),
GetPieceShortName(0),
gamblerSuffix);
}
else
{
_stprintf_s(feedbackDetail, feedbackDetailCapacity, _T("%s%s"), selectedOption.description, gamblerSuffix);
}
if (selectedOption.cursed)
{
_stprintf_s(
feedbackDetail + lstrlen(feedbackDetail),
feedbackDetailCapacity - lstrlen(feedbackDetail),
_T(" 诅咒缠身:下一次升级所需 EXP 提高 25%。"));
}
}
/**
* @brief
* @param selectedOption
* @param appliedSelections
* @param feedbackTitle
* @param feedbackDetail
*/
static void ApplyMarkedUpgradeOption(const UpgradeOption& selectedOption, int& appliedSelections, TCHAR feedbackTitle[], TCHAR feedbackDetail[])
{
TCHAR gamblerSuffix[64] = _T("");
int applyCount = RollGamblerApplyCount(gamblerSuffix, 64, true);
ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount);
upgradeUiState.totalChosenCount++;
if (selectedOption.cursed)
{
ApplyDestinyCurse();
}
if (appliedSelections == 0)
{
_stprintf_s(feedbackTitle, 64, _T("获得强化:%s"), selectedOption.name);
}
else
{
_stprintf_s(feedbackTitle, 64, _T("获得强化 x%d"), appliedSelections + 1);
}
if (lstrlen(feedbackDetail) > 0)
{
_stprintf_s(feedbackDetail + lstrlen(feedbackDetail), 128 - lstrlen(feedbackDetail), _T(""));
}
_stprintf_s(
feedbackDetail + lstrlen(feedbackDetail),
128 - lstrlen(feedbackDetail),
_T("%s%s%s"),
selectedOption.name,
gamblerSuffix,
selectedOption.cursed ? _T(" [诅咒]") : _T(""));
appliedSelections++;
}
/**
* @brief
*
@@ -2507,59 +2697,11 @@ void ConfirmUpgradeSelection()
}
UpgradeOption selectedOption = upgradeUiState.options[optionIndex];
TCHAR gamblerSuffix[64] = _T("");
int applyCount = RollGamblerApplyCount(gamblerSuffix, 64, true);
ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount);
upgradeUiState.totalChosenCount++;
if (selectedOption.cursed)
{
ApplyDestinyCurse();
}
if (appliedSelections == 0)
{
_stprintf_s(feedbackTitle, _T("获得强化:%s"), selectedOption.name);
}
else
{
_stprintf_s(feedbackTitle, _T("获得强化 x%d"), appliedSelections + 1);
}
if (lstrlen(feedbackDetail) > 0)
{
_stprintf_s(feedbackDetail + lstrlen(feedbackDetail), 128 - lstrlen(feedbackDetail), _T(""));
}
_stprintf_s(
feedbackDetail + lstrlen(feedbackDetail),
128 - lstrlen(feedbackDetail),
_T("%s%s%s"),
selectedOption.name,
gamblerSuffix,
selectedOption.cursed ? _T(" [诅咒]") : _T(""));
appliedSelections++;
ApplyMarkedUpgradeOption(selectedOption, appliedSelections, feedbackTitle, feedbackDetail);
}
SetFeedbackMessage(feedbackTitle, feedbackDetail, 12);
if (upgradeUiState.pendingCount > 0)
{
upgradeUiState.pendingCount--;
}
if (upgradeUiState.pendingCount > 0)
{
FillUpgradeOptions();
currentScreen = SCREEN_UPGRADE;
return;
}
upgradeUiState.optionCount = 0;
upgradeUiState.picksRemaining = 0;
upgradeUiState.markedCount = 0;
currentScreen = SCREEN_PLAYING;
ResolvePendingUpgradeShockwave();
PlayPendingLineClearEffect();
FinishUpgradeSelectionRound();
return;
}
@@ -2573,26 +2715,11 @@ void ConfirmUpgradeSelection()
TCHAR feedbackTitle[64];
TCHAR feedbackDetail[128];
_stprintf_s(feedbackTitle, _T("获得强化:%s"), selectedOption.name);
if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0)
{
_stprintf_s(
feedbackDetail,
_T("%s 块的生成概率提高%s"),
GetPieceShortName(0),
gamblerSuffix);
}
else
{
_stprintf_s(feedbackDetail, _T("%s%s"), selectedOption.description, gamblerSuffix);
}
BuildSingleUpgradeFeedback(selectedOption, gamblerSuffix, feedbackDetail, 128);
if (selectedOption.cursed)
{
ApplyDestinyCurse();
_stprintf_s(
feedbackDetail + lstrlen(feedbackDetail),
128 - lstrlen(feedbackDetail),
_T(" 诅咒缠身:下一次升级所需 EXP 提高 25%。"));
}
SetFeedbackMessage(feedbackTitle, feedbackDetail, 12);
@@ -2622,26 +2749,7 @@ void ConfirmUpgradeSelection()
return;
}
// 连续升级时重新生成下一轮强化;全部完成后回到游戏并播放延迟效果。
if (upgradeUiState.pendingCount > 0)
{
upgradeUiState.pendingCount--;
}
if (upgradeUiState.pendingCount > 0)
{
FillUpgradeOptions();
currentScreen = SCREEN_UPGRADE;
return;
}
upgradeUiState.optionCount = 0;
upgradeUiState.picksRemaining = 0;
upgradeUiState.markedCount = 0;
currentScreen = SCREEN_PLAYING;
ResolvePendingUpgradeShockwave();
PlayPendingLineClearEffect();
FinishUpgradeSelectionRound();
}
/**