Add requirement for play skill

Merge from develop
This commit is contained in:
Tran Hai Nam
2026-05-23 18:28:21 +07:00
parent 27cc75e3b3
commit ef5689055e
6 changed files with 256 additions and 80 deletions
@@ -67,6 +67,14 @@ namespace BrewMonster
[SerializeField]
private LogPanelAnimeScene logPanelAnimeScene;
[Header("Channeling (time_type = 2)")]
[Tooltip("Stops durative/channeling skill repeat and allows other skills again.")]
[SerializeField] private Button channelCancelButton;
private Coroutine _channelingCoroutine;
private int _channelingSkillId;
private bool _isChanneling;
/// <summary>
/// World position of the draggable target marker, read by CECSkillGfxMan.get_pos_by_id.
/// 可拖动目标标记的世界坐标,由 CECSkillGfxMan.get_pos_by_id 读取。
@@ -79,11 +87,19 @@ namespace BrewMonster
private void Awake()
{
Instance = this;
<<<<<<< HEAD
SkillStubs.Init();
=======
if (channelCancelButton != null)
channelCancelButton.onClick.AddListener(CancelChanneling);
>>>>>>> 6fde1d1583 (Add requirement for play skill)
}
private void OnDestroy()
{
if (channelCancelButton != null)
channelCancelButton.onClick.RemoveListener(CancelChanneling);
StopChanneling();
if (Instance == this)
Instance = null;
}
@@ -455,10 +471,81 @@ namespace BrewMonster
return root.GetComponentInChildren<Image>();
}
/// <summary>
/// Stops channeling repeat and clears pending attack events (wire to cancel button).
/// 停止引导重复并清理挂起的攻击事件(绑定到取消按钮)。
/// </summary>
public void CancelChanneling()
{
StopChanneling();
}
private void StopChanneling()
{
_isChanneling = false;
_channelingSkillId = 0;
if (_channelingCoroutine != null)
{
StopCoroutine(_channelingCoroutine);
_channelingCoroutine = null;
}
if (player == null)
return;
player.StopSkillAttackAction();
EventBus.PublishChannel(player.GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true));
CECAttackerEvents attackerEvents =
CECAttacksMan.Instance?.FindAttackByAttacker(player.GetPlayerInfo().cid);
if (attackerEvents)
attackerEvents.Signal();
}
/// <summary>
/// Durative skill: block other skills while an attack event has not fired yet.
/// 引导技能:攻击事件尚未触发时,阻止其它技能。
/// </summary>
private bool ShouldBlockOtherSkillTrigger(CECSkill skill)
{
if (!_isChanneling || skill == null)
return false;
if (skill.GetSkillID() == _channelingSkillId)
return false;
return HasPendingChannelAttackEvent();
}
private bool HasPendingChannelAttackEvent()
{
if (!_isChanneling || player == null)
return false;
CECAttackerEvents attackerEvents =
CECAttacksMan.Instance?.FindAttackByAttacker(player.GetPlayerInfo().cid);
if (!attackerEvents)
return false;
CECAttackEvent pAttack = attackerEvents.Find(_channelingSkillId, 0);
return pAttack != null && !pAttack.m_bDoFired;
}
public void LocalCastSkill(CECSkill skill)
{
if (player == null || skill == null)
return;
if (ShouldBlockOtherSkillTrigger(skill))
{
Debug.LogWarning($"[SkillTriggerPanel] Blocked skill {skill.GetSkillID()} — channeling skill {_channelingSkillId} attack event pending.");
return;
}
if (_isChanneling)
StopChanneling();
logPanelAnimeScene.Reset();
int skillId = skill.GetSkillID();
logPanelAnimeScene.AddCopyTextButton("ID", skillId.ToString());
@@ -493,56 +580,112 @@ namespace BrewMonster
// Debug.LogWarning($"[SkillTriggerPanel] composerMan is null — VFX skipped for skill {skillId}.");
// }
// 3. After 2s, play the release / 施放起+落 attack animation chain (local-only).
// 2 秒后播放施放攻击动画链(仅本地)
StartCoroutine(DelayedPlaySkillAttackAction(player, skillId));
// 3. After delay, play the release / 施放起+落 attack animation chain (local-only).
// 延迟后播放施放攻击动画链(仅本地)
if (skill.IsDurative())
{
_isChanneling = true;
_channelingSkillId = skillId;
_channelingCoroutine = StartCoroutine(ChannelingSkillRoutine(player, skillId));
}
else
{
StartCoroutine(DelayedPlaySkillAttackAction(player, skillId, 2f, replayAttackAnim: false));
}
}
private IEnumerator DelayedPlaySkillAttackAction(CECHostPlayer hostPlayer, int skillId)
/// <summary>
/// Durative (time_type = 2): first hit at 1s, then every 2s until cancel.
/// 引导技能:1 秒首次命中,之后每 2 秒重复,直到按下取消。
/// </summary>
private IEnumerator ChannelingSkillRoutine(CECHostPlayer hostPlayer, int skillId)
{
int attackTime = 0;
yield return new WaitForSeconds(1f);
if (!_isChanneling || hostPlayer == null)
yield break;
TriggerLocalSkillAttack(hostPlayer, skillId, replayAttackAnim: false);
while (_isChanneling && hostPlayer != null)
{
yield return new WaitForSeconds(5f);
if (!_isChanneling || hostPlayer == null)
yield break;
while (_isChanneling && HasPendingChannelAttackEvent())
yield return null;
if (!_isChanneling || hostPlayer == null)
yield break;
TriggerLocalSkillAttack(hostPlayer, skillId, replayAttackAnim: true);
}
}
private IEnumerator DelayedPlaySkillAttackAction(CECHostPlayer hostPlayer, int skillId, float delaySecs, bool replayAttackAnim)
{
yield return new WaitForSeconds(delaySecs);
if (hostPlayer == null)
yield break;
TriggerLocalSkillAttack(hostPlayer, skillId, replayAttackAnim);
}
/// <summary>
/// Local-only skill hit: mirrors <see cref="CECPlayer.PlayAttackEffect"/> for debug panel.
/// When <paramref name="replayAttackAnim"/> is true (channeling tick), signal prior attack so anim replays.
/// 本地技能命中:与 PlayAttackEffect 一致;引导重复 tick 时先 Signal 再重播攻击动作。
/// </summary>
private void TriggerLocalSkillAttack(CECHostPlayer hostPlayer, int skillId, bool replayAttackAnim)
{
int attackTime = 0;
CECAttackEvent pAttack = null;
// first try to find if there is already a skill attack event in attackman
int targetId = targetMarker != null
? targetMarker.GetNPCID()
: EncodeDebugNpcId();
CECAttackerEvents attackerEvents = CECAttacksMan.Instance.FindAttackByAttacker(hostPlayer.GetPlayerInfo().cid);
if (attackerEvents)
{
pAttack = attackerEvents.Find(skillId, 0);
if (pAttack != null)
{
// Ãæ¹¥»÷µÄ·ÇµÚÒ»´ÎÉ˺¦ÏûÏ¢
pAttack.AddTarget(DEBUG_TARGET_ID, 1, 1);
goto EXIT;
if (replayAttackAnim)
attackerEvents.Signal();
else
{
// 面攻击的非第一次伤害消息 / Non-first hit on same attack event
pAttack.AddTarget(targetId, 1, 1);
if (attackTime != 0)
attackTime = 0;
return;
}
}
else
{
attackerEvents.Signal();
}
}
if (ElementSkill.IsGoblinSkill((uint)skillId) &&
ElementSkill.GetType((uint)skillId) == 2)
ElementSkill.GetType((uint)skillId) == 2)
{
pAttack = CECAttacksMan.Instance.AddSkillAttack(
hostPlayer.GetPlayerInfo().cid, hostPlayer.GetPlayerInfo().cid, hostPlayer.GetPlayerInfo().cid, hostPlayer.GetWeaponID(), skillId, 0, 0x0200, 0);
hostPlayer.GetPlayerInfo().cid, hostPlayer.GetPlayerInfo().cid, hostPlayer.GetPlayerInfo().cid,
hostPlayer.GetWeaponID(), skillId, 0, 0x0200, 0);
}
else
{
// begin a skill attack
pAttack = CECAttacksMan.Instance.AddSkillAttack(
hostPlayer.GetPlayerInfo().cid, targetMarker.GetNPCID(), targetMarker.GetNPCID(), hostPlayer.GetWeaponID(), skillId, 0, 0x0200, 0);
hostPlayer.GetPlayerInfo().cid, targetId, targetId,
hostPlayer.GetWeaponID(), skillId, 0, 0x0200, 0);
}
bool ok = hostPlayer.PlaySkillAttackAction(skillId, 0, ref attackTime,0, pAttack);
bool ok = hostPlayer.PlaySkillAttackAction(skillId, 0, ref attackTime, 0, pAttack);
if (!ok)
BMLogger.LogWarning($"[SkillTriggerPanel] PlaySkillAttackAction returned false for skill {skillId} after delay — attack anim may not play.");
EXIT:
// // For skill attacking, time is always set to 0
BMLogger.LogWarning($"[SkillTriggerPanel] PlaySkillAttackAction returned false for skill {skillId} — attack anim may not play.");
if (attackTime != 0)
attackTime = 0;
}