Files
beyond/Assets/Scripts/Triggers/GenericQuestTrigger.cs

260 lines
11 KiB
C#

// --- THIS SCRIPT CAN BE PLACED ANYWHERE IN YOUR ASSETS FOLDER ---
using UnityEngine;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using PixelCrushers.DialogueSystem; // Required for [ConversationPopup] attribute
using PixelCrushers.QuestMachine;
using Invector.vCharacterController.vActions; // Required for the vTriggerGenericAction reference
using UnityEngine.Events;
namespace Beyond
{
[AddComponentMenu("Beyond/Quests/Generic Quest Trigger")]
[RequireComponent(typeof(vTriggerGenericAction))] // IMPORTANT: Ensures the Invector trigger is always on the same GameObject
public class GenericQuestTrigger : MonoBehaviour
{
#region --- ODIN-POWERED INSPECTOR ---
[Title("Invector Trigger Link")]
[InfoBox("This component links into the vTriggerGenericAction below and adds quest/dialogue functionality to its events.")]
[Required("A vTriggerGenericAction component is required on this GameObject.")]
[OnInspectorInit("FindTriggerReference")] // Odin attribute to auto-fill the reference on first view
public vTriggerGenericAction invectorTrigger;
[Title("Quest and Dialogue Actions")]
[ListDrawerSettings(Expanded = true, DraggableItems = true, NumberOfItemsPerPage = 10)]
public List<TriggeredAction> actions = new List<TriggeredAction>();
#endregion
private GameObject _lastInteractor;
// This is a helper method for the Odin attribute [OnInspectorInit]
private void FindTriggerReference()
{
if (invectorTrigger == null)
{
invectorTrigger = GetComponent<vTriggerGenericAction>();
}
}
#region --- CORE LOGIC (Lifecycle & Event Handlers) ---
private void Start()
{
if (invectorTrigger == null)
{
Debug.LogError("GenericQuestTrigger requires a vTriggerGenericAction component, but none was found. Disabling component.", this);
this.enabled = false;
return;
}
// Subscribe our methods to the events on the Invector component
invectorTrigger.OnPressActionInput.AddListener(() => HandleEvent(TriggerEventType.OnPressActionInput, _lastInteractor));
invectorTrigger.OnStartAnimation.AddListener(() => HandleEvent(TriggerEventType.OnStartAnimation, _lastInteractor));
invectorTrigger.OnEndAnimation.AddListener(() => HandleEvent(TriggerEventType.OnEndAnimation, _lastInteractor));
invectorTrigger.OnPlayerEnter.AddListener(HandlePlayerEnter);
invectorTrigger.OnPlayerExit.AddListener(HandlePlayerExit);
invectorTrigger.OnValidate.AddListener((interactor) => HandleEvent(TriggerEventType.OnValidate, interactor));
invectorTrigger.OnInvalidate.AddListener((interactor) => HandleEvent(TriggerEventType.OnInvalidate, interactor));
if (DialogueManager.instance != null)
{
DialogueManager.instance.conversationEnded += OnConversationEnded;
}
}
private void OnDisable()
{
if (DialogueManager.instance != null)
{
DialogueManager.instance.conversationEnded -= OnConversationEnded;
}
// It's good practice to unsubscribe from events when disabled/destroyed to prevent memory leaks
if (invectorTrigger != null)
{
invectorTrigger.OnPressActionInput.RemoveListener(() => HandleEvent(TriggerEventType.OnPressActionInput, _lastInteractor));
invectorTrigger.OnStartAnimation.RemoveListener(() => HandleEvent(TriggerEventType.OnStartAnimation, _lastInteractor));
invectorTrigger.OnEndAnimation.RemoveListener(() => HandleEvent(TriggerEventType.OnEndAnimation, _lastInteractor));
invectorTrigger.OnPlayerEnter.RemoveListener(HandlePlayerEnter);
invectorTrigger.OnPlayerExit.RemoveListener(HandlePlayerExit);
invectorTrigger.OnValidate.RemoveListener((interactor) => HandleEvent(TriggerEventType.OnValidate, interactor));
invectorTrigger.OnInvalidate.RemoveListener((interactor) => HandleEvent(TriggerEventType.OnInvalidate, interactor));
}
}
private void HandlePlayerEnter(GameObject interactor)
{
_lastInteractor = interactor;
HandleEvent(TriggerEventType.OnPlayerEnter, interactor);
}
private void HandlePlayerExit(GameObject interactor)
{
HandleEvent(TriggerEventType.OnPlayerExit, interactor);
_lastInteractor = null;
}
private void OnConversationEnded(Transform actor)
{
if (_lastInteractor != null && actor.gameObject == _lastInteractor)
{
HandleEvent(TriggerEventType.OnConversationEnd, actor.gameObject);
}
}
private void HandleEvent(TriggerEventType eventType, GameObject interactor)
{
foreach (var action in actions)
{
if (action.triggerEvent == eventType)
{
// Check if the optional Dialogue System condition is met
if (action.condition == null || action.condition.IsTrue(interactor?.transform))
{
PerformAction(action, interactor);
}
}
}
}
private void PerformAction(TriggeredAction action, GameObject interactor)
{
var actorTransform = interactor?.transform;
switch (action.actionType)
{
case ActionType.StartConversation:
if (!string.IsNullOrEmpty(action.conversation))
{
Transform conversant = action.conversant != null ? action.conversant : this.transform;
DialogueManager.StartConversation(action.conversation, actorTransform, conversant);
}
break;
case ActionType.ExecuteLua:
if (!string.IsNullOrEmpty(action.luaCode))
{
Lua.Run(action.luaCode, true);
}
break;
case ActionType.SendQuestMachineMessage:
if (!string.IsNullOrEmpty(action.questMachineMessage))
{
QuestMachineMessages.SendCompositeMessage(this, action.questMachineMessage);
}
break;
case ActionType.SetQuestState:
if (!string.IsNullOrEmpty(action.questID))
{
QuestMachine.SetQuestState(action.questID, action.questState);
}
break;
case ActionType.SetQuestNodeState:
if (!string.IsNullOrEmpty(action.questID) && !string.IsNullOrEmpty(action.questNodeID))
{
QuestMachine.SetQuestNodeState(action.questID, action.questNodeID, action.questNodeState);
}
break;
case ActionType.InvokeUnityEvent:
action.onExecute?.Invoke();
break;
}
}
#endregion
}
#region --- Action Definitions (Enums and Class) ---
public enum TriggerEventType
{
OnPlayerEnter,
OnPlayerExit,
OnValidate,
OnInvalidate,
OnPressActionInput,
OnStartAnimation,
OnEndAnimation,
OnConversationEnd
}
public enum ActionType
{
StartConversation,
ExecuteLua,
SendQuestMachineMessage,
SetQuestState,
SetQuestNodeState,
InvokeUnityEvent
}
[System.Serializable]
public class TriggeredAction
{
[HorizontalGroup("Top", 120)]
[BoxGroup("Top/Trigger", showLabel: false)]
[EnumToggleButtons, HideLabel]
public TriggerEventType triggerEvent;
[BoxGroup("Top/Action", showLabel: false)]
[EnumToggleButtons, HideLabel]
public ActionType actionType;
[BoxGroup("Settings")]
[Tooltip("Optional Dialogue System condition that must be true for this action to fire.")]
[DrawWithUnity] // <-- Tells Odin to use the Pixel Crushers drawer for this field.
public Condition condition;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.StartConversation)]
[ConversationPopup(false)] // <-- The Dialogue System attribute that creates the dropdown.
[DrawWithUnity] // <-- The Odin attribute that allows the above attribute to work.
public string conversation;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.StartConversation)]
[Tooltip("The other participant in the conversation. If unassigned, this trigger object will be the conversant.")]
public Transform conversant;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.ExecuteLua)]
[TextArea(2, 5)]
public string luaCode;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.SendQuestMachineMessage)]
public string questMachineMessage;
[BoxGroup("Settings")]
[ShowIf("@this.actionType == ActionType.SetQuestState || this.actionType == ActionType.SetQuestNodeState")]
[DrawWithUnity] // <-- Tells Odin to use the Quest Machine drawer for the quest ID field.
public string questID;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.SetQuestState)]
public PixelCrushers.QuestMachine.QuestState questState;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.SetQuestNodeState)]
[DrawWithUnity] // <-- Tells Odin to use the Quest Machine drawer for the node ID field.
public string questNodeID;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.SetQuestNodeState)]
public PixelCrushers.QuestMachine.QuestNodeState questNodeState;
[BoxGroup("Settings")]
[ShowIf("actionType", ActionType.InvokeUnityEvent)]
public UnityEvent onExecute;
}
#endregion
}