fix: delay after fire

This commit is contained in:
2026-07-08 19:20:30 +03:30
parent 6b9b9a0f3a
commit 2496580e86
3 changed files with 217 additions and 35 deletions
@@ -50,6 +50,14 @@ class HokmGame extends FlameGame {
List<String> _prevTrick = const [];
bool _highlightedTrick = false; // آیا کارتِ برنده‌ی دستِ کامل هایلایت شده (یک‌بار)
int _bestTrumpRank = 0; // بالاترین رتبه‌ی حکمِ بازی‌شده در دستِ جاری (برای افکتِ بریدن)
int _cutCombo = 0; // شمارِ بریدنِ پیاپیِ سریع (روبُریدن‌ها) برای افکتِ آتش
double _lastCutAt = -999; // زمانِ آخرین بریدن (برای پنجره‌ی کمبو)
// «مکثِ آتش»: پس از افکتِ آتشِ کمبو، صحنه کوتاه فریز می‌شود تا لحظه دیده شود؛
// وضعیت‌های ورودی بافر می‌شوند و پس از مکث اعمال می‌گردند (کاربر و بات فرقی ندارند).
bool _cutHold = false;
double _cutHoldUntil = 0;
GameState? _pendingState;
static const double _cutHoldSec = 1.0;
int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن
String _prevHandSig = ''; // امضای دست؛ برای تشخیصِ بُرِ مجدد (تغییرِ ۵ کارت در فازِ حکم)
bool _silent = false; // هنگام خروج از میز، دیگر صدایی پخش نشود
@@ -91,39 +99,12 @@ class HokmGame extends FlameGame {
_sub = cubit.stream.listen((ui) {
if (ui.state == null) return;
final s = ui.state!;
_checkTrickSounds(s);
// شروعِ دستِ جدید (پخشِ کارت): انیمیشن و صدای بُر زدن.
// دستِ اول تا پایانِ نمایشِ «جستجوی حریف» به تعویق می‌افتد تا انیمیشن
// زیرِ اوورلی پنهان نشود و صدا با تصویر هماهنگ باشد.
if (_prevHandSize == 0 && s.yourHand.isNotEmpty && !_silent) {
if (_introActive) {
_pendingShuffle = true;
} else {
_shuffleNow();
}
// در حینِ «مکثِ آتش»، آخرین وضعیت را بافر کن و بعد از مکث اعمالش کن.
if (_cutHold) {
_pendingState = s;
return;
}
// بُرِ مجدد: هنوز در فازِ حکم هستیم اما ۵ کارتِ حاکم عوض شده ⇒ انیمیشنِ بُر دوباره.
final sig = (s.yourHand.toList()..sort()).join(',');
if (!_silent &&
!_introActive &&
s.phase == 'choose_trump' &&
s.trump == null &&
_prevHandSize > 0 &&
_prevHandSig.isNotEmpty &&
sig != _prevHandSig) {
_shuffleNow();
}
_prevHandSig = sig;
// لحظه‌ی انتخابِ حکم: صدا + نمایشِ بزرگِ حکم در مرکز.
if (_prevTrump == null && s.trump != null && !_silent) {
AppSounds.selectHokm();
_spawnTrumpReveal(s.trump!);
_dealAllNow(); // پخشِ بقیه‌ی کارت‌ها (بازی تا پایانِ آن قفل است)
}
_prevTrump = s.trump;
_prevHandSize = s.yourHand.length;
_s = s;
_relayout();
_applyState(s);
});
if (cubit.state.state != null) {
_prevTrick = cubit.state.state!.trick.map((t) => t.card).toList();
@@ -135,6 +116,44 @@ class HokmGame extends FlameGame {
}
}
/// اعمالِ یک وضعیتِ بازی روی صحنه (صداها/انیمیشن‌ها + چیدمانِ دوباره). در حالتِ
/// عادی مستقیم از استریم صدا زده می‌شود؛ در «مکثِ آتش» پس از پایانِ مکث.
void _applyState(GameState s) {
_checkTrickSounds(s);
// شروعِ دستِ جدید (پخشِ کارت): انیمیشن و صدای بُر زدن.
// دستِ اول تا پایانِ نمایشِ «جستجوی حریف» به تعویق می‌افتد تا انیمیشن
// زیرِ اوورلی پنهان نشود و صدا با تصویر هماهنگ باشد.
if (_prevHandSize == 0 && s.yourHand.isNotEmpty && !_silent) {
if (_introActive) {
_pendingShuffle = true;
} else {
_shuffleNow();
}
}
// بُرِ مجدد: هنوز در فازِ حکم هستیم اما ۵ کارتِ حاکم عوض شده ⇒ انیمیشنِ بُر دوباره.
final sig = (s.yourHand.toList()..sort()).join(',');
if (!_silent &&
!_introActive &&
s.phase == 'choose_trump' &&
s.trump == null &&
_prevHandSize > 0 &&
_prevHandSig.isNotEmpty &&
sig != _prevHandSig) {
_shuffleNow();
}
_prevHandSig = sig;
// لحظه‌ی انتخابِ حکم: صدا + نمایشِ بزرگِ حکم در مرکز.
if (_prevTrump == null && s.trump != null && !_silent) {
AppSounds.selectHokm();
_spawnTrumpReveal(s.trump!);
_dealAllNow(); // پخشِ بقیه‌ی کارت‌ها (بازی تا پایانِ آن قفل است)
}
_prevTrump = s.trump;
_prevHandSize = s.yourHand.length;
_s = s;
_relayout();
}
@override
void onGameResize(Vector2 size) {
super.onGameResize(size);
@@ -176,8 +195,20 @@ class HokmGame extends FlameGame {
_bestTrumpRank = rank;
_shake = 1.0;
add(Lightning());
// کمبوِ بریدن: روبُریدن‌های سریعِ پیاپی (پنجره‌ی ~۶ ثانیه) را می‌شمارد.
_cutCombo = (_t - _lastCutAt < 6.0) ? _cutCombo + 1 : 1;
_lastCutAt = _t;
if (!AppSettings.I.lowGraphics.value) {
add(CutBurst()); // فلاش + شوک‌ویو + جرقه (کم‌مصرف: خاموش)
// سه بریدنِ پیاپی یا بیشتر ⇒ افکتِ آتش (هرچه کمبو بیشتر، شعله بلندتر).
if (_cutCombo >= 3) {
_shake = 1.4; // تکانِ قوی‌تر برای اوجِ کمبو
add(FireBurst(intensity: _cutCombo));
// مکثِ کوتاه پس از آتش تا لحظه دیده شود؛ کارتِ بعدی (کاربر یا بات)
// تا پایانِ مکث اعمال نمی‌شود.
_cutHold = true;
_cutHoldUntil = _t + _cutHoldSec;
}
}
AppSounds.cut(); // بریدن با حکم (شمشیر)
HapticFeedback.heavyImpact(); // ضربه‌ی لمسیِ بُرِش با حکم
@@ -193,6 +224,8 @@ class HokmGame extends FlameGame {
/// قطعِ صداهای بازی هنگام خروج از میز (جلوگیری از پخشِ صدای کارت پس از ترک).
void silence() {
_silent = true;
_cutHold = false; // مکثِ آتش را لغو کن تا وضعیتِ بافرشده اعمال/رها نشود
_pendingState = null;
AppSounds.stopHeartBeat();
}
@@ -276,6 +309,19 @@ class HokmGame extends FlameGame {
void update(double dt) {
super.update(dt);
_t += dt;
// پایانِ «مکثِ آتش»: آخرین وضعیتِ بافرشده را اعمال کن (یا صحنه را تازه کن).
if (_cutHold && _t >= _cutHoldUntil) {
_cutHold = false;
final p = _pendingState;
_pendingState = null;
if (!_silent) {
if (p != null) {
_applyState(p);
} else if (_s != null) {
_relayout();
}
}
}
if (_shake > 0) _shake = math.max(0, _shake - dt * 2.2);
_updateHeartBeat(dt);
}
@@ -376,8 +422,8 @@ class HokmGame extends FlameGame {
// آیا این کارت در نوبتِ فعلی قابل بازی است (با رعایت follow-suit)؟
bool _legal(String code) {
final s = _s!;
// هنگامِ اینترو/بُر زدن/پخشِ کارت‌ها، بازیِ کارت مجاز نیست.
if (_introActive || _shuffling || _dealing) return false;
// هنگامِ اینترو/بُر زدن/پخشِ کارت‌ها یا مکثِ آتش، بازیِ کارت مجاز نیست.
if (_introActive || _shuffling || _dealing || _cutHold) return false;
if (s.phase != 'playing' || s.trickDone || !s.isMyTurn) return false;
final lead = s.leadSuit;
if (lead == null || lead.isEmpty) return true;
@@ -471,6 +517,7 @@ class HokmGame extends FlameGame {
c.size = Vector2(hw, hh);
}
c.angle = a; // چرخشِ کارت هم‌راستا با شعاعِ قوس
c.homeAngle = a; // زاویه‌ی مرجع برای بازگشت پس از کشیدنِ ناقص
c.onPlay = legal ? () => cubit.playCard(code) : null;
c.dimmed = s.phase == 'playing' && s.isMyTurn && !legal;
c.home = target;