Files
AppleHillsProduction/Assets/Scripts/Interactions/BoosterPackPickup.cs
2025-11-20 16:16:14 +01:00

300 lines
12 KiB
C#

using UnityEngine;
using System.Collections;
using Unity.Cinemachine;
using Data.CardSystem;
using Pixelplacement;
using Core;
using UI;
namespace Interactions
{
/// <summary>
/// Special pickup that plays a fancy sequence when collected:
/// - Follower picks up normally
/// - Pauses follower movement
/// - Zooms camera in
/// - Grows/pulses the booster pack
/// - Animates to backpack icon
/// - Gives player a booster pack
/// - Restores camera and resumes movement
/// </summary>
public class BoosterPackPickup : Pickup
{
[Header("Booster Pack Sequence")]
[SerializeField] private GameObject glowVisualPrefab;
[SerializeField] private CinemachineCamera zoomCamera;
[SerializeField] private int boosterPackCount = 1;
[Header("Sequence Timing")]
[SerializeField] private float cameraBlendWait = 0.3f;
[SerializeField] private float growDuration = 0.25f;
[SerializeField] private float pulseDuration = 0.3f;
[SerializeField] private int pulseCount = 3;
[SerializeField] private float disappearDuration = 0.5f;
[Header("Animation Settings")]
[SerializeField] private float growScale = 1.5f;
[SerializeField] private float pulseScale = 1.2f;
private GameObject glowInstance;
private bool sequencePlaying;
protected override bool DoInteraction()
{
// Let the normal pickup flow happen first
bool success = base.DoInteraction();
// If pickup was successful and we're not already playing the sequence, start it
// IMPORTANT: We start the coroutine on the FollowerController, not on this object,
// because this object gets disabled immediately after pickup
if (success && !sequencePlaying)
{
var follower = FollowerController.FindInstance();
if (follower != null)
{
follower.StartCoroutine(BoosterSequence(follower));
}
else
{
Logging.Warning("[BoosterPackPickup] No follower found, cannot start sequence");
}
}
return success;
}
private IEnumerator BoosterSequence(FollowerController follower)
{
sequencePlaying = true;
// IMPORTANT: Disable all children on the original pickup GameObject
// This prevents glow effects and other child objects from appearing in Pulver's hand
foreach (Transform child in transform)
{
child.gameObject.SetActive(false);
}
// Wait one frame to ensure pickup is fully processed
yield return null;
// Verify follower still exists (safety check)
if (follower == null)
{
Logging.Warning("[BoosterPackPickup] Follower destroyed during sequence, aborting");
sequencePlaying = false;
yield break;
}
Transform heldItemTransform = follower.GetHeldItemTransform();
if (heldItemTransform == null)
{
Logging.Warning("[BoosterPackPickup] No held item transform found, skipping sequence");
sequencePlaying = false;
yield break;
}
// 1. Pause follower movement
follower.PauseMovement();
// 2. Activate zoom camera if assigned
if (zoomCamera != null)
{
// Unparent the camera from the booster pack (if it's parented)
zoomCamera.transform.SetParent(null);
// Position camera at follower's location
zoomCamera.transform.position = new Vector3(
follower.transform.position.x,
follower.transform.position.y,
zoomCamera.transform.position.z
);
// Make the blend instant by setting a custom blend hint
var brain = Camera.main?.GetComponent<CinemachineBrain>();
CinemachineBlendDefinition originalBlend = default;
if (brain != null)
{
originalBlend = brain.DefaultBlend;
brain.DefaultBlend = new CinemachineBlendDefinition(CinemachineBlendDefinition.Styles.EaseInOut, 1);
}
zoomCamera.Priority = 20;
zoomCamera.gameObject.SetActive(true);
// Wait a frame for the cut to take effect
yield return null;
// Restore original blend settings
if (brain != null)
{
brain.DefaultBlend = originalBlend;
}
}
// 3. Grow animation (no glow during this)
Vector3 originalScale = heldItemTransform.localScale;
Tween.LocalScale(heldItemTransform, originalScale * growScale, growDuration, 0f, Tween.EaseOutBack);
yield return new WaitForSeconds(growDuration);
// 4. Instantiate and pulse glow
if (glowVisualPrefab != null)
{
glowInstance = Instantiate(glowVisualPrefab, heldItemTransform);
glowInstance.transform.localPosition = Vector3.zero;
glowInstance.transform.localScale = Vector3.one;
for (int i = 0; i < pulseCount; i++)
{
Tween.LocalScale(glowInstance.transform, Vector3.one * pulseScale, pulseDuration, 0f, Tween.EaseIn);
yield return new WaitForSeconds(pulseDuration);
Tween.LocalScale(glowInstance.transform, Vector3.one, pulseDuration, 0f, Tween.EaseInOut);
yield return new WaitForSeconds(pulseDuration);
}
// Delete glow before flying to button
Destroy(glowInstance);
glowInstance = null;
}
// 5. Disappear to scrapbook button (backpack icon) - straight line movement
GameObject scrabookButton = PlayerHudManager.Instance?.GetScrabookButton();
if (scrabookButton != null)
{
// Get start position in world space
Vector3 startPos = heldItemTransform.position;
// Convert UI button position to world space
RectTransform buttonRect = scrabookButton.GetComponent<RectTransform>();
Vector3 targetWorldPos;
if (buttonRect != null)
{
// UI element - get screen position correctly
Vector3[] corners = new Vector3[4];
buttonRect.GetWorldCorners(corners);
// Get center of the button in screen space
Vector3 buttonCenter = (corners[0] + corners[2]) / 2f;
// For Screen Space Overlay, the corners are already in screen space
Canvas canvas = buttonRect.GetComponentInParent<Canvas>();
Vector3 buttonScreenPos;
if (canvas != null && canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
// Already in screen space
buttonScreenPos = buttonCenter;
}
else
{
// Convert from world to screen space
buttonScreenPos = RectTransformUtility.WorldToScreenPoint(canvas?.worldCamera ?? Camera.main, buttonCenter);
}
// Convert screen position to world space at the same Z as the booster pack
targetWorldPos = Camera.main.ScreenToWorldPoint(new Vector3(buttonScreenPos.x, buttonScreenPos.y, Mathf.Abs(Camera.main.transform.position.z - startPos.z)));
Logging.Debug($"[BoosterPackPickup] Button screen pos: {buttonScreenPos}, converted world pos: {targetWorldPos}");
}
else
{
// Not a UI element, use direct world position
targetWorldPos = scrabookButton.transform.position;
}
Logging.Debug($"[BoosterPackPickup] Flying from {startPos} to {targetWorldPos} (button: {scrabookButton.name})");
// Use custom tween with linear interpolation for straight line
float elapsed = 0f;
Vector3 startScale = heldItemTransform.localScale;
while (elapsed < disappearDuration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / disappearDuration);
// Linear interpolation for straight line movement
heldItemTransform.position = Vector3.Lerp(startPos, targetWorldPos, t);
// Ease in for scale (gets smaller as it approaches)
float scaleT = t * t; // Simple ease-in curve
heldItemTransform.localScale = Vector3.Lerp(startScale, Vector3.zero, scaleT);
yield return null;
}
// Ensure final position/scale
heldItemTransform.position = targetWorldPos;
heldItemTransform.localScale = Vector3.zero;
}
else
{
Logging.Warning("[BoosterPackPickup] No scrapbook button found from PlayerHudManager, skipping fly-to animation");
}
// 7. Give booster pack to player
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.AddBoosterPack(boosterPackCount);
Logging.Debug($"[BoosterPackPickup] Gave {boosterPackCount} booster pack(s) to player");
}
else
{
Logging.Warning("[BoosterPackPickup] CardSystemManager not found, cannot give booster pack");
}
// 8. Cleanup glow
if (glowInstance != null)
{
Destroy(glowInstance);
}
// 9. Blend back to main camera and cleanup
if (zoomCamera != null)
{
// Lower priority to blend back to main camera
zoomCamera.Priority = 0;
// Wait for blend back to complete (using default blend settings)
var brain = Camera.main?.GetComponent<CinemachineBrain>();
if (brain != null && brain.IsBlending)
{
// Wait for the blend to finish
while (brain.IsBlending)
{
yield return null;
}
}
else
{
// If not blending or no brain, just wait a short moment
yield return new WaitForSeconds(0.3f);
}
// Destroy the zoom camera
Destroy(zoomCamera.gameObject);
}
// 10. Clear the follower's held item since we "consumed" it
follower.ClearHeldItem();
// 11. Resume follower movement
follower.ResumeMovement();
sequencePlaying = false;
}
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
// Cleanup glow if still exists
if (glowInstance != null)
{
Destroy(glowInstance);
}
}
}
}