244 lines
7.8 KiB
C#
244 lines
7.8 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
namespace MedievalShopkeeper
|
|
{
|
|
public enum NPCState
|
|
{
|
|
Entering,
|
|
Browsing,
|
|
Queueing,
|
|
CheckingOut,
|
|
Leaving
|
|
}
|
|
|
|
[RequireComponent(typeof(NavMeshAgent))]
|
|
public class CustomerNPC : Interactable
|
|
{
|
|
[Header("NPC Data")]
|
|
public string npcName = "Peasant";
|
|
public float browseTimePerShelf = 2f;
|
|
|
|
private NPCState state;
|
|
private NavMeshAgent agent;
|
|
private List<ItemData> basket = new List<ItemData>();
|
|
private ShopTill targetTill;
|
|
|
|
public override string InteractionPrompt => $"Talk to {npcName}";
|
|
|
|
private void Start()
|
|
{
|
|
agent = GetComponent<NavMeshAgent>();
|
|
state = NPCState.Entering;
|
|
StartCoroutine(BrainRoutine());
|
|
}
|
|
|
|
private IEnumerator BrainRoutine()
|
|
{
|
|
state = NPCState.Browsing;
|
|
int itemsToBuy = Random.Range(1, 4);
|
|
|
|
for (int i = 0; i < itemsToBuy; i++)
|
|
{
|
|
// Find all shelves that ACTUALLY have items on them
|
|
Shelf[] allShelves = FindObjectsByType<Shelf>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
|
List<Shelf> stockedShelves = new List<Shelf>();
|
|
foreach (var shelf in allShelves)
|
|
{
|
|
if (shelf.HasStock) stockedShelves.Add(shelf);
|
|
}
|
|
|
|
if (stockedShelves.Count == 0)
|
|
{
|
|
Debug.Log($"[{npcName}] Looked around, but there are no shelves with stock right now.");
|
|
break;
|
|
}
|
|
|
|
Shelf targetShelf = stockedShelves[Random.Range(0, stockedShelves.Count)];
|
|
Debug.Log($"[{npcName}] Decided to check out {targetShelf.gameObject.name}...");
|
|
|
|
agent.SetDestination(targetShelf.InteractionTransform.position);
|
|
|
|
// Wait until we reach the shelf (or fail)
|
|
bool reached = false;
|
|
float timeout = 15f;
|
|
float timer = 0f;
|
|
|
|
while (timer < timeout)
|
|
{
|
|
if (!agent.pathPending)
|
|
{
|
|
if (agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f)
|
|
{
|
|
reached = true;
|
|
break;
|
|
}
|
|
if (agent.pathStatus == NavMeshPathStatus.PathPartial || agent.pathStatus == NavMeshPathStatus.PathInvalid)
|
|
{
|
|
Debug.LogWarning($"[{npcName}] Path to {targetShelf.gameObject.name} is completely blocked (Status: {agent.pathStatus}). Giving up on this shelf.");
|
|
break;
|
|
}
|
|
}
|
|
timer += Time.deltaTime;
|
|
yield return null;
|
|
}
|
|
|
|
if (reached)
|
|
{
|
|
Debug.Log($"[{npcName}] Successfully arrived at {targetShelf.gameObject.name}. Browsing...");
|
|
yield return new WaitForSeconds(browseTimePerShelf);
|
|
|
|
if (targetShelf.TryTakeItem(out ItemData item))
|
|
{
|
|
Debug.Log($"[{npcName}] Picked up 1x {item.itemName}!");
|
|
basket.Add(item);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[{npcName}] Bummer, someone took the last item before I could reach it.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[{npcName}] Timed out trying to reach {targetShelf.gameObject.name}. It might be physically unreachable.");
|
|
}
|
|
}
|
|
|
|
if (basket.Count > 0)
|
|
{
|
|
targetTill = FindFirstObjectByType<ShopTill>();
|
|
if (targetTill != null)
|
|
{
|
|
state = NPCState.Queueing;
|
|
targetTill.JoinQueue(this);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[{npcName}] No ShopTill found! Dropping items and leaving.");
|
|
ReturnItemsAndLeave();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[{npcName}] Couldn't find what I wanted. Leaving.");
|
|
ReturnItemsAndLeave();
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (state == NPCState.Queueing && targetTill != null)
|
|
{
|
|
Vector3 qPos = targetTill.GetQueuePosition(this);
|
|
agent.SetDestination(qPos);
|
|
|
|
// Ensure they actually reached the queue spot and aren't stuck outside on a partial path
|
|
if (targetTill.IsFrontOfQueue(this) && !agent.pathPending && agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f)
|
|
{
|
|
state = NPCState.CheckingOut;
|
|
targetTill.PlaceItemsOnCounter(this, basket);
|
|
}
|
|
}
|
|
}
|
|
|
|
public List<ItemData> GetBasket()
|
|
{
|
|
return basket;
|
|
}
|
|
|
|
public void FinishCheckoutAndLeave()
|
|
{
|
|
basket.Clear();
|
|
LeaveShop();
|
|
}
|
|
|
|
public void KickOut()
|
|
{
|
|
Debug.Log($"[{npcName}] 'Fine, I'm leaving!'");
|
|
if (targetTill != null && (state == NPCState.Queueing || state == NPCState.CheckingOut))
|
|
{
|
|
targetTill.LeaveQueue(this);
|
|
}
|
|
ReturnItemsAndLeave();
|
|
}
|
|
|
|
private void ReturnItemsAndLeave()
|
|
{
|
|
if (ShopInventory.Instance != null)
|
|
{
|
|
foreach (var item in basket)
|
|
{
|
|
if (item != null) ShopInventory.Instance.AddStock(item, 1);
|
|
}
|
|
}
|
|
basket.Clear();
|
|
LeaveShop();
|
|
}
|
|
|
|
private void LeaveShop()
|
|
{
|
|
if (state == NPCState.Leaving) return;
|
|
state = NPCState.Leaving;
|
|
|
|
StopAllCoroutines();
|
|
|
|
if (ShopDoor.Instance != null)
|
|
{
|
|
agent.SetDestination(ShopDoor.Instance.InteractionTransform.position);
|
|
}
|
|
|
|
StartCoroutine(WaitAndDestroy());
|
|
}
|
|
|
|
private IEnumerator WaitAndDestroy()
|
|
{
|
|
float timeout = 15f;
|
|
float timer = 0f;
|
|
|
|
while (timer < timeout)
|
|
{
|
|
if (!agent.pathPending && agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f)
|
|
{
|
|
break;
|
|
}
|
|
timer += Time.deltaTime;
|
|
yield return null;
|
|
}
|
|
|
|
Destroy(gameObject);
|
|
}
|
|
|
|
public override void Interact(PlayerController player)
|
|
{
|
|
base.Interact(player);
|
|
|
|
if (UIManager.Instance != null)
|
|
{
|
|
UIManager.Instance.OpenNPCMenu(this, player);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[CustomerNPC] You interacted with {npcName}, but no UIManager is present.");
|
|
}
|
|
}
|
|
|
|
public void TalkTo()
|
|
{
|
|
if (state == NPCState.Browsing)
|
|
{
|
|
Debug.Log($"[{npcName}] 'Just browsing, thank you!'");
|
|
}
|
|
else if (state == NPCState.Queueing)
|
|
{
|
|
Debug.Log($"[{npcName}] 'I am waiting to pay...'");
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[{npcName}] 'Hello shopkeeper!'");
|
|
}
|
|
}
|
|
}
|
|
}
|