using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 7 — What the pet does when you're not touching it: idle, wander, sleep,
/// react. Cozy Critters ("the pet can idly walk").
///
/// SETUP
/// 1. Pet: Sprite + Rigidbody2D (Gravity 0) + Collider2D + Tier 5 TopDownMover
/// (AI Controlled ✓) + Animator + Tier 1 PlayerAnimatorBridge + this script.
/// 2. Drag in the NeedStats you want it to react to (energy for sleeping, hunger for
/// begging). All optional — with none assigned it just idles and wanders.
/// 3. Wander Area: a BoxCollider2D (trigger) marking where it may walk, or leave empty
/// and it stays within Wander Radius of where it started. Rhys's "one area" fits either.
/// 4. Timings are ranges so it doesn't feel robotic: it idles 2-5s, then walks 1-3s, etc.
/// 5. Animator: the bridge drives Speed. Add bools "IsSleeping" and "IsHappy" if you want
/// dedicated animations — the brain sets them if they exist.
///
/// STATES
/// Idle – stands, occasionally looks around
/// Wander – walks to a random nearby point
/// Sleep – when energy is low; energy refills while sleeping
/// React – a short burst after being fed/played with (set by CareAction)
///
/// The pet never does anything punishing. Low needs make it slower and sadder, not dead.
/// </summary>
public class PetBrain : MonoBehaviour
{
public enum State { Idle, Wander, Sleep, React }
[Header("Needs (all optional)")]
public NeedStat energy;
public NeedStat happiness;
public NeedStat hunger;
[Header("Wandering")]
public BoxCollider2D wanderArea;
public float wanderRadius = 3f;
public Vector2 idleTimeRange = new Vector2(2f, 5f);
public Vector2 walkTimeRange = new Vector2(1f, 3f);
public float arriveDistance = 0.2f;
[Header("Sleeping")]
[Tooltip("Falls asleep when energy drops below this (0-1). 0 = never sleeps.")]
[Range(0f, 1f)] public float sleepBelowEnergy = 0.2f;
[Tooltip("Wakes when energy climbs above this.")]
[Range(0f, 1f)] public float wakeAboveEnergy = 0.8f;
public float energyRestoredPerMinute = 20f;
[Header("Mood affects movement")]
[Tooltip("Speed multiplier when happiness is at zero.")]
[Range(0.1f, 1f)] public float sadSpeedMultiplier = 0.6f;
[Header("Events")]
public UnityEvent<State> OnStateChanged;
public UnityEvent OnFellAsleep;
public UnityEvent OnWokeUp;
public State CurrentState { get; private set; } = State.Idle;
private TopDownMover mover;
private Animator anim;
private Vector2 homePosition;
private Vector2 target;
private float stateTimer;
private float baseSpeed;
static readonly int SleepHash = Animator.StringToHash("IsSleeping");
static readonly int HappyHash = Animator.StringToHash("IsHappy");
private bool hasSleep, hasHappy;
void Awake()
{
mover = GetComponent<TopDownMover>();
anim = GetComponent<Animator>();
if (mover != null) { mover.aiControlled = true; baseSpeed = mover.moveSpeed; }
if (anim != null)
foreach (var p in anim.parameters)
{
if (p.nameHash == SleepHash) hasSleep = true;
if (p.nameHash == HappyHash) hasHappy = true;
}
}
void Start()
{
homePosition = transform.position;
EnterState(State.Idle);
}
void Update()
{
// Mood -> speed
if (mover != null && happiness != null)
mover.moveSpeed = baseSpeed * Mathf.Lerp(sadSpeedMultiplier, 1f, happiness.Normalized);
if (hasHappy && happiness != null) anim.SetBool(HappyHash, happiness.Normalized > 0.6f);
// Sleep overrides everything
if (energy != null && sleepBelowEnergy > 0f)
{
if (CurrentState != State.Sleep && energy.Normalized <= sleepBelowEnergy) { EnterState(State.Sleep); return; }
if (CurrentState == State.Sleep && energy.Normalized >= wakeAboveEnergy) { EnterState(State.Idle); return; }
}
stateTimer -= Time.deltaTime;
switch (CurrentState)
{
case State.Idle:
mover?.SetMoveInput(Vector2.zero);
if (stateTimer <= 0f) EnterState(State.Wander);
break;
case State.Wander:
Vector2 toTarget = target - (Vector2)transform.position;
if (toTarget.magnitude < arriveDistance || stateTimer <= 0f) EnterState(State.Idle);
else mover?.SetMoveInput(toTarget.normalized);
break;
case State.Sleep:
mover?.SetMoveInput(Vector2.zero);
energy?.Add(energyRestoredPerMinute / 60f * Time.deltaTime);
break;
case State.React:
mover?.SetMoveInput(Vector2.zero);
if (stateTimer <= 0f) EnterState(State.Idle);
break;
}
}
void EnterState(State s)
{
if (s == CurrentState && s != State.Idle && s != State.Wander) return;
bool wasAsleep = CurrentState == State.Sleep;
CurrentState = s;
switch (s)
{
case State.Idle:
stateTimer = Random.Range(idleTimeRange.x, idleTimeRange.y);
break;
case State.Wander:
target = PickWanderTarget();
stateTimer = Random.Range(walkTimeRange.x, walkTimeRange.y);
break;
case State.Sleep:
OnFellAsleep?.Invoke();
break;
case State.React:
stateTimer = 1.5f;
break;
}
if (hasSleep) anim.SetBool(SleepHash, s == State.Sleep);
if (wasAsleep && s != State.Sleep) OnWokeUp?.Invoke();
OnStateChanged?.Invoke(s);
}
Vector2 PickWanderTarget()
{
if (wanderArea != null)
{
Bounds b = wanderArea.bounds;
return new Vector2(Random.Range(b.min.x, b.max.x), Random.Range(b.min.y, b.max.y));
}
return homePosition + Random.insideUnitCircle * wanderRadius;
}
/// <summary>CareAction calls this so the pet perks up when you interact with it.</summary>
public void React()
{
if (CurrentState == State.Sleep && energy != null && energy.Normalized < wakeAboveEnergy) return;
EnterState(State.React);
}
void OnDrawGizmosSelected()
{
Gizmos.color = new Color(0.5f, 1f, 0.8f, 0.5f);
if (wanderArea != null) Gizmos.DrawWireCube(wanderArea.bounds.center, wanderArea.bounds.size);
else Gizmos.DrawWireSphere(Application.isPlaying ? (Vector3)homePosition : transform.position, wanderRadius);
}
}