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 IsEnabled => _isEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Protected property to allow derived classes to set enabled state
|
||||
/// </summary>
|
||||
protected bool Enabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set => _isEnabled = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
@@ -254,6 +254,7 @@ namespace Core.Settings
|
||||
// AI Difficulty Settings
|
||||
Minigames.FortFight.Data.AIDifficulty DefaultAIDifficulty { get; } // Default difficulty level for AI
|
||||
Minigames.FortFight.Data.AIDifficultyData GetAIDifficultyData(Minigames.FortFight.Data.AIDifficulty difficulty);
|
||||
IReadOnlyList<Minigames.FortFight.Data.ProjectileType> AIAllowedProjectiles { get; } // Projectiles AI can use
|
||||
|
||||
// Weak point settings
|
||||
float WeakPointExplosionRadius { get; }
|
||||
|
||||
@@ -250,9 +250,17 @@ namespace Minigames.FortFight.AI
|
||||
// Get AI player index
|
||||
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>();
|
||||
foreach (ProjectileType type in System.Enum.GetValues(typeof(ProjectileType)))
|
||||
foreach (ProjectileType type in allowedTypes)
|
||||
{
|
||||
// Check if ammo is available (not on cooldown)
|
||||
if (_ammoManager.IsAmmoAvailable(type, playerIndex))
|
||||
@@ -269,7 +277,7 @@ namespace Minigames.FortFight.AI
|
||||
|
||||
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))
|
||||
{
|
||||
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))
|
||||
{
|
||||
return ProjectileType.CeilingFan; // Drop on weak points
|
||||
return ProjectileType.CeilingFan; // Drop on weak points (if allowed)
|
||||
}
|
||||
|
||||
// Default to first available
|
||||
|
||||
@@ -59,6 +59,15 @@ namespace Minigames.FortFight.Core
|
||||
[Tooltip("Default AI difficulty level for single-player games")]
|
||||
[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")]
|
||||
[Tooltip("Radius of explosion effect from weak points")]
|
||||
[SerializeField] private float weakPointExplosionRadius = 2.5f;
|
||||
@@ -149,6 +158,8 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
public AIDifficulty DefaultAIDifficulty => defaultAIDifficulty;
|
||||
|
||||
public IReadOnlyList<ProjectileType> AIAllowedProjectiles => aiAllowedProjectiles;
|
||||
|
||||
public float WeakPointExplosionRadius => weakPointExplosionRadius;
|
||||
public float WeakPointExplosionDamage => weakPointExplosionDamage;
|
||||
public float WeakPointExplosionForce => weakPointExplosionForce;
|
||||
|
||||
@@ -3,6 +3,7 @@ using AppleHills.Core.Settings;
|
||||
using Common.Input;
|
||||
using Core;
|
||||
using Core.Settings;
|
||||
using Input;
|
||||
using Minigames.FortFight.Data;
|
||||
using Minigames.FortFight.Projectiles;
|
||||
using UnityEngine;
|
||||
@@ -74,6 +75,8 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
private ProjectileConfig _currentAmmo;
|
||||
private ProjectileBase _activeProjectile;
|
||||
private int _ownerPlayerIndex = -1;
|
||||
private bool _isAIControlled = false;
|
||||
|
||||
public ProjectileBase ActiveProjectile => _activeProjectile;
|
||||
|
||||
@@ -104,7 +107,21 @@ namespace Minigames.FortFight.Core
|
||||
trajectoryPreview.ForceHide();
|
||||
}
|
||||
|
||||
// Only register input for human players, not AI
|
||||
if (!_isAIControlled)
|
||||
{
|
||||
// Call base class Enable which handles input registration and enabling
|
||||
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)
|
||||
@@ -150,6 +167,18 @@ namespace Minigames.FortFight.Core
|
||||
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>
|
||||
/// Launch a projectile with calculated force and direction
|
||||
/// </summary>
|
||||
@@ -175,6 +204,9 @@ namespace Minigames.FortFight.Core
|
||||
// Initialize projectile with its type (loads damage and mass from settings)
|
||||
_activeProjectile.Initialize(_currentAmmo.projectileType);
|
||||
|
||||
// Set projectile owner for input control
|
||||
_activeProjectile.SetOwner(_ownerPlayerIndex, _isAIControlled);
|
||||
|
||||
// Launch it
|
||||
_activeProjectile.Launch(direction, force);
|
||||
|
||||
@@ -201,7 +233,7 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public void LaunchWithVelocity(Vector2 velocity)
|
||||
{
|
||||
@@ -211,22 +243,57 @@ namespace Minigames.FortFight.Core
|
||||
return;
|
||||
}
|
||||
|
||||
// Get projectile mass to calculate force
|
||||
float mass = _currentAmmo.GetMass();
|
||||
|
||||
// Force = mass × velocity (for impulse-based launch)
|
||||
Vector2 direction = velocity.normalized;
|
||||
float speed = velocity.magnitude;
|
||||
float force = mass * speed;
|
||||
float desiredSpeed = velocity.magnitude;
|
||||
|
||||
// Use common method - ensures same physics constraints as player
|
||||
float force = CalculateClampedForce(desiredSpeed, mass);
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// Get currently active projectile (in flight)
|
||||
/// </summary>
|
||||
|
||||
@@ -70,20 +70,20 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
#region State
|
||||
|
||||
private TurnState currentTurnState = TurnState.PlayerOneTurn;
|
||||
private PlayerData playerOne;
|
||||
private PlayerData playerTwo;
|
||||
private PlayerData currentPlayer;
|
||||
private int turnCount = 0;
|
||||
private TurnState _currentTurnState = TurnState.PlayerOneTurn;
|
||||
private PlayerData _playerOne;
|
||||
private PlayerData _playerTwo;
|
||||
private PlayerData _currentPlayer;
|
||||
private int _turnCount = 0;
|
||||
|
||||
// Turn action management
|
||||
private ProjectileTurnAction currentTurnAction;
|
||||
private bool isTransitioning = false;
|
||||
private float transitionTimer = 0f;
|
||||
private ProjectileTurnAction _currentTurnAction;
|
||||
private bool _isTransitioning = false;
|
||||
private float _transitionTimer = 0f;
|
||||
|
||||
public TurnState CurrentTurnState => currentTurnState;
|
||||
public PlayerData CurrentPlayer => currentPlayer;
|
||||
public int TurnCount => turnCount;
|
||||
public TurnState CurrentTurnState => _currentTurnState;
|
||||
public PlayerData CurrentPlayer => _currentPlayer;
|
||||
public int TurnCount => _turnCount;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -128,8 +128,8 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public void Initialize(PlayerData pPlayerOne, PlayerData pPlayerTwo)
|
||||
{
|
||||
this.playerOne = pPlayerOne;
|
||||
this.playerTwo = pPlayerTwo;
|
||||
this._playerOne = pPlayerOne;
|
||||
this._playerTwo = pPlayerTwo;
|
||||
|
||||
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>
|
||||
public void StartGame()
|
||||
{
|
||||
turnCount = 0;
|
||||
currentTurnState = TurnState.PlayerOneTurn;
|
||||
currentPlayer = playerOne;
|
||||
_turnCount = 0;
|
||||
_currentTurnState = TurnState.PlayerOneTurn;
|
||||
_currentPlayer = _playerOne;
|
||||
|
||||
// Set initial input mode to UI
|
||||
if (Input.InputManager.Instance != null)
|
||||
@@ -149,8 +149,8 @@ namespace Minigames.FortFight.Core
|
||||
Input.InputManager.Instance.SetInputMode(Input.InputMode.UI);
|
||||
}
|
||||
|
||||
Logging.Debug($"[TurnManager] Game started. First turn: {currentPlayer.PlayerName}");
|
||||
OnTurnStarted?.Invoke(currentPlayer, currentTurnState);
|
||||
Logging.Debug($"[TurnManager] Game started. First turn: {_currentPlayer.PlayerName}");
|
||||
OnTurnStarted?.Invoke(_currentPlayer, _currentTurnState);
|
||||
|
||||
// Start turn action for first player
|
||||
StartTurnAction();
|
||||
@@ -163,26 +163,26 @@ namespace Minigames.FortFight.Core
|
||||
private void Update()
|
||||
{
|
||||
// Update current turn action
|
||||
if (currentTurnAction != null && !isTransitioning)
|
||||
if (_currentTurnAction != null && !_isTransitioning)
|
||||
{
|
||||
currentTurnAction.Update();
|
||||
_currentTurnAction.Update();
|
||||
|
||||
// Check if action is complete
|
||||
if (currentTurnAction.IsComplete)
|
||||
if (_currentTurnAction.IsComplete)
|
||||
{
|
||||
EndTurnAction();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle transition timing
|
||||
if (isTransitioning)
|
||||
if (_isTransitioning)
|
||||
{
|
||||
transitionTimer += Time.deltaTime;
|
||||
_transitionTimer += Time.deltaTime;
|
||||
|
||||
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||
float transitionDelay = settings?.TurnTransitionDelay ?? 1.5f;
|
||||
|
||||
if (transitionTimer >= transitionDelay)
|
||||
if (_transitionTimer >= transitionDelay)
|
||||
{
|
||||
CompleteTransition();
|
||||
}
|
||||
@@ -199,38 +199,40 @@ namespace Minigames.FortFight.Core
|
||||
private void StartTurnAction()
|
||||
{
|
||||
// Get the appropriate slingshot for current player
|
||||
SlingshotController activeSlingshot = GetSlingshotForPlayer(currentPlayer);
|
||||
SlingshotController activeSlingshot = GetSlingshotForPlayer(_currentPlayer);
|
||||
|
||||
if (activeSlingshot == null)
|
||||
{
|
||||
Logging.Error($"[TurnManager] No slingshot found for {currentPlayer.PlayerName}!");
|
||||
Logging.Error($"[TurnManager] No slingshot found for {_currentPlayer.PlayerName}!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and execute turn action with player index
|
||||
currentTurnAction = new ProjectileTurnAction(activeSlingshot, AmmunitionManager.Instance, cameraController, currentPlayer.PlayerIndex);
|
||||
// Set slingshot owner (used for projectile ownership tracking)
|
||||
activeSlingshot.SetOwner(_currentPlayer.PlayerIndex, _currentPlayer.IsAI);
|
||||
|
||||
// Set current ammo on slingshot for this player
|
||||
if (AmmunitionManager.Instance != null)
|
||||
{
|
||||
ProjectileConfig currentAmmo = AmmunitionManager.Instance.GetSelectedAmmoConfig(currentPlayer.PlayerIndex);
|
||||
ProjectileConfig currentAmmo = AmmunitionManager.Instance.GetSelectedAmmoConfig(_currentPlayer.PlayerIndex);
|
||||
if (currentAmmo != null)
|
||||
{
|
||||
activeSlingshot.SetAmmo(currentAmmo);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the action (enables slingshot)
|
||||
currentTurnAction.Execute();
|
||||
// Create and execute turn action for BOTH player and AI
|
||||
// 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
|
||||
if (Input.InputManager.Instance != null)
|
||||
// Only switch input mode for human players
|
||||
if (!_currentPlayer.IsAI && Input.InputManager.Instance != null)
|
||||
{
|
||||
Input.InputManager.Instance.RegisterOverrideConsumer(activeSlingshot);
|
||||
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>
|
||||
@@ -238,10 +240,10 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
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
|
||||
SlingshotController activeSlingshot = GetSlingshotForPlayer(currentPlayer);
|
||||
SlingshotController activeSlingshot = GetSlingshotForPlayer(_currentPlayer);
|
||||
if (activeSlingshot != null && Input.InputManager.Instance != null)
|
||||
{
|
||||
Input.InputManager.Instance.UnregisterOverrideConsumer(activeSlingshot);
|
||||
@@ -254,7 +256,7 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
|
||||
// Clear turn action
|
||||
currentTurnAction = null;
|
||||
_currentTurnAction = null;
|
||||
|
||||
// End the turn
|
||||
EndTurn();
|
||||
@@ -265,29 +267,29 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
private void EndTurn()
|
||||
{
|
||||
if (currentTurnState == TurnState.GameOver)
|
||||
if (_currentTurnState == TurnState.GameOver)
|
||||
{
|
||||
Logging.Warning("[TurnManager] Cannot end turn - game is over");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.Debug($"[TurnManager] Turn ended for {currentPlayer.PlayerName}");
|
||||
OnTurnEnded?.Invoke(currentPlayer);
|
||||
Logging.Debug($"[TurnManager] Turn ended for {_currentPlayer.PlayerName}");
|
||||
OnTurnEnded?.Invoke(_currentPlayer);
|
||||
|
||||
// Decrement ammunition cooldowns for this player
|
||||
if (AmmunitionManager.Instance != null)
|
||||
{
|
||||
AmmunitionManager.Instance.DecrementCooldowns(currentPlayer.PlayerIndex);
|
||||
AmmunitionManager.Instance.DecrementCooldowns(_currentPlayer.PlayerIndex);
|
||||
}
|
||||
|
||||
// Enter transition state (triggers wide view camera via OnTurnStarted)
|
||||
currentTurnState = TurnState.TransitioningTurn;
|
||||
_currentTurnState = TurnState.TransitioningTurn;
|
||||
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
|
||||
isTransitioning = true;
|
||||
transitionTimer = 0f;
|
||||
_isTransitioning = true;
|
||||
_transitionTimer = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -295,8 +297,8 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
private void CompleteTransition()
|
||||
{
|
||||
isTransitioning = false;
|
||||
transitionTimer = 0f;
|
||||
_isTransitioning = false;
|
||||
_transitionTimer = 0f;
|
||||
|
||||
AdvanceToNextPlayer();
|
||||
}
|
||||
@@ -306,22 +308,22 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
private void AdvanceToNextPlayer()
|
||||
{
|
||||
turnCount++;
|
||||
_turnCount++;
|
||||
|
||||
// Switch players
|
||||
if (currentPlayer == playerOne)
|
||||
if (_currentPlayer == _playerOne)
|
||||
{
|
||||
currentPlayer = playerTwo;
|
||||
currentTurnState = playerTwo.IsAI ? TurnState.AITurn : TurnState.PlayerTwoTurn;
|
||||
_currentPlayer = _playerTwo;
|
||||
_currentTurnState = _playerTwo.IsAI ? TurnState.AITurn : TurnState.PlayerTwoTurn;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentPlayer = playerOne;
|
||||
currentTurnState = TurnState.PlayerOneTurn;
|
||||
_currentPlayer = _playerOne;
|
||||
_currentTurnState = TurnState.PlayerOneTurn;
|
||||
}
|
||||
|
||||
Logging.Debug($"[TurnManager] Advanced to turn {turnCount}. Current player: {currentPlayer.PlayerName} (State: {currentTurnState})");
|
||||
OnTurnStarted?.Invoke(currentPlayer, currentTurnState);
|
||||
Logging.Debug($"[TurnManager] Advanced to turn {_turnCount}. Current player: {_currentPlayer.PlayerName} (State: {_currentTurnState})");
|
||||
OnTurnStarted?.Invoke(_currentPlayer, _currentTurnState);
|
||||
|
||||
// Start turn action for next player
|
||||
StartTurnAction();
|
||||
@@ -332,11 +334,11 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
private SlingshotController GetSlingshotForPlayer(PlayerData player)
|
||||
{
|
||||
if (player == playerOne)
|
||||
if (player == _playerOne)
|
||||
{
|
||||
return playerOneSlingshotController;
|
||||
}
|
||||
else if (player == playerTwo)
|
||||
else if (player == _playerTwo)
|
||||
{
|
||||
return playerTwoSlingshotController;
|
||||
}
|
||||
@@ -349,7 +351,7 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public void SetGameOver()
|
||||
{
|
||||
currentTurnState = TurnState.GameOver;
|
||||
_currentTurnState = TurnState.GameOver;
|
||||
Logging.Debug("[TurnManager] Game over state set");
|
||||
}
|
||||
|
||||
|
||||
@@ -51,11 +51,16 @@ namespace Minigames.FortFight.Projectiles
|
||||
indicator.SetActive(true);
|
||||
}
|
||||
|
||||
// Register with InputManager to capture tap-to-drop
|
||||
if (Input.InputManager.Instance != null)
|
||||
// Only register with InputManager if NOT AI-controlled
|
||||
// AI projectiles should not accept player input
|
||||
if (!IsAIControlled && Input.InputManager.Instance != null)
|
||||
{
|
||||
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 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
|
||||
|
||||
#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()
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Core;
|
||||
using System.Collections;
|
||||
using Core;
|
||||
using Core.Settings;
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.FortFight.Projectiles
|
||||
@@ -7,15 +9,76 @@ namespace Minigames.FortFight.Projectiles
|
||||
/// <summary>
|
||||
/// Trash Bag projectile - splits into multiple smaller pieces on impact.
|
||||
/// Deals AOE damage in a forward cone.
|
||||
/// Can be manually detonated mid-flight by tapping.
|
||||
/// </summary>
|
||||
public class TrashBagProjectile : ProjectileBase
|
||||
public class TrashBagProjectile : ProjectileBase, ITouchInputConsumer
|
||||
{
|
||||
[Header("Trash Bag Specific")]
|
||||
[Tooltip("Prefab for individual trash pieces (small debris)")]
|
||||
[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)
|
||||
{
|
||||
// Prevent double-explosion if already manually detonated
|
||||
if (hasExploded) return;
|
||||
|
||||
// Deal initial damage from trash bag itself
|
||||
var block = collision.gameObject.GetComponent<Fort.FortBlock>();
|
||||
if (block != null)
|
||||
@@ -28,19 +91,57 @@ namespace Minigames.FortFight.Projectiles
|
||||
var settings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||
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
|
||||
Vector2 hitNormal = collision.contacts[0].normal;
|
||||
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);
|
||||
|
||||
// Destroy trash bag after spawning pieces
|
||||
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>
|
||||
/// Spawn multiple trash pieces in a cone away from the hit surface.
|
||||
/// 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}");
|
||||
}
|
||||
}
|
||||
|
||||
#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