2026-09-03 16:35:05 +01:00
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.AI;
|
2026-09-04 16:33:41 +01:00
|
|
|
using EasyTalk.Display;
|
2026-09-03 16:35:05 +01:00
|
|
|
|
|
|
|
|
namespace MedievalShopkeeper
|
|
|
|
|
{
|
|
|
|
|
public enum NPCState
|
|
|
|
|
{
|
|
|
|
|
Entering,
|
|
|
|
|
Browsing,
|
|
|
|
|
Queueing,
|
|
|
|
|
CheckingOut,
|
2026-09-04 16:33:41 +01:00
|
|
|
Leaving,
|
|
|
|
|
Fleeing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public enum NPCIntention
|
|
|
|
|
{
|
|
|
|
|
Shopper,
|
|
|
|
|
Shoplifter,
|
|
|
|
|
Raider
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[RequireComponent(typeof(NavMeshAgent))]
|
|
|
|
|
public class CustomerNPC : Interactable
|
|
|
|
|
{
|
|
|
|
|
[Header("NPC Data")]
|
|
|
|
|
public string npcName = "Peasant";
|
|
|
|
|
public float browseTimePerShelf = 2f;
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
[Header("Dialogue (EasyTalk)")]
|
|
|
|
|
[Tooltip("Assign a ConversationDisplay or WorldspaceConversationDisplay attached to this NPC prefab")]
|
|
|
|
|
public ConversationDisplay speechBubble;
|
|
|
|
|
public float bubbleShowDuration = 4f;
|
|
|
|
|
private Coroutine hideBubbleRoutine;
|
|
|
|
|
|
|
|
|
|
[Header("Effects")]
|
|
|
|
|
[Tooltip("VFX to play when the NPC is stunned (e.g. caught stealing or kicked out).")]
|
|
|
|
|
[SerializeField] private GameObject stunVFXPrefab;
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
private NPCState state;
|
2026-09-04 16:33:41 +01:00
|
|
|
public NPCIntention intention { get; private set; }
|
2026-09-03 16:35:05 +01:00
|
|
|
private NavMeshAgent agent;
|
2026-09-04 16:33:41 +01:00
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
private List<ItemData> basket = new List<ItemData>();
|
2026-09-04 16:33:41 +01:00
|
|
|
private List<ItemData> shoppingList = new List<ItemData>();
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
private ShopTill targetTill;
|
2026-09-04 16:33:41 +01:00
|
|
|
private bool isCaught = false;
|
2026-09-03 16:35:05 +01:00
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
private Animator animator;
|
|
|
|
|
private int speedHash;
|
|
|
|
|
private int isMovingHash;
|
|
|
|
|
private int isCarryingHash;
|
|
|
|
|
private float originalSpeed;
|
2026-09-03 16:35:05 +01:00
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
public override string InteractionPrompt
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (intention == NPCIntention.Shoplifter && state == NPCState.Fleeing && basket.Count > 0 && !isCaught)
|
|
|
|
|
{
|
|
|
|
|
return "Catch Thief!";
|
|
|
|
|
}
|
|
|
|
|
return $"Talk to {npcName}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Awake()
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
|
|
|
|
agent = GetComponent<NavMeshAgent>();
|
2026-09-04 16:33:41 +01:00
|
|
|
if (agent != null) originalSpeed = agent.speed;
|
|
|
|
|
|
|
|
|
|
animator = GetComponentInChildren<Animator>();
|
|
|
|
|
if (animator != null)
|
|
|
|
|
{
|
|
|
|
|
speedHash = Animator.StringToHash("Speed");
|
|
|
|
|
isMovingHash = Animator.StringToHash("IsMoving");
|
|
|
|
|
isCarryingHash = Animator.StringToHash("IsCarrying");
|
|
|
|
|
|
|
|
|
|
animator.applyRootMotion = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void LateUpdate()
|
|
|
|
|
{
|
|
|
|
|
if (speechBubble != null && speechBubble.gameObject.activeInHierarchy && Camera.main != null)
|
|
|
|
|
{
|
|
|
|
|
speechBubble.transform.forward = Camera.main.transform.forward;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void PauseMovement()
|
|
|
|
|
{
|
|
|
|
|
if (agent != null && agent.isOnNavMesh)
|
|
|
|
|
{
|
|
|
|
|
agent.isStopped = true;
|
|
|
|
|
agent.velocity = Vector3.zero;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ResumeMovement()
|
|
|
|
|
{
|
|
|
|
|
if (agent != null && agent.isOnNavMesh)
|
|
|
|
|
{
|
|
|
|
|
agent.isStopped = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Initialize(NPCIntention newIntention, List<ItemData> newShoppingList)
|
|
|
|
|
{
|
|
|
|
|
intention = newIntention;
|
|
|
|
|
shoppingList = newShoppingList ?? new List<ItemData>();
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
state = NPCState.Entering;
|
2026-09-04 16:33:41 +01:00
|
|
|
|
|
|
|
|
if (GameManager.Instance != null)
|
|
|
|
|
{
|
|
|
|
|
GameManager.Instance.CustomerEntered();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (AudioManager.Instance != null && (intention == NPCIntention.Shoplifter || intention == NPCIntention.Raider))
|
|
|
|
|
{
|
|
|
|
|
AudioManager.Instance.AddThreat();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (intention == NPCIntention.Shoplifter) npcName = "Shady " + npcName;
|
|
|
|
|
if (intention == NPCIntention.Raider) npcName = "Angry " + npcName;
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
StartCoroutine(BrainRoutine());
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
private void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
if (AudioManager.Instance != null && (intention == NPCIntention.Shoplifter || intention == NPCIntention.Raider))
|
|
|
|
|
{
|
|
|
|
|
AudioManager.Instance.RemoveThreat();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
private IEnumerator BrainRoutine()
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
if (ShopDoor.Instance != null)
|
|
|
|
|
{
|
|
|
|
|
agent.SetDestination(ShopDoor.Instance.InteractionTransform.position);
|
|
|
|
|
float timer = 0f;
|
|
|
|
|
while (timer < 10f)
|
|
|
|
|
{
|
|
|
|
|
if (!agent.pathPending && agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!agent.isStopped)
|
|
|
|
|
{
|
|
|
|
|
timer += Time.deltaTime;
|
|
|
|
|
}
|
|
|
|
|
yield return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
state = NPCState.Browsing;
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
if (intention == NPCIntention.Raider)
|
|
|
|
|
{
|
|
|
|
|
yield return RaiderRoutine();
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<ItemData> itemsSought = new List<ItemData>(shoppingList);
|
|
|
|
|
if (itemsSought.Count == 0) itemsSought.Add(null);
|
|
|
|
|
|
|
|
|
|
foreach (var desiredItem in itemsSought)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
bool reached = false;
|
|
|
|
|
Shelf targetShelf = null;
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
Shelf[] allShelves = FindObjectsByType<Shelf>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
2026-09-04 16:33:41 +01:00
|
|
|
List<Shelf> validShelves = new List<Shelf>();
|
|
|
|
|
foreach (var s in allShelves)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
if (!s.HasStock || s.isBroken) continue;
|
|
|
|
|
|
|
|
|
|
if (desiredItem != null)
|
|
|
|
|
{
|
|
|
|
|
if (s.AssignedItem == desiredItem) validShelves.Add(s);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
validShelves.Add(s);
|
|
|
|
|
}
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
if (validShelves.Count == 0)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
string logStr = desiredItem != null ? desiredItem.itemName : "anything";
|
|
|
|
|
Speak($"I can't seem to find any {logStr}...");
|
|
|
|
|
continue;
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
targetShelf = validShelves[Random.Range(0, validShelves.Count)];
|
|
|
|
|
Debug.Log($"[{npcName}] Found {targetShelf.gameObject.name} which has what I need. Heading there...");
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
agent.SetDestination(targetShelf.InteractionTransform.position);
|
|
|
|
|
|
|
|
|
|
float timer = 0f;
|
2026-09-04 16:33:41 +01:00
|
|
|
float timeout = 15f;
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
while (timer < timeout)
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
if (!agent.pathPending && agent.pathStatus == NavMeshPathStatus.PathComplete)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
if (agent.remainingDistance < 1.5f)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
|
|
|
|
reached = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-04 16:33:41 +01:00
|
|
|
|
|
|
|
|
if (!agent.isStopped)
|
|
|
|
|
{
|
|
|
|
|
timer += Time.deltaTime;
|
|
|
|
|
}
|
2026-09-03 16:35:05 +01:00
|
|
|
yield return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reached)
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
Debug.Log($"[{npcName}] Arrived at {targetShelf.gameObject.name}. Browsing...");
|
2026-09-03 16:35:05 +01:00
|
|
|
yield return new WaitForSeconds(browseTimePerShelf);
|
|
|
|
|
|
|
|
|
|
if (targetShelf.TryTakeItem(out ItemData item))
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
Debug.Log($"[{npcName}] Took 1x {item.itemName}!");
|
2026-09-03 16:35:05 +01:00
|
|
|
basket.Add(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (basket.Count > 0)
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
if (intention == NPCIntention.Shoplifter)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
Debug.LogWarning($"[{npcName}] (Shoplifter) I'm stealing these and running for the door!");
|
|
|
|
|
LeaveShop();
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
targetTill = FindFirstObjectByType<ShopTill>();
|
|
|
|
|
if (targetTill != null)
|
|
|
|
|
{
|
|
|
|
|
state = NPCState.Queueing;
|
|
|
|
|
targetTill.JoinQueue(this);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"[{npcName}] No Till found! Dropping items and leaving.");
|
|
|
|
|
ReturnItemsAndLeave();
|
|
|
|
|
}
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
Speak("I couldn't find what I wanted.");
|
2026-09-03 16:35:05 +01:00
|
|
|
ReturnItemsAndLeave();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
private IEnumerator RaiderRoutine()
|
|
|
|
|
{
|
|
|
|
|
Shelf[] allShelves = FindObjectsByType<Shelf>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
|
|
|
|
if (allShelves.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
Shelf targetShelf = allShelves[Random.Range(0, allShelves.Length)];
|
|
|
|
|
Debug.LogWarning($"[{npcName}] (Raider) I'm going to smash {targetShelf.gameObject.name}!");
|
|
|
|
|
|
|
|
|
|
agent.SetDestination(targetShelf.InteractionTransform.position);
|
|
|
|
|
|
|
|
|
|
float timer = 0f;
|
|
|
|
|
while (timer < 10f)
|
|
|
|
|
{
|
|
|
|
|
if (!agent.pathPending && agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f)
|
|
|
|
|
{
|
|
|
|
|
yield return new WaitForSeconds(0.5f);
|
|
|
|
|
targetShelf.BreakShelf();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!agent.isStopped)
|
|
|
|
|
{
|
|
|
|
|
timer += Time.deltaTime;
|
|
|
|
|
}
|
|
|
|
|
yield return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LeaveShop();
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
private void Update()
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
if (animator != null && agent != null)
|
|
|
|
|
{
|
|
|
|
|
float currentSpeed = agent.velocity.magnitude;
|
|
|
|
|
bool isMoving = agent.velocity.sqrMagnitude > 0.05f && !agent.isStopped && agent.hasPath;
|
|
|
|
|
|
|
|
|
|
animator.SetFloat(speedHash, currentSpeed);
|
|
|
|
|
animator.SetBool(isMovingHash, isMoving);
|
|
|
|
|
animator.SetBool(isCarryingHash, basket.Count > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (agent != null && agent.isStopped) return;
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
if (state == NPCState.Queueing && targetTill != null)
|
|
|
|
|
{
|
|
|
|
|
Vector3 qPos = targetTill.GetQueuePosition(this);
|
|
|
|
|
agent.SetDestination(qPos);
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
{
|
|
|
|
|
if (targetTill != null && (state == NPCState.Queueing || state == NPCState.CheckingOut))
|
|
|
|
|
{
|
|
|
|
|
targetTill.LeaveQueue(this);
|
|
|
|
|
}
|
2026-09-04 16:33:41 +01:00
|
|
|
|
|
|
|
|
StartCoroutine(HitAndStunSequence("Fine, I'm leaving!"));
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
if (state == NPCState.Leaving || state == NPCState.Fleeing) return;
|
|
|
|
|
|
|
|
|
|
state = (intention == NPCIntention.Shoplifter || intention == NPCIntention.Raider) ? NPCState.Fleeing : NPCState.Leaving;
|
2026-09-03 16:35:05 +01:00
|
|
|
|
|
|
|
|
StopAllCoroutines();
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
if (state == NPCState.Fleeing && agent != null)
|
|
|
|
|
{
|
|
|
|
|
agent.speed *= 1.5f; // Run away faster!
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2026-09-04 16:33:41 +01:00
|
|
|
|
|
|
|
|
if (!agent.isStopped)
|
|
|
|
|
{
|
|
|
|
|
timer += Time.deltaTime;
|
|
|
|
|
}
|
2026-09-03 16:35:05 +01:00
|
|
|
yield return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
if (GameManager.Instance != null)
|
|
|
|
|
{
|
|
|
|
|
GameManager.Instance.CustomerLeft();
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
Destroy(gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Interact(PlayerController player)
|
|
|
|
|
{
|
|
|
|
|
base.Interact(player);
|
|
|
|
|
|
2026-09-04 16:33:41 +01:00
|
|
|
if (intention == NPCIntention.Shoplifter && state == NPCState.Fleeing && basket.Count > 0 && !isCaught)
|
|
|
|
|
{
|
|
|
|
|
CatchThief();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-03 16:35:05 +01:00
|
|
|
if (UIManager.Instance != null)
|
|
|
|
|
{
|
|
|
|
|
UIManager.Instance.OpenNPCMenu(this, player);
|
|
|
|
|
}
|
2026-09-04 16:33:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CatchThief()
|
|
|
|
|
{
|
|
|
|
|
isCaught = true;
|
|
|
|
|
Debug.Log($"<color=#00FF00>[Caught!] You caught {npcName} trying to steal {basket.Count} items!</color>");
|
|
|
|
|
StartCoroutine(HitAndStunSequence("Alright, alright! You caught me. Here!"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerator HitAndStunSequence(string dialogText)
|
|
|
|
|
{
|
|
|
|
|
PauseMovement();
|
|
|
|
|
|
|
|
|
|
PlayerController player = FindAnyObjectByType<PlayerController>();
|
|
|
|
|
if (player != null)
|
|
|
|
|
{
|
|
|
|
|
Vector3 lookPos = player.transform.position;
|
|
|
|
|
lookPos.y = transform.position.y;
|
|
|
|
|
transform.LookAt(lookPos);
|
|
|
|
|
|
|
|
|
|
Animator playerAnim = player.GetComponentInChildren<Animator>();
|
|
|
|
|
if (playerAnim != null) playerAnim.SetTrigger("Hit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield return new WaitForSeconds(0.4f);
|
|
|
|
|
|
|
|
|
|
if (animator != null)
|
|
|
|
|
{
|
|
|
|
|
animator.SetTrigger("Stunned");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stunVFXPrefab != null)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
Instantiate(stunVFXPrefab, transform.position + Vector3.up * 1.5f, Quaternion.identity);
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
2026-09-04 16:33:41 +01:00
|
|
|
|
|
|
|
|
Speak(dialogText);
|
|
|
|
|
|
|
|
|
|
yield return new WaitForSeconds(1.5f);
|
|
|
|
|
|
|
|
|
|
ResumeMovement();
|
|
|
|
|
ReturnItemsAndLeave();
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void TalkTo()
|
|
|
|
|
{
|
|
|
|
|
if (state == NPCState.Browsing)
|
2026-09-04 16:33:41 +01:00
|
|
|
Speak("Just browsing, thank you!");
|
2026-09-03 16:35:05 +01:00
|
|
|
else if (state == NPCState.Queueing)
|
2026-09-04 16:33:41 +01:00
|
|
|
Speak("I am waiting to pay...");
|
|
|
|
|
else if (state == NPCState.Fleeing)
|
|
|
|
|
Speak("You'll never catch me!");
|
|
|
|
|
else
|
|
|
|
|
Speak("Hello shopkeeper!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Speak(string text)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log($"[{npcName}] '{text}'");
|
|
|
|
|
|
|
|
|
|
if (speechBubble != null)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
speechBubble.Show();
|
|
|
|
|
speechBubble.SetCharacterName(npcName, npcName);
|
|
|
|
|
speechBubble.SetConversationText(text);
|
|
|
|
|
|
|
|
|
|
if (hideBubbleRoutine != null) StopCoroutine(hideBubbleRoutine);
|
|
|
|
|
hideBubbleRoutine = StartCoroutine(HideBubbleAfterDelay(bubbleShowDuration));
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
2026-09-04 16:33:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerator HideBubbleAfterDelay(float delay)
|
|
|
|
|
{
|
|
|
|
|
yield return new WaitForSeconds(delay);
|
|
|
|
|
if (speechBubble != null && gameObject.activeInHierarchy)
|
2026-09-03 16:35:05 +01:00
|
|
|
{
|
2026-09-04 16:33:41 +01:00
|
|
|
speechBubble.Hide();
|
2026-09-03 16:35:05 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|