Update double input issues, pigman is no longer unbound, limit his weapon access too
This commit is contained in:
@@ -124,6 +124,15 @@ namespace Common.Input
|
|||||||
public bool IsDragging => _isDragging;
|
public bool IsDragging => _isDragging;
|
||||||
public bool IsEnabled => _isEnabled;
|
public bool IsEnabled => _isEnabled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protected property to allow derived classes to set enabled state
|
||||||
|
/// </summary>
|
||||||
|
protected bool Enabled
|
||||||
|
{
|
||||||
|
get => _isEnabled;
|
||||||
|
set => _isEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Lifecycle
|
#region Lifecycle
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ namespace Core.Settings
|
|||||||
// AI Difficulty Settings
|
// AI Difficulty Settings
|
||||||
Minigames.FortFight.Data.AIDifficulty DefaultAIDifficulty { get; } // Default difficulty level for AI
|
Minigames.FortFight.Data.AIDifficulty DefaultAIDifficulty { get; } // Default difficulty level for AI
|
||||||
Minigames.FortFight.Data.AIDifficultyData GetAIDifficultyData(Minigames.FortFight.Data.AIDifficulty difficulty);
|
Minigames.FortFight.Data.AIDifficultyData GetAIDifficultyData(Minigames.FortFight.Data.AIDifficulty difficulty);
|
||||||
|
IReadOnlyList<Minigames.FortFight.Data.ProjectileType> AIAllowedProjectiles { get; } // Projectiles AI can use
|
||||||
|
|
||||||
// Weak point settings
|
// Weak point settings
|
||||||
float WeakPointExplosionRadius { get; }
|
float WeakPointExplosionRadius { get; }
|
||||||
|
|||||||
@@ -250,9 +250,17 @@ namespace Minigames.FortFight.AI
|
|||||||
// Get AI player index
|
// Get AI player index
|
||||||
int playerIndex = _turnManager.CurrentPlayer.PlayerIndex;
|
int playerIndex = _turnManager.CurrentPlayer.PlayerIndex;
|
||||||
|
|
||||||
// Get available projectiles (check cooldowns via ammo manager)
|
// Get allowed projectiles for AI from settings
|
||||||
|
var allowedTypes = _cachedSettings?.AIAllowedProjectiles;
|
||||||
|
if (allowedTypes == null || allowedTypes.Count == 0)
|
||||||
|
{
|
||||||
|
Logging.Warning("[FortFightAIController] No allowed projectiles configured! Using all types as fallback.");
|
||||||
|
allowedTypes = new List<ProjectileType>((ProjectileType[])System.Enum.GetValues(typeof(ProjectileType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by cooldown availability
|
||||||
var availableTypes = new List<ProjectileType>();
|
var availableTypes = new List<ProjectileType>();
|
||||||
foreach (ProjectileType type in System.Enum.GetValues(typeof(ProjectileType)))
|
foreach (ProjectileType type in allowedTypes)
|
||||||
{
|
{
|
||||||
// Check if ammo is available (not on cooldown)
|
// Check if ammo is available (not on cooldown)
|
||||||
if (_ammoManager.IsAmmoAvailable(type, playerIndex))
|
if (_ammoManager.IsAmmoAvailable(type, playerIndex))
|
||||||
@@ -269,7 +277,7 @@ namespace Minigames.FortFight.AI
|
|||||||
|
|
||||||
float enemyHpPercent = _fortManager.PlayerFort.HpPercentage;
|
float enemyHpPercent = _fortManager.PlayerFort.HpPercentage;
|
||||||
|
|
||||||
// Strategic selection based on fort HP
|
// Strategic selection based on fort HP (only from allowed projectiles)
|
||||||
if (enemyHpPercent > 70f && availableTypes.Contains(ProjectileType.TrashBag))
|
if (enemyHpPercent > 70f && availableTypes.Contains(ProjectileType.TrashBag))
|
||||||
{
|
{
|
||||||
return ProjectileType.TrashBag; // Spread damage early game
|
return ProjectileType.TrashBag; // Spread damage early game
|
||||||
@@ -280,7 +288,7 @@ namespace Minigames.FortFight.AI
|
|||||||
}
|
}
|
||||||
else if (_targetBlock != null && _targetBlock.IsWeakPoint && availableTypes.Contains(ProjectileType.CeilingFan))
|
else if (_targetBlock != null && _targetBlock.IsWeakPoint && availableTypes.Contains(ProjectileType.CeilingFan))
|
||||||
{
|
{
|
||||||
return ProjectileType.CeilingFan; // Drop on weak points
|
return ProjectileType.CeilingFan; // Drop on weak points (if allowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to first available
|
// Default to first available
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ namespace Minigames.FortFight.Core
|
|||||||
[Tooltip("Default AI difficulty level for single-player games")]
|
[Tooltip("Default AI difficulty level for single-player games")]
|
||||||
[SerializeField] private AIDifficulty defaultAIDifficulty = AIDifficulty.Medium;
|
[SerializeField] private AIDifficulty defaultAIDifficulty = AIDifficulty.Medium;
|
||||||
|
|
||||||
|
[Tooltip("Projectile types the AI is allowed to use (excludes CeilingFan by default as it requires precise timing)")]
|
||||||
|
[SerializeField] private List<ProjectileType> aiAllowedProjectiles = new List<ProjectileType>
|
||||||
|
{
|
||||||
|
ProjectileType.Toaster,
|
||||||
|
ProjectileType.Vacuum,
|
||||||
|
ProjectileType.TrashBag
|
||||||
|
// CeilingFan excluded by default - requires precise tap timing
|
||||||
|
};
|
||||||
|
|
||||||
[Header("Weak Point Settings")]
|
[Header("Weak Point Settings")]
|
||||||
[Tooltip("Radius of explosion effect from weak points")]
|
[Tooltip("Radius of explosion effect from weak points")]
|
||||||
[SerializeField] private float weakPointExplosionRadius = 2.5f;
|
[SerializeField] private float weakPointExplosionRadius = 2.5f;
|
||||||
@@ -149,6 +158,8 @@ namespace Minigames.FortFight.Core
|
|||||||
|
|
||||||
public AIDifficulty DefaultAIDifficulty => defaultAIDifficulty;
|
public AIDifficulty DefaultAIDifficulty => defaultAIDifficulty;
|
||||||
|
|
||||||
|
public IReadOnlyList<ProjectileType> AIAllowedProjectiles => aiAllowedProjectiles;
|
||||||
|
|
||||||
public float WeakPointExplosionRadius => weakPointExplosionRadius;
|
public float WeakPointExplosionRadius => weakPointExplosionRadius;
|
||||||
public float WeakPointExplosionDamage => weakPointExplosionDamage;
|
public float WeakPointExplosionDamage => weakPointExplosionDamage;
|
||||||
public float WeakPointExplosionForce => weakPointExplosionForce;
|
public float WeakPointExplosionForce => weakPointExplosionForce;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using AppleHills.Core.Settings;
|
|||||||
using Common.Input;
|
using Common.Input;
|
||||||
using Core;
|
using Core;
|
||||||
using Core.Settings;
|
using Core.Settings;
|
||||||
|
using Input;
|
||||||
using Minigames.FortFight.Data;
|
using Minigames.FortFight.Data;
|
||||||
using Minigames.FortFight.Projectiles;
|
using Minigames.FortFight.Projectiles;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -74,6 +75,8 @@ namespace Minigames.FortFight.Core
|
|||||||
|
|
||||||
private ProjectileConfig _currentAmmo;
|
private ProjectileConfig _currentAmmo;
|
||||||
private ProjectileBase _activeProjectile;
|
private ProjectileBase _activeProjectile;
|
||||||
|
private int _ownerPlayerIndex = -1;
|
||||||
|
private bool _isAIControlled = false;
|
||||||
|
|
||||||
public ProjectileBase ActiveProjectile => _activeProjectile;
|
public ProjectileBase ActiveProjectile => _activeProjectile;
|
||||||
|
|
||||||
@@ -104,7 +107,21 @@ namespace Minigames.FortFight.Core
|
|||||||
trajectoryPreview.ForceHide();
|
trajectoryPreview.ForceHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only register input for human players, not AI
|
||||||
|
if (!_isAIControlled)
|
||||||
|
{
|
||||||
|
// Call base class Enable which handles input registration and enabling
|
||||||
base.Enable();
|
base.Enable();
|
||||||
|
if (showDebugLogs) Logging.Debug($"[SlingshotController] Enabled with input (Player controlled)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For AI, only show trajectory preview without registering for input
|
||||||
|
// We don't call base.Enable() to avoid input registration
|
||||||
|
Enabled = true; // Use protected property from base class
|
||||||
|
ShowPreview();
|
||||||
|
if (showDebugLogs) Logging.Debug($"[SlingshotController] Enabled without input (AI controlled)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void StartDrag(Vector2 worldPosition)
|
protected override void StartDrag(Vector2 worldPosition)
|
||||||
@@ -150,6 +167,18 @@ namespace Minigames.FortFight.Core
|
|||||||
if (showDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
|
if (showDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the owner of this slingshot (used for projectile ownership tracking)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="playerIndex">Player index (0 or 1)</param>
|
||||||
|
/// <param name="isAI">Whether this slingshot is AI-controlled</param>
|
||||||
|
public void SetOwner(int playerIndex, bool isAI)
|
||||||
|
{
|
||||||
|
_ownerPlayerIndex = playerIndex;
|
||||||
|
_isAIControlled = isAI;
|
||||||
|
if (showDebugLogs) Logging.Debug($"[SlingshotController] Owner set to Player {playerIndex}, AI: {isAI}");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Launch a projectile with calculated force and direction
|
/// Launch a projectile with calculated force and direction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -175,6 +204,9 @@ namespace Minigames.FortFight.Core
|
|||||||
// Initialize projectile with its type (loads damage and mass from settings)
|
// Initialize projectile with its type (loads damage and mass from settings)
|
||||||
_activeProjectile.Initialize(_currentAmmo.projectileType);
|
_activeProjectile.Initialize(_currentAmmo.projectileType);
|
||||||
|
|
||||||
|
// Set projectile owner for input control
|
||||||
|
_activeProjectile.SetOwner(_ownerPlayerIndex, _isAIControlled);
|
||||||
|
|
||||||
// Launch it
|
// Launch it
|
||||||
_activeProjectile.Launch(direction, force);
|
_activeProjectile.Launch(direction, force);
|
||||||
|
|
||||||
@@ -201,7 +233,7 @@ namespace Minigames.FortFight.Core
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Launch projectile with a specific velocity (for AI ballistic calculations)
|
/// Launch projectile with a specific velocity (for AI ballistic calculations)
|
||||||
/// Calculates the required force based on projectile mass
|
/// Calculates the required force based on projectile mass and respects slingshot limits
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void LaunchWithVelocity(Vector2 velocity)
|
public void LaunchWithVelocity(Vector2 velocity)
|
||||||
{
|
{
|
||||||
@@ -211,22 +243,57 @@ namespace Minigames.FortFight.Core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get projectile mass to calculate force
|
|
||||||
float mass = _currentAmmo.GetMass();
|
float mass = _currentAmmo.GetMass();
|
||||||
|
|
||||||
// Force = mass × velocity (for impulse-based launch)
|
|
||||||
Vector2 direction = velocity.normalized;
|
Vector2 direction = velocity.normalized;
|
||||||
float speed = velocity.magnitude;
|
float desiredSpeed = velocity.magnitude;
|
||||||
float force = mass * speed;
|
|
||||||
|
// Use common method - ensures same physics constraints as player
|
||||||
|
float force = CalculateClampedForce(desiredSpeed, mass);
|
||||||
|
|
||||||
if (showDebugLogs)
|
if (showDebugLogs)
|
||||||
|
{
|
||||||
|
float actualSpeed = force / mass;
|
||||||
|
if (actualSpeed < desiredSpeed)
|
||||||
|
{
|
||||||
|
float maxForce = Config?.baseLaunchForce * Config?.maxForceMultiplier ?? 20f;
|
||||||
|
Logging.Debug($"[Slingshot] AI velocity clamped - Requested: {desiredSpeed:F2} m/s, Actual: {actualSpeed:F2} m/s (mass: {mass:F2}, maxForce: {maxForce:F2})");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Logging.Debug($"[Slingshot] LaunchWithVelocity - Velocity: {velocity}, Mass: {mass:F2}, Force: {force:F2}");
|
Logging.Debug($"[Slingshot] LaunchWithVelocity - Velocity: {velocity}, Mass: {mass:F2}, Force: {force:F2}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchProjectile(direction, force);
|
LaunchProjectile(direction, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate maximum achievable velocity for current projectile given slingshot constraints.
|
||||||
|
/// This ensures both player and AI respect the same physical limits.
|
||||||
|
/// </summary>
|
||||||
|
public float GetMaxVelocity()
|
||||||
|
{
|
||||||
|
if (_currentAmmo == null)
|
||||||
|
{
|
||||||
|
return 20f; // Default fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
float maxForce = Config?.baseLaunchForce * Config?.maxForceMultiplier ?? 20f;
|
||||||
|
float mass = _currentAmmo.GetMass();
|
||||||
|
return maxForce / mass; // Physics: v = F / m
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert desired velocity to force, clamped to slingshot's physical limits.
|
||||||
|
/// Used internally to ensure AI respects same constraints as player.
|
||||||
|
/// </summary>
|
||||||
|
private float CalculateClampedForce(float desiredSpeed, float mass)
|
||||||
|
{
|
||||||
|
float desiredForce = mass * desiredSpeed;
|
||||||
|
float maxForce = Config?.baseLaunchForce * Config?.maxForceMultiplier ?? 20f;
|
||||||
|
return Mathf.Min(desiredForce, maxForce);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get currently active projectile (in flight)
|
/// Get currently active projectile (in flight)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -70,20 +70,20 @@ namespace Minigames.FortFight.Core
|
|||||||
|
|
||||||
#region State
|
#region State
|
||||||
|
|
||||||
private TurnState currentTurnState = TurnState.PlayerOneTurn;
|
private TurnState _currentTurnState = TurnState.PlayerOneTurn;
|
||||||
private PlayerData playerOne;
|
private PlayerData _playerOne;
|
||||||
private PlayerData playerTwo;
|
private PlayerData _playerTwo;
|
||||||
private PlayerData currentPlayer;
|
private PlayerData _currentPlayer;
|
||||||
private int turnCount = 0;
|
private int _turnCount = 0;
|
||||||
|
|
||||||
// Turn action management
|
// Turn action management
|
||||||
private ProjectileTurnAction currentTurnAction;
|
private ProjectileTurnAction _currentTurnAction;
|
||||||
private bool isTransitioning = false;
|
private bool _isTransitioning = false;
|
||||||
private float transitionTimer = 0f;
|
private float _transitionTimer = 0f;
|
||||||
|
|
||||||
public TurnState CurrentTurnState => currentTurnState;
|
public TurnState CurrentTurnState => _currentTurnState;
|
||||||
public PlayerData CurrentPlayer => currentPlayer;
|
public PlayerData CurrentPlayer => _currentPlayer;
|
||||||
public int TurnCount => turnCount;
|
public int TurnCount => _turnCount;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -128,8 +128,8 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(PlayerData pPlayerOne, PlayerData pPlayerTwo)
|
public void Initialize(PlayerData pPlayerOne, PlayerData pPlayerTwo)
|
||||||
{
|
{
|
||||||
this.playerOne = pPlayerOne;
|
this._playerOne = pPlayerOne;
|
||||||
this.playerTwo = pPlayerTwo;
|
this._playerTwo = pPlayerTwo;
|
||||||
|
|
||||||
Logging.Debug($"[TurnManager] Initialized with P1: {pPlayerOne.PlayerName} (AI: {pPlayerOne.IsAI}), P2: {pPlayerTwo.PlayerName} (AI: {pPlayerTwo.IsAI})");
|
Logging.Debug($"[TurnManager] Initialized with P1: {pPlayerOne.PlayerName} (AI: {pPlayerOne.IsAI}), P2: {pPlayerTwo.PlayerName} (AI: {pPlayerTwo.IsAI})");
|
||||||
}
|
}
|
||||||
@@ -139,9 +139,9 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartGame()
|
public void StartGame()
|
||||||
{
|
{
|
||||||
turnCount = 0;
|
_turnCount = 0;
|
||||||
currentTurnState = TurnState.PlayerOneTurn;
|
_currentTurnState = TurnState.PlayerOneTurn;
|
||||||
currentPlayer = playerOne;
|
_currentPlayer = _playerOne;
|
||||||
|
|
||||||
// Set initial input mode to UI
|
// Set initial input mode to UI
|
||||||
if (Input.InputManager.Instance != null)
|
if (Input.InputManager.Instance != null)
|
||||||
@@ -149,8 +149,8 @@ namespace Minigames.FortFight.Core
|
|||||||
Input.InputManager.Instance.SetInputMode(Input.InputMode.UI);
|
Input.InputManager.Instance.SetInputMode(Input.InputMode.UI);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.Debug($"[TurnManager] Game started. First turn: {currentPlayer.PlayerName}");
|
Logging.Debug($"[TurnManager] Game started. First turn: {_currentPlayer.PlayerName}");
|
||||||
OnTurnStarted?.Invoke(currentPlayer, currentTurnState);
|
OnTurnStarted?.Invoke(_currentPlayer, _currentTurnState);
|
||||||
|
|
||||||
// Start turn action for first player
|
// Start turn action for first player
|
||||||
StartTurnAction();
|
StartTurnAction();
|
||||||
@@ -163,26 +163,26 @@ namespace Minigames.FortFight.Core
|
|||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
// Update current turn action
|
// Update current turn action
|
||||||
if (currentTurnAction != null && !isTransitioning)
|
if (_currentTurnAction != null && !_isTransitioning)
|
||||||
{
|
{
|
||||||
currentTurnAction.Update();
|
_currentTurnAction.Update();
|
||||||
|
|
||||||
// Check if action is complete
|
// Check if action is complete
|
||||||
if (currentTurnAction.IsComplete)
|
if (_currentTurnAction.IsComplete)
|
||||||
{
|
{
|
||||||
EndTurnAction();
|
EndTurnAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle transition timing
|
// Handle transition timing
|
||||||
if (isTransitioning)
|
if (_isTransitioning)
|
||||||
{
|
{
|
||||||
transitionTimer += Time.deltaTime;
|
_transitionTimer += Time.deltaTime;
|
||||||
|
|
||||||
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||||
float transitionDelay = settings?.TurnTransitionDelay ?? 1.5f;
|
float transitionDelay = settings?.TurnTransitionDelay ?? 1.5f;
|
||||||
|
|
||||||
if (transitionTimer >= transitionDelay)
|
if (_transitionTimer >= transitionDelay)
|
||||||
{
|
{
|
||||||
CompleteTransition();
|
CompleteTransition();
|
||||||
}
|
}
|
||||||
@@ -199,38 +199,40 @@ namespace Minigames.FortFight.Core
|
|||||||
private void StartTurnAction()
|
private void StartTurnAction()
|
||||||
{
|
{
|
||||||
// Get the appropriate slingshot for current player
|
// Get the appropriate slingshot for current player
|
||||||
SlingshotController activeSlingshot = GetSlingshotForPlayer(currentPlayer);
|
SlingshotController activeSlingshot = GetSlingshotForPlayer(_currentPlayer);
|
||||||
|
|
||||||
if (activeSlingshot == null)
|
if (activeSlingshot == null)
|
||||||
{
|
{
|
||||||
Logging.Error($"[TurnManager] No slingshot found for {currentPlayer.PlayerName}!");
|
Logging.Error($"[TurnManager] No slingshot found for {_currentPlayer.PlayerName}!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and execute turn action with player index
|
// Set slingshot owner (used for projectile ownership tracking)
|
||||||
currentTurnAction = new ProjectileTurnAction(activeSlingshot, AmmunitionManager.Instance, cameraController, currentPlayer.PlayerIndex);
|
activeSlingshot.SetOwner(_currentPlayer.PlayerIndex, _currentPlayer.IsAI);
|
||||||
|
|
||||||
// Set current ammo on slingshot for this player
|
// Set current ammo on slingshot for this player
|
||||||
if (AmmunitionManager.Instance != null)
|
if (AmmunitionManager.Instance != null)
|
||||||
{
|
{
|
||||||
ProjectileConfig currentAmmo = AmmunitionManager.Instance.GetSelectedAmmoConfig(currentPlayer.PlayerIndex);
|
ProjectileConfig currentAmmo = AmmunitionManager.Instance.GetSelectedAmmoConfig(_currentPlayer.PlayerIndex);
|
||||||
if (currentAmmo != null)
|
if (currentAmmo != null)
|
||||||
{
|
{
|
||||||
activeSlingshot.SetAmmo(currentAmmo);
|
activeSlingshot.SetAmmo(currentAmmo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the action (enables slingshot)
|
// Create and execute turn action for BOTH player and AI
|
||||||
currentTurnAction.Execute();
|
// This enables slingshot (trajectory preview) and subscribes to launch events
|
||||||
|
_currentTurnAction = new ProjectileTurnAction(activeSlingshot, AmmunitionManager.Instance, cameraController, _currentPlayer.PlayerIndex);
|
||||||
|
_currentTurnAction.Execute(); // Always execute - handles trajectory & event subscription
|
||||||
|
|
||||||
// Register slingshot as input consumer and switch to Game mode
|
// Only switch input mode for human players
|
||||||
if (Input.InputManager.Instance != null)
|
if (!_currentPlayer.IsAI && Input.InputManager.Instance != null)
|
||||||
{
|
{
|
||||||
Input.InputManager.Instance.RegisterOverrideConsumer(activeSlingshot);
|
|
||||||
Input.InputManager.Instance.SetInputMode(Input.InputMode.Game);
|
Input.InputManager.Instance.SetInputMode(Input.InputMode.Game);
|
||||||
}
|
}
|
||||||
|
// Note: Input registration now handled by SlingshotController.Enable() based on _isAIControlled flag
|
||||||
|
|
||||||
Logging.Debug($"[TurnManager] Started turn action for {currentPlayer.PlayerName}");
|
Logging.Debug($"[TurnManager] Started turn action for {_currentPlayer.PlayerName} (AI: {_currentPlayer.IsAI})");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -238,10 +240,10 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void EndTurnAction()
|
private void EndTurnAction()
|
||||||
{
|
{
|
||||||
Logging.Debug($"[TurnManager] Ending turn action for {currentPlayer.PlayerName}");
|
Logging.Debug($"[TurnManager] Ending turn action for {_currentPlayer.PlayerName}");
|
||||||
|
|
||||||
// Get active slingshot and unregister from input
|
// Get active slingshot and unregister from input
|
||||||
SlingshotController activeSlingshot = GetSlingshotForPlayer(currentPlayer);
|
SlingshotController activeSlingshot = GetSlingshotForPlayer(_currentPlayer);
|
||||||
if (activeSlingshot != null && Input.InputManager.Instance != null)
|
if (activeSlingshot != null && Input.InputManager.Instance != null)
|
||||||
{
|
{
|
||||||
Input.InputManager.Instance.UnregisterOverrideConsumer(activeSlingshot);
|
Input.InputManager.Instance.UnregisterOverrideConsumer(activeSlingshot);
|
||||||
@@ -254,7 +256,7 @@ namespace Minigames.FortFight.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear turn action
|
// Clear turn action
|
||||||
currentTurnAction = null;
|
_currentTurnAction = null;
|
||||||
|
|
||||||
// End the turn
|
// End the turn
|
||||||
EndTurn();
|
EndTurn();
|
||||||
@@ -265,29 +267,29 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void EndTurn()
|
private void EndTurn()
|
||||||
{
|
{
|
||||||
if (currentTurnState == TurnState.GameOver)
|
if (_currentTurnState == TurnState.GameOver)
|
||||||
{
|
{
|
||||||
Logging.Warning("[TurnManager] Cannot end turn - game is over");
|
Logging.Warning("[TurnManager] Cannot end turn - game is over");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.Debug($"[TurnManager] Turn ended for {currentPlayer.PlayerName}");
|
Logging.Debug($"[TurnManager] Turn ended for {_currentPlayer.PlayerName}");
|
||||||
OnTurnEnded?.Invoke(currentPlayer);
|
OnTurnEnded?.Invoke(_currentPlayer);
|
||||||
|
|
||||||
// Decrement ammunition cooldowns for this player
|
// Decrement ammunition cooldowns for this player
|
||||||
if (AmmunitionManager.Instance != null)
|
if (AmmunitionManager.Instance != null)
|
||||||
{
|
{
|
||||||
AmmunitionManager.Instance.DecrementCooldowns(currentPlayer.PlayerIndex);
|
AmmunitionManager.Instance.DecrementCooldowns(_currentPlayer.PlayerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter transition state (triggers wide view camera via OnTurnStarted)
|
// Enter transition state (triggers wide view camera via OnTurnStarted)
|
||||||
currentTurnState = TurnState.TransitioningTurn;
|
_currentTurnState = TurnState.TransitioningTurn;
|
||||||
OnTurnTransitioning?.Invoke();
|
OnTurnTransitioning?.Invoke();
|
||||||
OnTurnStarted?.Invoke(currentPlayer, currentTurnState); // Fire for camera switch to wide view
|
OnTurnStarted?.Invoke(_currentPlayer, _currentTurnState); // Fire for camera switch to wide view
|
||||||
|
|
||||||
// Start transition timer
|
// Start transition timer
|
||||||
isTransitioning = true;
|
_isTransitioning = true;
|
||||||
transitionTimer = 0f;
|
_transitionTimer = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -295,8 +297,8 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void CompleteTransition()
|
private void CompleteTransition()
|
||||||
{
|
{
|
||||||
isTransitioning = false;
|
_isTransitioning = false;
|
||||||
transitionTimer = 0f;
|
_transitionTimer = 0f;
|
||||||
|
|
||||||
AdvanceToNextPlayer();
|
AdvanceToNextPlayer();
|
||||||
}
|
}
|
||||||
@@ -306,22 +308,22 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void AdvanceToNextPlayer()
|
private void AdvanceToNextPlayer()
|
||||||
{
|
{
|
||||||
turnCount++;
|
_turnCount++;
|
||||||
|
|
||||||
// Switch players
|
// Switch players
|
||||||
if (currentPlayer == playerOne)
|
if (_currentPlayer == _playerOne)
|
||||||
{
|
{
|
||||||
currentPlayer = playerTwo;
|
_currentPlayer = _playerTwo;
|
||||||
currentTurnState = playerTwo.IsAI ? TurnState.AITurn : TurnState.PlayerTwoTurn;
|
_currentTurnState = _playerTwo.IsAI ? TurnState.AITurn : TurnState.PlayerTwoTurn;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
currentPlayer = playerOne;
|
_currentPlayer = _playerOne;
|
||||||
currentTurnState = TurnState.PlayerOneTurn;
|
_currentTurnState = TurnState.PlayerOneTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.Debug($"[TurnManager] Advanced to turn {turnCount}. Current player: {currentPlayer.PlayerName} (State: {currentTurnState})");
|
Logging.Debug($"[TurnManager] Advanced to turn {_turnCount}. Current player: {_currentPlayer.PlayerName} (State: {_currentTurnState})");
|
||||||
OnTurnStarted?.Invoke(currentPlayer, currentTurnState);
|
OnTurnStarted?.Invoke(_currentPlayer, _currentTurnState);
|
||||||
|
|
||||||
// Start turn action for next player
|
// Start turn action for next player
|
||||||
StartTurnAction();
|
StartTurnAction();
|
||||||
@@ -332,11 +334,11 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private SlingshotController GetSlingshotForPlayer(PlayerData player)
|
private SlingshotController GetSlingshotForPlayer(PlayerData player)
|
||||||
{
|
{
|
||||||
if (player == playerOne)
|
if (player == _playerOne)
|
||||||
{
|
{
|
||||||
return playerOneSlingshotController;
|
return playerOneSlingshotController;
|
||||||
}
|
}
|
||||||
else if (player == playerTwo)
|
else if (player == _playerTwo)
|
||||||
{
|
{
|
||||||
return playerTwoSlingshotController;
|
return playerTwoSlingshotController;
|
||||||
}
|
}
|
||||||
@@ -349,7 +351,7 @@ namespace Minigames.FortFight.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetGameOver()
|
public void SetGameOver()
|
||||||
{
|
{
|
||||||
currentTurnState = TurnState.GameOver;
|
_currentTurnState = TurnState.GameOver;
|
||||||
Logging.Debug("[TurnManager] Game over state set");
|
Logging.Debug("[TurnManager] Game over state set");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,16 @@ namespace Minigames.FortFight.Projectiles
|
|||||||
indicator.SetActive(true);
|
indicator.SetActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register with InputManager to capture tap-to-drop
|
// Only register with InputManager if NOT AI-controlled
|
||||||
if (Input.InputManager.Instance != null)
|
// AI projectiles should not accept player input
|
||||||
|
if (!IsAIControlled && Input.InputManager.Instance != null)
|
||||||
{
|
{
|
||||||
Input.InputManager.Instance.RegisterOverrideConsumer(this);
|
Input.InputManager.Instance.RegisterOverrideConsumer(this);
|
||||||
Logging.Debug("[CeilingFanProjectile] Tap-to-drop now available");
|
Logging.Debug("[CeilingFanProjectile] Tap-to-drop now available (Player controlled)");
|
||||||
|
}
|
||||||
|
else if (IsAIControlled)
|
||||||
|
{
|
||||||
|
Logging.Debug("[CeilingFanProjectile] AI-controlled projectile, input disabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,17 @@ namespace Minigames.FortFight.Projectiles
|
|||||||
public Vector2 LaunchDirection { get; protected set; }
|
public Vector2 LaunchDirection { get; protected set; }
|
||||||
public float LaunchForce { get; protected set; }
|
public float LaunchForce { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Player index who owns this projectile (0 = Player 1, 1 = Player 2/AI)
|
||||||
|
/// Used to determine if player input should be accepted for ability activation
|
||||||
|
/// </summary>
|
||||||
|
public int OwnerPlayerIndex { get; private set; } = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this projectile is controlled by AI
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAIControlled { get; private set; } = false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Timeout
|
#region Timeout
|
||||||
@@ -116,6 +127,17 @@ namespace Minigames.FortFight.Projectiles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the owner of this projectile (used to control ability activation)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="playerIndex">Player index (0 or 1)</param>
|
||||||
|
/// <param name="isAI">Whether this projectile is controlled by AI</param>
|
||||||
|
public void SetOwner(int playerIndex, bool isAI)
|
||||||
|
{
|
||||||
|
OwnerPlayerIndex = playerIndex;
|
||||||
|
IsAIControlled = isAI;
|
||||||
|
}
|
||||||
|
|
||||||
internal override void OnManagedAwake()
|
internal override void OnManagedAwake()
|
||||||
{
|
{
|
||||||
base.OnManagedAwake();
|
base.OnManagedAwake();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Core;
|
using System.Collections;
|
||||||
|
using Core;
|
||||||
using Core.Settings;
|
using Core.Settings;
|
||||||
|
using Input;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Minigames.FortFight.Projectiles
|
namespace Minigames.FortFight.Projectiles
|
||||||
@@ -7,15 +9,76 @@ namespace Minigames.FortFight.Projectiles
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trash Bag projectile - splits into multiple smaller pieces on impact.
|
/// Trash Bag projectile - splits into multiple smaller pieces on impact.
|
||||||
/// Deals AOE damage in a forward cone.
|
/// Deals AOE damage in a forward cone.
|
||||||
|
/// Can be manually detonated mid-flight by tapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TrashBagProjectile : ProjectileBase
|
public class TrashBagProjectile : ProjectileBase, ITouchInputConsumer
|
||||||
{
|
{
|
||||||
[Header("Trash Bag Specific")]
|
[Header("Trash Bag Specific")]
|
||||||
[Tooltip("Prefab for individual trash pieces (small debris)")]
|
[Tooltip("Prefab for individual trash pieces (small debris)")]
|
||||||
[SerializeField] private GameObject trashPiecePrefab;
|
[SerializeField] private GameObject trashPiecePrefab;
|
||||||
|
|
||||||
|
[Tooltip("Visual indicator showing tap-to-explode is available")]
|
||||||
|
[SerializeField] private GameObject indicator;
|
||||||
|
|
||||||
|
private bool inputEnabled = false;
|
||||||
|
private bool hasExploded = false;
|
||||||
|
|
||||||
|
public override void Launch(Vector2 direction, float force)
|
||||||
|
{
|
||||||
|
base.Launch(direction, force);
|
||||||
|
|
||||||
|
// Start activation delay coroutine for tap-to-explode
|
||||||
|
StartCoroutine(ActivationDelayCoroutine());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator ActivationDelayCoroutine()
|
||||||
|
{
|
||||||
|
// Get activation delay from settings (use TrashBag-specific or default to 0.3s)
|
||||||
|
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||||
|
float activationDelay = settings?.CeilingFanActivationDelay ?? 0.3f; // Reuse CeilingFan delay setting
|
||||||
|
|
||||||
|
// Wait for delay
|
||||||
|
yield return new WaitForSeconds(activationDelay);
|
||||||
|
|
||||||
|
// Enable input and show indicator (if not already exploded)
|
||||||
|
if (!hasExploded && !AbilityActivated)
|
||||||
|
{
|
||||||
|
inputEnabled = true;
|
||||||
|
|
||||||
|
if (indicator != null)
|
||||||
|
{
|
||||||
|
indicator.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only register with InputManager if NOT AI-controlled
|
||||||
|
if (!IsAIControlled && InputManager.Instance != null)
|
||||||
|
{
|
||||||
|
InputManager.Instance.RegisterOverrideConsumer(this);
|
||||||
|
Logging.Debug("[TrashBagProjectile] Tap-to-explode now available (Player controlled)");
|
||||||
|
}
|
||||||
|
else if (IsAIControlled)
|
||||||
|
{
|
||||||
|
Logging.Debug("[TrashBagProjectile] AI-controlled projectile, input disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ActivateAbility()
|
||||||
|
{
|
||||||
|
base.ActivateAbility();
|
||||||
|
|
||||||
|
if (AbilityActivated && !hasExploded)
|
||||||
|
{
|
||||||
|
Logging.Debug("[TrashBagProjectile] Ability activated - manual detonation");
|
||||||
|
ExplodeAtCurrentPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnHit(Collision2D collision)
|
protected override void OnHit(Collision2D collision)
|
||||||
{
|
{
|
||||||
|
// Prevent double-explosion if already manually detonated
|
||||||
|
if (hasExploded) return;
|
||||||
|
|
||||||
// Deal initial damage from trash bag itself
|
// Deal initial damage from trash bag itself
|
||||||
var block = collision.gameObject.GetComponent<Fort.FortBlock>();
|
var block = collision.gameObject.GetComponent<Fort.FortBlock>();
|
||||||
if (block != null)
|
if (block != null)
|
||||||
@@ -28,19 +91,57 @@ namespace Minigames.FortFight.Projectiles
|
|||||||
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||||
int pieceCount = settings?.TrashBagPieceCount ?? 8;
|
int pieceCount = settings?.TrashBagPieceCount ?? 8;
|
||||||
|
|
||||||
Logging.Debug($"[TrashBagProjectile] Splitting into {pieceCount} pieces");
|
Logging.Debug($"[TrashBagProjectile] Impact explosion - splitting into {pieceCount} pieces");
|
||||||
|
|
||||||
// Get contact normal and impact point
|
// Get contact normal and impact point
|
||||||
Vector2 hitNormal = collision.contacts[0].normal;
|
Vector2 hitNormal = collision.contacts[0].normal;
|
||||||
Vector2 impactPoint = collision.contacts[0].point;
|
Vector2 impactPoint = collision.contacts[0].point;
|
||||||
|
|
||||||
// Spawn trash pieces (NOT parented, so they persist as debris)
|
// Mark as exploded and spawn trash pieces
|
||||||
|
hasExploded = true;
|
||||||
SpawnTrashPieces(impactPoint, hitNormal);
|
SpawnTrashPieces(impactPoint, hitNormal);
|
||||||
|
|
||||||
// Destroy trash bag after spawning pieces
|
// Destroy trash bag after spawning pieces
|
||||||
DestroyProjectile();
|
DestroyProjectile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Explode at current position (manual detonation)
|
||||||
|
/// </summary>
|
||||||
|
private void ExplodeAtCurrentPosition()
|
||||||
|
{
|
||||||
|
if (hasExploded) return;
|
||||||
|
|
||||||
|
hasExploded = true;
|
||||||
|
|
||||||
|
// Unregister from input immediately
|
||||||
|
UnregisterFromInput();
|
||||||
|
|
||||||
|
// Hide indicator
|
||||||
|
if (indicator != null)
|
||||||
|
{
|
||||||
|
indicator.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current position and use velocity as "hit normal" direction
|
||||||
|
Vector2 explosionPoint = transform.position;
|
||||||
|
Vector2 explosionDirection = rb2D.linearVelocity.normalized;
|
||||||
|
if (explosionDirection == Vector2.zero)
|
||||||
|
{
|
||||||
|
explosionDirection = LaunchDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||||
|
int pieceCount = settings?.TrashBagPieceCount ?? 8;
|
||||||
|
Logging.Debug($"[TrashBagProjectile] Manual detonation - splitting into {pieceCount} pieces");
|
||||||
|
|
||||||
|
// Spawn trash pieces
|
||||||
|
SpawnTrashPieces(explosionPoint, explosionDirection);
|
||||||
|
|
||||||
|
// Destroy the trash bag
|
||||||
|
DestroyProjectile();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spawn multiple trash pieces in a cone away from the hit surface.
|
/// Spawn multiple trash pieces in a cone away from the hit surface.
|
||||||
/// Uses hit normal + projectile momentum for realistic splash effect.
|
/// Uses hit normal + projectile momentum for realistic splash effect.
|
||||||
@@ -103,6 +204,64 @@ namespace Minigames.FortFight.Projectiles
|
|||||||
Logging.Debug($"[TrashBagProjectile] Spawned trash piece {i} in direction {pieceDirection}");
|
Logging.Debug($"[TrashBagProjectile] Spawned trash piece {i} in direction {pieceDirection}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region ITouchInputConsumer Implementation
|
||||||
|
|
||||||
|
public void OnTap(Vector2 worldPosition)
|
||||||
|
{
|
||||||
|
// Only respond if input is enabled
|
||||||
|
if (inputEnabled && !hasExploded && !AbilityActivated)
|
||||||
|
{
|
||||||
|
Logging.Debug("[TrashBagProjectile] Tap detected - activating manual detonation");
|
||||||
|
|
||||||
|
// Hide indicator
|
||||||
|
if (indicator != null)
|
||||||
|
{
|
||||||
|
indicator.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivateAbility();
|
||||||
|
|
||||||
|
// Unregister immediately after tap
|
||||||
|
UnregisterFromInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHoldStart(Vector2 worldPosition)
|
||||||
|
{
|
||||||
|
// Not used for trash bag
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHoldMove(Vector2 worldPosition)
|
||||||
|
{
|
||||||
|
// Not used for trash bag
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHoldEnd(Vector2 worldPosition)
|
||||||
|
{
|
||||||
|
// Not used for trash bag
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister from input manager
|
||||||
|
/// </summary>
|
||||||
|
private void UnregisterFromInput()
|
||||||
|
{
|
||||||
|
if (InputManager.Instance != null)
|
||||||
|
{
|
||||||
|
InputManager.Instance.UnregisterOverrideConsumer(this);
|
||||||
|
inputEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected override void DestroyProjectile()
|
||||||
|
{
|
||||||
|
// Ensure we unregister from input before destruction
|
||||||
|
UnregisterFromInput();
|
||||||
|
base.DestroyProjectile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user