Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppleHills.Core.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Developer settings for Fort Fight minigame technical configuration.
|
||||
/// These settings are separate from gameplay/design settings and focus on debugging and testing.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "FortFightDeveloperSettings", menuName = "AppleHills/Developer Settings/Fort Fight", order = 2)]
|
||||
public class FortFightDeveloperSettings : BaseDeveloperSettings
|
||||
{
|
||||
[Header("AI Debug Visualization")]
|
||||
[Tooltip("Show AI debug visuals (target circles, trajectory lines)")]
|
||||
[SerializeField] private bool showAIDebugVisuals = true;
|
||||
|
||||
[Tooltip("Color for AI target indicator")]
|
||||
[SerializeField] private Color aiDebugTargetColor = Color.red;
|
||||
|
||||
[Tooltip("Radius of AI target circle")]
|
||||
[SerializeField] private float aiDebugCircleRadius = 0.5f;
|
||||
|
||||
[Tooltip("Color for AI trajectory line")]
|
||||
[SerializeField] private Color aiDebugTrajectoryColor = Color.yellow;
|
||||
|
||||
[Header("Debug Logging")]
|
||||
[Tooltip("Show debug logs from SlingshotController")]
|
||||
[SerializeField] private bool slingshotShowDebugLogs = true;
|
||||
|
||||
[Tooltip("Show debug logs from AmmunitionManager")]
|
||||
[SerializeField] private bool ammunitionShowDebugLogs = true;
|
||||
|
||||
[Tooltip("Show debug info from FortController")]
|
||||
[SerializeField] private bool fortShowDebugInfo = true;
|
||||
|
||||
[Header("UI Debug Display")]
|
||||
[Tooltip("Show numerical HP values in FortHealthUI")]
|
||||
[SerializeField] private bool healthUIDebugDisplay = false;
|
||||
|
||||
[Header("Testing & Debug Prefabs")]
|
||||
[Tooltip("Debug prefab for player fort (testing purposes)")]
|
||||
[SerializeField] private GameObject debugPlayerFortPrefab;
|
||||
|
||||
[Tooltip("Debug prefab for enemy fort (testing purposes)")]
|
||||
[SerializeField] private GameObject debugEnemyFortPrefab;
|
||||
|
||||
// AI Debug Visualization properties
|
||||
public bool ShowAIDebugVisuals => showAIDebugVisuals;
|
||||
public Color AIDebugTargetColor => aiDebugTargetColor;
|
||||
public float AIDebugCircleRadius => aiDebugCircleRadius;
|
||||
public Color AIDebugTrajectoryColor => aiDebugTrajectoryColor;
|
||||
|
||||
// Debug Logging properties
|
||||
public bool SlingshotShowDebugLogs => slingshotShowDebugLogs;
|
||||
public bool AmmunitionShowDebugLogs => ammunitionShowDebugLogs;
|
||||
public bool FortShowDebugInfo => fortShowDebugInfo;
|
||||
|
||||
// UI Debug Display properties
|
||||
public bool HealthUIDebugDisplay => healthUIDebugDisplay;
|
||||
|
||||
// Testing & Debug Prefabs properties
|
||||
public GameObject DebugPlayerFortPrefab => debugPlayerFortPrefab;
|
||||
public GameObject DebugEnemyFortPrefab => debugEnemyFortPrefab;
|
||||
|
||||
public override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
// Validate radius
|
||||
aiDebugCircleRadius = Mathf.Max(0.1f, aiDebugCircleRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d354649c25014aa2a7b3b45841834f74
|
||||
timeCreated: 1764841328
|
||||
@@ -223,6 +223,10 @@ namespace AppleHills.Core.Settings
|
||||
System.Collections.Generic.List<Minigames.FortFight.Settings.BlockMaterialConfig> MaterialConfigs { get; }
|
||||
System.Collections.Generic.List<Minigames.FortFight.Settings.BlockSizeConfig> SizeConfigs { get; }
|
||||
|
||||
// AI Difficulty Settings
|
||||
Minigames.FortFight.Data.AIDifficulty DefaultAIDifficulty { get; } // Default difficulty level for AI
|
||||
Minigames.FortFight.Data.AIDifficultyData GetAIDifficultyData(Minigames.FortFight.Data.AIDifficulty difficulty);
|
||||
|
||||
// Weak point settings
|
||||
float WeakPointExplosionRadius { get; }
|
||||
float WeakPointExplosionDamage { get; }
|
||||
|
||||
@@ -90,5 +90,35 @@ namespace AppleHills
|
||||
}
|
||||
|
||||
// Add more methods as needed for other settings
|
||||
|
||||
/// <summary>
|
||||
/// Get developer settings for editor-mode usage (OnDrawGizmos, etc.)
|
||||
/// This handles both editor and play mode seamlessly.
|
||||
/// </summary>
|
||||
public static T GetDeveloperSettingsForEditor<T>() where T : AppleHills.Core.Settings.BaseDeveloperSettings
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
// In editor mode (not playing), use reflection to access EditorSettingsProvider
|
||||
// This avoids direct reference to Editor assembly from runtime code
|
||||
var editorProviderType = System.Type.GetType("AppleHills.Editor.EditorSettingsProvider, Assembly-CSharp-Editor");
|
||||
if (editorProviderType != null)
|
||||
{
|
||||
var method = editorProviderType.GetMethod("GetDeveloperSettings",
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
if (method != null)
|
||||
{
|
||||
var genericMethod = method.MakeGenericMethod(typeof(T));
|
||||
return genericMethod.Invoke(null, null) as T;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
|
||||
// In play mode, use GameManager
|
||||
return GameManager.GetDeveloperSettings<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,51 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Minigames.FortFight.Core;
|
||||
using Minigames.FortFight.Data;
|
||||
using Minigames.FortFight.Fort;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.FortFight.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// AI controller for the PigMan opponent.
|
||||
/// Phase 1: Stubbed implementation - just simulates taking a turn.
|
||||
/// Phase 4: Full implementation with trajectory calculation and target selection.
|
||||
/// AI controller for Fort Fight opponent.
|
||||
/// Implements trajectory calculation, target selection, and shot execution with configurable difficulty.
|
||||
/// Includes debug visualization and thinking animations.
|
||||
/// </summary>
|
||||
public class FortFightAIController : ManagedBehaviour
|
||||
{
|
||||
[Header("AI Settings (Stubbed)")]
|
||||
[SerializeField] private float aiThinkTime = 1.5f; // Time AI "thinks" before acting
|
||||
#region Inspector Properties
|
||||
|
||||
private TurnManager turnManager;
|
||||
private bool isThinking = false;
|
||||
[Header("AI References")]
|
||||
[SerializeField] private SlingshotController aiSlingshot;
|
||||
[Tooltip("Optional: Animator for AI character thinking animations")]
|
||||
[SerializeField] private Animator aiAnimator;
|
||||
|
||||
[Header("UI References")]
|
||||
[SerializeField] private GameObject thinkingIndicator;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private State
|
||||
|
||||
private TurnManager _turnManager;
|
||||
private AmmunitionManager _ammoManager;
|
||||
private FortManager _fortManager;
|
||||
private bool _isExecutingTurn = false;
|
||||
|
||||
// Current target info
|
||||
private FortBlock _targetBlock;
|
||||
private Vector2 _targetPosition;
|
||||
private ProjectileType _selectedAmmo;
|
||||
|
||||
// Settings cache
|
||||
private AppleHills.Core.Settings.IFortFightSettings _cachedSettings;
|
||||
private AppleHills.Core.Settings.FortFightDeveloperSettings _cachedDevSettings;
|
||||
private AIDifficultyData _currentDifficultyData;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
@@ -27,19 +54,57 @@ namespace Minigames.FortFight.AI
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
// Get reference to turn manager via singleton
|
||||
turnManager = TurnManager.Instance;
|
||||
// Load settings
|
||||
_cachedSettings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IFortFightSettings>();
|
||||
_cachedDevSettings = GameManager.GetDeveloperSettings<AppleHills.Core.Settings.FortFightDeveloperSettings>();
|
||||
|
||||
if (turnManager == null)
|
||||
if (_cachedSettings == null)
|
||||
{
|
||||
Logging.Error("[FortFightAIController] Failed to load FortFightSettings!");
|
||||
}
|
||||
|
||||
// Load difficulty configuration from settings
|
||||
if (_cachedSettings != null)
|
||||
{
|
||||
AIDifficulty difficulty = _cachedSettings.DefaultAIDifficulty;
|
||||
_currentDifficultyData = _cachedSettings.GetAIDifficultyData(difficulty);
|
||||
Logging.Debug($"[FortFightAIController] Loaded AI difficulty: {difficulty} - Angle: ±{_currentDifficultyData.angleDeviation}°, Force: ±{_currentDifficultyData.forceDeviation * 100}%");
|
||||
}
|
||||
|
||||
// Get references to managers via singletons
|
||||
_turnManager = TurnManager.Instance;
|
||||
_ammoManager = AmmunitionManager.Instance;
|
||||
_fortManager = FortManager.Instance;
|
||||
|
||||
if (_turnManager == null)
|
||||
{
|
||||
Logging.Error("[FortFightAIController] TurnManager not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscribe to turn events
|
||||
turnManager.OnTurnStarted += OnTurnStarted;
|
||||
if (_ammoManager == null)
|
||||
{
|
||||
Logging.Error("[FortFightAIController] AmmunitionManager not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.Debug("[FortFightAIController] AI initialized");
|
||||
if (_fortManager == null)
|
||||
{
|
||||
Logging.Error("[FortFightAIController] FortManager not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (aiSlingshot == null)
|
||||
{
|
||||
Logging.Error("[FortFightAIController] AI Slingshot not assigned!");
|
||||
}
|
||||
|
||||
// Subscribe to turn events
|
||||
_turnManager.OnTurnStarted += OnTurnStarted;
|
||||
|
||||
bool debugVisualsEnabled = _cachedDevSettings?.ShowAIDebugVisuals ?? false;
|
||||
AIDifficulty activeDifficulty = _cachedSettings?.DefaultAIDifficulty ?? AIDifficulty.Medium;
|
||||
Logging.Debug($"[FortFightAIController] AI initialized - Difficulty: {activeDifficulty}, Debug Visuals: {debugVisualsEnabled}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -52,49 +117,403 @@ namespace Minigames.FortFight.AI
|
||||
private void OnTurnStarted(PlayerData currentPlayer, TurnState turnState)
|
||||
{
|
||||
// Only act if it's AI's turn
|
||||
if (turnState == TurnState.AITurn && !isThinking)
|
||||
if (turnState == TurnState.AITurn && !_isExecutingTurn)
|
||||
{
|
||||
StartCoroutine(ExecuteAITurn());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the AI's turn (stubbed for Phase 1)
|
||||
/// Execute the AI's turn with thinking phases and shot execution
|
||||
/// </summary>
|
||||
private IEnumerator ExecuteAITurn()
|
||||
{
|
||||
isThinking = true;
|
||||
_isExecutingTurn = true;
|
||||
|
||||
Logging.Debug($"[FortFightAIController] AI is thinking... (for {aiThinkTime}s)");
|
||||
Logging.Debug("[FortFightAIController] === AI TURN START ===");
|
||||
|
||||
// Simulate AI "thinking"
|
||||
yield return new WaitForSeconds(aiThinkTime);
|
||||
// Phase 1: Thinking - Target Selection
|
||||
yield return StartCoroutine(ThinkingPhase("Analyzing targets..."));
|
||||
_targetBlock = SelectBestTarget();
|
||||
|
||||
// STUBBED: Perform AI action
|
||||
Logging.Debug("[FortFightAIController] AI takes action! (STUBBED - no actual projectile fired yet)");
|
||||
if (_targetBlock == null)
|
||||
{
|
||||
Logging.Warning("[FortFightAIController] No valid target found! Skipping turn.");
|
||||
_isExecutingTurn = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// TODO Phase 4: AI should trigger its slingshot to fire projectile here
|
||||
// Turn will automatically advance when AI's projectile settles (via ProjectileTurnAction)
|
||||
// Do NOT manually call EndTurn() - it's now private and automatic
|
||||
_targetPosition = _targetBlock.transform.position;
|
||||
Logging.Debug($"[FortFightAIController] Target selected: {_targetBlock.gameObject.name} at {_targetPosition}");
|
||||
|
||||
// NOTE: For now, AI turn will hang until Phase 4 AI projectile system is implemented
|
||||
// To test without AI, use TwoPlayer mode
|
||||
// Phase 2: Thinking - Ammunition Selection
|
||||
yield return StartCoroutine(ThinkingPhase("Selecting ammunition..."));
|
||||
_selectedAmmo = ChooseProjectile();
|
||||
Logging.Debug($"[FortFightAIController] Ammo selected: {_selectedAmmo}");
|
||||
|
||||
isThinking = false;
|
||||
Logging.Warning("[FortFightAIController] AI turn stubbed - Phase 4 needed for AI projectile firing");
|
||||
// Phase 3: Thinking - Trajectory Calculation
|
||||
yield return StartCoroutine(ThinkingPhase("Calculating trajectory..."));
|
||||
Vector2 launchVector = CalculateTrajectoryWithDeviation(aiSlingshot.transform.position, _targetPosition);
|
||||
Logging.Debug($"[FortFightAIController] Trajectory calculated: {launchVector}");
|
||||
|
||||
// Phase 4: Execute Shot
|
||||
yield return new WaitForSeconds(0.3f); // Brief pause before shot
|
||||
ExecuteShot(launchVector);
|
||||
|
||||
Logging.Debug("[FortFightAIController] === AI TURN SHOT EXECUTED ===");
|
||||
_isExecutingTurn = false;
|
||||
|
||||
// Turn will automatically end when projectile settles (handled by TurnManager)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thinking phase with random duration and optional animation trigger
|
||||
/// </summary>
|
||||
private IEnumerator ThinkingPhase(string thinkingAction)
|
||||
{
|
||||
float thinkTime = Random.Range(_currentDifficultyData.thinkTimeMin, _currentDifficultyData.thinkTimeMax);
|
||||
Logging.Debug($"[FortFightAIController] {thinkingAction} ({thinkTime:F1}s)");
|
||||
|
||||
// TODO: Play some funny stuff here?
|
||||
// TODO: Placeholder "thinking" exlamation point
|
||||
if (thinkingIndicator != null)
|
||||
{
|
||||
thinkingIndicator.SetActive(true);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(thinkTime);
|
||||
|
||||
if (thinkingIndicator != null)
|
||||
{
|
||||
thinkingIndicator.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI Logic
|
||||
|
||||
/// <summary>
|
||||
/// Select the best target block to aim at
|
||||
/// Priority: Weak points > Low HP blocks > Random block
|
||||
/// </summary>
|
||||
private FortBlock SelectBestTarget()
|
||||
{
|
||||
if (_fortManager.PlayerFort == null)
|
||||
{
|
||||
Logging.Error("[FortFightAIController] Player fort not found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<FortBlock> weakPoints = _fortManager.PlayerFort.GetWeakPoints();
|
||||
|
||||
// Priority 1: Target weak points
|
||||
if (weakPoints != null && weakPoints.Count > 0)
|
||||
{
|
||||
FortBlock target = weakPoints[Random.Range(0, weakPoints.Count)];
|
||||
Logging.Debug($"[FortFightAIController] Targeting weak point: {target.gameObject.name}");
|
||||
return target;
|
||||
}
|
||||
|
||||
// Priority 2: Target lowest HP block
|
||||
List<FortBlock> allBlocks = new List<FortBlock>();
|
||||
foreach (Transform child in _fortManager.PlayerFort.transform)
|
||||
{
|
||||
FortBlock block = child.GetComponent<FortBlock>();
|
||||
if (block != null && !block.IsDestroyed)
|
||||
{
|
||||
allBlocks.Add(block);
|
||||
}
|
||||
}
|
||||
|
||||
if (allBlocks.Count == 0)
|
||||
{
|
||||
Logging.Warning("[FortFightAIController] No blocks available to target!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort by HP and pick from bottom 30%
|
||||
allBlocks.Sort((a, b) => a.CurrentHp.CompareTo(b.CurrentHp));
|
||||
int targetRange = Mathf.Max(1, allBlocks.Count / 3);
|
||||
FortBlock lowestHpTarget = allBlocks[Random.Range(0, targetRange)];
|
||||
|
||||
Logging.Debug($"[FortFightAIController] Targeting low HP block: {lowestHpTarget.gameObject.name} (HP: {lowestHpTarget.CurrentHp})");
|
||||
return lowestHpTarget;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose the best projectile type for current situation
|
||||
/// </summary>
|
||||
private ProjectileType ChooseProjectile()
|
||||
{
|
||||
// Get AI player index
|
||||
int playerIndex = _turnManager.CurrentPlayer.PlayerIndex;
|
||||
|
||||
// Get available projectiles (check cooldowns via ammo manager)
|
||||
var availableTypes = new List<ProjectileType>();
|
||||
foreach (ProjectileType type in System.Enum.GetValues(typeof(ProjectileType)))
|
||||
{
|
||||
// Check if ammo is available (not on cooldown)
|
||||
if (_ammoManager.IsAmmoAvailable(type, playerIndex))
|
||||
{
|
||||
availableTypes.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
if (availableTypes.Count == 0)
|
||||
{
|
||||
Logging.Warning("[FortFightAIController] No ammo available! Using Toaster as default.");
|
||||
return ProjectileType.Toaster;
|
||||
}
|
||||
|
||||
float enemyHpPercent = _fortManager.PlayerFort.HpPercentage;
|
||||
|
||||
// Strategic selection based on fort HP
|
||||
if (enemyHpPercent > 70f && availableTypes.Contains(ProjectileType.TrashBag))
|
||||
{
|
||||
return ProjectileType.TrashBag; // Spread damage early game
|
||||
}
|
||||
else if (enemyHpPercent > 40f && availableTypes.Contains(ProjectileType.Vacuum))
|
||||
{
|
||||
return ProjectileType.Vacuum; // Destruction machine mid-game
|
||||
}
|
||||
else if (_targetBlock != null && _targetBlock.IsWeakPoint && availableTypes.Contains(ProjectileType.CeilingFan))
|
||||
{
|
||||
return ProjectileType.CeilingFan; // Drop on weak points
|
||||
}
|
||||
|
||||
// Default to first available
|
||||
return availableTypes[Random.Range(0, availableTypes.Count)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate ballistic trajectory to hit target using proper parabolic flight physics
|
||||
/// Returns velocity vector needed to hit the target
|
||||
/// </summary>
|
||||
private Vector2 CalculateTrajectoryWithDeviation(Vector2 from, Vector2 to)
|
||||
{
|
||||
// Get gravity from Physics2D settings
|
||||
float gravity = Mathf.Abs(Physics2D.gravity.y);
|
||||
if (gravity < 0.1f) gravity = 9.81f; // Fallback to standard gravity
|
||||
|
||||
// Calculate displacement
|
||||
Vector2 displacement = to - from;
|
||||
float horizontalDist = displacement.x;
|
||||
float verticalDist = displacement.y;
|
||||
|
||||
// Calculate perfect launch velocity using ballistic trajectory equations
|
||||
// We'll use a 45-degree angle as base (optimal for distance) and adjust
|
||||
Vector2 perfectVelocity = CalculateBallisticVelocity(horizontalDist, verticalDist, gravity);
|
||||
|
||||
if (perfectVelocity == Vector2.zero)
|
||||
{
|
||||
// Fallback if physics calculation fails
|
||||
Logging.Warning("[FortFightAIController] Ballistic calculation failed, using fallback");
|
||||
perfectVelocity = displacement.normalized * 15f;
|
||||
}
|
||||
|
||||
// Apply difficulty-based deviation to velocity
|
||||
Vector2 deviatedVelocity = ApplyDeviation(perfectVelocity);
|
||||
|
||||
// Log detailed trajectory info
|
||||
float angle = Mathf.Atan2(deviatedVelocity.y, deviatedVelocity.x) * Mathf.Rad2Deg;
|
||||
float speed = deviatedVelocity.magnitude;
|
||||
Logging.Debug($"[FortFightAIController] Ballistic Trajectory: angle={angle:F1}°, speed={speed:F1} m/s, distance={displacement.magnitude:F1}m, gravity={gravity:F1}");
|
||||
|
||||
return deviatedVelocity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate required velocity to hit target using ballistic equations
|
||||
/// Uses quadratic formula to solve for launch angle given distance and height
|
||||
/// </summary>
|
||||
private Vector2 CalculateBallisticVelocity(float dx, float dy, float gravity)
|
||||
{
|
||||
// Try multiple launch angles and pick the first valid solution
|
||||
// We prefer lower angles (30-60 degrees) for more direct shots
|
||||
float[] preferredAngles = { 45f, 40f, 50f, 35f, 55f, 30f, 60f };
|
||||
|
||||
foreach (float angleDegrees in preferredAngles)
|
||||
{
|
||||
float angleRad = angleDegrees * Mathf.Deg2Rad;
|
||||
|
||||
// Calculate required speed for this angle
|
||||
// Formula: v = sqrt((g * dx^2) / (2 * cos^2(θ) * (dx * tan(θ) - dy)))
|
||||
float cosTheta = Mathf.Cos(angleRad);
|
||||
float tanTheta = Mathf.Tan(angleRad);
|
||||
|
||||
float denominator = 2f * cosTheta * cosTheta * (dx * tanTheta - dy);
|
||||
|
||||
if (denominator > 0)
|
||||
{
|
||||
float speedSquared = (gravity * dx * dx) / denominator;
|
||||
|
||||
if (speedSquared > 0)
|
||||
{
|
||||
float speed = Mathf.Sqrt(speedSquared);
|
||||
|
||||
// Clamp speed to reasonable range (5-25 m/s)
|
||||
speed = Mathf.Clamp(speed, 5f, 25f);
|
||||
|
||||
// Calculate velocity components
|
||||
float vx = speed * cosTheta * Mathf.Sign(dx);
|
||||
float vy = speed * Mathf.Sin(angleRad);
|
||||
|
||||
return new Vector2(vx, vy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid solution found, use simple physics
|
||||
// Estimate time of flight and calculate velocity
|
||||
float distance = Mathf.Sqrt(dx * dx + dy * dy);
|
||||
float estimatedTime = Mathf.Sqrt(2f * distance / gravity);
|
||||
|
||||
if (estimatedTime > 0)
|
||||
{
|
||||
float vx = dx / estimatedTime;
|
||||
float vy = dy / estimatedTime + 0.5f * gravity * estimatedTime;
|
||||
return new Vector2(vx, vy);
|
||||
}
|
||||
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply difficulty-based deviation to velocity
|
||||
/// </summary>
|
||||
private Vector2 ApplyDeviation(Vector2 perfectVelocity)
|
||||
{
|
||||
float angleDeviation = GetAngleDeviation();
|
||||
float speedDeviation = GetForceDeviation(); // Reuse for speed deviation
|
||||
|
||||
// Convert velocity to polar coordinates
|
||||
float speed = perfectVelocity.magnitude;
|
||||
float angle = Mathf.Atan2(perfectVelocity.y, perfectVelocity.x) * Mathf.Rad2Deg;
|
||||
|
||||
// Apply angle deviation
|
||||
angle += Random.Range(-angleDeviation, angleDeviation);
|
||||
|
||||
// Apply speed deviation
|
||||
speed *= Random.Range(1f - speedDeviation, 1f + speedDeviation);
|
||||
|
||||
// Convert back to cartesian
|
||||
float angleRad = angle * Mathf.Deg2Rad;
|
||||
return new Vector2(Mathf.Cos(angleRad), Mathf.Sin(angleRad)) * speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get angle deviation based on current difficulty data
|
||||
/// </summary>
|
||||
private float GetAngleDeviation()
|
||||
{
|
||||
return _currentDifficultyData.angleDeviation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get force deviation based on current difficulty data
|
||||
/// </summary>
|
||||
private float GetForceDeviation()
|
||||
{
|
||||
return _currentDifficultyData.forceDeviation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the shot by launching projectile from AI slingshot with calculated velocity
|
||||
/// </summary>
|
||||
private void ExecuteShot(Vector2 launchVelocity)
|
||||
{
|
||||
if (aiSlingshot == null)
|
||||
{
|
||||
Logging.Error("[FortFightAIController] AI Slingshot not assigned! Cannot execute shot.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set ammunition type for AI player
|
||||
int playerIndex = _turnManager.CurrentPlayer.PlayerIndex;
|
||||
_ammoManager.SelectAmmo(_selectedAmmo, playerIndex);
|
||||
|
||||
// Get the selected ammo config and set it on the slingshot
|
||||
ProjectileConfig ammoConfig = _ammoManager.GetSelectedAmmoConfig(playerIndex);
|
||||
if (ammoConfig != null)
|
||||
{
|
||||
aiSlingshot.SetAmmo(ammoConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Error("[FortFightAIController] Failed to get ammo config!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire projectile using velocity-based launch (proper ballistic physics)
|
||||
aiSlingshot.LaunchWithVelocity(launchVelocity);
|
||||
|
||||
// Trigger shot animation if animator exists
|
||||
if (aiAnimator != null)
|
||||
{
|
||||
aiAnimator.SetTrigger("Shoot");
|
||||
}
|
||||
|
||||
Logging.Debug($"[FortFightAIController] Shot executed: {_selectedAmmo} with velocity {launchVelocity}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Visualization
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// Get developer settings (works in both editor and play mode)
|
||||
var devSettings = AppleHills.SettingsAccess.GetDeveloperSettingsForEditor<AppleHills.Core.Settings.FortFightDeveloperSettings>();
|
||||
if (devSettings == null || !devSettings.ShowAIDebugVisuals) return;
|
||||
|
||||
// Only draw during play mode when AI is actively executing
|
||||
if (!Application.isPlaying) return;
|
||||
|
||||
// Draw target circle
|
||||
if (_targetBlock != null && _isExecutingTurn)
|
||||
{
|
||||
Gizmos.color = devSettings.AIDebugTargetColor;
|
||||
DrawCircle(_targetPosition, devSettings.AIDebugCircleRadius);
|
||||
|
||||
// Draw line from slingshot to target
|
||||
if (aiSlingshot != null)
|
||||
{
|
||||
Gizmos.color = devSettings.AIDebugTrajectoryColor;
|
||||
Gizmos.DrawLine(aiSlingshot.transform.position, _targetPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a circle gizmo (approximation)
|
||||
/// </summary>
|
||||
private void DrawCircle(Vector2 center, float radius)
|
||||
{
|
||||
int segments = 32;
|
||||
float angleStep = 360f / segments;
|
||||
Vector3 prevPoint = center + new Vector2(radius, 0);
|
||||
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
float angle = angleStep * i * Mathf.Deg2Rad;
|
||||
Vector3 newPoint = center + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
|
||||
Gizmos.DrawLine(prevPoint, newPoint);
|
||||
prevPoint = newPoint;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanup
|
||||
|
||||
internal override void OnManagedDestroy()
|
||||
{
|
||||
base.OnManagedDestroy();
|
||||
|
||||
if (turnManager != null)
|
||||
if (_turnManager != null)
|
||||
{
|
||||
turnManager.OnTurnStarted -= OnTurnStarted;
|
||||
_turnManager.OnTurnStarted -= OnTurnStarted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,23 +44,33 @@ namespace Minigames.FortFight.Core
|
||||
[Tooltip("Default projectile type selected at game start")]
|
||||
[SerializeField] private ProjectileType defaultProjectileType = ProjectileType.Toaster;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLogs = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Settings
|
||||
|
||||
private IFortFightSettings cachedSettings;
|
||||
private IFortFightSettings _cachedSettings;
|
||||
private IFortFightSettings CachedSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedSettings == null)
|
||||
if (_cachedSettings == null)
|
||||
{
|
||||
cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||
_cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||
}
|
||||
return cachedSettings;
|
||||
return _cachedSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private FortFightDeveloperSettings _cachedDevSettings;
|
||||
private FortFightDeveloperSettings CachedDevSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedDevSettings == null)
|
||||
{
|
||||
_cachedDevSettings = GameManager.GetDeveloperSettings<FortFightDeveloperSettings>();
|
||||
}
|
||||
return _cachedDevSettings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +83,8 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShowDebugLogs => CachedDevSettings?.AmmunitionShowDebugLogs ?? false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -97,7 +109,7 @@ namespace Minigames.FortFight.Core
|
||||
#region State
|
||||
|
||||
// Per-player ammunition state (encapsulates cooldowns, selection, usage)
|
||||
private Dictionary<int, PlayerAmmoState> playerStates = new Dictionary<int, PlayerAmmoState>();
|
||||
private Dictionary<int, PlayerAmmoState> _playerStates = new Dictionary<int, PlayerAmmoState>();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -127,12 +139,12 @@ namespace Minigames.FortFight.Core
|
||||
for (int playerIndex = 0; playerIndex < MaxPlayers; playerIndex++)
|
||||
{
|
||||
// Create player state with default ammo
|
||||
playerStates[playerIndex] = new PlayerAmmoState(playerIndex, defaultProjectileType);
|
||||
_playerStates[playerIndex] = new PlayerAmmoState(playerIndex, defaultProjectileType);
|
||||
|
||||
// Initialize cooldowns for all projectile types
|
||||
foreach (var config in configs)
|
||||
{
|
||||
playerStates[playerIndex].InitializeCooldown(config.projectileType);
|
||||
_playerStates[playerIndex].InitializeCooldown(config.projectileType);
|
||||
}
|
||||
|
||||
// Select default ammo (triggers event)
|
||||
@@ -146,14 +158,14 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public void DecrementCooldowns(int playerIndex)
|
||||
{
|
||||
if (!playerStates.ContainsKey(playerIndex))
|
||||
if (!_playerStates.ContainsKey(playerIndex))
|
||||
{
|
||||
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrement cooldowns and get completed types
|
||||
List<ProjectileType> completedCooldowns = playerStates[playerIndex].DecrementCooldowns();
|
||||
List<ProjectileType> completedCooldowns = _playerStates[playerIndex].DecrementCooldowns();
|
||||
|
||||
// Fire events for completed cooldowns
|
||||
var settings = CachedSettings;
|
||||
@@ -162,7 +174,7 @@ namespace Minigames.FortFight.Core
|
||||
var config = settings?.GetProjectileConfig(type);
|
||||
if (config != null)
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} cooldown completed");
|
||||
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} cooldown completed");
|
||||
OnAmmoCooldownCompleted?.Invoke(type);
|
||||
}
|
||||
}
|
||||
@@ -179,7 +191,7 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public bool SelectAmmo(ProjectileType type, int playerIndex)
|
||||
{
|
||||
if (!playerStates.ContainsKey(playerIndex))
|
||||
if (!_playerStates.ContainsKey(playerIndex))
|
||||
{
|
||||
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
|
||||
return false;
|
||||
@@ -196,12 +208,12 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
if (!IsAmmoAvailable(type, playerIndex))
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} is on cooldown");
|
||||
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} is on cooldown");
|
||||
return false;
|
||||
}
|
||||
|
||||
playerStates[playerIndex].SelectedAmmo = type;
|
||||
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex} selected: {config.displayName}");
|
||||
_playerStates[playerIndex].SelectedAmmo = type;
|
||||
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex} selected: {config.displayName}");
|
||||
|
||||
OnAmmoSelected?.Invoke(type, playerIndex);
|
||||
return true;
|
||||
@@ -212,9 +224,9 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public ProjectileType GetSelectedAmmoType(int playerIndex)
|
||||
{
|
||||
if (playerStates.ContainsKey(playerIndex))
|
||||
if (_playerStates.ContainsKey(playerIndex))
|
||||
{
|
||||
return playerStates[playerIndex].SelectedAmmo;
|
||||
return _playerStates[playerIndex].SelectedAmmo;
|
||||
}
|
||||
return defaultProjectileType;
|
||||
}
|
||||
@@ -233,12 +245,12 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public bool IsAmmoAvailable(ProjectileType type, int playerIndex)
|
||||
{
|
||||
if (!playerStates.ContainsKey(playerIndex))
|
||||
if (!_playerStates.ContainsKey(playerIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return playerStates[playerIndex].IsAmmoAvailable(type);
|
||||
return _playerStates[playerIndex].IsAmmoAvailable(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -246,12 +258,12 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public int GetCooldownRemaining(ProjectileType type, int playerIndex)
|
||||
{
|
||||
if (!playerStates.ContainsKey(playerIndex))
|
||||
if (!_playerStates.ContainsKey(playerIndex))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return playerStates[playerIndex].GetCooldown(type);
|
||||
return _playerStates[playerIndex].GetCooldown(type);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -263,7 +275,7 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public void UseAmmo(ProjectileType type, int playerIndex)
|
||||
{
|
||||
if (!playerStates.ContainsKey(playerIndex))
|
||||
if (!_playerStates.ContainsKey(playerIndex))
|
||||
{
|
||||
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
|
||||
return;
|
||||
@@ -279,10 +291,10 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
|
||||
// Set cooldown and record usage
|
||||
playerStates[playerIndex].SetCooldown(type, config.cooldownTurns);
|
||||
playerStates[playerIndex].RecordUsage(type);
|
||||
_playerStates[playerIndex].SetCooldown(type, config.cooldownTurns);
|
||||
_playerStates[playerIndex].RecordUsage(type);
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} used - cooldown: {config.cooldownTurns} turns");
|
||||
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} used - cooldown: {config.cooldownTurns} turns");
|
||||
|
||||
OnAmmoCooldownStarted?.Invoke(type, config.cooldownTurns);
|
||||
}
|
||||
@@ -298,7 +310,7 @@ namespace Minigames.FortFight.Core
|
||||
{
|
||||
var configs = AvailableConfigs;
|
||||
|
||||
foreach (var playerState in playerStates.Values)
|
||||
foreach (var playerState in _playerStates.Values)
|
||||
{
|
||||
foreach (var config in configs)
|
||||
{
|
||||
@@ -306,7 +318,7 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[AmmunitionManager] All cooldowns reset for all players");
|
||||
if (ShowDebugLogs) Logging.Debug("[AmmunitionManager] All cooldowns reset for all players");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -32,6 +32,18 @@ namespace Minigames.FortFight.Core
|
||||
new BlockSizeConfig { size = BlockSize.Large, hpMultiplier = 2f, massMultiplier = 2f }
|
||||
};
|
||||
|
||||
[Header("AI Difficulty Configurations")]
|
||||
[Tooltip("AI behavior parameters for each difficulty level")]
|
||||
[SerializeField] private List<AIDifficultyConfig> aiDifficultyConfigs = new List<AIDifficultyConfig>
|
||||
{
|
||||
new AIDifficultyConfig { difficulty = AIDifficulty.Easy, data = new AIDifficultyData(45f, 0.3f, 0.5f, 2f) }, // ±45° angle, ±30% force, 0.5-2s think time
|
||||
new AIDifficultyConfig { difficulty = AIDifficulty.Medium, data = new AIDifficultyData(30f, 0.2f, 0.5f, 2f) }, // ±30° angle, ±20% force, 0.5-2s think time
|
||||
new AIDifficultyConfig { difficulty = AIDifficulty.Hard, data = new AIDifficultyData(10f, 0.1f, 0.5f, 2f) } // ±10° angle, ±10% force, 0.5-2s think time
|
||||
};
|
||||
|
||||
[Tooltip("Default AI difficulty level for single-player games")]
|
||||
[SerializeField] private AIDifficulty defaultAIDifficulty = AIDifficulty.Medium;
|
||||
|
||||
[Header("Weak Point Settings")]
|
||||
[Tooltip("Radius of explosion effect from weak points")]
|
||||
[SerializeField] private float weakPointExplosionRadius = 2.5f;
|
||||
@@ -133,6 +145,8 @@ namespace Minigames.FortFight.Core
|
||||
public List<BlockMaterialConfig> MaterialConfigs => materialConfigs;
|
||||
public List<BlockSizeConfig> SizeConfigs => sizeConfigs;
|
||||
|
||||
public AIDifficulty DefaultAIDifficulty => defaultAIDifficulty;
|
||||
|
||||
public float WeakPointExplosionRadius => weakPointExplosionRadius;
|
||||
public float WeakPointExplosionDamage => weakPointExplosionDamage;
|
||||
public float WeakPointExplosionForce => weakPointExplosionForce;
|
||||
@@ -209,6 +223,23 @@ namespace Minigames.FortFight.Core
|
||||
return sizeConfigs.FirstOrDefault(c => c.size == size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get AI difficulty configuration data by difficulty level
|
||||
/// </summary>
|
||||
public AIDifficultyData GetAIDifficultyData(AIDifficulty difficulty)
|
||||
{
|
||||
var config = aiDifficultyConfigs.FirstOrDefault(c => c.difficulty == difficulty);
|
||||
if (config != null)
|
||||
{
|
||||
return config.data;
|
||||
}
|
||||
|
||||
// Fallback to Medium difficulty if not found
|
||||
Debug.LogWarning($"[FortFightSettings] AI difficulty data not found for {difficulty}, using Medium as fallback");
|
||||
var mediumConfig = aiDifficultyConfigs.FirstOrDefault(c => c.difficulty == AIDifficulty.Medium);
|
||||
return mediumConfig != null ? mediumConfig.data : new AIDifficultyData(30f, 0.2f, 0.5f, 2f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Validation
|
||||
|
||||
@@ -34,9 +34,6 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
[Header("Fort Prefabs")]
|
||||
[SerializeField] private GameObject[] premadeFortPrefabs;
|
||||
[Tooltip("Leave empty to spawn random forts. Assign specific prefabs for testing.")]
|
||||
[SerializeField] private GameObject debugPlayerFortPrefab;
|
||||
[SerializeField] private GameObject debugEnemyFortPrefab;
|
||||
|
||||
[Header("Spawn Points")]
|
||||
[SerializeField] private Transform playerSpawnPoint;
|
||||
@@ -45,6 +42,26 @@ namespace Minigames.FortFight.Core
|
||||
[Header("Settings")]
|
||||
[SerializeField] private bool useDebugForts = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Developer Settings
|
||||
|
||||
private AppleHills.Core.Settings.FortFightDeveloperSettings _cachedDevSettings;
|
||||
private AppleHills.Core.Settings.FortFightDeveloperSettings CachedDevSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedDevSettings == null)
|
||||
{
|
||||
_cachedDevSettings = GameManager.GetDeveloperSettings<AppleHills.Core.Settings.FortFightDeveloperSettings>();
|
||||
}
|
||||
return _cachedDevSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject DebugPlayerFortPrefab => CachedDevSettings?.DebugPlayerFortPrefab;
|
||||
private GameObject DebugEnemyFortPrefab => CachedDevSettings?.DebugEnemyFortPrefab;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -121,9 +138,9 @@ namespace Minigames.FortFight.Core
|
||||
Logging.Debug("[FortManager] Spawning forts for both players");
|
||||
|
||||
// Spawn player fort
|
||||
if (useDebugForts && debugPlayerFortPrefab != null)
|
||||
if (useDebugForts && DebugPlayerFortPrefab != null)
|
||||
{
|
||||
PlayerFort = SpawnFort(debugPlayerFortPrefab, playerSpawnPoint, "Player Fort");
|
||||
PlayerFort = SpawnFort(DebugPlayerFortPrefab, playerSpawnPoint, "Player Fort");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -136,9 +153,9 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
|
||||
// Spawn enemy fort
|
||||
if (useDebugForts && debugEnemyFortPrefab != null)
|
||||
if (useDebugForts && DebugEnemyFortPrefab != null)
|
||||
{
|
||||
EnemyFort = SpawnFort(debugEnemyFortPrefab, enemySpawnPoint, "Enemy Fort");
|
||||
EnemyFort = SpawnFort(DebugEnemyFortPrefab, enemySpawnPoint, "Enemy Fort");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -28,27 +28,38 @@ namespace Minigames.FortFight.Core
|
||||
[Tooltip("Trajectory preview component")]
|
||||
[SerializeField] private TrajectoryPreview trajectoryPreview;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLogs = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Settings
|
||||
|
||||
private IFortFightSettings cachedSettings;
|
||||
private IFortFightSettings _cachedSettings;
|
||||
private IFortFightSettings CachedSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedSettings == null)
|
||||
if (_cachedSettings == null)
|
||||
{
|
||||
cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||
_cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
|
||||
}
|
||||
return cachedSettings;
|
||||
return _cachedSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private FortFightDeveloperSettings _cachedDevSettings;
|
||||
private FortFightDeveloperSettings CachedDevSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedDevSettings == null)
|
||||
{
|
||||
_cachedDevSettings = GameManager.GetDeveloperSettings<FortFightDeveloperSettings>();
|
||||
}
|
||||
return _cachedDevSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private float MaxForce => CachedSettings?.BaseLaunchForce ?? 20f;
|
||||
private bool ShowDebugLogs => CachedDevSettings?.SlingshotShowDebugLogs ?? false;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -63,12 +74,12 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
#region State
|
||||
|
||||
private bool isDragging;
|
||||
private Vector2 dragStartPosition;
|
||||
private ProjectileConfig currentAmmo;
|
||||
private ProjectileBase activeProjectile;
|
||||
private bool _isDragging;
|
||||
private Vector2 _dragStartPosition;
|
||||
private ProjectileConfig _currentAmmo;
|
||||
private ProjectileBase _activeProjectile;
|
||||
|
||||
public bool IsDragging => isDragging;
|
||||
public bool IsDragging => _isDragging;
|
||||
public bool IsEnabled { get; private set; } = true;
|
||||
|
||||
#endregion
|
||||
@@ -118,13 +129,13 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
public void OnHoldMove(Vector2 worldPosition)
|
||||
{
|
||||
if (!IsEnabled || !isDragging) return;
|
||||
if (!IsEnabled || !_isDragging) return;
|
||||
UpdateDrag(worldPosition);
|
||||
}
|
||||
|
||||
public void OnHoldEnd(Vector2 worldPosition)
|
||||
{
|
||||
if (!IsEnabled || !isDragging) return;
|
||||
if (!IsEnabled || !_isDragging) return;
|
||||
EndDrag(worldPosition);
|
||||
}
|
||||
|
||||
@@ -134,16 +145,16 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
private void StartDrag(Vector2 worldPosition)
|
||||
{
|
||||
if (currentAmmo == null)
|
||||
if (_currentAmmo == null)
|
||||
{
|
||||
if (showDebugLogs) Logging.Warning("[SlingshotController] No ammo selected!");
|
||||
if (ShowDebugLogs) Logging.Warning("[SlingshotController] No ammo selected!");
|
||||
return;
|
||||
}
|
||||
|
||||
isDragging = true;
|
||||
_isDragging = true;
|
||||
// Use the projectile spawn point as the anchor, not the touch position
|
||||
// This makes it work like Angry Birds - pull back from slingshot to launch forward
|
||||
dragStartPosition = projectileSpawnPoint.position;
|
||||
_dragStartPosition = projectileSpawnPoint.position;
|
||||
|
||||
// Show trajectory preview
|
||||
if (trajectoryPreview != null)
|
||||
@@ -151,14 +162,14 @@ namespace Minigames.FortFight.Core
|
||||
trajectoryPreview.Show();
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[SlingshotController] Started drag at {worldPosition}, anchor at spawn point {dragStartPosition}");
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Started drag at {worldPosition}, anchor at spawn point {_dragStartPosition}");
|
||||
}
|
||||
|
||||
private void UpdateDrag(Vector2 currentWorldPosition)
|
||||
{
|
||||
// Calculate drag vector from spawn point to current drag position
|
||||
// Pull back (away from spawn) = launch forward (toward spawn direction)
|
||||
Vector2 dragVector = dragStartPosition - currentWorldPosition;
|
||||
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
|
||||
|
||||
// Calculate force and direction
|
||||
float dragDistance = dragVector.magnitude;
|
||||
@@ -172,10 +183,10 @@ namespace Minigames.FortFight.Core
|
||||
Vector2 direction = dragVector.normalized;
|
||||
|
||||
// Update trajectory preview with projectile mass
|
||||
if (trajectoryPreview != null && currentAmmo != null)
|
||||
if (trajectoryPreview != null && _currentAmmo != null)
|
||||
{
|
||||
Vector2 worldStartPos = projectileSpawnPoint.position;
|
||||
float mass = currentAmmo.GetMass();
|
||||
float mass = _currentAmmo.GetMass();
|
||||
|
||||
// Debug: Log trajectory calculation (uncomment for debugging)
|
||||
// if (showDebugLogs && Time.frameCount % 30 == 0) // Log every 30 frames to avoid spam
|
||||
@@ -189,7 +200,7 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
private void EndDrag(Vector2 currentWorldPosition)
|
||||
{
|
||||
isDragging = false;
|
||||
_isDragging = false;
|
||||
|
||||
// Hide trajectory
|
||||
if (trajectoryPreview != null)
|
||||
@@ -198,7 +209,7 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
|
||||
// Calculate final launch parameters from spawn point to final drag position
|
||||
Vector2 dragVector = dragStartPosition - currentWorldPosition;
|
||||
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
|
||||
float dragDistance = dragVector.magnitude;
|
||||
float dragRatio = Mathf.Clamp01(dragDistance / maxDragDistance);
|
||||
|
||||
@@ -216,9 +227,9 @@ namespace Minigames.FortFight.Core
|
||||
// Launch projectile if force exceeds minimum
|
||||
if (force >= minForce)
|
||||
{
|
||||
if (showDebugLogs && currentAmmo != null)
|
||||
if (ShowDebugLogs && _currentAmmo != null)
|
||||
{
|
||||
float mass = currentAmmo.GetMass();
|
||||
float mass = _currentAmmo.GetMass();
|
||||
float velocity = force / mass;
|
||||
Logging.Debug($"[Slingshot] Launch - Force: {force:F2}, Mass: {mass:F2}, Velocity: {velocity:F2}, Dir: {direction}");
|
||||
}
|
||||
@@ -227,7 +238,7 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug($"[SlingshotController] Drag too short - force {force:F2} < min {minForce:F2}");
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Drag too short - force {force:F2} < min {minForce:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,8 +251,8 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public void SetAmmo(ProjectileConfig ammoConfig)
|
||||
{
|
||||
currentAmmo = ammoConfig;
|
||||
if (showDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
|
||||
_currentAmmo = ammoConfig;
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,28 +260,28 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
private void LaunchProjectile(Vector2 direction, float force)
|
||||
{
|
||||
if (currentAmmo == null || currentAmmo.prefab == null)
|
||||
if (_currentAmmo == null || _currentAmmo.prefab == null)
|
||||
{
|
||||
Logging.Error("[SlingshotController] Cannot launch - no ammo or prefab!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn projectile
|
||||
GameObject projectileObj = Instantiate(currentAmmo.prefab, projectileSpawnPoint.position, Quaternion.identity);
|
||||
activeProjectile = projectileObj.GetComponent<Projectiles.ProjectileBase>();
|
||||
GameObject projectileObj = Instantiate(_currentAmmo.prefab, projectileSpawnPoint.position, Quaternion.identity);
|
||||
_activeProjectile = projectileObj.GetComponent<ProjectileBase>();
|
||||
|
||||
if (activeProjectile == null)
|
||||
if (_activeProjectile == null)
|
||||
{
|
||||
Logging.Error($"[SlingshotController] Projectile prefab {currentAmmo.prefab.name} missing ProjectileBase component!");
|
||||
Logging.Error($"[SlingshotController] Projectile prefab {_currentAmmo.prefab.name} missing ProjectileBase component!");
|
||||
Destroy(projectileObj);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize projectile with its type (loads damage and mass from settings)
|
||||
activeProjectile.Initialize(currentAmmo.projectileType);
|
||||
_activeProjectile.Initialize(_currentAmmo.projectileType);
|
||||
|
||||
// Launch it
|
||||
activeProjectile.Launch(direction, force);
|
||||
_activeProjectile.Launch(direction, force);
|
||||
|
||||
// Lock trajectory to show the shot path
|
||||
if (trajectoryPreview != null)
|
||||
@@ -279,10 +290,46 @@ namespace Minigames.FortFight.Core
|
||||
trajectoryPreview.LockTrajectory(lockDuration);
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[SlingshotController] Launched {currentAmmo?.displayName ?? "projectile"} with force {force}");
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Launched {_currentAmmo?.displayName ?? "projectile"} with force {force}");
|
||||
|
||||
// Fire event
|
||||
OnProjectileLaunched?.Invoke(activeProjectile);
|
||||
OnProjectileLaunched?.Invoke(_activeProjectile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method for AI to simulate a drag-and-launch action
|
||||
/// </summary>
|
||||
public void SimulateDragLaunch(Vector2 direction, float force)
|
||||
{
|
||||
LaunchProjectile(direction, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launch projectile with a specific velocity (for AI ballistic calculations)
|
||||
/// Calculates the required force based on projectile mass
|
||||
/// </summary>
|
||||
public void LaunchWithVelocity(Vector2 velocity)
|
||||
{
|
||||
if (_currentAmmo == null)
|
||||
{
|
||||
Logging.Error("[SlingshotController] Cannot launch - no ammo selected!");
|
||||
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;
|
||||
|
||||
if (ShowDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[Slingshot] LaunchWithVelocity - Velocity: {velocity}, Mass: {mass:F2}, Force: {force:F2}");
|
||||
}
|
||||
|
||||
LaunchProjectile(direction, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -290,7 +337,7 @@ namespace Minigames.FortFight.Core
|
||||
/// </summary>
|
||||
public ProjectileBase GetActiveProjectile()
|
||||
{
|
||||
return activeProjectile;
|
||||
return _activeProjectile;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -303,7 +350,7 @@ namespace Minigames.FortFight.Core
|
||||
public void Enable()
|
||||
{
|
||||
IsEnabled = true;
|
||||
if (showDebugLogs) Logging.Debug("[SlingshotController] Enabled");
|
||||
if (ShowDebugLogs) Logging.Debug("[SlingshotController] Enabled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -312,17 +359,16 @@ namespace Minigames.FortFight.Core
|
||||
public void Disable()
|
||||
{
|
||||
IsEnabled = false;
|
||||
isDragging = false;
|
||||
_isDragging = false;
|
||||
|
||||
if (trajectoryPreview != null)
|
||||
{
|
||||
trajectoryPreview.Hide();
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug("[SlingshotController] Disabled");
|
||||
if (ShowDebugLogs) Logging.Debug("[SlingshotController] Disabled");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
51
Assets/Scripts/Minigames/FortFight/Data/AIDifficultyData.cs
Normal file
51
Assets/Scripts/Minigames/FortFight/Data/AIDifficultyData.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.FortFight.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration data for AI difficulty levels.
|
||||
/// Defines how accurate and fast the AI behaves at each difficulty tier.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct AIDifficultyData
|
||||
{
|
||||
[Tooltip("Angle deviation in degrees (±)")]
|
||||
public float angleDeviation;
|
||||
|
||||
[Tooltip("Force/speed deviation as percentage (0.2 = ±20%)")]
|
||||
public float forceDeviation;
|
||||
|
||||
[Tooltip("Minimum thinking time in seconds")]
|
||||
public float thinkTimeMin;
|
||||
|
||||
[Tooltip("Maximum thinking time in seconds")]
|
||||
public float thinkTimeMax;
|
||||
|
||||
/// <summary>
|
||||
/// Create AI difficulty data with specified parameters
|
||||
/// </summary>
|
||||
public AIDifficultyData(float angleDeviation, float forceDeviation, float thinkTimeMin, float thinkTimeMax)
|
||||
{
|
||||
this.angleDeviation = angleDeviation;
|
||||
this.forceDeviation = forceDeviation;
|
||||
this.thinkTimeMin = thinkTimeMin;
|
||||
this.thinkTimeMax = thinkTimeMax;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper class to serialize AI difficulty configuration in Unity inspector.
|
||||
/// Maps difficulty level to its configuration data.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AIDifficultyConfig
|
||||
{
|
||||
[Tooltip("Difficulty level")]
|
||||
public AIDifficulty difficulty;
|
||||
|
||||
[Tooltip("Configuration data for this difficulty")]
|
||||
public AIDifficultyData data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9da4870f05ff4968a77b4e790efbb264
|
||||
timeCreated: 1764841304
|
||||
@@ -51,5 +51,15 @@
|
||||
CeilingFan, // Drops straight down
|
||||
TrashBag // Explodes on impact
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI difficulty levels with varying accuracy and behavior
|
||||
/// </summary>
|
||||
public enum AIDifficulty
|
||||
{
|
||||
Easy, // Large deviations, slower thinking
|
||||
Medium, // Moderate deviations, moderate thinking
|
||||
Hard // Minimal deviations, faster thinking
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace Minigames.FortFight.Fort
|
||||
public float CurrentHp => currentHp;
|
||||
public float MaxHp => maxHp;
|
||||
public float HpPercentage => maxHp > 0 ? (currentHp / maxHp) * 100f : 0f;
|
||||
public bool IsDestroyed => isDestroyed;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -18,9 +18,6 @@ namespace Minigames.FortFight.Fort
|
||||
[Header("Fort Configuration")]
|
||||
[SerializeField] private string fortName = "Unnamed Fort";
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugInfo = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -45,26 +42,26 @@ namespace Minigames.FortFight.Fort
|
||||
#region Properties
|
||||
|
||||
public string FortName => fortName;
|
||||
public float MaxFortHp => maxFortHp;
|
||||
public float CurrentFortHp => currentFortHp;
|
||||
public float HpPercentage => maxFortHp > 0 ? (currentFortHp / maxFortHp) * 100f : 0f;
|
||||
public int TotalBlockCount => blocks.Count;
|
||||
public int InitialBlockCount => initialBlockCount;
|
||||
public float MaxFortHp => _maxFortHp;
|
||||
public float CurrentFortHp => _currentFortHp;
|
||||
public float HpPercentage => _maxFortHp > 0 ? (_currentFortHp / _maxFortHp) * 100f : 0f;
|
||||
public int TotalBlockCount => _blocks.Count;
|
||||
public int InitialBlockCount => _initialBlockCount;
|
||||
public bool IsDefeated { get; private set; }
|
||||
|
||||
// Aliases for consistency
|
||||
public float MaxHp => maxFortHp;
|
||||
public float CurrentHp => currentFortHp;
|
||||
public float MaxHp => _maxFortHp;
|
||||
public float CurrentHp => _currentFortHp;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private State
|
||||
|
||||
private List<FortBlock> blocks = new List<FortBlock>();
|
||||
private float maxFortHp = 0f;
|
||||
private float currentFortHp = 0f;
|
||||
private int initialBlockCount = 0;
|
||||
private bool isInitialized = false;
|
||||
private List<FortBlock> _blocks = new List<FortBlock>();
|
||||
private float _maxFortHp = 0f;
|
||||
private float _currentFortHp = 0f;
|
||||
private int _initialBlockCount = 0;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
// Cached settings
|
||||
private IFortFightSettings _cachedSettings;
|
||||
@@ -80,6 +77,21 @@ namespace Minigames.FortFight.Fort
|
||||
}
|
||||
}
|
||||
|
||||
private FortFightDeveloperSettings _cachedDevSettings;
|
||||
private FortFightDeveloperSettings CachedDevSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedDevSettings == null)
|
||||
{
|
||||
_cachedDevSettings = GameManager.GetDeveloperSettings<FortFightDeveloperSettings>();
|
||||
}
|
||||
return _cachedDevSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShowDebugInfo => CachedDevSettings?.FortShowDebugInfo ?? false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
@@ -97,7 +109,7 @@ namespace Minigames.FortFight.Fort
|
||||
base.OnManagedDestroy();
|
||||
|
||||
// Unsubscribe from all block events
|
||||
foreach (FortBlock block in blocks)
|
||||
foreach (FortBlock block in _blocks)
|
||||
{
|
||||
if (block != null)
|
||||
{
|
||||
@@ -106,7 +118,7 @@ namespace Minigames.FortFight.Fort
|
||||
}
|
||||
}
|
||||
|
||||
blocks.Clear();
|
||||
_blocks.Clear();
|
||||
|
||||
// Clear events
|
||||
OnFortDamaged = null;
|
||||
@@ -124,7 +136,7 @@ namespace Minigames.FortFight.Fort
|
||||
/// </summary>
|
||||
private void InitializeFort()
|
||||
{
|
||||
if (isInitialized)
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logging.Warning($"[FortController] {fortName} already initialized!");
|
||||
return;
|
||||
@@ -138,7 +150,7 @@ namespace Minigames.FortFight.Fort
|
||||
// Step 2: Register with central manager
|
||||
RegisterWithManager();
|
||||
|
||||
isInitialized = true;
|
||||
_isInitialized = true;
|
||||
Logging.Debug($"[FortController] {fortName} - Initialization complete");
|
||||
}
|
||||
|
||||
@@ -176,10 +188,10 @@ namespace Minigames.FortFight.Fort
|
||||
}
|
||||
|
||||
// Step 3: Initialize current HP to match max HP (sum of all blocks)
|
||||
currentFortHp = maxFortHp;
|
||||
_currentFortHp = _maxFortHp;
|
||||
|
||||
initialBlockCount = blocks.Count;
|
||||
Logging.Debug($"[FortController] {fortName} - Initialized and registered {blocks.Count} blocks, Total HP: {maxFortHp:F0}");
|
||||
_initialBlockCount = _blocks.Count;
|
||||
Logging.Debug($"[FortController] {fortName} - Initialized and registered {_blocks.Count} blocks, Total HP: {_maxFortHp:F0}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,18 +223,18 @@ namespace Minigames.FortFight.Fort
|
||||
{
|
||||
if (block == null) return;
|
||||
|
||||
if (!blocks.Contains(block))
|
||||
if (!_blocks.Contains(block))
|
||||
{
|
||||
blocks.Add(block);
|
||||
_blocks.Add(block);
|
||||
|
||||
// Only add to max HP, current HP will be calculated once at end of initialization
|
||||
maxFortHp += block.MaxHp;
|
||||
_maxFortHp += block.MaxHp;
|
||||
|
||||
// Subscribe to block events
|
||||
block.OnBlockDestroyed += HandleBlockDestroyed;
|
||||
block.OnBlockDamaged += HandleBlockDamaged;
|
||||
|
||||
if (showDebugInfo)
|
||||
if (ShowDebugInfo)
|
||||
{
|
||||
Logging.Debug($"[FortController] Registered block: {block.gameObject.name} ({block.Material} {block.Size}, HP: {block.MaxHp})");
|
||||
}
|
||||
@@ -234,7 +246,7 @@ namespace Minigames.FortFight.Fort
|
||||
/// </summary>
|
||||
public List<FortBlock> GetWeakPoints()
|
||||
{
|
||||
return blocks.Where(b => b != null && b.IsWeakPoint).ToList();
|
||||
return _blocks.Where(b => b != null && b.IsWeakPoint).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -242,7 +254,7 @@ namespace Minigames.FortFight.Fort
|
||||
/// </summary>
|
||||
public List<FortBlock> GetAllBlocks()
|
||||
{
|
||||
return new List<FortBlock>(blocks);
|
||||
return new List<FortBlock>(_blocks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -250,8 +262,8 @@ namespace Minigames.FortFight.Fort
|
||||
/// </summary>
|
||||
public FortBlock GetRandomBlock()
|
||||
{
|
||||
if (blocks.Count == 0) return null;
|
||||
return blocks[UnityEngine.Random.Range(0, blocks.Count)];
|
||||
if (_blocks.Count == 0) return null;
|
||||
return _blocks[UnityEngine.Random.Range(0, _blocks.Count)];
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -265,7 +277,7 @@ namespace Minigames.FortFight.Fort
|
||||
Logging.Debug($"[FortController] {fortName} - Block destroyed: {block.gameObject.name}");
|
||||
|
||||
// Remove from list
|
||||
blocks.Remove(block);
|
||||
_blocks.Remove(block);
|
||||
|
||||
// Recalculate HP by summing all remaining blocks (consistent calculation method)
|
||||
RecalculateFortHp();
|
||||
@@ -277,9 +289,9 @@ namespace Minigames.FortFight.Fort
|
||||
// Check defeat condition
|
||||
CheckDefeatCondition();
|
||||
|
||||
if (showDebugInfo)
|
||||
if (ShowDebugInfo)
|
||||
{
|
||||
Logging.Debug($"[FortController] {fortName} - HP: {currentFortHp:F0}/{maxFortHp:F0} ({HpPercentage:F1}%), Blocks: {blocks.Count}/{initialBlockCount}");
|
||||
Logging.Debug($"[FortController] {fortName} - HP: {_currentFortHp:F0}/{_maxFortHp:F0} ({HpPercentage:F1}%), Blocks: {_blocks.Count}/{_initialBlockCount}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,18 +317,18 @@ namespace Minigames.FortFight.Fort
|
||||
/// </summary>
|
||||
private void RecalculateFortHp()
|
||||
{
|
||||
currentFortHp = 0f;
|
||||
foreach (var block in blocks)
|
||||
_currentFortHp = 0f;
|
||||
foreach (var block in _blocks)
|
||||
{
|
||||
if (block != null)
|
||||
{
|
||||
currentFortHp += block.CurrentHp;
|
||||
_currentFortHp += block.CurrentHp;
|
||||
}
|
||||
}
|
||||
|
||||
if (showDebugInfo)
|
||||
if (ShowDebugInfo)
|
||||
{
|
||||
Logging.Debug($"[FortController] {fortName} - HP recalculated: {currentFortHp:F0}/{maxFortHp:F0} ({HpPercentage:F1}%)");
|
||||
Logging.Debug($"[FortController] {fortName} - HP recalculated: {_currentFortHp:F0}/{_maxFortHp:F0} ({HpPercentage:F1}%)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +347,7 @@ namespace Minigames.FortFight.Fort
|
||||
float defeatThreshold = CachedSettings?.FortDefeatThreshold ?? 0.3f;
|
||||
float defeatThresholdPercent = defeatThreshold * 100f;
|
||||
|
||||
Logging.Debug($"[FortController] {fortName} - Checking defeat: HP={currentFortHp:F1}/{maxFortHp:F1} ({HpPercentage:F1}%) vs threshold={defeatThresholdPercent:F1}%");
|
||||
Logging.Debug($"[FortController] {fortName} - Checking defeat: HP={_currentFortHp:F1}/{_maxFortHp:F1} ({HpPercentage:F1}%) vs threshold={defeatThresholdPercent:F1}%");
|
||||
|
||||
// Defeat if HP at or below threshold
|
||||
if (HpPercentage <= defeatThresholdPercent)
|
||||
@@ -361,7 +373,7 @@ namespace Minigames.FortFight.Fort
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!showDebugInfo || !Application.isPlaying) return;
|
||||
if (!ShowDebugInfo || !Application.isPlaying) return;
|
||||
|
||||
// Display fort HP in scene view (for testing)
|
||||
Vector3 screenPos = Camera.main.WorldToScreenPoint(transform.position + Vector3.up * 2f);
|
||||
|
||||
@@ -32,8 +32,6 @@ namespace Minigames.FortFight.UI
|
||||
[SerializeField] private Core.FortManager fortManager;
|
||||
|
||||
[Header("Debug Display")]
|
||||
[Tooltip("Show numerical HP values (current/max)")]
|
||||
[SerializeField] private bool debugDisplay = false;
|
||||
[Tooltip("Text field to display 'current/max' HP values")]
|
||||
[SerializeField] private TextMeshProUGUI debugHpText;
|
||||
|
||||
@@ -41,7 +39,20 @@ namespace Minigames.FortFight.UI
|
||||
|
||||
#region Private State
|
||||
|
||||
private FortController trackedFort;
|
||||
private FortController _trackedFort;
|
||||
private AppleHills.Core.Settings.FortFightDeveloperSettings _cachedDevSettings;
|
||||
|
||||
private bool DebugDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedDevSettings == null)
|
||||
{
|
||||
_cachedDevSettings = GameManager.GetDeveloperSettings<AppleHills.Core.Settings.FortFightDeveloperSettings>();
|
||||
}
|
||||
return _cachedDevSettings?.HealthUIDebugDisplay ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -69,9 +80,9 @@ namespace Minigames.FortFight.UI
|
||||
base.OnManagedDestroy();
|
||||
|
||||
// Unsubscribe from fort events
|
||||
if (trackedFort != null)
|
||||
if (_trackedFort != null)
|
||||
{
|
||||
trackedFort.OnFortDamaged -= OnFortDamaged;
|
||||
_trackedFort.OnFortDamaged -= OnFortDamaged;
|
||||
}
|
||||
|
||||
// Unsubscribe from fort manager
|
||||
@@ -154,15 +165,15 @@ namespace Minigames.FortFight.UI
|
||||
}
|
||||
|
||||
// Unsubscribe from previous fort
|
||||
if (trackedFort != null)
|
||||
if (_trackedFort != null)
|
||||
{
|
||||
trackedFort.OnFortDamaged -= OnFortDamaged;
|
||||
_trackedFort.OnFortDamaged -= OnFortDamaged;
|
||||
}
|
||||
|
||||
trackedFort = fort;
|
||||
_trackedFort = fort;
|
||||
|
||||
// Subscribe to fort events
|
||||
trackedFort.OnFortDamaged += OnFortDamaged;
|
||||
_trackedFort.OnFortDamaged += OnFortDamaged;
|
||||
|
||||
// Initialize UI
|
||||
if (fortNameText != null)
|
||||
@@ -181,7 +192,7 @@ namespace Minigames.FortFight.UI
|
||||
|
||||
private void OnFortDamaged(float damage, float hpPercentage)
|
||||
{
|
||||
Logging.Debug($"[FortHealthUI] OnFortDamaged received! Damage: {damage}, HP%: {hpPercentage:F1}%, Fort: {trackedFort?.FortName}");
|
||||
Logging.Debug($"[FortHealthUI] OnFortDamaged received! Damage: {damage}, HP%: {hpPercentage:F1}%, Fort: {_trackedFort?.FortName}");
|
||||
UpdateDisplay();
|
||||
}
|
||||
|
||||
@@ -191,15 +202,15 @@ namespace Minigames.FortFight.UI
|
||||
|
||||
private void UpdateDisplay()
|
||||
{
|
||||
if (trackedFort == null)
|
||||
if (_trackedFort == null)
|
||||
{
|
||||
Logging.Warning("[FortHealthUI] UpdateDisplay called but trackedFort is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
float hpPercent = trackedFort.HpPercentage;
|
||||
float hpPercent = _trackedFort.HpPercentage;
|
||||
|
||||
Logging.Debug($"[FortHealthUI] UpdateDisplay - Fort: {trackedFort.FortName}, HP: {hpPercent:F1}%");
|
||||
Logging.Debug($"[FortHealthUI] UpdateDisplay - Fort: {_trackedFort.FortName}, HP: {hpPercent:F1}%");
|
||||
|
||||
// Update slider
|
||||
if (hpSlider != null)
|
||||
@@ -226,11 +237,11 @@ namespace Minigames.FortFight.UI
|
||||
// Update debug HP display (current/max)
|
||||
if (debugHpText != null)
|
||||
{
|
||||
if (debugDisplay)
|
||||
if (DebugDisplay)
|
||||
{
|
||||
debugHpText.gameObject.SetActive(true);
|
||||
float currentHp = trackedFort.CurrentHp;
|
||||
float maxHp = trackedFort.MaxHp;
|
||||
float currentHp = _trackedFort.CurrentHp;
|
||||
float maxHp = _trackedFort.MaxHp;
|
||||
debugHpText.text = $"{currentHp:F0}/{maxHp:F0}";
|
||||
}
|
||||
else
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace Minigames.FortFight.UI
|
||||
[Header("Optional Visual Elements")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
[SerializeField] private GameObject playerActionPanel;
|
||||
[SerializeField] private GameObject aiTurnPanel;
|
||||
|
||||
private TurnManager turnManager;
|
||||
|
||||
@@ -128,11 +127,6 @@ namespace Minigames.FortFight.UI
|
||||
{
|
||||
playerActionPanel.SetActive(false);
|
||||
}
|
||||
|
||||
if (aiTurnPanel != null)
|
||||
{
|
||||
aiTurnPanel.SetActive(true);
|
||||
}
|
||||
}
|
||||
else if (turnState == TurnState.PlayerOneTurn || turnState == TurnState.PlayerTwoTurn)
|
||||
{
|
||||
@@ -141,11 +135,6 @@ namespace Minigames.FortFight.UI
|
||||
{
|
||||
playerActionPanel.SetActive(true);
|
||||
}
|
||||
|
||||
if (aiTurnPanel != null)
|
||||
{
|
||||
aiTurnPanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user