From 7d5f26435b7dcbba06bbcb31a5821b5c53161556 Mon Sep 17 00:00:00 2001 From: Chomper9981 Date: Tue, 7 Apr 2026 16:48:54 +0700 Subject: [PATCH] Add anti spam quick bar --- Assets/Scripts/CECHostPlayer.Skill.cs | 75 ++++++++++++++++++++++++++- Docs/ApplySkillShortcut-AntiSpam.md | 30 +++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 Docs/ApplySkillShortcut-AntiSpam.md diff --git a/Assets/Scripts/CECHostPlayer.Skill.cs b/Assets/Scripts/CECHostPlayer.Skill.cs index 0f87543568..72d1d6458e 100644 --- a/Assets/Scripts/CECHostPlayer.Skill.cs +++ b/Assets/Scripts/CECHostPlayer.Skill.cs @@ -17,6 +17,70 @@ namespace BrewMonster { public partial class CECHostPlayer { + /// + /// Anti-spam gate for (client-side input flood control). + /// + /// Purpose: + /// - Prevents rapid repeated shortcut-trigger calls (mouse/touch spam, key repeat, UI double-fire) + /// from spamming trace/cast requests and causing unstable client behavior. + /// + /// + /// Design: + /// - A small global minimum interval (ANY) blocks ultra-high-frequency bursts across all skills. + /// - A per-skill minimum interval (PER_SKILL) blocks repeatedly pressing the same skill rapidly. + /// - Exception: "press again to release a charging skill immediately" is allowed (not blocked), + /// so charge mechanics remain responsive. + /// + /// + /// Notes: + /// - Uses so throttling remains consistent under timeScale changes. + /// - This is client-side hygiene only; server cooldown/validation is still authoritative. + /// + /// + /// + /// ApplySkillShortcut 的反刷 (客户端输入洪泛控制) + /// + /// 目的: + /// - 防止鼠标/触摸狂点、按键连发、UI 重复触发导致的频繁施法请求,避免客户端不稳定。 + /// + /// 设计: + /// - ANY:对所有技能共用的极短间隔,过滤超高频 burst。 + /// - PER_SKILL:对同一技能的短间隔,过滤同技能连点。 + /// - 例外:充能技能“再次按下立即释放”不会被拦截,保证充能手感。 + /// + /// 备注: + /// - 使用 Time.unscaledTime,避免 timeScale 变化导致反刷失效。 + /// - 仅客户端防抖/限流,服务器校验与冷却依然为准。 + /// + private const float APPLY_SKILL_SHORTCUT_MIN_INTERVAL_ANY = 0.05f; + private const float APPLY_SKILL_SHORTCUT_MIN_INTERVAL_PER_SKILL = 0.12f; + + private float _applySkillShortcut_lastAnyTime = -999f; + private readonly Dictionary _applySkillShortcut_lastSkillTime = new Dictionary(64); + + private bool ShouldBlockApplySkillShortcutSpam(int idSkill, bool allowChargeRelease) + { + // Use unscaled time so spam protection still works during slow-mo/timeScale changes. + // 使用 unscaledTime,避免 timeScale 变化导致反刷失效 + float now = Time.unscaledTime; + + if (now - _applySkillShortcut_lastAnyTime < APPLY_SKILL_SHORTCUT_MIN_INTERVAL_ANY) + return true; + + if (!allowChargeRelease) + { + if (_applySkillShortcut_lastSkillTime.TryGetValue(idSkill, out float lastSkillTime)) + { + if (now - lastSkillTime < APPLY_SKILL_SHORTCUT_MIN_INTERVAL_PER_SKILL) + return true; + } + } + + _applySkillShortcut_lastAnyTime = now; + _applySkillShortcut_lastSkillTime[idSkill] = now; + return false; + } + public struct SkillShortCutConfig { public int setNum; @@ -506,8 +570,15 @@ namespace BrewMonster //// If we press a chargeable skill again when it's being charged, //// we cast it out at once - if (IsSpellingMagic() && m_pCurSkill != null && m_pCurSkill.IsCharging() && - m_pCurSkill.GetSkillID() == pSkill.GetSkillID()) + bool allowChargeRelease = IsSpellingMagic() && m_pCurSkill != null && m_pCurSkill.IsCharging() && + m_pCurSkill.GetSkillID() == pSkill.GetSkillID(); + + // Anti-spam: block rapid-fire shortcut calls (except charge-release press) + // 反刷:阻止短时间内狂点快捷键(充能释放第二次按下除外) + if (ShouldBlockApplySkillShortcutSpam(idSkill, allowChargeRelease)) + return false; + + if (allowChargeRelease) { m_pCurSkill.EndCharging(); UnityGameSession.c2s_SendCmdContinueAction(); diff --git a/Docs/ApplySkillShortcut-AntiSpam.md b/Docs/ApplySkillShortcut-AntiSpam.md new file mode 100644 index 0000000000..061b10e66b --- /dev/null +++ b/Docs/ApplySkillShortcut-AntiSpam.md @@ -0,0 +1,30 @@ +# ApplySkillShortcut Anti-spam + +## What it is +`CECHostPlayer.ApplySkillShortcut(...)` has a small client-side anti-spam gate to prevent input floods (touch/mouse spam, key repeat, UI double-fire) from generating excessive trace/cast work. + +This is **not** a replacement for server cooldowns or validation. It’s client-side hygiene to keep the client stable and reduce redundant requests. + +## How it works +The gate is implemented inside `Assets/Scripts/CECHostPlayer.Skill.cs`: + +- **Global minimum interval (ANY)**: blocks ultra-high-frequency bursts across *all* skills. +- **Per-skill minimum interval (PER_SKILL)**: blocks rapidly pressing the *same* skill repeatedly. +- **Charge exception**: if the player is charging a skill and presses the **same** skill again to release immediately, that press is **allowed** (so charging remains responsive). + +The timing source is `Time.unscaledTime` so the throttle stays consistent even if `Time.timeScale` changes. + +## Tunables +In `CECHostPlayer.Skill.cs`: + +- `APPLY_SKILL_SHORTCUT_MIN_INTERVAL_ANY` (default `0.05s`) +- `APPLY_SKILL_SHORTCUT_MIN_INTERVAL_PER_SKILL` (default `0.12s`) + +If you need to tweak feel: + +- Lower **ANY** carefully; it protects against accidental double-fire from UI/input systems. +- Lower **PER_SKILL** if skills feel “eaten” during fast tapping, but keep it high enough to block spam. + +## Why the charge exception exists +Chargeable skills commonly use a second press to “cast now”. Blocking that second press makes charging feel broken, so the anti-spam explicitly does not block it. +