680 lines
25 KiB
C#
680 lines
25 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using System.Collections;
|
|
|
|
/// <summary>
|
|
/// CashBoxImportController — Manages cash import to a TeamVault's CashBox.
|
|
/// Features:
|
|
/// - Spawns CashBox prefab at vault position with correct transform
|
|
/// - Countdown window based on import amount
|
|
/// - Reverse-plays "close" animation to open, then plays forward to close
|
|
/// - Camera focuses on player and cashbox during import
|
|
/// - Freezes player movement during import
|
|
/// - Sound effects for open/close/counting
|
|
///
|
|
/// Attach to the TeamVault or a manager object.
|
|
/// </summary>
|
|
public class CashBoxImportController : MonoBehaviour
|
|
{
|
|
[Header("CashBox Prefab")]
|
|
[Tooltip("CashBox prefab to spawn at the vault")]
|
|
[SerializeField] private GameObject cashBoxPrefab;
|
|
|
|
[Header("Spawn Transform (from prefab inspector)")]
|
|
[SerializeField] private Vector3 spawnOffset = Vector3.zero;
|
|
[SerializeField] private Vector3 spawnRotation = new Vector3(-90f, 0f, 0f);
|
|
[SerializeField] private Vector3 spawnScale = new Vector3(60f, 60f, 60f);
|
|
|
|
[Header("Animation Settings")]
|
|
[Tooltip("Name of the close animation state")]
|
|
[SerializeField] private string closeAnimationName = "close";
|
|
[SerializeField] private float openAnimationDuration = 1f;
|
|
[SerializeField] private float closeAnimationDuration = 1f;
|
|
|
|
[Header("Import Settings")]
|
|
[Tooltip("Seconds per $100 imported")]
|
|
[SerializeField] private float countdownPerHundred = 0.5f;
|
|
[SerializeField] private float minimumCountdown = 1f;
|
|
[SerializeField] private float maximumCountdown = 10f;
|
|
|
|
[Header("Camera Settings")]
|
|
[SerializeField] private float cameraFocusDistance = 5f;
|
|
[SerializeField] private float cameraFocusHeight = 2f;
|
|
[SerializeField] private float cameraSmoothSpeed = 5f;
|
|
|
|
[Header("Audio")]
|
|
[Tooltip("Sound when CashBox opens")]
|
|
[SerializeField] private AudioClip openSound;
|
|
[Tooltip("Sound when CashBox closes")]
|
|
[SerializeField] private AudioClip closeSound;
|
|
[Tooltip("Looping sound during cash counting")]
|
|
[SerializeField] private AudioClip countingSound;
|
|
[Tooltip("Sound when import completes")]
|
|
[SerializeField] private AudioClip completeSound;
|
|
[SerializeField] private float soundVolume = 1f;
|
|
|
|
[Header("UI References")]
|
|
[SerializeField] private GameObject importUIPanel;
|
|
[SerializeField] private TMP_Text countdownText;
|
|
[Tooltip("Separate TMP for status label (e.g. 'IMPORTING'). Assign a TextMeshPro in your scene.")]
|
|
[SerializeField] private TMP_Text statusText;
|
|
[SerializeField] private TMP_Text amountText;
|
|
[SerializeField] private Image progressDial;
|
|
[Tooltip("Optional sprite for the circular dial (e.g. Thick_Dial)")]
|
|
[SerializeField] private Sprite dialSprite;
|
|
[Header("Scene References")]
|
|
[Tooltip("Drag the TimeDial GameObject from the scene here (will use its Image component)")]
|
|
[SerializeField] private GameObject progressDialObject;
|
|
|
|
// Runtime
|
|
private GameObject _spawnedCashBox;
|
|
private Animator _cashBoxAnimator;
|
|
private AudioSource _audioSource;
|
|
private TeamVault _vault;
|
|
private Camera _mainCamera;
|
|
private Vector3 _originalCameraPosition;
|
|
private Quaternion _originalCameraRotation;
|
|
private Transform _originalCameraParent;
|
|
private bool _isImporting;
|
|
private CashCarrier _currentCarrier;
|
|
|
|
public bool IsImporting => _isImporting;
|
|
|
|
private void Awake()
|
|
{
|
|
_vault = GetComponent<TeamVault>();
|
|
_audioSource = GetComponent<AudioSource>();
|
|
if (_audioSource == null)
|
|
_audioSource = gameObject.AddComponent<AudioSource>();
|
|
|
|
_audioSource.playOnAwake = false;
|
|
_audioSource.spatialBlend = 0f; // 2D sound for UI feedback
|
|
|
|
_mainCamera = Camera.main;
|
|
|
|
// If a GameObject was provided (e.g. your TimeDial), try to grab its Image component
|
|
if (progressDial == null && progressDialObject != null)
|
|
{
|
|
var img = progressDialObject.GetComponent<Image>();
|
|
if (img == null) img = progressDialObject.GetComponentInChildren<Image>();
|
|
if (img != null)
|
|
{
|
|
progressDial = img;
|
|
Debug.Log("[CashBoxImportController] Assigned progressDial from progressDialObject");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[CashBoxImportController] progressDialObject does not contain an Image component");
|
|
}
|
|
}
|
|
|
|
// If a dial sprite was provided and progressDial exists, apply it
|
|
if (progressDial != null && dialSprite != null)
|
|
{
|
|
progressDial.sprite = dialSprite;
|
|
progressDial.type = Image.Type.Filled;
|
|
progressDial.fillMethod = Image.FillMethod.Radial360;
|
|
progressDial.fillOrigin = (int)Image.Origin360.Top;
|
|
progressDial.fillClockwise = true;
|
|
progressDial.fillAmount = 0f;
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
// Auto-setup UI if not assigned
|
|
if (importUIPanel == null)
|
|
CreateImportUI();
|
|
else
|
|
importUIPanel.SetActive(false);
|
|
|
|
// Auto-spawn CashBox on start if prefab is assigned
|
|
if (cashBoxPrefab != null && _spawnedCashBox == null)
|
|
SpawnCashBox();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configure the controller from TeamVault at runtime (when added dynamically).
|
|
/// </summary>
|
|
public void Configure(GameObject prefab, Vector3 offset, Vector3 rotation, Vector3 scale)
|
|
{
|
|
cashBoxPrefab = prefab;
|
|
spawnOffset = offset;
|
|
spawnRotation = rotation;
|
|
spawnScale = scale;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spawn the CashBox at vault position. Call this on game start or when needed.
|
|
/// </summary>
|
|
public void SpawnCashBox()
|
|
{
|
|
if (cashBoxPrefab == null)
|
|
{
|
|
Debug.LogWarning("[CashBoxImportController] No CashBox prefab assigned!");
|
|
return;
|
|
}
|
|
|
|
if (_spawnedCashBox != null)
|
|
{
|
|
Debug.Log("[CashBoxImportController] CashBox already spawned");
|
|
return;
|
|
}
|
|
|
|
// Hide the vault's own visual mesh/renderers so only the CashBox is visible
|
|
HideVaultVisuals();
|
|
|
|
// Spawn at vault position with specified transform
|
|
Vector3 spawnPos = transform.position + spawnOffset;
|
|
Quaternion spawnRot = Quaternion.Euler(spawnRotation);
|
|
|
|
_spawnedCashBox = Instantiate(cashBoxPrefab, spawnPos, spawnRot, transform);
|
|
_spawnedCashBox.transform.localScale = spawnScale;
|
|
_spawnedCashBox.name = "CashBox_" + (_vault != null ? _vault.TeamTag : "Vault");
|
|
|
|
_cashBoxAnimator = _spawnedCashBox.GetComponent<Animator>();
|
|
if (_cashBoxAnimator == null)
|
|
_cashBoxAnimator = _spawnedCashBox.GetComponentInChildren<Animator>();
|
|
|
|
Debug.Log($"[CashBoxImportController] Spawned CashBox at {spawnPos}, rotation {spawnRotation}, scale {spawnScale}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disable the vault's own mesh renderers so only the CashBox prefab shows.
|
|
/// Skips any renderers on the spawned CashBox itself.
|
|
/// </summary>
|
|
private void HideVaultVisuals()
|
|
{
|
|
// Disable all renderers on the vault GameObject (not children that are the CashBox)
|
|
foreach (var renderer in GetComponents<Renderer>())
|
|
{
|
|
renderer.enabled = false;
|
|
}
|
|
|
|
// Also disable renderers on existing vault children (the old "vault box")
|
|
// but skip any previously spawned CashBox
|
|
for (int i = 0; i < transform.childCount; i++)
|
|
{
|
|
Transform child = transform.GetChild(i);
|
|
if (_spawnedCashBox != null && child.gameObject == _spawnedCashBox) continue;
|
|
if (child.name.StartsWith("CashBox_")) continue; // Skip our CashBox
|
|
|
|
foreach (var renderer in child.GetComponentsInChildren<Renderer>())
|
|
{
|
|
renderer.enabled = false;
|
|
Debug.Log($"[CashBoxImportController] Disabled vault visual: {renderer.gameObject.name}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start the import process for a carrier depositing cash.
|
|
/// </summary>
|
|
public void StartImport(CashCarrier carrier)
|
|
{
|
|
if (_isImporting)
|
|
{
|
|
Debug.LogWarning("[CashBoxImportController] Import already in progress!");
|
|
return;
|
|
}
|
|
|
|
if (carrier == null || carrier.CarriedCash <= 0)
|
|
{
|
|
Debug.LogWarning("[CashBoxImportController] No cash to import!");
|
|
return;
|
|
}
|
|
|
|
// Spawn CashBox if not already
|
|
if (_spawnedCashBox == null)
|
|
SpawnCashBox();
|
|
|
|
if (_spawnedCashBox == null)
|
|
{
|
|
Debug.LogError("[CashBoxImportController] Failed to spawn CashBox!");
|
|
return;
|
|
}
|
|
|
|
_currentCarrier = carrier;
|
|
StartCoroutine(ImportRoutine(carrier));
|
|
}
|
|
|
|
private IEnumerator ImportRoutine(CashCarrier carrier)
|
|
{
|
|
_isImporting = true;
|
|
float cashAmount = carrier.CarriedCash;
|
|
|
|
// Calculate countdown based on amount
|
|
float countdownDuration = Mathf.Clamp(
|
|
(cashAmount / 100f) * countdownPerHundred,
|
|
minimumCountdown,
|
|
maximumCountdown
|
|
);
|
|
|
|
Debug.Log($"[CashBoxImportController] Starting import of ${cashAmount:F0}, duration {countdownDuration:F1}s");
|
|
|
|
// 1. Freeze player movement
|
|
FreezeCarrier(carrier, true);
|
|
|
|
// 2. Focus camera
|
|
yield return StartCoroutine(FocusCamera(carrier.transform, _spawnedCashBox.transform));
|
|
|
|
// 3. Show UI
|
|
ShowImportUI(cashAmount, countdownDuration);
|
|
|
|
// 4. Play open animation (reverse of close)
|
|
yield return StartCoroutine(PlayAnimationReverse(closeAnimationName, openAnimationDuration));
|
|
PlaySound(openSound);
|
|
|
|
// 5. Countdown with cash counting sound
|
|
yield return StartCoroutine(CountdownRoutine(cashAmount, countdownDuration));
|
|
|
|
// 6. Play close animation (forward)
|
|
yield return StartCoroutine(PlayAnimationForward(closeAnimationName, closeAnimationDuration));
|
|
PlaySound(closeSound);
|
|
|
|
// 7. Deposit the cash to vault
|
|
if (_vault != null && carrier != null)
|
|
{
|
|
float deposited = carrier.DepositToVault(_vault);
|
|
Debug.Log($"[CashBoxImportController] Deposited ${deposited:F0} to vault. Carrier now has ${carrier.CarriedCash:F0}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[CashBoxImportController] Deposit FAILED — vault: {_vault}, carrier: {carrier}");
|
|
}
|
|
|
|
// Force-clear any remaining carried cash display
|
|
// DepositToVault clears bags, but fire event again as safety
|
|
GameEvents.FireCashDeposited(
|
|
carrier != null ? carrier.TeamTag : "Player",
|
|
cashAmount, 1);
|
|
|
|
PlaySound(completeSound);
|
|
|
|
// 8. Hide UI
|
|
HideImportUI();
|
|
|
|
// 9. Restore camera
|
|
yield return StartCoroutine(RestoreCamera());
|
|
|
|
// 10. Unfreeze player
|
|
FreezeCarrier(carrier, false);
|
|
|
|
_isImporting = false;
|
|
_currentCarrier = null;
|
|
|
|
Debug.Log($"[CashBoxImportController] Import complete!");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Play animation in reverse by stepping normalizedTime from 1 to 0.
|
|
/// </summary>
|
|
private IEnumerator PlayAnimationReverse(string animName, float duration)
|
|
{
|
|
if (_cashBoxAnimator == null) yield break;
|
|
|
|
float elapsed = 0f;
|
|
|
|
// Start at end of animation (normalizedTime = 1)
|
|
_cashBoxAnimator.Play(animName, 0, 1f);
|
|
_cashBoxAnimator.speed = 0f; // Pause normal playback
|
|
|
|
while (elapsed < duration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float normalizedTime = 1f - (elapsed / duration); // Go from 1 to 0
|
|
normalizedTime = Mathf.Clamp01(normalizedTime);
|
|
|
|
// Continuously set the animation time
|
|
_cashBoxAnimator.Play(animName, 0, normalizedTime);
|
|
yield return null;
|
|
}
|
|
|
|
// Ensure we end at time 0 (fully open)
|
|
_cashBoxAnimator.Play(animName, 0, 0f);
|
|
_cashBoxAnimator.speed = 1f; // Restore normal speed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Play animation forward normally.
|
|
/// </summary>
|
|
private IEnumerator PlayAnimationForward(string animName, float duration)
|
|
{
|
|
if (_cashBoxAnimator == null) yield break;
|
|
|
|
// Play from start
|
|
_cashBoxAnimator.speed = 1f;
|
|
_cashBoxAnimator.Play(animName, 0, 0f);
|
|
|
|
yield return new WaitForSeconds(duration);
|
|
}
|
|
|
|
private IEnumerator FocusCamera(Transform playerTransform, Transform boxTransform)
|
|
{
|
|
if (_mainCamera == null) yield break;
|
|
|
|
// Store original camera state
|
|
_originalCameraPosition = _mainCamera.transform.position;
|
|
_originalCameraRotation = _mainCamera.transform.rotation;
|
|
_originalCameraParent = _mainCamera.transform.parent;
|
|
|
|
// Detach camera temporarily
|
|
_mainCamera.transform.SetParent(null);
|
|
|
|
// Calculate target position (between player and box, offset back)
|
|
Vector3 midPoint = (playerTransform.position + boxTransform.position) / 2f;
|
|
Vector3 dirToBox = (boxTransform.position - playerTransform.position).normalized;
|
|
Vector3 sideDir = Vector3.Cross(dirToBox, Vector3.up).normalized;
|
|
|
|
Vector3 targetPos = midPoint + sideDir * cameraFocusDistance + Vector3.up * cameraFocusHeight;
|
|
Quaternion targetRot = Quaternion.LookRotation(midPoint - targetPos);
|
|
|
|
// Smooth move camera
|
|
float elapsed = 0f;
|
|
float focusDuration = 0.5f;
|
|
|
|
while (elapsed < focusDuration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float t = elapsed / focusDuration;
|
|
t = t * t * (3f - 2f * t); // Smoothstep
|
|
|
|
_mainCamera.transform.position = Vector3.Lerp(_originalCameraPosition, targetPos, t);
|
|
_mainCamera.transform.rotation = Quaternion.Slerp(_originalCameraRotation, targetRot, t);
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
private IEnumerator RestoreCamera()
|
|
{
|
|
if (_mainCamera == null) yield break;
|
|
|
|
Vector3 currentPos = _mainCamera.transform.position;
|
|
Quaternion currentRot = _mainCamera.transform.rotation;
|
|
|
|
float elapsed = 0f;
|
|
float restoreDuration = 0.5f;
|
|
|
|
while (elapsed < restoreDuration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float t = elapsed / restoreDuration;
|
|
t = t * t * (3f - 2f * t); // Smoothstep
|
|
|
|
_mainCamera.transform.position = Vector3.Lerp(currentPos, _originalCameraPosition, t);
|
|
_mainCamera.transform.rotation = Quaternion.Slerp(currentRot, _originalCameraRotation, t);
|
|
yield return null;
|
|
}
|
|
|
|
// Re-parent camera
|
|
if (_originalCameraParent != null)
|
|
_mainCamera.transform.SetParent(_originalCameraParent);
|
|
}
|
|
|
|
private IEnumerator CountdownRoutine(float totalAmount, float duration)
|
|
{
|
|
float elapsed = 0f;
|
|
|
|
// Start counting sound loop
|
|
if (countingSound != null)
|
|
{
|
|
_audioSource.clip = countingSound;
|
|
_audioSource.loop = true;
|
|
_audioSource.volume = soundVolume * 0.7f;
|
|
_audioSource.Play();
|
|
}
|
|
|
|
while (elapsed < duration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float progress = elapsed / duration;
|
|
float remaining = duration - elapsed;
|
|
float countedAmount = totalAmount * progress;
|
|
|
|
// Update UI
|
|
UpdateImportUI(remaining, countedAmount, totalAmount, progress);
|
|
|
|
yield return null;
|
|
}
|
|
|
|
// Stop counting sound
|
|
if (_audioSource.isPlaying && _audioSource.clip == countingSound)
|
|
_audioSource.Stop();
|
|
|
|
// Final UI update
|
|
UpdateImportUI(0f, totalAmount, totalAmount, 1f);
|
|
}
|
|
|
|
// Saved component states before freeze (restore exactly on unfreeze)
|
|
private bool _carrierWasKinematic;
|
|
private bool _aiWasEnabled;
|
|
private bool _playerScriptWasEnabled;
|
|
private bool _navAgentWasEnabled;
|
|
private bool _navAgentWasStopped;
|
|
private bool _cashAIWasEnabled;
|
|
private bool _playerMovementWasEnabled;
|
|
|
|
private void FreezeCarrier(CashCarrier carrier, bool freeze)
|
|
{
|
|
if (carrier == null) return;
|
|
|
|
// CharacterAIController
|
|
var aiController = carrier.GetComponent<CharacterAIController>();
|
|
// PlayerScript
|
|
var playerScript = carrier.GetComponent<PlayerScript>();
|
|
// PlayerMovementBehaviour
|
|
var playerMovement = carrier.GetComponent<PlayerMovementBehaviour>();
|
|
// NavMeshAgent
|
|
var navAgent = carrier.GetComponent<UnityEngine.AI.NavMeshAgent>();
|
|
// CashSystemAI
|
|
var cashAI = carrier.GetComponent<CashSystemAI>();
|
|
// Rigidbody
|
|
var rb = carrier.GetComponent<Rigidbody>();
|
|
|
|
if (freeze)
|
|
{
|
|
// ─── SAVE original states BEFORE disabling ───
|
|
_aiWasEnabled = aiController != null && aiController.enabled;
|
|
_playerScriptWasEnabled = playerScript != null && playerScript.enabled;
|
|
_playerMovementWasEnabled = playerMovement != null && playerMovement.enabled;
|
|
_navAgentWasEnabled = navAgent != null && navAgent.enabled;
|
|
_navAgentWasStopped = navAgent != null && navAgent.enabled && navAgent.isStopped;
|
|
_cashAIWasEnabled = cashAI != null && cashAI.enabled;
|
|
|
|
// Disable everything
|
|
if (aiController != null) aiController.enabled = false;
|
|
if (playerScript != null) playerScript.enabled = false;
|
|
if (playerMovement != null) playerMovement.enabled = false;
|
|
if (cashAI != null) cashAI.enabled = false;
|
|
if (navAgent != null && navAgent.enabled) navAgent.isStopped = true;
|
|
|
|
if (rb != null)
|
|
{
|
|
_carrierWasKinematic = rb.isKinematic;
|
|
rb.isKinematic = true;
|
|
rb.linearVelocity = Vector3.zero;
|
|
rb.angularVelocity = Vector3.zero;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ─── RESTORE exactly the original states ───
|
|
// Only re-enable components that were enabled BEFORE freeze
|
|
if (aiController != null) aiController.enabled = _aiWasEnabled;
|
|
if (playerScript != null) playerScript.enabled = _playerScriptWasEnabled;
|
|
if (playerMovement != null) playerMovement.enabled = _playerMovementWasEnabled;
|
|
if (cashAI != null) cashAI.enabled = _cashAIWasEnabled;
|
|
|
|
if (navAgent != null)
|
|
{
|
|
// Only touch the agent if it was enabled before freeze
|
|
if (_navAgentWasEnabled && navAgent.enabled)
|
|
navAgent.isStopped = _navAgentWasStopped;
|
|
}
|
|
|
|
if (rb != null)
|
|
{
|
|
rb.isKinematic = _carrierWasKinematic;
|
|
}
|
|
}
|
|
|
|
Debug.Log($"[CashBoxImportController] Carrier movement {(freeze ? "frozen" : "restored")}");
|
|
}
|
|
|
|
|
|
private void PlaySound(AudioClip clip)
|
|
{
|
|
if (clip == null || _audioSource == null) return;
|
|
_audioSource.PlayOneShot(clip, soundVolume);
|
|
}
|
|
|
|
// ─── UI Management ───────────────────────────────────────
|
|
|
|
private void CreateImportUI()
|
|
{
|
|
// Create a simple runtime UI panel
|
|
Canvas canvas = FindObjectOfType<Canvas>();
|
|
if (canvas == null)
|
|
{
|
|
GameObject canvasObj = new GameObject("ImportUICanvas");
|
|
canvas = canvasObj.AddComponent<Canvas>();
|
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
canvasObj.AddComponent<CanvasScaler>();
|
|
canvasObj.AddComponent<GraphicRaycaster>();
|
|
}
|
|
|
|
// Create panel
|
|
importUIPanel = new GameObject("ImportUIPanel");
|
|
importUIPanel.transform.SetParent(canvas.transform, false);
|
|
|
|
RectTransform panelRect = importUIPanel.AddComponent<RectTransform>();
|
|
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
|
|
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
|
|
panelRect.sizeDelta = new Vector2(400f, 150f);
|
|
panelRect.anchoredPosition = new Vector2(0f, 100f);
|
|
|
|
Image panelBg = importUIPanel.AddComponent<Image>();
|
|
panelBg.color = new Color(0f, 0f, 0f, 0.8f);
|
|
|
|
// Create countdown text
|
|
GameObject countdownObj = new GameObject("CountdownText");
|
|
countdownObj.transform.SetParent(importUIPanel.transform, false);
|
|
RectTransform countdownRect = countdownObj.AddComponent<RectTransform>();
|
|
countdownRect.anchorMin = new Vector2(0, 0.6f);
|
|
countdownRect.anchorMax = new Vector2(1, 1f);
|
|
countdownRect.offsetMin = Vector2.zero;
|
|
countdownRect.offsetMax = Vector2.zero;
|
|
|
|
countdownText = countdownObj.AddComponent<TextMeshProUGUI>();
|
|
countdownText.text = "IMPORTING...";
|
|
countdownText.fontSize = 36;
|
|
countdownText.alignment = TextAlignmentOptions.Center;
|
|
countdownText.color = Color.white;
|
|
|
|
// Create amount text
|
|
GameObject amountObj = new GameObject("AmountText");
|
|
amountObj.transform.SetParent(importUIPanel.transform, false);
|
|
RectTransform amountRect = amountObj.AddComponent<RectTransform>();
|
|
amountRect.anchorMin = new Vector2(0, 0.3f);
|
|
amountRect.anchorMax = new Vector2(1, 0.6f);
|
|
amountRect.offsetMin = Vector2.zero;
|
|
amountRect.offsetMax = Vector2.zero;
|
|
|
|
amountText = amountObj.AddComponent<TextMeshProUGUI>();
|
|
amountText.text = "$0";
|
|
amountText.fontSize = 28;
|
|
amountText.alignment = TextAlignmentOptions.Center;
|
|
amountText.color = new Color(0.2f, 1f, 0.2f);
|
|
|
|
// Create circular progress dial (radial Image)
|
|
GameObject dialObj = new GameObject("ProgressDial");
|
|
dialObj.transform.SetParent(importUIPanel.transform, false);
|
|
RectTransform dialRect = dialObj.AddComponent<RectTransform>();
|
|
dialRect.anchorMin = new Vector2(0.35f, 0.05f);
|
|
dialRect.anchorMax = new Vector2(0.65f, 0.35f);
|
|
dialRect.offsetMin = Vector2.zero;
|
|
dialRect.offsetMax = Vector2.zero;
|
|
|
|
progressDial = dialObj.AddComponent<Image>();
|
|
if (dialSprite != null)
|
|
progressDial.sprite = dialSprite;
|
|
progressDial.type = Image.Type.Filled;
|
|
progressDial.fillMethod = Image.FillMethod.Radial360;
|
|
progressDial.fillOrigin = (int)Image.Origin360.Top;
|
|
progressDial.fillClockwise = true;
|
|
progressDial.fillAmount = 0f;
|
|
progressDial.preserveAspect = true;
|
|
|
|
importUIPanel.SetActive(false);
|
|
}
|
|
|
|
private void ShowImportUI(float totalAmount, float duration)
|
|
{
|
|
if (importUIPanel != null)
|
|
importUIPanel.SetActive(true);
|
|
|
|
UpdateImportUI(duration, 0f, totalAmount, 0f);
|
|
}
|
|
|
|
private void HideImportUI()
|
|
{
|
|
if (importUIPanel != null)
|
|
importUIPanel.SetActive(false);
|
|
}
|
|
|
|
private void UpdateImportUI(float remaining, float counted, float total, float progress)
|
|
{
|
|
// Timer text: only the seconds number
|
|
if (countdownText != null)
|
|
countdownText.text = remaining > 0 ? $"{remaining:F1}" : "0";
|
|
|
|
// Status text: label on a separate TMP
|
|
if (statusText != null)
|
|
statusText.text = remaining > 0 ? "IMPORTING" : "COMPLETE!";
|
|
|
|
if (amountText != null)
|
|
amountText.text = $"${counted:F0} / ${total:F0}";
|
|
|
|
if (progressDial != null)
|
|
progressDial.fillAmount = Mathf.Clamp01(progress);
|
|
}
|
|
|
|
// ─── Public API ──────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Check if a carrier can start importing (not already importing).
|
|
/// </summary>
|
|
public bool CanImport(CashCarrier carrier)
|
|
{
|
|
return !_isImporting && carrier != null && carrier.CarriedCash > 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel current import (emergency use only).
|
|
/// </summary>
|
|
public void CancelImport()
|
|
{
|
|
if (!_isImporting) return;
|
|
|
|
StopAllCoroutines();
|
|
|
|
if (_currentCarrier != null)
|
|
FreezeCarrier(_currentCarrier, false);
|
|
|
|
HideImportUI();
|
|
_isImporting = false;
|
|
_currentCarrier = null;
|
|
|
|
// Restore camera immediately
|
|
if (_mainCamera != null && _originalCameraParent != null)
|
|
{
|
|
_mainCamera.transform.SetParent(_originalCameraParent);
|
|
_mainCamera.transform.position = _originalCameraPosition;
|
|
_mainCamera.transform.rotation = _originalCameraRotation;
|
|
}
|
|
|
|
Debug.Log("[CashBoxImportController] Import cancelled");
|
|
}
|
|
}
|