2670 lines
78 KiB
C++
2670 lines
78 KiB
C++
#include "stdafx.h"
|
||
#include "TetrisLogicInternal.h"
|
||
|
||
/**
|
||
* @brief Rogue 创新玩法独立实现文件。
|
||
*
|
||
* 本文件集中放置从基础俄罗斯方块版本扩展出的创新设计,包括强化池、
|
||
* 等级成长、特殊方块、主动技能、难度收缩、连锁奖励和升级选择流程。
|
||
* 代码保持过程式函数组织,不使用 class、继承或多态。
|
||
*/
|
||
|
||
enum UpgradeId
|
||
{
|
||
UPGRADE_SCORE_MULTIPLIER = 0,
|
||
UPGRADE_EXP_MULTIPLIER = 1,
|
||
UPGRADE_SLOW_FALL = 2,
|
||
UPGRADE_COMBO_BONUS = 3,
|
||
UPGRADE_PREVIEW_PLUS_ONE = 4,
|
||
UPGRADE_LAST_CHANCE = 5,
|
||
UPGRADE_HOLD_UNLOCK = 6,
|
||
UPGRADE_PRESSURE_RELIEF = 7,
|
||
UPGRADE_SWEEPER = 8,
|
||
UPGRADE_EXPLOSIVE_PIECE = 9,
|
||
UPGRADE_STABLE_STRUCTURE = 10,
|
||
UPGRADE_DOUBLE_GROWTH = 11,
|
||
UPGRADE_PIECE_TUNING = 12,
|
||
UPGRADE_GAMBLER = 13,
|
||
UPGRADE_CHAIN_BLAST = 14,
|
||
UPGRADE_CHAIN_BOMB = 15,
|
||
UPGRADE_LASER_PIECE = 16,
|
||
UPGRADE_THUNDER_TETRIS = 17,
|
||
UPGRADE_THUNDER_LASER = 18,
|
||
UPGRADE_FEVER_MODE = 19,
|
||
UPGRADE_RAGE_STACK = 20,
|
||
UPGRADE_INFINITE_FEVER = 21,
|
||
UPGRADE_SCREEN_BOMB = 22,
|
||
UPGRADE_TERMINAL_CLEAR = 23,
|
||
UPGRADE_DUAL_CHOICE = 24,
|
||
UPGRADE_DESTINY_WHEEL = 25,
|
||
UPGRADE_PERFECT_ROTATE = 26,
|
||
UPGRADE_TIME_DILATION = 27,
|
||
UPGRADE_HIGH_PRESSURE = 28,
|
||
UPGRADE_TETRIS_GAMBLE = 29,
|
||
UPGRADE_EXTREME_PLAYER = 30,
|
||
UPGRADE_UPGRADE_SHOCKWAVE = 31,
|
||
UPGRADE_EVOLUTION_IMPACT = 32,
|
||
UPGRADE_CONTROL_MASTER = 33,
|
||
UPGRADE_BLOCK_STORM = 34,
|
||
UPGRADE_CROSS_PIECE = 35,
|
||
UPGRADE_BLACK_HOLE = 36,
|
||
UPGRADE_AIR_RESHAPE = 37,
|
||
UPGRADE_RAINBOW_PIECE = 38,
|
||
UPGRADE_VOID_CORE = 39
|
||
};
|
||
|
||
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_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_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_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 翻倍。") },
|
||
{ UPGRADE_RAGE_STACK, 1, 84, false, _T("怒火连段"), _T("进阶"), _T("连续消行越多,得分倍率追加越高。") },
|
||
{ UPGRADE_INFINITE_FEVER, 1, 110, false, _T("无尽狂热"), _T("进化"), _T("狂热期间消行可延长狂热时间;连击越高,倍率越凶。") },
|
||
{ UPGRADE_SCREEN_BOMB, 1, 90, false, _T("清屏炸弹"), _T("进阶"), _T("立刻获得 1 枚炸弹;之后累计消行 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 个会携带诅咒。") },
|
||
{ UPGRADE_PERFECT_ROTATE, 1, 82, false, _T("完美旋转"), _T("操作"), _T("旋转受阻时,自动尝试左右各偏移 1 格完成修正。") },
|
||
{ UPGRADE_TIME_DILATION, 1, 80, false, _T("时间缓流"), _T("保命"), _T("棋盘接近顶端时自动减速,为补救留下反应空间。") },
|
||
{ UPGRADE_HIGH_PRESSURE, 1, 70, false, _T("高压悬赏"), _T("风险"), _T("下落速度提高 15%,但得分与 EXP 额外提高 50%。") },
|
||
{ UPGRADE_TETRIS_GAMBLE, 1, 78, false, _T("豪赌四消"), _T("风险"), _T("1~3 消收益降低,但四消收益暴增。") },
|
||
{ UPGRADE_EXTREME_PLAYER, 1, 114, false, _T("极限玩家"), _T("进化"), _T("融合高压悬赏与豪赌四消。速度更快,四消后短暂缓速。") },
|
||
{ UPGRADE_UPGRADE_SHOCKWAVE, 1, 106, false, _T("升级冲击波"), _T("进阶"), _T("即将升级时自动清除底部 2 行,让强化节奏更猛烈。") },
|
||
{ 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_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_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("选择强化时,有概率效果翻倍,也有概率本次落空。") }
|
||
};
|
||
|
||
static constexpr int kUpgradePoolSize = sizeof(kUpgradePool) / sizeof(kUpgradePool[0]);
|
||
static constexpr int kDifficultyStepMs = 30000;
|
||
static constexpr int kDifficultySpeedStepMs = 18;
|
||
static constexpr int kMaxRogueLockedRows = 4;
|
||
static constexpr int kDifficultyLevelsPerLockedRow = 3;
|
||
static constexpr int kFeverLineThreshold = 12;
|
||
static constexpr int kFeverDurationTicks = 12;
|
||
static constexpr int kScreenBombLineThreshold = 16;
|
||
static constexpr int kExplosiveBaseInterval = 6;
|
||
static constexpr int kExplosiveMinInterval = 3;
|
||
static int pendingUpgradeShockwaveRows = 0;
|
||
static bool pendingEvolutionImpactShockwave = false;
|
||
|
||
static int GetUpgradeCurrentLevel(int upgradeId);
|
||
static bool IsUpgradePrerequisiteConsumed(int upgradeId);
|
||
static int GetUpgradeBaseRarity(int upgradeId);
|
||
static int GetUpgradeDynamicWeight(const UpgradeEntry& entry);
|
||
static const TCHAR* GetPieceShortName(int pieceType);
|
||
static bool IsUpgradeSelectable(const UpgradeEntry& entry);
|
||
static int GetTopOccupiedRow();
|
||
static int GetSweeperThreshold();
|
||
static bool RollExplosivePiece();
|
||
static bool RollLaserPiece();
|
||
static bool RollCrossPiece();
|
||
static bool RollRainbowPiece();
|
||
static int TriggerBlackHole();
|
||
static int TriggerChainBlast(int lineAnchor);
|
||
static int RollNextPieceType();
|
||
static int GetRogueScoreByLines(int linesCleared);
|
||
static int GetRogueExpByLines(int linesCleared);
|
||
static int ApplyLevelProgress(PlayerStats& stats);
|
||
static int TriggerUpgradeShockwave(int rowsToClear);
|
||
static void ResolvePendingUpgradeShockwave();
|
||
static void FillUpgradeOptions();
|
||
static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount);
|
||
static void ApplyDestinyCurse();
|
||
static void ClearLockedRows();
|
||
|
||
/**
|
||
* @brief 限制 Rogue 模式的下一方块预览数量。
|
||
*/
|
||
static int GetNextPreviewLimit()
|
||
{
|
||
if (currentMode != MODE_ROGUE)
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
if (rogueStats.previewCount < 1)
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
if (rogueStats.previewCount > 3)
|
||
{
|
||
return 3;
|
||
}
|
||
|
||
return rogueStats.previewCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取 Rogue 难度系统当前封锁的底部行数。
|
||
*/
|
||
int GetRogueLockedRows()
|
||
{
|
||
if (currentMode != MODE_ROGUE)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
if (rogueStats.lockedRows < 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
return rogueStats.lockedRows > kMaxRogueLockedRows ? kMaxRogueLockedRows : rogueStats.lockedRows;
|
||
}
|
||
|
||
/**
|
||
* @brief 计算当前模式下棋盘可操作区域的高度。
|
||
*/
|
||
int GetRoguePlayableHeight()
|
||
{
|
||
return nGameHeight - GetRogueLockedRows();
|
||
}
|
||
|
||
/**
|
||
* @brief 清空 Rogue 模式中被难度系统封锁的底部区域。
|
||
*/
|
||
static void ClearLockedRows()
|
||
{
|
||
int lockedRows = GetRogueLockedRows();
|
||
for (int y = nGameHeight - lockedRows; y < nGameHeight; y++)
|
||
{
|
||
if (y < 0 || y >= nGameHeight)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
workRegion[y][x] = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 根据经过时间推进 Rogue 难度、封锁行数和下落速度。
|
||
*/
|
||
void AdvanceRogueDifficulty(int elapsedMs)
|
||
{
|
||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag || elapsedMs <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
rogueStats.difficultyElapsedMs += elapsedMs;
|
||
bool difficultyChanged = false;
|
||
|
||
while (rogueStats.difficultyElapsedMs >= kDifficultyStepMs)
|
||
{
|
||
rogueStats.difficultyElapsedMs -= kDifficultyStepMs;
|
||
rogueStats.difficultyLevel++;
|
||
difficultyChanged = true;
|
||
}
|
||
|
||
if (!difficultyChanged)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int nextLockedRows = rogueStats.difficultyLevel / kDifficultyLevelsPerLockedRow;
|
||
if (nextLockedRows > kMaxRogueLockedRows)
|
||
{
|
||
nextLockedRows = kMaxRogueLockedRows;
|
||
}
|
||
|
||
bool lockedRowsChanged = nextLockedRows > rogueStats.lockedRows;
|
||
if (lockedRowsChanged)
|
||
{
|
||
rogueStats.lockedRows = nextLockedRows;
|
||
ClearLockedRows();
|
||
ComputeTarget();
|
||
}
|
||
|
||
currentFallInterval = GetRogueFallInterval();
|
||
|
||
TCHAR difficultyDetail[128];
|
||
_stprintf_s(
|
||
difficultyDetail,
|
||
_T("危险等级 Lv.%d,上方压力增强,底部封锁 %d/%d 行。"),
|
||
rogueStats.difficultyLevel,
|
||
GetRogueLockedRows(),
|
||
kMaxRogueLockedRows);
|
||
SetFeedbackMessage(lockedRowsChanged ? _T("战场收缩") : _T("压力升高"), difficultyDetail, 12);
|
||
}
|
||
|
||
/**
|
||
* @brief 读取指定强化在当前 Rogue 属性中的等级或拥有状态。
|
||
*/
|
||
static int GetUpgradeCurrentLevel(int upgradeId)
|
||
{
|
||
switch (upgradeId)
|
||
{
|
||
case UPGRADE_SCORE_MULTIPLIER:
|
||
return rogueStats.scoreUpgradeLevel;
|
||
case UPGRADE_EXP_MULTIPLIER:
|
||
return rogueStats.expUpgradeLevel;
|
||
case UPGRADE_SLOW_FALL:
|
||
return rogueStats.slowFallStacks;
|
||
case UPGRADE_COMBO_BONUS:
|
||
return rogueStats.comboBonusStacks;
|
||
case UPGRADE_PREVIEW_PLUS_ONE:
|
||
return rogueStats.previewUpgradeLevel;
|
||
case UPGRADE_LAST_CHANCE:
|
||
return rogueStats.lastChanceUpgradeLevel;
|
||
case UPGRADE_HOLD_UNLOCK:
|
||
return rogueStats.holdUnlocked;
|
||
case UPGRADE_PRESSURE_RELIEF:
|
||
return rogueStats.pressureReliefLevel;
|
||
case UPGRADE_SWEEPER:
|
||
return rogueStats.sweeperLevel;
|
||
case UPGRADE_EXPLOSIVE_PIECE:
|
||
return rogueStats.explosiveLevel;
|
||
case UPGRADE_CHAIN_BLAST:
|
||
return rogueStats.chainBlastLevel;
|
||
case UPGRADE_CHAIN_BOMB:
|
||
return rogueStats.chainBombLevel;
|
||
case UPGRADE_LASER_PIECE:
|
||
return rogueStats.laserLevel;
|
||
case UPGRADE_THUNDER_TETRIS:
|
||
return rogueStats.thunderTetrisLevel;
|
||
case UPGRADE_THUNDER_LASER:
|
||
return rogueStats.thunderLaserLevel;
|
||
case UPGRADE_FEVER_MODE:
|
||
return rogueStats.feverLevel;
|
||
case UPGRADE_RAGE_STACK:
|
||
return rogueStats.rageStackLevel;
|
||
case UPGRADE_INFINITE_FEVER:
|
||
return rogueStats.infiniteFeverLevel;
|
||
case UPGRADE_SCREEN_BOMB:
|
||
return rogueStats.screenBombLevel;
|
||
case UPGRADE_TERMINAL_CLEAR:
|
||
return rogueStats.terminalClearLevel;
|
||
case UPGRADE_DUAL_CHOICE:
|
||
return rogueStats.dualChoiceLevel;
|
||
case UPGRADE_DESTINY_WHEEL:
|
||
return rogueStats.destinyWheelLevel;
|
||
case UPGRADE_PERFECT_ROTATE:
|
||
return rogueStats.perfectRotateLevel;
|
||
case UPGRADE_TIME_DILATION:
|
||
return rogueStats.timeDilationLevel;
|
||
case UPGRADE_HIGH_PRESSURE:
|
||
return rogueStats.highPressureLevel;
|
||
case UPGRADE_TETRIS_GAMBLE:
|
||
return rogueStats.tetrisGambleLevel;
|
||
case UPGRADE_EXTREME_PLAYER:
|
||
return rogueStats.extremePlayerLevel;
|
||
case UPGRADE_UPGRADE_SHOCKWAVE:
|
||
return rogueStats.upgradeShockwaveLevel;
|
||
case UPGRADE_EVOLUTION_IMPACT:
|
||
return rogueStats.evolutionImpactLevel;
|
||
case UPGRADE_CONTROL_MASTER:
|
||
return rogueStats.controlMasterLevel;
|
||
case UPGRADE_BLOCK_STORM:
|
||
return rogueStats.blockStormLevel;
|
||
case UPGRADE_CROSS_PIECE:
|
||
return rogueStats.crossPieceLevel;
|
||
case UPGRADE_BLACK_HOLE:
|
||
return rogueStats.blackHoleLevel;
|
||
case UPGRADE_AIR_RESHAPE:
|
||
return rogueStats.reshapeLevel;
|
||
case UPGRADE_RAINBOW_PIECE:
|
||
return rogueStats.rainbowPieceLevel;
|
||
case UPGRADE_VOID_CORE:
|
||
return rogueStats.voidCoreLevel;
|
||
case UPGRADE_STABLE_STRUCTURE:
|
||
return rogueStats.stableStructureLevel;
|
||
case UPGRADE_DOUBLE_GROWTH:
|
||
return rogueStats.doubleGrowthLevel;
|
||
case UPGRADE_GAMBLER:
|
||
return rogueStats.gamblerLevel;
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 返回进化类强化在界面中展示的合成路线说明。
|
||
*/
|
||
const TCHAR* GetUpgradeSynthesisPath(int upgradeId)
|
||
{
|
||
switch (upgradeId)
|
||
{
|
||
case UPGRADE_CHAIN_BOMB:
|
||
return _T("\u5408\u6210\uff1a\u7206\u7834\u65b9\u5757 -> \u8fde\u73af\u70b8\u5f39");
|
||
case UPGRADE_THUNDER_LASER:
|
||
return _T("\u5408\u6210\uff1a\u96f7\u9706\u56db\u6d88 + \u6fc0\u5149\u65b9\u5757");
|
||
case UPGRADE_INFINITE_FEVER:
|
||
return _T("\u5408\u6210\uff1a\u72c2\u70ed\u6a21\u5f0f + \u66b4\u8d70\u5806\u53e0");
|
||
case UPGRADE_TERMINAL_CLEAR:
|
||
return _T("\u5408\u6210\uff1a\u6700\u540e\u4e00\u640f + \u6e05\u5c4f\u70b8\u5f39 + \u72c2\u70ed");
|
||
case UPGRADE_DESTINY_WHEEL:
|
||
return _T("\u5408\u6210\uff1a\u53cc\u91cd\u9009\u62e9 -> \u547d\u8fd0\u8f6e\u76d8");
|
||
case UPGRADE_CONTROL_MASTER:
|
||
return _T("\u5408\u6210\uff1aHold \u89e3\u9501 + \u989d\u5916\u9884\u89c8");
|
||
case UPGRADE_EXTREME_PLAYER:
|
||
return _T("\u5408\u6210\uff1a\u9ad8\u538b\u5956\u52b1 + \u8d4c\u547d\u56db\u6d88");
|
||
case UPGRADE_EVOLUTION_IMPACT:
|
||
return _T("\u5408\u6210\uff1a\u5347\u7ea7\u51b2\u51fb\u6ce2 + \u6210\u957f\u6838\u5fc3");
|
||
case UPGRADE_VOID_CORE:
|
||
return _T("\u5408\u6210\uff1a\u9ed1\u6d1e + \u5f69\u8679\u65b9\u5757");
|
||
default:
|
||
return _T("");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 获取指定强化的基础稀有度分类。
|
||
*/
|
||
static int GetUpgradeBaseRarity(int upgradeId)
|
||
{
|
||
switch (upgradeId)
|
||
{
|
||
case UPGRADE_SCORE_MULTIPLIER:
|
||
case UPGRADE_EXP_MULTIPLIER:
|
||
case UPGRADE_SLOW_FALL:
|
||
case UPGRADE_COMBO_BONUS:
|
||
case UPGRADE_PRESSURE_RELIEF:
|
||
case UPGRADE_SWEEPER:
|
||
case UPGRADE_PIECE_TUNING:
|
||
return UPGRADE_RARITY_COMMON;
|
||
|
||
case UPGRADE_PREVIEW_PLUS_ONE:
|
||
case UPGRADE_LAST_CHANCE:
|
||
case UPGRADE_HOLD_UNLOCK:
|
||
case UPGRADE_EXPLOSIVE_PIECE:
|
||
case UPGRADE_CHAIN_BLAST:
|
||
case UPGRADE_LASER_PIECE:
|
||
case UPGRADE_THUNDER_TETRIS:
|
||
case UPGRADE_FEVER_MODE:
|
||
case UPGRADE_RAGE_STACK:
|
||
case UPGRADE_SCREEN_BOMB:
|
||
case UPGRADE_PERFECT_ROTATE:
|
||
case UPGRADE_TIME_DILATION:
|
||
case UPGRADE_HIGH_PRESSURE:
|
||
case UPGRADE_TETRIS_GAMBLE:
|
||
case UPGRADE_BLOCK_STORM:
|
||
case UPGRADE_CROSS_PIECE:
|
||
case UPGRADE_BLACK_HOLE:
|
||
case UPGRADE_AIR_RESHAPE:
|
||
case UPGRADE_STABLE_STRUCTURE:
|
||
case UPGRADE_DOUBLE_GROWTH:
|
||
return UPGRADE_RARITY_UNCOMMON;
|
||
|
||
case UPGRADE_RAINBOW_PIECE:
|
||
case UPGRADE_GAMBLER:
|
||
return UPGRADE_RARITY_RARE;
|
||
|
||
default:
|
||
return UPGRADE_RARITY_RARE;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 判断指定强化是否已被更高阶合成强化替代。
|
||
*/
|
||
static bool IsUpgradePrerequisiteConsumed(int upgradeId)
|
||
{
|
||
switch (upgradeId)
|
||
{
|
||
case UPGRADE_EXPLOSIVE_PIECE:
|
||
return rogueStats.chainBombLevel > 0;
|
||
case UPGRADE_LASER_PIECE:
|
||
case UPGRADE_THUNDER_TETRIS:
|
||
return rogueStats.thunderLaserLevel > 0;
|
||
case UPGRADE_FEVER_MODE:
|
||
return rogueStats.infiniteFeverLevel > 0 || rogueStats.terminalClearLevel > 0;
|
||
case UPGRADE_RAGE_STACK:
|
||
return rogueStats.infiniteFeverLevel > 0;
|
||
case UPGRADE_LAST_CHANCE:
|
||
case UPGRADE_SCREEN_BOMB:
|
||
return rogueStats.terminalClearLevel > 0;
|
||
case UPGRADE_DUAL_CHOICE:
|
||
return rogueStats.destinyWheelLevel > 0;
|
||
case UPGRADE_HOLD_UNLOCK:
|
||
case UPGRADE_PREVIEW_PLUS_ONE:
|
||
return rogueStats.controlMasterLevel > 0;
|
||
case UPGRADE_HIGH_PRESSURE:
|
||
case UPGRADE_TETRIS_GAMBLE:
|
||
return rogueStats.extremePlayerLevel > 0;
|
||
case UPGRADE_UPGRADE_SHOCKWAVE:
|
||
case UPGRADE_DOUBLE_GROWTH:
|
||
return rogueStats.evolutionImpactLevel > 0;
|
||
case UPGRADE_BLACK_HOLE:
|
||
case UPGRADE_RAINBOW_PIECE:
|
||
return rogueStats.voidCoreLevel > 0;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 根据当前局势动态计算强化选项的抽取权重。
|
||
*/
|
||
static int GetUpgradeDynamicWeight(const UpgradeEntry& entry)
|
||
{
|
||
int weight = entry.baseWeight;
|
||
|
||
if (entry.id == UPGRADE_PRESSURE_RELIEF)
|
||
{
|
||
int topRow = GetTopOccupiedRow();
|
||
if (topRow >= 0)
|
||
{
|
||
int occupiedHeight = GetRoguePlayableHeight() - topRow;
|
||
if (occupiedHeight >= 14)
|
||
{
|
||
weight += 50;
|
||
}
|
||
else if (occupiedHeight <= 7)
|
||
{
|
||
weight -= 25;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (entry.id == UPGRADE_LAST_CHANCE && rogueStats.lastChanceCount > 0)
|
||
{
|
||
weight -= 40;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_SLOW_FALL && rogueStats.slowFallStacks >= 4)
|
||
{
|
||
weight -= 20;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_SCREEN_BOMB && rogueStats.screenBombCount > 0)
|
||
{
|
||
weight += 20;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_BLACK_HOLE && rogueStats.blackHoleCharges > 0)
|
||
{
|
||
weight += 20;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_AIR_RESHAPE && rogueStats.reshapeCharges > 0)
|
||
{
|
||
weight += 20;
|
||
}
|
||
|
||
int rarity = GetUpgradeBaseRarity(entry.id);
|
||
if (rarity == UPGRADE_RARITY_UNCOMMON)
|
||
{
|
||
weight = weight * 65 / 100;
|
||
}
|
||
else if (rarity == UPGRADE_RARITY_RARE)
|
||
{
|
||
weight = weight * 40 / 100;
|
||
}
|
||
|
||
return weight < 1 ? 1 : weight;
|
||
}
|
||
|
||
/**
|
||
* @brief 把方块类型编号转换为界面展示用的短名称。
|
||
*/
|
||
static const TCHAR* GetPieceShortName(int pieceType)
|
||
{
|
||
static const TCHAR* kNames[7] =
|
||
{
|
||
_T("I"), _T("T"), _T("L"), _T("J"), _T("O"), _T("S"), _T("Z")
|
||
};
|
||
|
||
if (pieceType < 0 || pieceType >= 7)
|
||
{
|
||
return _T("?");
|
||
}
|
||
|
||
return kNames[pieceType];
|
||
}
|
||
|
||
/**
|
||
* @brief 判断强化是否满足当前等级、前置和互斥条件。
|
||
*/
|
||
static bool IsUpgradeSelectable(const UpgradeEntry& entry)
|
||
{
|
||
if (IsUpgradePrerequisiteConsumed(entry.id))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_CHAIN_BOMB)
|
||
{
|
||
return rogueStats.explosiveLevel > 0 &&
|
||
rogueStats.chainBombLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_THUNDER_LASER)
|
||
{
|
||
return rogueStats.thunderTetrisLevel > 0 &&
|
||
rogueStats.laserLevel > 0 &&
|
||
rogueStats.thunderLaserLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_INFINITE_FEVER)
|
||
{
|
||
return rogueStats.feverLevel > 0 &&
|
||
rogueStats.rageStackLevel > 0 &&
|
||
rogueStats.infiniteFeverLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_TERMINAL_CLEAR)
|
||
{
|
||
return rogueStats.lastChanceUpgradeLevel > 0 &&
|
||
rogueStats.screenBombLevel > 0 &&
|
||
rogueStats.feverLevel > 0 &&
|
||
rogueStats.terminalClearLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_DESTINY_WHEEL)
|
||
{
|
||
return rogueStats.dualChoiceLevel > 0 &&
|
||
rogueStats.destinyWheelLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_CONTROL_MASTER)
|
||
{
|
||
return rogueStats.holdUnlocked > 0 &&
|
||
rogueStats.previewUpgradeLevel > 0 &&
|
||
rogueStats.controlMasterLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_EXTREME_PLAYER)
|
||
{
|
||
return rogueStats.highPressureLevel > 0 &&
|
||
rogueStats.tetrisGambleLevel > 0 &&
|
||
rogueStats.extremePlayerLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_VOID_CORE)
|
||
{
|
||
return rogueStats.blackHoleLevel > 0 &&
|
||
rogueStats.rainbowPieceLevel > 0 &&
|
||
rogueStats.voidCoreLevel == 0;
|
||
}
|
||
|
||
if (entry.id == UPGRADE_EVOLUTION_IMPACT)
|
||
{
|
||
return rogueStats.upgradeShockwaveLevel > 0 &&
|
||
rogueStats.doubleGrowthLevel > 0 &&
|
||
rogueStats.evolutionImpactLevel == 0;
|
||
}
|
||
|
||
if (entry.repeatable)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (entry.maxLevel <= 0)
|
||
{
|
||
return GetUpgradeCurrentLevel(entry.id) == 0;
|
||
}
|
||
|
||
return GetUpgradeCurrentLevel(entry.id) < entry.maxLevel;
|
||
}
|
||
|
||
/**
|
||
* @brief 查找棋盘中最高的已占用行,用于评估局势压力。
|
||
*/
|
||
static int GetTopOccupiedRow()
|
||
{
|
||
for (int i = 0; i < GetRoguePlayableHeight(); i++)
|
||
{
|
||
for (int j = 0; j < nGameWidth; j++)
|
||
{
|
||
if (workRegion[i][j] != 0)
|
||
{
|
||
return i;
|
||
}
|
||
}
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* @brief 计算底线清道夫触发一次自动清底需要的消行充能。
|
||
*/
|
||
static int GetSweeperThreshold()
|
||
{
|
||
int reduction = (rogueStats.sweeperLevel - 1);
|
||
int threshold = 5 - reduction;
|
||
return threshold < 2 ? 2 : threshold;
|
||
}
|
||
|
||
/**
|
||
* @brief 根据爆破强化等级随机判定当前方块是否获得爆破特性。
|
||
*/
|
||
static bool RollExplosivePiece()
|
||
{
|
||
if (currentMode != MODE_ROGUE || rogueStats.explosiveLevel <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
int explosiveInterval = kExplosiveBaseInterval - (rogueStats.explosiveLevel - 1);
|
||
if (explosiveInterval < kExplosiveMinInterval)
|
||
{
|
||
explosiveInterval = kExplosiveMinInterval;
|
||
}
|
||
|
||
if (rogueStats.feverTicks > 0 && explosiveInterval > kExplosiveMinInterval)
|
||
{
|
||
explosiveInterval--;
|
||
}
|
||
|
||
if (rogueStats.explosivePieceCounter < explosiveInterval)
|
||
{
|
||
rogueStats.explosivePieceCounter++;
|
||
}
|
||
|
||
if (rogueStats.explosivePieceCounter < explosiveInterval)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
rogueStats.explosivePieceCounter = 0;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 根据激光强化等级随机判定当前方块是否获得激光特性。
|
||
*/
|
||
static bool RollLaserPiece()
|
||
{
|
||
if (currentMode != MODE_ROGUE || rogueStats.laserLevel <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
int chancePercent = 16 + (rogueStats.laserLevel - 1) * 10;
|
||
if (chancePercent > 55)
|
||
{
|
||
chancePercent = 55;
|
||
}
|
||
|
||
if (rogueStats.feverTicks > 0)
|
||
{
|
||
chancePercent += 15;
|
||
if (chancePercent > 70)
|
||
{
|
||
chancePercent = 70;
|
||
}
|
||
}
|
||
|
||
return (rand() % 100) < chancePercent;
|
||
}
|
||
|
||
/**
|
||
* @brief 根据十字强化等级随机判定当前方块是否获得十字清除特性。
|
||
*/
|
||
static bool RollCrossPiece()
|
||
{
|
||
if (currentMode != MODE_ROGUE || rogueStats.crossPieceLevel <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
int chancePercent = 14 + (rogueStats.crossPieceLevel - 1) * 8;
|
||
if (chancePercent > 45)
|
||
{
|
||
chancePercent = 45;
|
||
}
|
||
|
||
if (rogueStats.feverTicks > 0)
|
||
{
|
||
chancePercent += 15;
|
||
if (chancePercent > 60)
|
||
{
|
||
chancePercent = 60;
|
||
}
|
||
}
|
||
|
||
return (rand() % 100) < chancePercent;
|
||
}
|
||
|
||
/**
|
||
* @brief 根据彩虹强化等级随机判定当前方块是否获得行清除与染色特性。
|
||
*/
|
||
static bool RollRainbowPiece()
|
||
{
|
||
if (currentMode != MODE_ROGUE || rogueStats.rainbowPieceLevel <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
int chancePercent = 18;
|
||
if (rogueStats.feverTicks > 0)
|
||
{
|
||
chancePercent += 17;
|
||
}
|
||
return (rand() % 100) < chancePercent;
|
||
}
|
||
|
||
/**
|
||
* @brief 判断棋盘格是否属于彩虹方块固定后的特殊格。
|
||
*/
|
||
bool IsRainbowBoardCell(int cellValue)
|
||
{
|
||
return cellValue == 8;
|
||
}
|
||
|
||
/**
|
||
* @brief 触发小型黑洞,随机吞噬限定数量的固定方块。
|
||
*/
|
||
int TriggerMiniBlackHole(int maxCellsToClear)
|
||
{
|
||
int blockCounts[8] = { 0 };
|
||
|
||
for (int y = 0; y < GetRoguePlayableHeight(); y++)
|
||
{
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
int cell = workRegion[y][x];
|
||
if (cell >= 1 && cell <= 7)
|
||
{
|
||
blockCounts[cell]++;
|
||
}
|
||
}
|
||
}
|
||
|
||
int targetBlock = 0;
|
||
int maxCount = 0;
|
||
for (int block = 1; block <= 7; block++)
|
||
{
|
||
if (blockCounts[block] > maxCount)
|
||
{
|
||
maxCount = blockCounts[block];
|
||
targetBlock = block;
|
||
}
|
||
}
|
||
|
||
if (targetBlock == 0 || maxCellsToClear <= 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[16] = {};
|
||
for (int y = GetRoguePlayableHeight() - 1; y >= 0 && clearedCellCount < maxCellsToClear; y--)
|
||
{
|
||
for (int x = 0; x < nGameWidth && clearedCellCount < maxCellsToClear; x++)
|
||
{
|
||
if (workRegion[y][x] == targetBlock)
|
||
{
|
||
if (clearedCellCount < 16)
|
||
{
|
||
clearedCells[clearedCellCount].x = x;
|
||
clearedCells[clearedCellCount].y = y;
|
||
}
|
||
workRegion[y][x] = 0;
|
||
clearedCellCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
TriggerColoredCellClearEffect(clearedCells, clearedCellCount < 16 ? clearedCellCount : 16, RGB(24, 20, 28), true);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 为当前活动方块刷新爆破、激光、十字和彩虹等特殊标记。
|
||
*/
|
||
void RollCurrentPieceSpecialFlags(bool allowRandomSpecials)
|
||
{
|
||
if (!allowRandomSpecials)
|
||
{
|
||
currentPieceIsExplosive = false;
|
||
currentPieceIsLaser = false;
|
||
currentPieceIsCross = false;
|
||
currentPieceIsRainbow = false;
|
||
return;
|
||
}
|
||
|
||
if (currentMode == MODE_ROGUE && rogueStats.pendingRainbowPieceCount > 0)
|
||
{
|
||
rogueStats.pendingRainbowPieceCount--;
|
||
currentPieceIsExplosive = false;
|
||
currentPieceIsLaser = false;
|
||
currentPieceIsCross = false;
|
||
currentPieceIsRainbow = true;
|
||
return;
|
||
}
|
||
|
||
currentPieceIsExplosive = RollExplosivePiece();
|
||
currentPieceIsLaser = !currentPieceIsExplosive && RollLaserPiece();
|
||
currentPieceIsCross = !currentPieceIsExplosive && !currentPieceIsLaser && RollCrossPiece();
|
||
currentPieceIsRainbow = !currentPieceIsExplosive && !currentPieceIsLaser && !currentPieceIsCross && RollRainbowPiece();
|
||
}
|
||
|
||
/**
|
||
* @brief 以指定棋盘格为中心清除爆破范围内的固定方块。
|
||
*/
|
||
int ClearExplosiveAreaAt(int centerY, int centerX)
|
||
{
|
||
int radius = (currentMode == MODE_ROGUE && rogueStats.chainBombLevel > 0) ? 2 : 1;
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[25] = {};
|
||
|
||
for (int y = centerY - radius; y <= centerY + radius; y++)
|
||
{
|
||
for (int x = centerX - radius; x <= centerX + radius; x++)
|
||
{
|
||
if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth)
|
||
{
|
||
if (workRegion[y][x] != 0)
|
||
{
|
||
if (clearedCellCount < 25)
|
||
{
|
||
clearedCells[clearedCellCount].x = x;
|
||
clearedCells[clearedCellCount].y = y;
|
||
}
|
||
clearedCellCount++;
|
||
workRegion[y][x] = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
TriggerColoredCellClearEffect(clearedCells, clearedCellCount < 25 ? clearedCellCount : 25, RGB(255, 116, 78), true);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 清除指定列中的所有固定方块并返回清除数量。
|
||
*/
|
||
int ClearColumnAt(int column)
|
||
{
|
||
return ClearColumnAtWithColor(column, RGB(120, 232, 255));
|
||
}
|
||
|
||
/**
|
||
* @brief 使用指定颜色特效清除指定列并返回清除数量。
|
||
*/
|
||
int ClearColumnAtWithColor(int column, COLORREF flashColor)
|
||
{
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[20] = {};
|
||
|
||
if (column < 0 || column >= nGameWidth)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
for (int y = 0; y < GetRoguePlayableHeight(); y++)
|
||
{
|
||
if (workRegion[y][column] != 0)
|
||
{
|
||
if (clearedCellCount < 20)
|
||
{
|
||
clearedCells[clearedCellCount].x = column;
|
||
clearedCells[clearedCellCount].y = y;
|
||
}
|
||
workRegion[y][column] = 0;
|
||
clearedCellCount++;
|
||
}
|
||
}
|
||
|
||
TriggerColoredCellClearEffect(clearedCells, clearedCellCount, flashColor, false);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 清除指定行中的所有固定方块并返回清除数量。
|
||
*/
|
||
int ClearRowAt(int row)
|
||
{
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[10] = {};
|
||
|
||
if (row < 0 || row >= GetRoguePlayableHeight())
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
if (workRegion[row][x] != 0)
|
||
{
|
||
if (clearedCellCount < 10)
|
||
{
|
||
clearedCells[clearedCellCount].x = x;
|
||
clearedCells[clearedCellCount].y = row;
|
||
}
|
||
workRegion[row][x] = 0;
|
||
clearedCellCount++;
|
||
}
|
||
}
|
||
|
||
TriggerColoredCellClearEffect(clearedCells, clearedCellCount, RGB(196, 255, 132), false);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 触发彩虹方块行清除与覆盖行染色效果。
|
||
*/
|
||
int TriggerRainbowColorShift(int anchorRow, int minRow, int maxRow, int& recoloredCount)
|
||
{
|
||
recoloredCount = 0;
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[10] = {};
|
||
Point recoloredCells[40] = {};
|
||
|
||
if (anchorRow < 0)
|
||
{
|
||
anchorRow = 0;
|
||
}
|
||
if (anchorRow >= GetRoguePlayableHeight())
|
||
{
|
||
anchorRow = GetRoguePlayableHeight() - 1;
|
||
}
|
||
if (minRow < 0)
|
||
{
|
||
minRow = 0;
|
||
}
|
||
if (maxRow >= GetRoguePlayableHeight())
|
||
{
|
||
maxRow = GetRoguePlayableHeight() - 1;
|
||
}
|
||
|
||
int rowColorCounts[8] = {};
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
int cell = workRegion[anchorRow][x];
|
||
if (cell >= 1 && cell <= 7)
|
||
{
|
||
rowColorCounts[cell]++;
|
||
}
|
||
}
|
||
|
||
int rowTargetColor = 0;
|
||
int rowTargetCount = 0;
|
||
for (int cell = 1; cell <= 7; cell++)
|
||
{
|
||
if (rowColorCounts[cell] > rowTargetCount)
|
||
{
|
||
rowTargetColor = cell;
|
||
rowTargetCount = rowColorCounts[cell];
|
||
}
|
||
}
|
||
|
||
if (rowTargetColor > 0)
|
||
{
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
if (workRegion[anchorRow][x] == rowTargetColor)
|
||
{
|
||
if (clearedCellCount < 10)
|
||
{
|
||
clearedCells[clearedCellCount].x = x;
|
||
clearedCells[clearedCellCount].y = anchorRow;
|
||
}
|
||
workRegion[anchorRow][x] = 0;
|
||
clearedCellCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
int boardColorCounts[8] = {};
|
||
for (int y = 0; y < GetRoguePlayableHeight(); y++)
|
||
{
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
int cell = workRegion[y][x];
|
||
if (cell >= 1 && cell <= 7)
|
||
{
|
||
boardColorCounts[cell]++;
|
||
}
|
||
}
|
||
}
|
||
|
||
int boardTargetColor = 0;
|
||
int boardTargetCount = 0;
|
||
for (int cell = 1; cell <= 7; cell++)
|
||
{
|
||
if (boardColorCounts[cell] > boardTargetCount)
|
||
{
|
||
boardTargetColor = cell;
|
||
boardTargetCount = boardColorCounts[cell];
|
||
}
|
||
}
|
||
|
||
if (boardTargetColor == 0)
|
||
{
|
||
boardTargetColor = rowTargetColor > 0 ? rowTargetColor : 1;
|
||
}
|
||
|
||
for (int y = minRow; y <= maxRow; y++)
|
||
{
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
if (workRegion[y][x] != 0 && workRegion[y][x] != boardTargetColor)
|
||
{
|
||
workRegion[y][x] = boardTargetColor;
|
||
if (recoloredCount < 40)
|
||
{
|
||
recoloredCells[recoloredCount].x = x;
|
||
recoloredCells[recoloredCount].y = y;
|
||
}
|
||
recoloredCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
TriggerColoredCellClearEffect(clearedCells, clearedCellCount, RGB(255, 255, 255), true);
|
||
TriggerColoredCellClearEffect(recoloredCells, recoloredCount < 40 ? recoloredCount : 40, RGB(218, 178, 255), false);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 触发黑洞,吞噬棋盘中数量最多的一类固定方块。
|
||
*/
|
||
static int TriggerBlackHole()
|
||
{
|
||
int blockCounts[8] = { 0 };
|
||
for (int y = 0; y < GetRoguePlayableHeight(); y++)
|
||
{
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
int cell = workRegion[y][x];
|
||
if (cell >= 1 && cell <= 7)
|
||
{
|
||
blockCounts[cell]++;
|
||
}
|
||
}
|
||
}
|
||
|
||
int targetBlock = 0;
|
||
int maxCount = 0;
|
||
for (int block = 1; block <= 7; block++)
|
||
{
|
||
if (blockCounts[block] > maxCount)
|
||
{
|
||
maxCount = blockCounts[block];
|
||
targetBlock = block;
|
||
}
|
||
}
|
||
|
||
if (targetBlock == 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[200] = {};
|
||
for (int y = 0; y < GetRoguePlayableHeight(); y++)
|
||
{
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
if (workRegion[y][x] == targetBlock)
|
||
{
|
||
if (clearedCellCount < 200)
|
||
{
|
||
clearedCells[clearedCellCount].x = x;
|
||
clearedCells[clearedCellCount].y = y;
|
||
}
|
||
workRegion[y][x] = 0;
|
||
clearedCellCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
TriggerColoredCellClearEffect(clearedCells, clearedCellCount < 200 ? clearedCellCount : 200, RGB(24, 20, 28), true);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 引爆清屏炸弹,清除当前可玩区域底部多行方块。
|
||
*/
|
||
int TriggerScreenBomb()
|
||
{
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[50] = {};
|
||
|
||
for (int i = 0; i < 5; i++)
|
||
{
|
||
int row = GetRoguePlayableHeight() - 1 - i;
|
||
if (row < 0)
|
||
{
|
||
break;
|
||
}
|
||
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
if (workRegion[row][x] != 0)
|
||
{
|
||
if (clearedCellCount < 50)
|
||
{
|
||
clearedCells[clearedCellCount].x = x;
|
||
clearedCells[clearedCellCount].y = row;
|
||
}
|
||
clearedCellCount++;
|
||
}
|
||
}
|
||
|
||
DeleteOneLine(row);
|
||
}
|
||
|
||
TriggerCellClearEffect(clearedCells, clearedCellCount < 50 ? clearedCellCount : 50, true);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 围绕消行位置触发连锁火花,随机破坏附近固定方块。
|
||
*/
|
||
static int TriggerChainBlast(int lineAnchor)
|
||
{
|
||
if (currentMode != MODE_ROGUE || rogueStats.chainBlastLevel <= 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
int clearedCellCount = 0;
|
||
Point clearedCells[16] = {};
|
||
int blastCount = 2 + rogueStats.chainBlastLevel;
|
||
|
||
for (int blastIndex = 0; blastIndex < blastCount; blastIndex++)
|
||
{
|
||
int randomY = lineAnchor + (rand() % 3) - 1;
|
||
int randomX = rand() % nGameWidth;
|
||
|
||
if (randomY < 0)
|
||
{
|
||
randomY = 0;
|
||
}
|
||
if (randomY >= GetRoguePlayableHeight())
|
||
{
|
||
randomY = GetRoguePlayableHeight() - 1;
|
||
}
|
||
|
||
if (workRegion[randomY][randomX] != 0)
|
||
{
|
||
if (clearedCellCount < 16)
|
||
{
|
||
clearedCells[clearedCellCount].x = randomX;
|
||
clearedCells[clearedCellCount].y = randomY;
|
||
}
|
||
workRegion[randomY][randomX] = 0;
|
||
clearedCellCount++;
|
||
}
|
||
}
|
||
|
||
TriggerCellClearEffect(clearedCells, clearedCellCount < 16 ? clearedCellCount : 16, false);
|
||
return clearedCellCount;
|
||
}
|
||
|
||
/**
|
||
* @brief 根据 Rogue 强化和权重系统随机生成下一个方块类型。
|
||
*/
|
||
static int RollNextPieceType()
|
||
{
|
||
if (currentMode == MODE_ROGUE && rogueStats.blockStormPiecesRemaining > 0)
|
||
{
|
||
rogueStats.blockStormPiecesRemaining--;
|
||
return 0;
|
||
}
|
||
|
||
int weights[7] = { 100, 100, 100, 100, 100, 100, 100 };
|
||
|
||
if (currentMode == MODE_ROGUE)
|
||
{
|
||
weights[0] += rogueStats.pieceTuningLevels[0] * 55;
|
||
if (weights[0] > 430)
|
||
{
|
||
weights[0] = 430;
|
||
}
|
||
}
|
||
|
||
int totalWeight = 0;
|
||
for (int i = 0; i < 7; i++)
|
||
{
|
||
totalWeight += weights[i];
|
||
}
|
||
|
||
int roll = rand() % totalWeight;
|
||
for (int i = 0; i < 7; i++)
|
||
{
|
||
if (roll < weights[i])
|
||
{
|
||
return i;
|
||
}
|
||
roll -= weights[i];
|
||
}
|
||
|
||
return rand() % 7;
|
||
}
|
||
|
||
/**
|
||
* @brief 尝试用稳定结构强化填补局部空洞并返回填补数量。
|
||
*/
|
||
int TryStabilizeBoard()
|
||
{
|
||
if (currentMode != MODE_ROGUE || rogueStats.stableStructureLevel <= 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
int triggerChance = 25 + (rogueStats.stableStructureLevel - 1) * 12;
|
||
if (triggerChance > 70)
|
||
{
|
||
triggerChance = 70;
|
||
}
|
||
|
||
if ((rand() % 100) >= triggerChance)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
for (int y = GetRoguePlayableHeight() - 2; y >= 1; y--)
|
||
{
|
||
for (int x = 1; x < nGameWidth - 1; x++)
|
||
{
|
||
if (workRegion[y][x] != 0)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
bool hasSupportBelow = workRegion[y + 1][x] != 0;
|
||
bool hasLeftNeighbor = workRegion[y][x - 1] != 0;
|
||
bool hasRightNeighbor = workRegion[y][x + 1] != 0;
|
||
|
||
if (hasSupportBelow && hasLeftNeighbor && hasRightNeighbor)
|
||
{
|
||
int fillValue = workRegion[y + 1][x];
|
||
if (fillValue == 0)
|
||
{
|
||
fillValue = hasLeftNeighbor ? workRegion[y][x - 1] : workRegion[y][x + 1];
|
||
}
|
||
workRegion[y][x] = fillValue;
|
||
return 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 重置下一方块队列并按当前预览上限填充。
|
||
*/
|
||
void ResetNextQueue()
|
||
{
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
nextTypes[i] = RollNextPieceType();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 取出队首方块并补充新的下一方块。
|
||
*/
|
||
int ConsumeNextType()
|
||
{
|
||
int nextType = nextTypes[0];
|
||
nextTypes[0] = nextTypes[1];
|
||
nextTypes[1] = nextTypes[2];
|
||
nextTypes[2] = RollNextPieceType();
|
||
return nextType;
|
||
}
|
||
|
||
/**
|
||
* @brief 按 Rogue 规则计算指定消行数对应的基础得分。
|
||
*/
|
||
static int GetRogueScoreByLines(int linesCleared)
|
||
{
|
||
switch (linesCleared)
|
||
{
|
||
case 1: return 100;
|
||
case 2: return 300;
|
||
case 3: return 500;
|
||
case 4: return 800;
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 按 Rogue 规则计算指定消行数对应的基础经验。
|
||
*/
|
||
static int GetRogueExpByLines(int linesCleared)
|
||
{
|
||
switch (linesCleared)
|
||
{
|
||
case 1: return 12;
|
||
case 2: return 32;
|
||
case 3: return 58;
|
||
case 4: return 100;
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 结算经验条并返回本次连续升级次数。
|
||
*/
|
||
static int ApplyLevelProgress(PlayerStats& stats)
|
||
{
|
||
int levelUps = 0;
|
||
|
||
while (stats.requiredExp > 0 && stats.exp >= stats.requiredExp)
|
||
{
|
||
stats.exp -= stats.requiredExp;
|
||
stats.level++;
|
||
stats.requiredExp = 6 + stats.level * 5;
|
||
levelUps++;
|
||
}
|
||
|
||
return levelUps;
|
||
}
|
||
|
||
/**
|
||
* @brief 触发升级冲击波,清除底部指定数量的行。
|
||
*/
|
||
static int TriggerUpgradeShockwave(int rowsToClear)
|
||
{
|
||
int clearedRows = 0;
|
||
|
||
for (int i = 0; i < rowsToClear; i++)
|
||
{
|
||
DeleteOneLine(GetRoguePlayableHeight() - 1);
|
||
clearedRows++;
|
||
}
|
||
|
||
return clearedRows;
|
||
}
|
||
|
||
/**
|
||
* @brief 按权重和当前局势生成升级菜单中的候选强化。
|
||
*/
|
||
static void FillUpgradeOptions()
|
||
{
|
||
int selectableIndexes[kUpgradePoolSize] = { 0 };
|
||
int selectableWeights[kUpgradePoolSize] = { 0 };
|
||
int selectableCount = 0;
|
||
|
||
for (int i = 0; i < kUpgradePoolSize; i++)
|
||
{
|
||
if (IsUpgradeSelectable(kUpgradePool[i]))
|
||
{
|
||
selectableIndexes[selectableCount] = i;
|
||
selectableWeights[selectableCount] = GetUpgradeDynamicWeight(kUpgradePool[i]);
|
||
selectableCount++;
|
||
}
|
||
}
|
||
|
||
int optionLimit = (rogueStats.destinyWheelLevel > 0) ? 6 : 3;
|
||
int optionCount = selectableCount < optionLimit ? selectableCount : optionLimit;
|
||
upgradeUiState.optionCount = optionCount;
|
||
upgradeUiState.selectedIndex = 0;
|
||
upgradeUiState.picksRemaining = (rogueStats.dualChoiceLevel > 0 || rogueStats.destinyWheelLevel > 0) ? 2 : 1;
|
||
upgradeUiState.markedCount = 0;
|
||
for (int i = 0; i < 6; i++)
|
||
{
|
||
upgradeUiState.marked[i] = false;
|
||
}
|
||
|
||
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 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;
|
||
|
||
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];
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 综合难度、强化和临时状态计算 Rogue 模式下落间隔。
|
||
*/
|
||
int GetRogueFallInterval()
|
||
{
|
||
int baseInterval = 500 + rogueStats.slowFallStacks * 80;
|
||
baseInterval -= rogueStats.difficultyLevel * kDifficultySpeedStepMs;
|
||
|
||
if (rogueStats.highPressureLevel > 0)
|
||
{
|
||
baseInterval = baseInterval * 85 / 100;
|
||
}
|
||
|
||
if (rogueStats.extremePlayerLevel > 0)
|
||
{
|
||
baseInterval = baseInterval * 90 / 100;
|
||
}
|
||
|
||
if (rogueStats.extremeDangerLevel > 0)
|
||
{
|
||
baseInterval = baseInterval * (100 - rogueStats.extremeDangerLevel * 8) / 100;
|
||
}
|
||
|
||
if (currentMode == MODE_ROGUE && rogueStats.feverTicks > 0)
|
||
{
|
||
baseInterval += 120;
|
||
}
|
||
|
||
if (rogueStats.timeDilationTicks > 0)
|
||
{
|
||
baseInterval = baseInterval * 130 / 100;
|
||
}
|
||
|
||
if (rogueStats.extremeSlowTicks > 0)
|
||
{
|
||
baseInterval += 180;
|
||
}
|
||
|
||
if (rogueStats.holdSlowTicks > 0)
|
||
{
|
||
baseInterval += 140;
|
||
}
|
||
|
||
if (baseInterval < 120)
|
||
{
|
||
baseInterval = 120;
|
||
}
|
||
|
||
return baseInterval;
|
||
}
|
||
|
||
/**
|
||
* @brief 根据强化编号把对应效果写入 Rogue 属性。
|
||
*/
|
||
static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount)
|
||
{
|
||
if (applyCount <= 0)
|
||
{
|
||
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;
|
||
currentFallInterval = GetRogueFallInterval();
|
||
break;
|
||
case UPGRADE_PREVIEW_PLUS_ONE:
|
||
for (int i = 0; i < applyCount; i++)
|
||
{
|
||
if (rogueStats.previewCount < 3)
|
||
{
|
||
rogueStats.previewCount++;
|
||
}
|
||
}
|
||
rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1;
|
||
break;
|
||
case UPGRADE_LAST_CHANCE:
|
||
rogueStats.lastChanceCount += applyCount;
|
||
rogueStats.lastChanceUpgradeLevel += applyCount;
|
||
break;
|
||
case UPGRADE_HOLD_UNLOCK:
|
||
rogueStats.holdUnlocked = 1;
|
||
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;
|
||
break;
|
||
case UPGRADE_EXPLOSIVE_PIECE:
|
||
rogueStats.explosiveLevel += applyCount;
|
||
break;
|
||
case UPGRADE_CHAIN_BLAST:
|
||
rogueStats.chainBlastLevel = 1;
|
||
break;
|
||
case UPGRADE_CHAIN_BOMB:
|
||
rogueStats.chainBombLevel = 1;
|
||
break;
|
||
case UPGRADE_LASER_PIECE:
|
||
rogueStats.laserLevel += applyCount;
|
||
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 = 1;
|
||
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:
|
||
rogueStats.blockStormLevel = 1;
|
||
rogueStats.blockStormPiecesRemaining = 5;
|
||
nextTypes[0] = 0;
|
||
nextTypes[1] = 0;
|
||
nextTypes[2] = 0;
|
||
break;
|
||
case UPGRADE_CROSS_PIECE:
|
||
rogueStats.crossPieceLevel += applyCount;
|
||
break;
|
||
case UPGRADE_BLACK_HOLE:
|
||
rogueStats.blackHoleLevel = 1;
|
||
rogueStats.blackHoleCharges += 2 * applyCount;
|
||
break;
|
||
case UPGRADE_AIR_RESHAPE:
|
||
rogueStats.reshapeLevel = 1;
|
||
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;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 为技能清除的格子结算得分和经验奖励。
|
||
*/
|
||
void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain, bool allowLevelProgress)
|
||
{
|
||
scoreGain = 0;
|
||
expGain = 0;
|
||
if (currentMode != MODE_ROGUE || clearedCells <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
scoreGain = clearedCells * rogueStats.scoreMultiplierPercent / 100;
|
||
expGain = clearedCells * rogueStats.expMultiplierPercent / 100;
|
||
|
||
if (rogueStats.doubleGrowthLevel > 0)
|
||
{
|
||
int growthMultiplierPercent = 100 + rogueStats.doubleGrowthLevel * 15;
|
||
scoreGain = scoreGain * growthMultiplierPercent / 100;
|
||
expGain = expGain * growthMultiplierPercent / 100;
|
||
}
|
||
|
||
if (rogueStats.feverTicks > 0)
|
||
{
|
||
scoreGain *= 2;
|
||
expGain *= 2;
|
||
}
|
||
|
||
if (scoreGain < clearedCells)
|
||
{
|
||
scoreGain = clearedCells;
|
||
}
|
||
if (expGain < clearedCells)
|
||
{
|
||
expGain = clearedCells;
|
||
}
|
||
|
||
rogueStats.score += scoreGain;
|
||
rogueStats.exp += expGain;
|
||
tScore = rogueStats.score;
|
||
|
||
if (rogueStats.screenBombLevel > 0)
|
||
{
|
||
rogueStats.screenBombCharge += clearedCells;
|
||
while (rogueStats.screenBombCharge >= kScreenBombLineThreshold)
|
||
{
|
||
rogueStats.screenBombCharge -= kScreenBombLineThreshold;
|
||
rogueStats.screenBombCount++;
|
||
}
|
||
}
|
||
|
||
if (allowLevelProgress)
|
||
{
|
||
int levelUps = ApplyLevelProgress(rogueStats);
|
||
if (levelUps > 0)
|
||
{
|
||
upgradeUiState.pendingCount += levelUps;
|
||
OpenUpgradeMenu();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 检查 Rogue 经验是否升级,并在需要时打开强化选择界面。
|
||
*/
|
||
void CheckRogueLevelProgress()
|
||
{
|
||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int levelUps = ApplyLevelProgress(rogueStats);
|
||
if (levelUps <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
upgradeUiState.pendingCount += levelUps;
|
||
|
||
int shockwaveRows = 0;
|
||
if (rogueStats.evolutionImpactLevel > 0)
|
||
{
|
||
shockwaveRows = 3;
|
||
rogueStats.feverTicks = kFeverDurationTicks;
|
||
currentFallInterval = GetRogueFallInterval();
|
||
}
|
||
else if (rogueStats.upgradeShockwaveLevel > 0)
|
||
{
|
||
shockwaveRows = 2;
|
||
}
|
||
|
||
if (shockwaveRows > 0)
|
||
{
|
||
pendingUpgradeShockwaveRows = shockwaveRows;
|
||
pendingEvolutionImpactShockwave = rogueStats.evolutionImpactLevel > 0;
|
||
}
|
||
|
||
TCHAR feedbackTitle[64];
|
||
TCHAR feedbackDetail[128];
|
||
_stprintf_s(feedbackTitle, _T("灵感涌现 x%d"), levelUps);
|
||
_stprintf_s(
|
||
feedbackDetail,
|
||
_T("等级 Lv.%d EXP %d/%d 选择新的强化"),
|
||
rogueStats.level,
|
||
rogueStats.exp,
|
||
rogueStats.requiredExp);
|
||
SetFeedbackMessage(feedbackTitle, feedbackDetail, 10);
|
||
|
||
OpenUpgradeMenu();
|
||
}
|
||
|
||
/**
|
||
* @brief 对棋盘固定方块应用重力,使悬空方块自然下落。
|
||
*/
|
||
void ApplyBoardGravity()
|
||
{
|
||
int playableHeight = GetRoguePlayableHeight();
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
int writeY = playableHeight - 1;
|
||
for (int y = playableHeight - 1; y >= 0; y--)
|
||
{
|
||
if (workRegion[y][x] != 0)
|
||
{
|
||
int cell = workRegion[y][x];
|
||
workRegion[y][x] = 0;
|
||
workRegion[writeY][x] = cell;
|
||
writeY--;
|
||
}
|
||
}
|
||
|
||
for (int y = writeY; y >= 0; y--)
|
||
{
|
||
workRegion[y][x] = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 结算一次标准消行带来的 Rogue 得分、经验、连击和派生效果。
|
||
*/
|
||
void ApplyLineClearResult(int linesCleared)
|
||
{
|
||
if (linesCleared <= 0)
|
||
{
|
||
if (currentMode == MODE_ROGUE)
|
||
{
|
||
rogueStats.comboChain = 0;
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (currentMode == MODE_CLASSIC)
|
||
{
|
||
classicStats.totalLinesCleared += linesCleared;
|
||
classicStats.score += linesCleared * 100;
|
||
tScore = classicStats.score;
|
||
return;
|
||
}
|
||
|
||
int scoreGain = GetRogueScoreByLines(linesCleared);
|
||
scoreGain = scoreGain * rogueStats.scoreMultiplierPercent / 100;
|
||
|
||
int expGain = GetRogueExpByLines(linesCleared);
|
||
expGain = expGain * rogueStats.expMultiplierPercent / 100;
|
||
|
||
if (rogueStats.tetrisGambleLevel > 0)
|
||
{
|
||
if (linesCleared == 4)
|
||
{
|
||
scoreGain *= 4;
|
||
}
|
||
else
|
||
{
|
||
scoreGain = scoreGain * 50 / 100;
|
||
}
|
||
}
|
||
|
||
if (rogueStats.doubleGrowthLevel > 0)
|
||
{
|
||
int growthMultiplierPercent = 100 + rogueStats.doubleGrowthLevel * 15;
|
||
scoreGain = scoreGain * growthMultiplierPercent / 100;
|
||
expGain = expGain * growthMultiplierPercent / 100;
|
||
}
|
||
|
||
if (rogueStats.feverTicks > 0)
|
||
{
|
||
scoreGain *= 2;
|
||
expGain *= 2;
|
||
}
|
||
|
||
int gamblerBonusPercent = 0;
|
||
if (rogueStats.gamblerLevel > 0)
|
||
{
|
||
int variance = 20 + (rogueStats.gamblerLevel - 1) * 10;
|
||
if (variance > 50)
|
||
{
|
||
variance = 50;
|
||
}
|
||
|
||
gamblerBonusPercent = (rand() % (variance * 2 + 1)) - variance;
|
||
scoreGain = scoreGain * (100 + gamblerBonusPercent) / 100;
|
||
expGain = expGain * (100 + gamblerBonusPercent) / 100;
|
||
}
|
||
|
||
rogueStats.comboChain++;
|
||
if (rogueStats.comboBonusStacks > 0 && rogueStats.comboChain > 1)
|
||
{
|
||
scoreGain += (rogueStats.comboChain - 1) * rogueStats.comboBonusStacks * 50;
|
||
}
|
||
|
||
if (rogueStats.rageStackLevel > 0 && rogueStats.comboChain > 1)
|
||
{
|
||
int rageBonusPercent = (rogueStats.comboChain - 1) * 15;
|
||
if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0)
|
||
{
|
||
rageBonusPercent += (rogueStats.comboChain - 1) * 10;
|
||
}
|
||
scoreGain += scoreGain * rageBonusPercent / 100;
|
||
}
|
||
|
||
rogueStats.totalLinesCleared += linesCleared;
|
||
rogueStats.score += scoreGain;
|
||
rogueStats.exp += expGain;
|
||
|
||
if (rogueStats.feverLevel > 0)
|
||
{
|
||
rogueStats.feverLineCharge += linesCleared;
|
||
while (rogueStats.feverLineCharge >= kFeverLineThreshold)
|
||
{
|
||
rogueStats.feverLineCharge -= kFeverLineThreshold;
|
||
rogueStats.feverTicks = kFeverDurationTicks;
|
||
currentFallInterval = GetRogueFallInterval();
|
||
SetFeedbackMessage(_T("\u8fdb\u5165\u72c2\u70ed\u6a21\u5f0f"), _T("\u672a\u6765 12 \u79d2\u5f97\u5206 / EXP x2\uff0c\u4e0b\u843d\u66f4\u6162\uff0c\u7279\u6b8a\u65b9\u5757\u66f4\u6613\u51fa\u73b0\u3002"), 12);
|
||
}
|
||
}
|
||
|
||
if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0)
|
||
{
|
||
rogueStats.feverTicks += 1;
|
||
if (linesCleared == 4)
|
||
{
|
||
rogueStats.feverTicks += 3;
|
||
}
|
||
if (rogueStats.feverTicks > 24)
|
||
{
|
||
rogueStats.feverTicks = 24;
|
||
}
|
||
currentFallInterval = GetRogueFallInterval();
|
||
}
|
||
|
||
if (rogueStats.extremePlayerLevel > 0 && linesCleared == 4)
|
||
{
|
||
rogueStats.extremeSlowTicks = 5;
|
||
rogueStats.extremeDangerTicks = 30;
|
||
currentFallInterval = GetRogueFallInterval();
|
||
SetFeedbackMessage(_T("\u6781\u9650\u73a9\u5bb6\u89e6\u53d1"), _T("\u56db\u6d88\u6210\u529f\uff0c\u63a5\u4e0b\u6765 5 \u79d2\u77ed\u6682\u7f13\u901f\u8865\u4f4d\u3002"), 12);
|
||
}
|
||
|
||
if (rogueStats.screenBombLevel > 0)
|
||
{
|
||
rogueStats.screenBombCharge += linesCleared;
|
||
while (rogueStats.screenBombCharge >= kScreenBombLineThreshold)
|
||
{
|
||
rogueStats.screenBombCharge -= kScreenBombLineThreshold;
|
||
rogueStats.screenBombCount++;
|
||
SetFeedbackMessage(_T("\u6e05\u5c4f\u70b8\u5f39\u5c31\u7eea"), _T("\u5df2\u83b7\u5f97 1 \u6b21\u6e05\u9664\u5e95\u90e8 5 \u884c\u7684\u673a\u4f1a\u3002"), 12);
|
||
}
|
||
}
|
||
|
||
if (rogueStats.chainBlastLevel > 0)
|
||
{
|
||
int chainBlastCells = 0;
|
||
for (int i = 0; i < linesCleared; i++)
|
||
{
|
||
chainBlastCells += TriggerChainBlast(GetRoguePlayableHeight() - 1 - i);
|
||
}
|
||
|
||
if (chainBlastCells > 0)
|
||
{
|
||
int chainBlastScore = 0;
|
||
int chainBlastExp = 0;
|
||
AwardRogueSkillClearRewards(chainBlastCells, chainBlastScore, chainBlastExp, false);
|
||
ApplyBoardGravity();
|
||
|
||
TCHAR blastDetail[128];
|
||
_stprintf_s(
|
||
blastDetail,
|
||
_T("余波炸裂,清除 %d 格 +%d 分 +%d EXP"),
|
||
chainBlastCells,
|
||
chainBlastScore,
|
||
chainBlastExp);
|
||
SetFeedbackMessage(_T("连锁火花"), blastDetail, 12);
|
||
}
|
||
}
|
||
|
||
if (linesCleared >= 3 && rogueStats.thunderTetrisLevel > 0)
|
||
{
|
||
int thunderRowsCleared = 0;
|
||
Point thunderCells[20] = {};
|
||
for (int i = 0; i < 2; i++)
|
||
{
|
||
int randomRow = rand() % GetRoguePlayableHeight();
|
||
for (int x = 0; x < nGameWidth; x++)
|
||
{
|
||
if (workRegion[randomRow][x] != 0)
|
||
{
|
||
if (thunderRowsCleared < 20)
|
||
{
|
||
thunderCells[thunderRowsCleared].x = x;
|
||
thunderCells[thunderRowsCleared].y = randomRow;
|
||
}
|
||
workRegion[randomRow][x] = 0;
|
||
thunderRowsCleared++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (thunderRowsCleared > 0)
|
||
{
|
||
TriggerCellClearEffect(thunderCells, thunderRowsCleared < 20 ? thunderRowsCleared : 20, true);
|
||
int thunderScore = 0;
|
||
int thunderExp = 0;
|
||
AwardRogueSkillClearRewards(thunderRowsCleared, thunderScore, thunderExp, false);
|
||
ApplyBoardGravity();
|
||
SetFeedbackMessage(_T("雷霆四消"), _T("雷击落下,额外清理了 2 行范围内的方块。"), 12);
|
||
}
|
||
}
|
||
|
||
if (linesCleared >= 3 && rogueStats.thunderLaserLevel > 0)
|
||
{
|
||
int laserCellsCleared = 0;
|
||
for (int i = 0; i < 2; i++)
|
||
{
|
||
laserCellsCleared += ClearColumnAt(rand() % nGameWidth);
|
||
}
|
||
|
||
if (laserCellsCleared > 0)
|
||
{
|
||
int laserScore = 0;
|
||
int laserExp = 0;
|
||
AwardRogueSkillClearRewards(laserCellsCleared, laserScore, laserExp, false);
|
||
ApplyBoardGravity();
|
||
|
||
TCHAR thunderLaserDetail[128];
|
||
_stprintf_s(
|
||
thunderLaserDetail,
|
||
_T("雷光穿透,清除 %d 格 +%d 分 +%d EXP"),
|
||
laserCellsCleared,
|
||
laserScore,
|
||
laserExp);
|
||
SetFeedbackMessage(_T("雷霆棱镜"), thunderLaserDetail, 12);
|
||
}
|
||
}
|
||
|
||
if (rogueStats.sweeperLevel > 0)
|
||
{
|
||
rogueStats.sweeperCharge += linesCleared;
|
||
int sweeperThreshold = GetSweeperThreshold();
|
||
int clearedBySweeper = 0;
|
||
|
||
while (rogueStats.sweeperCharge >= sweeperThreshold)
|
||
{
|
||
rogueStats.sweeperCharge -= sweeperThreshold;
|
||
DeleteOneLine(GetRoguePlayableHeight() - 1);
|
||
clearedBySweeper++;
|
||
}
|
||
|
||
if (clearedBySweeper > 0)
|
||
{
|
||
TCHAR sweeperDetail[128];
|
||
_stprintf_s(
|
||
sweeperDetail,
|
||
_T("清扫底部 %d 行 充能 %d/%d"),
|
||
clearedBySweeper,
|
||
rogueStats.sweeperCharge,
|
||
sweeperThreshold);
|
||
SetFeedbackMessage(_T("底线清道夫"), sweeperDetail, 12);
|
||
}
|
||
}
|
||
|
||
int levelUps = ApplyLevelProgress(rogueStats);
|
||
upgradeUiState.pendingCount += levelUps;
|
||
tScore = rogueStats.score;
|
||
|
||
if (levelUps > 0)
|
||
{
|
||
int shockwaveRows = 0;
|
||
if (rogueStats.evolutionImpactLevel > 0)
|
||
{
|
||
shockwaveRows = 3;
|
||
rogueStats.feverTicks = kFeverDurationTicks;
|
||
currentFallInterval = GetRogueFallInterval();
|
||
}
|
||
else if (rogueStats.upgradeShockwaveLevel > 0)
|
||
{
|
||
shockwaveRows = 2;
|
||
}
|
||
|
||
if (shockwaveRows > 0)
|
||
{
|
||
pendingUpgradeShockwaveRows = shockwaveRows;
|
||
pendingEvolutionImpactShockwave = rogueStats.evolutionImpactLevel > 0;
|
||
}
|
||
|
||
TCHAR feedbackTitle[64];
|
||
TCHAR feedbackDetail[128];
|
||
_stprintf_s(feedbackTitle, _T("灵感涌现 x%d"), levelUps);
|
||
_stprintf_s(
|
||
feedbackDetail,
|
||
_T("等级 Lv.%d EXP %d/%d 选择新的强化"),
|
||
rogueStats.level,
|
||
rogueStats.exp,
|
||
rogueStats.requiredExp);
|
||
SetFeedbackMessage(feedbackTitle, feedbackDetail, 10);
|
||
|
||
OpenUpgradeMenu();
|
||
}
|
||
|
||
currentFallInterval = GetRogueFallInterval();
|
||
}
|
||
|
||
/**
|
||
* @brief 应用命运轮盘的诅咒,提高下一次升级所需经验。
|
||
*/
|
||
static void ApplyDestinyCurse()
|
||
{
|
||
rogueStats.requiredExp = rogueStats.requiredExp * 125 / 100;
|
||
if (rogueStats.requiredExp < 10)
|
||
{
|
||
rogueStats.requiredExp = 10;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 在升级选择结束后处理延迟触发的升级冲击波。
|
||
*/
|
||
static void ResolvePendingUpgradeShockwave()
|
||
{
|
||
if (pendingUpgradeShockwaveRows <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int shockwaveRows = pendingUpgradeShockwaveRows;
|
||
bool evolutionImpact = pendingEvolutionImpactShockwave;
|
||
pendingUpgradeShockwaveRows = 0;
|
||
pendingEvolutionImpactShockwave = false;
|
||
|
||
int clearedRows = TriggerUpgradeShockwave(shockwaveRows);
|
||
int effectRows[4] = {};
|
||
int effectRowCount = clearedRows;
|
||
if (effectRowCount > 4)
|
||
{
|
||
effectRowCount = 4;
|
||
}
|
||
for (int i = 0; i < effectRowCount; i++)
|
||
{
|
||
effectRows[i] = GetRoguePlayableHeight() - 1 - i;
|
||
}
|
||
TriggerLineClearEffect(effectRows, effectRowCount, clearedRows);
|
||
int scoreGain = 0;
|
||
int expGain = 0;
|
||
AwardRogueSkillClearRewards(clearedRows * nGameWidth, scoreGain, expGain, true);
|
||
|
||
TCHAR shockwaveDetail[128];
|
||
if (evolutionImpact)
|
||
{
|
||
_stprintf_s(
|
||
shockwaveDetail,
|
||
_T("进化能量爆发,清除底部 %d 行 +%d 分 +%d EXP。"),
|
||
clearedRows,
|
||
scoreGain,
|
||
expGain);
|
||
SetFeedbackMessage(_T("进化冲击"), shockwaveDetail, 14);
|
||
}
|
||
else
|
||
{
|
||
_stprintf_s(shockwaveDetail, _T("灵感涌现后,冲击波清除底部 %d 行 +%d 分 +%d EXP。"), clearedRows, scoreGain, expGain);
|
||
SetFeedbackMessage(_T("升级冲击波"), shockwaveDetail, 12);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 重置 Rogue 模式中等待播放的特殊视觉事件。
|
||
*/
|
||
void ResetPendingRogueVisualEvents()
|
||
{
|
||
pendingUpgradeShockwaveRows = 0;
|
||
pendingEvolutionImpactShockwave = false;
|
||
}
|
||
|
||
/**
|
||
* @brief 在 Rogue 模式中打开升级强化选择界面。
|
||
*/
|
||
void OpenUpgradeMenu()
|
||
{
|
||
if (currentMode != MODE_ROGUE || upgradeUiState.pendingCount <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
FillUpgradeOptions();
|
||
currentScreen = SCREEN_UPGRADE;
|
||
}
|
||
|
||
/**
|
||
* @brief 确认升级菜单中的选择并应用对应强化效果。
|
||
*/
|
||
void ConfirmUpgradeSelection()
|
||
{
|
||
if (currentScreen != SCREEN_UPGRADE || upgradeUiState.optionCount <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (upgradeUiState.picksRemaining > 1)
|
||
{
|
||
if (upgradeUiState.markedCount != upgradeUiState.picksRemaining)
|
||
{
|
||
SetFeedbackMessage(_T("选择未完成"), _T("请先标记足够的强化,再按 Enter 确认。"), 10);
|
||
return;
|
||
}
|
||
|
||
TCHAR feedbackTitle[64] = _T("获得强化");
|
||
TCHAR feedbackDetail[128] = _T("");
|
||
int appliedSelections = 0;
|
||
|
||
for (int optionIndex = 0; optionIndex < upgradeUiState.optionCount; optionIndex++)
|
||
{
|
||
if (!upgradeUiState.marked[optionIndex])
|
||
{
|
||
continue;
|
||
}
|
||
|
||
UpgradeOption selectedOption = upgradeUiState.options[optionIndex];
|
||
int applyCount = 1;
|
||
TCHAR gamblerSuffix[64] = _T("");
|
||
|
||
if (currentMode == MODE_ROGUE && rogueStats.gamblerLevel > 0)
|
||
{
|
||
int gamblerChance = 20 + (rogueStats.gamblerLevel - 1) * 5;
|
||
if (gamblerChance > 40)
|
||
{
|
||
gamblerChance = 40;
|
||
}
|
||
|
||
int roll = rand() % 100;
|
||
if (roll < gamblerChance)
|
||
{
|
||
applyCount = 2;
|
||
_stprintf_s(gamblerSuffix, _T("(翻倍)"));
|
||
}
|
||
else if (roll >= 100 - gamblerChance)
|
||
{
|
||
applyCount = 0;
|
||
_stprintf_s(gamblerSuffix, _T("(落空)"));
|
||
}
|
||
}
|
||
|
||
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++;
|
||
}
|
||
|
||
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();
|
||
return;
|
||
}
|
||
|
||
UpgradeOption selectedOption = upgradeUiState.options[upgradeUiState.selectedIndex];
|
||
int applyCount = 1;
|
||
TCHAR gamblerSuffix[64] = _T("");
|
||
|
||
if (currentMode == MODE_ROGUE && rogueStats.gamblerLevel > 0)
|
||
{
|
||
int gamblerChance = 20 + (rogueStats.gamblerLevel - 1) * 5;
|
||
if (gamblerChance > 40)
|
||
{
|
||
gamblerChance = 40;
|
||
}
|
||
|
||
int roll = rand() % 100;
|
||
if (roll < gamblerChance)
|
||
{
|
||
applyCount = 2;
|
||
_stprintf_s(gamblerSuffix, _T(" 赌徒命中:效果翻倍"));
|
||
}
|
||
else if (roll >= 100 - gamblerChance)
|
||
{
|
||
applyCount = 0;
|
||
_stprintf_s(gamblerSuffix, _T(" 赌徒失手:本次落空"));
|
||
}
|
||
}
|
||
|
||
ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount);
|
||
upgradeUiState.totalChosenCount++;
|
||
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);
|
||
}
|
||
|
||
if (selectedOption.cursed)
|
||
{
|
||
ApplyDestinyCurse();
|
||
_stprintf_s(
|
||
feedbackDetail + lstrlen(feedbackDetail),
|
||
128 - lstrlen(feedbackDetail),
|
||
_T(" 诅咒缠身:下一次升级所需 EXP 提高 25%。"));
|
||
}
|
||
|
||
SetFeedbackMessage(feedbackTitle, feedbackDetail, 12);
|
||
|
||
if (upgradeUiState.picksRemaining > 0)
|
||
{
|
||
upgradeUiState.picksRemaining--;
|
||
}
|
||
|
||
for (int i = upgradeUiState.selectedIndex; i + 1 < upgradeUiState.optionCount; i++)
|
||
{
|
||
upgradeUiState.options[i] = upgradeUiState.options[i + 1];
|
||
}
|
||
if (upgradeUiState.optionCount > 0)
|
||
{
|
||
upgradeUiState.optionCount--;
|
||
}
|
||
|
||
if (upgradeUiState.optionCount > 0 && upgradeUiState.selectedIndex >= upgradeUiState.optionCount)
|
||
{
|
||
upgradeUiState.selectedIndex = upgradeUiState.optionCount - 1;
|
||
}
|
||
|
||
if (upgradeUiState.picksRemaining > 0 && upgradeUiState.optionCount > 0)
|
||
{
|
||
currentScreen = SCREEN_UPGRADE;
|
||
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();
|
||
}
|
||
|
||
/**
|
||
* @brief 执行 Hold 操作,在备用仓与当前方块之间交换。
|
||
*/
|
||
void HoldCurrentPiece()
|
||
{
|
||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||
{
|
||
return;
|
||
}
|
||
if (rogueStats.holdUnlocked == 0)
|
||
{
|
||
SetFeedbackMessage(_T("备用仓未开启"), _T("获得备用仓强化后,才能用 C 或 Shift 暂存方块。"), 10);
|
||
return;
|
||
}
|
||
if (holdUsedThisTurn)
|
||
{
|
||
SetFeedbackMessage(_T("备用仓冷却中"), _T("每个方块落地前只能暂存一次。"), 10);
|
||
return;
|
||
}
|
||
|
||
int previousHoldType = holdType;
|
||
holdType = type;
|
||
state = 0;
|
||
holdUsedThisTurn = true;
|
||
|
||
if (previousHoldType < 0)
|
||
{
|
||
type = ConsumeNextType();
|
||
nType = nextTypes[0];
|
||
RollCurrentPieceSpecialFlags(true);
|
||
}
|
||
else
|
||
{
|
||
type = previousHoldType;
|
||
RollCurrentPieceSpecialFlags(false);
|
||
}
|
||
|
||
point = GetSpawnPoint(type);
|
||
target = point;
|
||
if (currentMode == MODE_ROGUE && rogueStats.controlMasterLevel > 0)
|
||
{
|
||
rogueStats.holdSlowTicks = 4;
|
||
currentFallInterval = GetRogueFallInterval();
|
||
}
|
||
|
||
if (!IsPiecePlacementValid(type, state, point))
|
||
{
|
||
gameOverFlag = true;
|
||
SetFeedbackMessage(_T("交换失败"), _T("换出的方块无处落位,战局崩塌。"), 12);
|
||
return;
|
||
}
|
||
|
||
ComputeTarget();
|
||
|
||
if (previousHoldType < 0)
|
||
{
|
||
SetFeedbackMessage(_T("已存入备用仓"), _T("当前方块已暂存,新的方块进入战场。"), 10);
|
||
}
|
||
else
|
||
{
|
||
SetFeedbackMessage(_T("备用仓交换"), _T("已换出备用方块。"), 10);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 响应玩家主动使用清屏炸弹的操作。
|
||
*/
|
||
void UseScreenBomb()
|
||
{
|
||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||
{
|
||
return;
|
||
}
|
||
if (rogueStats.screenBombLevel <= 0)
|
||
{
|
||
SetFeedbackMessage(_T("清屏炸弹未获得"), _T("掌握清屏炸弹后,才能按 X 主动引爆。"), 10);
|
||
return;
|
||
}
|
||
if (rogueStats.screenBombCount <= 0)
|
||
{
|
||
SetFeedbackMessage(_T("清屏炸弹未就绪"), _T("继续消行充能,充满后即可使用。"), 10);
|
||
return;
|
||
}
|
||
|
||
rogueStats.screenBombCount--;
|
||
int clearedCells = TriggerScreenBomb();
|
||
ApplyBoardGravity();
|
||
int scoreGain = 0;
|
||
int expGain = 0;
|
||
AwardRogueSkillClearRewards(clearedCells, scoreGain, expGain, true);
|
||
|
||
TCHAR detail[128];
|
||
_stprintf_s(
|
||
detail,
|
||
_T("炸开底部 5 行,清除 %d 格 +%d 分 +%d EXP"),
|
||
clearedCells,
|
||
scoreGain,
|
||
expGain);
|
||
SetFeedbackMessage(_T("清屏炸弹引爆"), detail, 12);
|
||
|
||
currentFallInterval = GetRogueFallInterval();
|
||
ComputeTarget();
|
||
}
|
||
|
||
/**
|
||
* @brief 响应玩家主动释放黑洞的操作。
|
||
*/
|
||
void UseBlackHole()
|
||
{
|
||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||
{
|
||
return;
|
||
}
|
||
if (rogueStats.blackHoleLevel <= 0)
|
||
{
|
||
SetFeedbackMessage(_T("黑洞尚未觉醒"), _T("获得黑洞奇点后,才能按 Z 释放。"), 10);
|
||
return;
|
||
}
|
||
if (rogueStats.blackHoleCharges <= 0)
|
||
{
|
||
SetFeedbackMessage(_T("黑洞能量不足"), _T("没有可释放的黑洞次数。"), 10);
|
||
return;
|
||
}
|
||
|
||
int clearedCells = TriggerBlackHole();
|
||
if (clearedCells <= 0)
|
||
{
|
||
SetFeedbackMessage(_T("黑洞无处落点"), _T("棋盘上没有可吞噬的固定方块。"), 10);
|
||
return;
|
||
}
|
||
|
||
rogueStats.blackHoleCharges--;
|
||
|
||
ApplyBoardGravity();
|
||
int scoreGain = 0;
|
||
int expGain = 0;
|
||
AwardRogueSkillClearRewards(clearedCells, scoreGain, expGain, true);
|
||
|
||
if (rogueStats.voidCoreLevel > 0)
|
||
{
|
||
rogueStats.pendingRainbowPieceCount++;
|
||
}
|
||
|
||
TCHAR detail[128];
|
||
if (rogueStats.voidCoreLevel > 0)
|
||
{
|
||
_stprintf_s(
|
||
detail,
|
||
_T("吞噬最多的一种方块,清除 %d 格 +%d 分 +%d EXP 并召来 1 个彩虹方块"),
|
||
clearedCells,
|
||
scoreGain,
|
||
expGain);
|
||
}
|
||
else
|
||
{
|
||
_stprintf_s(
|
||
detail,
|
||
_T("吞噬最多的一种方块,清除 %d 格 +%d 分 +%d EXP"),
|
||
clearedCells,
|
||
scoreGain,
|
||
expGain);
|
||
}
|
||
SetFeedbackMessage(_T("黑洞展开"), detail, 12);
|
||
|
||
currentFallInterval = GetRogueFallInterval();
|
||
ComputeTarget();
|
||
}
|
||
|
||
/**
|
||
* @brief 响应玩家主动使用空中换形,将当前方块重塑为 I 块。
|
||
*/
|
||
void UseAirReshape()
|
||
{
|
||
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
|
||
{
|
||
return;
|
||
}
|
||
if (rogueStats.reshapeLevel <= 0)
|
||
{
|
||
SetFeedbackMessage(_T("换形术未掌握"), _T("获得空中换形后,才能按 V 重塑方块。"), 10);
|
||
return;
|
||
}
|
||
if (rogueStats.reshapeCharges <= 0)
|
||
{
|
||
SetFeedbackMessage(_T("换形能量不足"), _T("没有可使用的换形次数。"), 10);
|
||
return;
|
||
}
|
||
|
||
const int targetType = 0;
|
||
const int candidateStates[2] = { 0, 1 };
|
||
const int candidateOffsets[5] = { 0, -1, 1, -2, 2 };
|
||
Point originalPoint = point;
|
||
int originalType = type;
|
||
int originalState = state;
|
||
bool transformed = false;
|
||
|
||
for (int stateIndex = 0; stateIndex < 2 && !transformed; stateIndex++)
|
||
{
|
||
int nextState = candidateStates[stateIndex];
|
||
for (int offsetIndex = 0; offsetIndex < 5; offsetIndex++)
|
||
{
|
||
Point candidatePoint = originalPoint;
|
||
candidatePoint.x += candidateOffsets[offsetIndex];
|
||
|
||
if (IsPiecePlacementValid(targetType, nextState, candidatePoint))
|
||
{
|
||
type = targetType;
|
||
state = nextState;
|
||
point = candidatePoint;
|
||
transformed = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!transformed)
|
||
{
|
||
type = originalType;
|
||
state = originalState;
|
||
point = originalPoint;
|
||
SetFeedbackMessage(_T("换形失败"), _T("空间不足,无法重塑为 I 块。"), 10);
|
||
return;
|
||
}
|
||
|
||
rogueStats.reshapeCharges--;
|
||
|
||
TCHAR detail[128];
|
||
_stprintf_s(detail, _T("方块已重塑为 I 块,剩余 %d 次。"), rogueStats.reshapeCharges);
|
||
SetFeedbackMessage(_T("空中换形"), detail, 12);
|
||
ComputeTarget();
|
||
}
|