continuing with combat, sword combos working, not finished, blockign working, need to build blockpush and improving bow combat

This commit is contained in:
2026-09-30 16:39:18 +01:00
parent 7486c8d310
commit 4980fb8128
2095 changed files with 1069655 additions and 652 deletions

View File

@@ -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!");