using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using DG.Tweening; namespace CryingSnow.FarmingIsland { public class Stall : MonoBehaviour, IProp { [Tooltip("Maximum time allowed for selling all items at the stall.")] [SerializeField] private float maxPurchaseTime = 3f; [Tooltip("Transform representing the merchant character at the stall.")] [SerializeField] private Transform merchant; [Tooltip("List of items that can be accepted and sold at this stall.")] [SerializeField] private List acceptedItems; public List AcceptedItems => acceptedItems; public Location Location => Location.Stall; void Awake() { // Initialize stall and merchant scales to zero for initial animation. transform.localScale = Vector3.zero; merchant.localScale = Vector3.zero; } void OnEnable() { // Register all accepted items to the pool acceptedItems.ForEach(item => PoolManager.Instance.RegisterItem(item)); } /// /// Handles the animation sequence for revealing the stall and merchant. /// /// Whether the animation should be instant or gradual. /// Callback to invoke when the animation is complete. public void Animate(bool instant, System.Action onFinished) { if (instant) { // Instantly scale both the stall and merchant to full size. transform.localScale = Vector3.one; merchant.localScale = Vector3.one; onFinished?.Invoke(); return; } // Animate the stall's scale with a bounce effect, followed by the merchant. transform.DOScale(Vector3.one, 1f) .SetEase(Ease.OutBounce) .OnComplete(() => { merchant.DOScale(Vector3.one, 0.5f) .SetEase(Ease.OutBounce); onFinished?.Invoke(); }); } void OnTriggerEnter(Collider other) { // Start selling items when the player enters the stall's trigger zone. if (other.CompareTag("Player")) { var inventory = other.GetComponent().Inventory; StartCoroutine(SellItems(inventory)); } } void OnTriggerExit(Collider other) { // Stop selling items if the player leaves the stall's trigger zone. if (other.CompareTag("Player")) { StopAllCoroutines(); } } /// /// Coroutine for handling the process of selling items from the player's inventory. /// Items are sold over time until either all valid items are sold or the maximum time is reached. /// IEnumerator SellItems(Inventory inventory) { float elapsedTime = 0f; // Tracks the elapsed time during selling. float initialPitch = 1f; // The starting pitch for the sound effect. float finalPitch = 3f; // The ending pitch for the sound effect. // Get the list of items in the inventory that are accepted by the stall. var validItems = inventory.Items.Where(i => acceptedItems.Contains(i.Data)).ToList(); while (validItems.Count > 0 && elapsedTime < maxPurchaseTime) { var item = validItems.FirstOrDefault(); // Select the first valid item. float timeRatio = Mathf.Clamp01(elapsedTime / maxPurchaseTime); // Calculate the progress ratio. float remainingTime = maxPurchaseTime - elapsedTime; // Calculate how much of the item should be sold based on time and amount remaining. int deductionAmount = Mathf.Max(1, Mathf.RoundToInt((item.Amount * (1 - timeRatio)) / remainingTime)); int actualDeduction = Mathf.Min(deductionAmount, item.Amount); // Increase the player's coin count and update the inventory. IslandManager.Instance.Coin += actualDeduction * item.Data.Price; inventory.SubtractItem(item.ItemId, actualDeduction); // Refresh the list of valid items after each sale. validItems = inventory.Items.Where(i => acceptedItems.Contains(i.Data)).ToList(); // Adjust the pitch of the sound effect as the selling progresses. float currentPitch = Mathf.Lerp(initialPitch, finalPitch, timeRatio); AudioManager.Instance.PlaySFX(AudioID.Pop, pitch: currentPitch); // Visualize the sale by animating the dropped item. var drop = PoolManager.Instance.SpawnObject(item.Data.Drop.name).transform; drop.position = inventory.transform.TransformPoint(Vector3.up * 2f); AnimateDropItem(drop); yield return new WaitForSeconds(0.05f); // Small delay between item sales. elapsedTime += Time.deltaTime; // Increment the elapsed time. } } /// /// Animates the dropping of a sold item in front of the stall. /// /// The transform of the dropped item. void AnimateDropItem(Transform drop) { Vector3 stallFront = transform.TransformPoint(Vector3.forward * 3f); // Target position in front of the stall. Vector3 randomPos = (stallFront + Random.insideUnitSphere).Flatten(); // Randomize the exact drop location. // Animate the drop with a jump effect followed by a bounce. drop.DOJump(randomPos, 2f, 1, 0.5f) .SetEase(Ease.InQuad) .OnComplete(() => { drop.DOJump(drop.position, 1f, 1, 0.5f) .SetEase(Ease.OutBounce) .OnComplete(() => { PoolManager.Instance.ReturnObject(drop.gameObject); // Return the drop object to the pool. AnimateCoin(drop.position); // Trigger the coin animation. }); }); } /// /// Animates the appearance of a coin at the given spawn point. /// /// The location where the coin should appear. void AnimateCoin(Vector3 spawnPoint) { var coin = PoolManager.Instance.SpawnObject("Coin").transform; coin.position = spawnPoint; AudioManager.Instance.PlaySFX(AudioID.Coin); // Play the coin sound effect. coin.DOJump(coin.position, 0.5f, 2, 0.5f) .SetEase(Ease.Linear) .OnComplete(() => PoolManager.Instance.ReturnObject(coin.gameObject)); // Return the coin object to the pool. } } }