continuing with combat, sword combos working, not finished, blockign working, need to build blockpush and improving bow combat
This commit is contained in:
@@ -21,6 +21,16 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
[Tooltip("The active weapon used for damage and stats.")]
|
||||
[SerializeField] private Weapon _currentWeapon;
|
||||
|
||||
[Header("Combo System")]
|
||||
[Tooltip("The UI Image (like a Chain Icon) that appears when the player is allowed to combo.")]
|
||||
[SerializeField] private UnityEngine.UI.Image _comboIndicatorUI;
|
||||
[SerializeField] private Sprite _comboStandbySprite;
|
||||
[SerializeField] private Sprite _comboMissedSprite;
|
||||
|
||||
[SerializeField] private Color _comboStandbyColor = Color.white;
|
||||
[SerializeField] private Color _comboSuccessColor = Color.green;
|
||||
[SerializeField] private Color _comboMissedColor = Color.red;
|
||||
|
||||
[Tooltip("The Transform socket where the weapon is held when equipped (e.g., Prop_R)")]
|
||||
[SerializeField] private Transform _equippedSocket;
|
||||
|
||||
@@ -103,6 +113,12 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
}
|
||||
|
||||
private bool _isAttacking;
|
||||
public bool IsAttacking => _isAttacking;
|
||||
|
||||
// --- Debug Tracking ---
|
||||
private float _lastAttackInputTime = 0f;
|
||||
private float _lastAttackAnimationEndTime = 0f;
|
||||
private bool _wasAttackingLastFrame = false;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
@@ -127,6 +143,21 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Track exactly when the attack animation finishes
|
||||
if (!_isAttacking && _wasAttackingLastFrame)
|
||||
{
|
||||
_lastAttackAnimationEndTime = Time.time;
|
||||
_comboSuccessfullyBuffered = false;
|
||||
_isComboWindowOpen = false;
|
||||
|
||||
// Forcefully hide the UI just in case an animation was interrupted before its Close event fired!
|
||||
if (_comboIndicatorUI != null)
|
||||
{
|
||||
_comboIndicatorUI.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
_wasAttackingLastFrame = _isAttacking;
|
||||
|
||||
if (_meleeLayerIndex != -1)
|
||||
{
|
||||
@@ -245,10 +276,39 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
|
||||
#region Input Handlers
|
||||
|
||||
private float _comboPenaltyEndTime = 0f;
|
||||
|
||||
private void OnAttackStarted(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!_isEquipped || _isBlocking) return;
|
||||
|
||||
// 1. Penalty Check (Are we locked out for missing a combo?)
|
||||
if (Time.time < _comboPenaltyEndTime)
|
||||
{
|
||||
Debug.Log("[Combat Debug] Attack locked out! You must recover from your missed combo.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Stamina Check (Requires at least 15 stamina to swing the sword)
|
||||
if (_playerStats != null && _playerStats.currentStamina < 15f)
|
||||
{
|
||||
Debug.Log("[Combat Debug] Not enough stamina to attack!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Input Debounce (0.4s)
|
||||
// This is long enough to stop jitter, but short enough that if you click
|
||||
// in the middle of your current attack, it successfully buffers the next Combo hit!
|
||||
if (Time.time < _lastAttackInputTime + 0.4f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Debug Log ---
|
||||
Debug.Log($"[Combat Debug] Attack PRESSED at {Time.time:F2}s. (Last pressed: {_lastAttackInputTime:F2}s). If not playing, it last finished at: {_lastAttackAnimationEndTime:F2}s");
|
||||
_lastAttackInputTime = Time.time;
|
||||
// -----------------
|
||||
|
||||
_isPoweringUp = true;
|
||||
if (_animator != null)
|
||||
{
|
||||
@@ -260,6 +320,10 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
{
|
||||
if (_isPoweringUp)
|
||||
{
|
||||
// --- Debug Log ---
|
||||
Debug.Log($"[Combat Debug] Attack RELEASED at {Time.time:F2}s. Triggering Attack()!");
|
||||
// -----------------
|
||||
|
||||
_isPoweringUp = false;
|
||||
|
||||
if (_animator != null)
|
||||
@@ -280,6 +344,8 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
if (_playerStats != null)
|
||||
{
|
||||
_playerStats.staminaRegenMultiplier = 0.5f; // Half regen while blocking
|
||||
_playerStats.isBlocking = true;
|
||||
_playerStats.currentBlockReduction = _currentWeapon != null ? _currentWeapon.blockReductionPercentage : 0.5f;
|
||||
}
|
||||
|
||||
// Slow down to walk speed while blocking
|
||||
@@ -295,6 +361,7 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
if (_playerStats != null)
|
||||
{
|
||||
_playerStats.staminaRegenMultiplier = 1f; // Restore regen rate
|
||||
_playerStats.isBlocking = false;
|
||||
}
|
||||
|
||||
// Return to normal speed
|
||||
@@ -306,19 +373,99 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
|
||||
#region Combat Actions
|
||||
|
||||
private float _lastStaminaDrainTime = 0f;
|
||||
|
||||
private bool _isComboWindowOpen = false;
|
||||
private bool _comboSuccessfullyBuffered = false;
|
||||
|
||||
// --- Animation Events to add to your attack clips! ---
|
||||
public void OpenComboWindow()
|
||||
{
|
||||
_isComboWindowOpen = true;
|
||||
_comboSuccessfullyBuffered = false; // Reset the buffer for the new attack!
|
||||
|
||||
if (_comboIndicatorUI != null)
|
||||
{
|
||||
_comboIndicatorUI.sprite = _comboStandbySprite;
|
||||
_comboIndicatorUI.color = _comboStandbyColor;
|
||||
_comboIndicatorUI.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseComboWindow()
|
||||
{
|
||||
_isComboWindowOpen = false;
|
||||
if (_comboIndicatorUI != null)
|
||||
{
|
||||
// Only hide it if it hasn't been changed to the missed sprite (so missed icon has time to show)
|
||||
if (_comboIndicatorUI.sprite == _comboStandbySprite || _comboIndicatorUI.color == _comboSuccessColor)
|
||||
{
|
||||
_comboIndicatorUI.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// -----------------------------------------------------
|
||||
|
||||
public void Attack()
|
||||
{
|
||||
if (_animator != null)
|
||||
{
|
||||
// Optional: You can check if there's enough stamina to attack here
|
||||
// e.g. if (_playerStats != null && !_playerStats.HasStaminaPercentage(0.1f)) return;
|
||||
|
||||
if (_playerStats != null)
|
||||
if (_isAttacking)
|
||||
{
|
||||
_playerStats.UseStamina(15f); // Use 15 stamina per swing
|
||||
// If they already earned the combo, ignore their extra spam clicks!
|
||||
// Otherwise, the penalty block below will wipe their hard-earned trigger!
|
||||
if (_comboSuccessfullyBuffered) return;
|
||||
|
||||
// If they click during an attack, it MUST be inside the combo window!
|
||||
if (_isComboWindowOpen)
|
||||
{
|
||||
Debug.Log("[Combat Debug] PERFECT TIMING! Combo buffered.");
|
||||
_animator.SetTrigger(_attackHash);
|
||||
_comboSuccessfullyBuffered = true; // Lock in the success!
|
||||
|
||||
if (_comboIndicatorUI != null)
|
||||
{
|
||||
_comboIndicatorUI.color = _comboSuccessColor;
|
||||
}
|
||||
|
||||
// We set it to false so they can't trigger again, but we don't call CloseComboWindow
|
||||
// directly so the Success color stays visible for a split second!
|
||||
_isComboWindowOpen = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[Combat Debug] Combo Dropped! Clicked too early or too late.");
|
||||
|
||||
// PUNISHMENT: Lock out attacks for 1 full second and explicitly wipe the trigger
|
||||
// so the Animator is absolutely forbidden from storing up a delayed attack!
|
||||
_comboPenaltyEndTime = Time.time + 1.0f;
|
||||
_animator.ResetTrigger(_attackHash);
|
||||
|
||||
if (_comboIndicatorUI != null)
|
||||
{
|
||||
_comboIndicatorUI.sprite = _comboMissedSprite;
|
||||
_comboIndicatorUI.color = _comboMissedColor;
|
||||
_comboIndicatorUI.gameObject.SetActive(true);
|
||||
|
||||
StopCoroutine(HideMissedComboIcon());
|
||||
StartCoroutine(HideMissedComboIcon());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_animator.SetTrigger(_attackHash);
|
||||
else
|
||||
{
|
||||
// Not attacking, just start the first attack normally
|
||||
_animator.SetTrigger(_attackHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator HideMissedComboIcon()
|
||||
{
|
||||
yield return new WaitForSeconds(0.4f);
|
||||
if (_comboIndicatorUI != null && _comboIndicatorUI.sprite == _comboMissedSprite)
|
||||
{
|
||||
_comboIndicatorUI.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +484,13 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
public void DealDamage()
|
||||
{
|
||||
Debug.Log("DealDamage called!");
|
||||
|
||||
// Drain stamina EXACTLY when the sword swings, not when the button is clicked!
|
||||
if (_playerStats != null)
|
||||
{
|
||||
_playerStats.UseStamina(15f);
|
||||
}
|
||||
|
||||
if (_currentWeapon == null)
|
||||
{
|
||||
Debug.LogWarning("No weapon assigned to deal damage!");
|
||||
@@ -357,42 +511,52 @@ public class MeleeCombatLayerController : MonoBehaviour
|
||||
foreach (Collider hit in hitColliders)
|
||||
{
|
||||
Debug.Log($"Melee hit {hit.name} for {_currentWeapon.damage} damage!");
|
||||
hit.GetComponent<WorldObject>()?.TakeDamage(_currentWeapon.damage);
|
||||
hit.GetComponent<NPCController>()?.TakeDamage(_currentWeapon.damage);
|
||||
hit.GetComponent<PlayerStats>()?.TakeDamage(_currentWeapon.damage);
|
||||
hit.GetComponentInParent<WorldObject>()?.TakeDamage(_currentWeapon.damage);
|
||||
hit.GetComponentInParent<NPCController>()?.TakeDamage(_currentWeapon.damage);
|
||||
hit.GetComponentInParent<PlayerStats>()?.TakeDamage(_currentWeapon.damage);
|
||||
}
|
||||
}
|
||||
|
||||
// Add hashes for blocking animations
|
||||
private readonly int _blockHitHash = Animator.StringToHash("BlockHit");
|
||||
private readonly int _blockBreakHash = Animator.StringToHash("BlockBreak");
|
||||
private readonly int _blockPushHash = Animator.StringToHash("BlockPush");
|
||||
|
||||
public void TakeDamage(float amount)
|
||||
{
|
||||
if (_isBlocking)
|
||||
{
|
||||
float blockReduction = _currentWeapon != null ? _currentWeapon.blockReductionPercentage : 0.5f;
|
||||
float blockedAmount = amount * blockReduction;
|
||||
float damageTaken = amount - blockedAmount;
|
||||
|
||||
if (_playerStats != null)
|
||||
{
|
||||
if (_playerStats.currentStamina >= amount)
|
||||
// We consume stamina equal to the damage blocked
|
||||
if (_playerStats.currentStamina >= blockedAmount)
|
||||
{
|
||||
Debug.Log($"Blocked {amount} damage! Cost {amount} stamina.");
|
||||
_playerStats.UseStamina(amount);
|
||||
Debug.Log($"Blocked {blockedAmount} damage! Cost {blockedAmount} stamina. Took {damageTaken} damage.");
|
||||
_playerStats.UseStamina(blockedAmount);
|
||||
_playerStats.TakeDamage(damageTaken);
|
||||
|
||||
if (_animator != null) _animator.SetTrigger(_blockHitHash);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Block broken! Took {amount} damage.");
|
||||
Debug.Log($"Block broken! Took full {amount} damage.");
|
||||
_isBlocking = false;
|
||||
_playerStats.staminaRegenMultiplier = 1f;
|
||||
_playerStats.UseStamina(_playerStats.currentStamina); // Drain rest of stamina
|
||||
_playerStats.PauseStaminaRegen(3f); // Pause regen for 3 seconds
|
||||
_inputReader.onWalkToggled?.Invoke(); // Stop walking slow
|
||||
|
||||
// Optionally, play a guard break animation here if you have one
|
||||
// _animator.SetTrigger("GuardBreak");
|
||||
_playerStats.TakeDamage(amount);
|
||||
|
||||
if (_animator != null) _animator.SetTrigger(_blockBreakHash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Blocked {amount} damage!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"Took {amount} damage!");
|
||||
|
||||
Reference in New Issue
Block a user