Forum begins after the advertisement:


[Part 32] NPC Dialogue Conditions (specifically Jeff & Ben)

Home Forums Video Game Tutorial Series Creating a Farming RPG in Unity [Part 32] NPC Dialogue Conditions (specifically Jeff & Ben)

Viewing 20 posts - 1 through 20 (of 34 total)
  • Author
    Posts
  • #17588
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    I am curious as to what exactly has been placed as the different conditions for Jeff’s and Ben’s dialogue. Following the tutorial, I haven’t been able to see exactly what has been put as all these conditions. Since I haven’t modified them myself after the final shot of it in the series, I have been left with an error, and also just some curiosity regarding the dialogue conditions, as I think it is one of the things that gives the game the most life. The current error I encounter is this: “No such value DayOfWeek found.”, which occurs after trying to speak to Ben after 8 PM. I totally understand what this error comes from, but I am just not sure what it has been changed to (I really want the same dialogue condition-system as you). My current dialogues look like this:

    Jeff: Morning: Conditions: Key: Timestamp.hour Value Type: Int Equal: 10

    Dialogue Line: Speaker: Jeff Message: “My bones tell me it is exactly 10 am in the morning.”

    General Morning Greeting: Conditions: Key: Timestamp.hour Value Type: Int Less Than: 12

    Dialogue Line: Speaker: Jeff Message: “Good Morning!”

    Afternoon Greeting: Conditions: Key: Timestamp.hour Value Type: Int Greater Than Or Equal To: 12

    Dialogue Line: Speaker: Jeff Message: “My bones tell me it is exactly 10 am in the morning.”

    Ben: Forest Dialogue: Conditions: Key: Location Value Type: Int Equal: 6

    Dialogue Line: Speaker: Ben Message: “Mornin! Isn’t it a nice day to go fishing?”

    Shop Dialogue: Conditions: Key: Location Value Type: Int Equal: 3

    Dialogue Line: Speaker: Ben Message: “Do you love animals? I certainly do.” Speaker: Ben Message: “I’m always happy to help!”

    Shop Friday Dialogue: Conditions: Key: Location Value Type: Int Equal: 3 Key: DayOfWeek Value Type: String Equal: “Friday”

    Dialogue Line: Speaker: Ben Message: “Business has been really good tonight. I’m pleased.”

    Shop with 2 Hearts: Conditions: Key: Location Value Type: Int Equal: 3

    Dialogue Line: Speaker: Ben Message: “It’s always good to see you!”

    Could you please share your dialogues system regarding Jeff and Ben? Thank you in advance – I really enjoy the videos!

    #17597
    Jonathan Teo
    Level 18
    Moderator
    Helpful?
    Up
    0
    ::

    Are you all caught up to the latest part?

    As of Part 32: Ben Forest Dialogue Condition: Location = 5 Ben: Mornin! Isn’t it a nice day to go fishing?

    Shop Dialogue Location = 3 Ben Hey there {PlayerName}, do you love animals? I certainly do. Ben I’m always happy to help!

    Shop Friday Dialogue Location = 3 Timestamp.DayOfWeek = 6 Ben Business has been really good tonight. I’m pleased.

    Shop with 2 Hearts Location = 3 NPCRelationship_Ben.Hearts >= 2 Ben It’s always good to see you!

    First Animal Location = 3 AnimalCount = 1 Ben Take good care of your first animal.

    Subsequent Animals Location = 3 AnimalCount > 1 Ben You have {AnimalCount} animals under your belt. Ben That number will keep increasing. Keep at it {PlayerName}.

    Jeff General Morning Greeting Timestamp.hour < 12 Jeff Good morning!

    Afternoon Greeting Timestamp.hour >= 12 Timestamp.hour < 18 Jeff Good afternoon!

    Evening Greeting Timestamp.hour > 18 Jeff Good evening!

    #17598
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    1
    ::

    Thank you very much! I’ve only just made it to part 32, so I am currently unable to talk to Ben, but at least I know that it will be fixed later :)

      1 anonymous person
    has upvoted this post.
    #17599
    Tarha Apustaja
    Level 5
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    It sounds like you’re playing through a game or a series and have encountered a point where you can’t interact with a character named Ben yet. If you have any questions about the game, need help with something specific, or want to discuss strategies or tips, feel free to ask! I’m here to help.

    #17600
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    1
    ::

    Hey, so I’ve just completed the final episode and has encountered some problems with the NPC’s (especially Ben and Abby). Whenever I try to interact with them, it casts the errors: “InvalidCastException: Specified cast is not valid.” for Ben. “No such value NPCRelationship_Abby.Hearts found.” for Abby.

    I now that it doesn’t necessarily have anything to do with this dialogue problem, but I also constantly get the error: “”SetDestination” can only be called on an active agent that has been placed on a NavMesh. UnityEngine.AI.NavMeshAgent:SetDestination (UnityEngine.Vector3)”

    Other than this issue, everything else is working (I think). This series has been an epic journey, and I’ve learned so much, so thank you so much!

      1 anonymous person
    has upvoted this post.
    #17601
    Jonathan Teo
    Level 18
    Moderator
    Helpful?
    Up
    1
    ::

    Don’t worry about those errors for now, they don’t affect the game function. We just have to add certain preliminary checks to not have those errors thrown. We’re glad you’re learning a lot from our tutorial series!

      1 anonymous person
    has upvoted this post.
    #17606
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    1
    ::

    Well, it doesn’t crash the game, but it makes it impossible to talk to Ben whenever he’s in the Yodel Ranch, and Abby after the very first interaction with her.

      1 anonymous person
    has upvoted this post.
    #17612
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    How do I fix this?

    #17614
    Jonathan Teo
    Level 18
    Moderator
    Helpful?
    Up
    0
    ::

    Does the game freeze after the error is thrown?

    #17615
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    No, it doesn’t freeze after the error is thrown, but it does so Ben doesn’t cast a dialogue at all, and Abby doesn’t cast a dialogue after the initial one.

    #17624
    Jonathan Teo
    Level 18
    Moderator
    Helpful?
    Up
    0
    ::

    Is your default dialogue set? Also post all the relevant scripts from the character to the dialogue

    #17630
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    Yes, my default dialogue is set. Here are the relevant scripts:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    [System.Serializable]
    public class DialogueCondition: IConditional
    {
        //An identifier for the dialogue condition for easier editing
        public string id; 
        public BlackboardCondition[] conditions;
        public List<DialogueLine> dialogueLine;
    
    
        public bool CheckConditions(out int conditionsMet)
        {
            IConditional conditionChecker = this;
            return conditionChecker.CheckConditions(conditions, out conditionsMet);
        }
    }
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    [System.Serializable]
    public struct DialogueLine
    {
        public string speaker;
        [TextArea(2,5)]
        public string message;
    
        public DialogueLine(string speaker, string message)
        {
            this.speaker = speaker;
            this.message = message;
        }
    }
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;
    using System.Text.RegularExpressions;
    
    public class DialogueManager : MonoBehaviour
    {
        public static DialogueManager Instance { get; private set; }
    
        [Header("Dialogue Components")]
        public GameObject dialoguePanel;
        public TextMeshProUGUI speakerText;
        public TextMeshProUGUI dialogueText;
    
        //The lines to queue during the dialogue sequence
        Queue<DialogueLine> dialogueQueue;
        Action onDialogueEnd = null;
    
        bool isTyping = false;
    
        //TODO: Implement a proper player control stop mechanism
        PlayerController playerController;
    
        private void Awake()
        {
            //If there is more than one instance, destroy the extra
            if (Instance != null && Instance != this)
            {
                Destroy(this);
            }
            else
            {
                //Set the static instance to this instance
                Instance = this;
            }
        }
    
        private void Start()
        {
            playerController = FindFirstObjectByType<PlayerController>();
        }
    
        //Initialises the dialogue
        public void StartDialogue(List<DialogueLine> dialogueLinesToQueue)
        {
            //Convert the list to a queue
            dialogueQueue = new Queue<DialogueLine>(dialogueLinesToQueue);
    
    
            if (playerController != null)
            {
                playerController.enabled = false;
            }
    
            UpdateDialogue();
        }
    
        //Initialises the dialogue, but with an Action to execute once it finishes
        public void StartDialogue(List<DialogueLine> dialogueLinesToQueue, Action onDialogueEnd)
        {
            StartDialogue(dialogueLinesToQueue);
            this.onDialogueEnd = onDialogueEnd;
    
        }
    
        //Cycle through the dialogue lines
        public void UpdateDialogue()
        {
            if (isTyping)
            {
                isTyping = false;
                return;
            }
    
            //Reset our dialogue text
            dialogueText.text = string.Empty;
    
            //Check if there are any more lines in the queue
            if(dialogueQueue.Count == 0)
            {
                //If not, end the dialogue
                EndDialogue();
                return;
            }
    
            //The current dialogue line to put in
            DialogueLine line = dialogueQueue.Dequeue();
    
            Talk(line.speaker, ParseVariables(line.message));
        }
    
        //Closes the dialogue
        public void EndDialogue()
        {
            //Close the dialogue panel
            dialoguePanel.SetActive(false);
    
            if (playerController != null)
            {
                playerController.enabled = true;
            }
    
            //Invoke whatever Action queued on dialogue end (if any)
            onDialogueEnd?.Invoke();
    
    
    
            //Reset the Action
            onDialogueEnd = null;
    
    
        }
    
        public void Talk(string speaker, string message)
        {
            //Set the dialogue panel active
            dialoguePanel.SetActive(true);
    
            //Set the speaker text to the speaker
            speakerText.text = speaker;
    
            //If there is no speaker, do not show the speaker text panel
            speakerText.transform.parent.gameObject.SetActive(speaker != "");
    
            //Set the dialogue text to the message
            //dialogueText.text = message;
            StartCoroutine(TypeText(message));
        }
    
        IEnumerator TypeText(string textToType)
        {
            isTyping = true;
            //Convert the string to an array of chars
            char[] charsToType = textToType.ToCharArray();
            for(int i =0; i < charsToType.Length; i++)
            {
                dialogueText.text += charsToType[i];
                yield return new WaitForEndOfFrame();
    
                //Skip the typing sequence and just show the full text
                if (!isTyping)
                {
                    dialogueText.text = textToType;
                    //Break out from the loop
                    break;
                }
            }
    
            //Typing sequence complete
            isTyping = false;
        }
    
        //Converts a simple string into a List of Dialogue lines to put into DialogueManager
        public static List<DialogueLine> CreateSimpleMessage(string message)
        {
            //The Dialogue line we want to output
            DialogueLine messageDialogueLine = new DialogueLine("",message);
    
            List<DialogueLine> listToReturn = new List<DialogueLine>();
    
            listToReturn.Add(messageDialogueLine);
    
            return listToReturn;
        }
    
        //Filter to see if there is any dialogue lines we can overwrite with
        public static List<DialogueLine> SelectDialogue(List<DialogueLine> dialogueToExecute, DialogueCondition[] conditions)
        {
            //Replace the dialogue set with the highest condition score
            int highestConditionScore = -1;
            foreach(DialogueCondition condition in conditions)
            {
                //Check if conditions met first
                if(condition.CheckConditions(out int score))
                {
                    if(score > highestConditionScore)
                    {
                        highestConditionScore = score;
                        dialogueToExecute = condition.dialogueLine;
                        Debug.Log("Will play " + condition.id);
                    }
                }
            }
    
    
    
            return dialogueToExecute;
        }
    
        /// <summary>
        /// Any {} in the message will be retrieved from the blackboard
        /// </summary>
        /// <param name="="message">The string to pass in </param>
        /// <returns></returns>
        string ParseVariables(string message)
        {
            if(GameStateManager.Instance != null)
            {
                //Get the blackboard
                GameBlackboard blackboard = GameStateManager.Instance.GetBlackboard();
    
                if(blackboard != null)
                {
                    //Look for strings enclosed with {}
                    string pattern = @"\{([^}]+?)\}";
                    //Regex replacement step
                    message = Regex.Replace(message, pattern, match => {
                        //The variable name enclosed in the "{}"
                        string variableName = match.Groups[1].Value;
    
                        //If there is a string value, return it
                        if (blackboard.TryGetValueAsString(variableName, out string strValue))
                        {
                            return strValue;
                        }
                        //Nothing found, so nothing is returned
                        return "";
                    });
    
                }
            }
            return message;
        }
    }
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RelationshipStats : MonoBehaviour
    {
    
        const string RELATIONSHIP_PREFIX = "NPCRelationship_";
        //The relationship data of all the NPCs that the player has met in the game
        public static List<NPCRelationshipState> relationships = new List<NPCRelationshipState>();
    
        public enum GiftReaction
        {
            Like, Dislike, Neutral
        }
    
    
        public static void LoadStats()
        {
            relationships = new List<NPCRelationshipState>();
            GameBlackboard blackboard = GameStateManager.Instance.GetBlackboard();
            foreach (CharacterData c in NPCManager.Instance.Characters()) {
                if (blackboard.TryGetValue(RELATIONSHIP_PREFIX + c.name, out NPCRelationshipState rs))
                {
                    relationships.Add(rs);
                }
    
            }
    
            /*
            if (relationshipsToLoad == null)
            {
                relationships = new List<NPCRelationshipState>();
                return;
            }
            relationships = relationshipsToLoad;*/
        }
    
        //Check if the player has met this NPC.
        public static bool FirstMeeting(CharacterData character)
        {
            GameBlackboard blackboard = GameStateManager.Instance.GetBlackboard();
            return !blackboard.ContainsKey(RELATIONSHIP_PREFIX + character.name);
            //return !relationships.Exists(i => i.name == character.name);
        }
    
        //Get relationship information about the character
        public static NPCRelationshipState GetRelationship(CharacterData character)
        {
            //Check if it is the first meeting
            if (FirstMeeting(character)) return null;
            GameBlackboard blackboard = GameStateManager.Instance.GetBlackboard();
            if (blackboard.TryGetValue(RELATIONSHIP_PREFIX + character.name, out NPCRelationshipState relationship));
            return relationship;
    
    
            //return relationships.Find(i => i.name == character.name);
        }
    
        //Add the character to the relationships data
        public static void UnlockCharacter(CharacterData character)
        {
            GameBlackboard blackboard = GameStateManager.Instance.GetBlackboard();
            NPCRelationshipState relationship = new NPCRelationshipState(character.name);
            blackboard.SetValue(RELATIONSHIP_PREFIX + character.name, relationship);
            relationships.Add(relationship);
        }
    
        //Improve the relationship with and NPC
        public static void AddFriendPoints(CharacterData character, int points)
        {
            if (FirstMeeting(character))
            {
                Debug.LogError("The player has not met this character yet!");
                return;
            }
    
            GetRelationship(character).friendshipPoints += points;
        }
    
        //Check if this is the first conversation the player is having with the NPC today
        public static bool IsFirstConversationOfTheDay(CharacterData character)
        {
            //If the player is meeting him for the first time, definitely is
            if (FirstMeeting(character)) return true;
    
            NPCRelationshipState npc = GetRelationship(character);
            return !npc.hasTalkedToday;
        }
    
        //Check if the player has already given this character a gift today
        public static bool GiftGivenToday(CharacterData character)
        {
            NPCRelationshipState npc = GetRelationship(character);
            return npc.giftGivenToday;
        }
    
        public static GiftReaction GetReactionToGift(CharacterData character, ItemData item)
        {
            //If it's in the list of liked items, it means the character likes it
            if (character.likes.Contains(item)) return GiftReaction.Like;
            //If it's in the list of disliked items, it means the character dislikes it
            if (character.dislikes.Contains(item)) return GiftReaction.Dislike;
    
            return GiftReaction.Neutral;
    
        }
    
        //Check if it's the character's birthday
        public static bool IsBirthday(CharacterData character)
        {
            GameTimestamp birthday = character.birthday;
            GameTimestamp today = TimeManager.Instance.GetGameTimestamp();
    
            return (today.day == birthday.day) && (today.season == birthday.season);
        }
    
        public static bool IsBirthday(CharacterData character, GameTimestamp today)
        {
            GameTimestamp birthday = character.birthday;
    
    
            return (today.day == birthday.day) && (today.season == birthday.season);
        }
    
        public static CharacterData WhoseBirthday(GameTimestamp timestamp)
        {
    
            foreach (CharacterData c in NPCManager.Instance.Characters())
            {
                if (IsBirthday(c, timestamp))
                {
                    return c;
                }
            }
            return null;
        }
    
    }
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    [System.Serializable]
    public class NPCRelationshipState
    {
        public string name;
        public int friendshipPoints;
    
        public bool hasTalkedToday;
        public bool giftGivenToday;
    
        public NPCRelationshipState(string name, int friendshipPoints)
        {
            this.name = name;
            this.friendshipPoints = friendshipPoints;
        }
    
        public NPCRelationshipState(string name)
        {
            this.name = name;
            friendshipPoints = 0;
        }
    
        //Ever 250 friendship points is a heart
        public float Hearts
        {
            get { return friendshipPoints / 250; }
        }
    
    }
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
    public class NPCManager : MonoBehaviour, ITimeTracker
    {
        public static NPCManager Instance { get; private set; }
        bool paused;
        private void Awake()
        {
            //If there is more than one instance, destroy the extra
            if (Instance != null && Instance != this)
            {
                Destroy(this);
            }
            else
            {
                //Set the static instance to this instance
                Instance = this;
            }
        }
    
    
    
        List<CharacterData> characters = null;
    
        List<NPCScheduleData> npcSchedules;
    
        [SerializeField]
        List<NPCLocationState> npcLocations;
    
    
        //Load all character data
        public List<CharacterData> Characters()
        {
            if (characters != null) return characters;
            CharacterData[] characterDatabase = Resources.LoadAll<CharacterData>("Characters");
            characters = characterDatabase.ToList();
            return characters;
        }
    
        private void OnEnable()
        {
            //Load NPC Schedules
            NPCScheduleData[] schedules = Resources.LoadAll<NPCScheduleData>("Schedules");
            npcSchedules = schedules.ToList();
            InitNPCLocations();
    
        }
    
        //Pause NPC Spawning logic
        public void Pause()
        {
            paused = true;
            //Kill the npcs
            InteractableCharacter[] npcs = Object.FindObjectsByType<InteractableCharacter>(sortMode: default);
            foreach(InteractableCharacter npc in npcs)
            {
                Destroy(npc);
            }
        }
    
        public void Continue()
        {
            paused = false;
            RenderNPCs();
        }
    
        private void Start()
        {
            //Add this to TimeManager's Listener list
            TimeManager.Instance.RegisterTracker(this);
            SceneTransitionManager.Instance.onLocationLoad.AddListener(RenderNPCs);
        }
    
        private void InitNPCLocations()
        {
            npcLocations = new List<NPCLocationState>();
            foreach (CharacterData character in Characters())
            {
                npcLocations.Add(new NPCLocationState(character));
            }
    
        }
    
        void RenderNPCs()
        {
            foreach(NPCLocationState npc in npcLocations)
            {
                if(npc.location == SceneTransitionManager.Instance.currentLocation)
                {
                    Instantiate(npc.character.prefab, npc.coord, Quaternion.Euler(npc.facing));
                }
            }
        }
    
        void SpawnInNPC(CharacterData npc, SceneTransitionManager.Location comingFrom)
        {
            Transform start = LocationManager.Instance.GetPlayerStartingPosition(comingFrom);
            Instantiate(npc.prefab, start.position, start.rotation);
    
        }
    
        public void ClockUpdate(GameTimestamp timestamp) {
            UpdateNPCLocations(timestamp);
        }
    
        public NPCLocationState GetNPCLocation(string name)
        {
            return npcLocations.Find(x => x.character.name == name);
        }
    
        private void UpdateNPCLocations(GameTimestamp timestamp)
        {
            if (paused) return;
    
            for (int i = 0; i < npcLocations.Count; i++)
            {
                NPCLocationState npcLocator = npcLocations[i];
                SceneTransitionManager.Location previousLocation = npcLocator.location;
                //Find the schedule belonging to the NPC
                NPCScheduleData schedule = npcSchedules.Find(x => x.character == npcLocator.character);
                if (schedule == null)
                {
                    Debug.LogError("No schedule found for " + npcLocator.character.name);
                    continue;
                }
    
                //Current time
    
                GameTimestamp.DayOfTheWeek dayOfWeek = timestamp.GetDayOfTheWeek();
    
                //Find the events that correspond to the current time
                //E.g. if the event is set to 8am, the current time must be after 8am, so the hour of timeNow has to be greater than the event
                //Either the day of the week matches or it is set to ignore the day of the week
                List<ScheduleEvent> eventsToConsider = schedule.npcScheduleList.FindAll(x => x.time.hour <= timestamp.hour && (x.dayOfTheWeek == dayOfWeek || x.ignoreDayOfTheWeek));
                //Check if the events are empty
                if (eventsToConsider.Count < 1)
                {
                    Debug.LogError("None found for " + npcLocator.character.name);
                    Debug.LogError(timestamp.hour);
                    continue;
                }
    
                //Remove all the events with the hour that is lower than the max time (The time has already elapsed)
                int maxHour = eventsToConsider.Max(x => x.time.hour);
                eventsToConsider.RemoveAll(x => x.time.hour < maxHour);
    
                //Get the event with the highest priority
                ScheduleEvent eventToExecute = eventsToConsider.OrderByDescending(x => x.priority).First();
                //Set the NPC Locator value accordingly
                npcLocations[i] = new NPCLocationState(schedule.character, eventToExecute.location, eventToExecute.coord, eventToExecute.facing);
                SceneTransitionManager.Location newLocation = eventToExecute.location;
                //If there has been a change in location
                if(newLocation != previousLocation)
                {
                    Debug.Log("New location: " + newLocation);
                    //If the location is where we are
                    if(SceneTransitionManager.Instance.currentLocation == newLocation)
                    {
                        SpawnInNPC(schedule.character, previousLocation);
                    }
                }
            }
    
        }
    }
    #17631
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    It is worth noting, that the dialogue did work before updating the character’s dialogue, so that seems to be the cause.

    #17637
    Jonathan Teo
    Level 18
    Moderator
    Helpful?
    Up
    0
    ::

    So far your code seems alright, maybe @Tarha can give a diagnosis on it. I’m assuming IConditional is the same as the IBlackboardConditional interface. Can I see your InteractableCharacter code? Particularly the TriggerDialogue function.

    If the problem isn’t with the code, check that your dialogues are set up the same as mine because now that I’ve gone back to see your setup, I’ve noted a few inconsistencies:

    1. You set the forest dialogue location to be 6 instead of 5
    2. You used a string to denote the Day of Week but it is represented by numbers Timestamp.DayOfWeek = 6 (where 6 is Friday)
    3. Check that your time ranges for the conditions align
    #17640
    Tarha Apustaja
    Level 5
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    Based on the code provided, the potential issue could be in the SelectDialogue method in the DialogueManager class. The method only selects a dialogue if the conditions are met and have a higher condition score.

    If Ben or Abby have no dialogue conditions that are currently met, or if their initial dialogue conditions are not being reset, they might not have any dialogue to display after the first interaction.

    To troubleshoot, I recommend:

    1. Check the DialogueCondition setup for Ben and Abby
    2. Verify if their dialogue conditions are being properly reset
    3. Add debug logs in the SelectDialogue method to see which conditions are being evaluated

    Would you like me to help you investigate the specific dialogue conditions for Ben and Abby?

    #17641
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    The reason I set the Location to “6” in the Forest Dialogue is because, it for some reason is the sixth scene in my scene list (has the id “6”). After you sent your dialogues, I immediately switched them out, so I’ve fixed things like Timestamp.DayOfWeek being proceeded as an int instead of a string. This is my “InteractableCharacter” script:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    [RequireComponent(typeof(CharacterMovement))]
    public class InteractableCharacter : InteractableObject
    {
        public CharacterData characterData;
    
        //Cache the relationship data of the NPC so we can access it
        NPCRelationshipState relationship;
    
        //The rotation it should be facing by default
        Quaternion defaultRotation;
        //Check if the LookAt coroutine is currently being executed
        bool isTurning = false;
    
        CharacterMovement movement;
    
        private void Start()
        {
            relationship = RelationshipStats.GetRelationship(characterData);
    
            movement = GetComponent<CharacterMovement>();
    
            //Cache the original rotation of the character
            defaultRotation = transform.rotation;
    
            //Add listener
            GameStateManager.Instance.onIntervalUpdate.AddListener(OnIntervalUpdate);
        }
    
        void OnIntervalUpdate()
        {
            //Get data on its location
            NPCLocationState locationState = NPCManager.Instance.GetNPCLocation(characterData.name);
            movement.MoveTo(locationState);
            StartCoroutine(LookAt(Quaternion.Euler(locationState.facing)));
    
        }
    
        public override void Pickup()
        {
            LookAtPlayer();
            TriggerDialogue();
        }
    
        #region Rotation
        void LookAtPlayer()
        {
            //Get the player's transform
            Transform player = FindObjectOfType<PlayerController>().transform;
    
            //Get a vector for the direction towards the player
            Vector3 dir = player.position - transform.position;
            //Lock the y axis of the vector so the npc doesn't look up or down to face the player
            dir.y = 0;
            //Convert the direction vector into a quaternion
            Quaternion lookRot = Quaternion.LookRotation(dir);
            //Look at the player
            StartCoroutine(LookAt(lookRot));
    
        }
        //Coroutine for the character to progressively turn towards a rotation
        IEnumerator LookAt(Quaternion lookRot)
        {
            //Check if the coroutine is already running
            if (isTurning)
            {
                //Stop the coroutine
                isTurning = false;
            }
            else
            {
                isTurning = true;
            }
    
            while(transform.rotation != lookRot)
            {
                if (!isTurning)
                {
                    //Stop coroutine execution
                    yield break;
                }
    
                //Dont do anything if moving
                if (!movement.IsMoving()) transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRot, 720 * Time.fixedDeltaTime);
                yield return new WaitForFixedUpdate();
            }
            isTurning = false;
        }
    
        //Rotate back to its original rotation
        void ResetRotation()
        {
            StartCoroutine(LookAt(defaultRotation));
        }
        #endregion
    
        #region Conversation Interactions
        void TriggerDialogue()
        {
            movement.ToggleMovement(false);
            //Check if the player is holding anything
            if (InventoryManager.Instance.SlotEquipped(InventorySlot.InventoryType.Item))
            {
                //Switch over to the Gift Dialogue function
                GiftDialogue();
                return;
            }
    
            List<DialogueLine> dialogueToHave = characterData.defaultDialogue;
    
            System.Action onDialogueEnd = () =>
            {
                //Allow for movement
                movement.ToggleMovement(true);
                //Continue going to its destination if it was on the way/Reset its initial position
                OnIntervalUpdate();
            };
    
            //Have the character reset their rotation after the conversation is over
            //onDialogueEnd += ResetRotation;
    
            //Do the checks to determine which dialogue to put out
    
            //Filter through the available dialogues through arbitration
            dialogueToHave = DialogueManager.SelectDialogue(dialogueToHave, characterData.dialogues);
    
            //Is the player meeting for the first time?
            if (RelationshipStats.FirstMeeting(characterData))
            {
                //Assign the first meet dialogue
                dialogueToHave = characterData.onFirstMeet;
                onDialogueEnd += OnFirstMeeting;
    
            }
    
            if (RelationshipStats.IsFirstConversationOfTheDay(characterData))
            {
                onDialogueEnd += OnFirstConversation;
            }
    
            DialogueManager.Instance.StartDialogue(dialogueToHave, onDialogueEnd);
        }
    
        //Handle Gift Giving
        void GiftDialogue()
        {
            if (!EligibleForGift()) return;
    
            //Get the ItemSlotData of what the player is holding
            ItemSlotData handSlot = InventoryManager.Instance.GetEquippedSlot(InventorySlot.InventoryType.Item);
    
            List<DialogueLine> dialogueToHave = characterData.neutralGiftDialogue;
    
            System.Action onDialogueEnd = () =>
            {
                //Continue going to its destination if it was on the way/Reset its initial position
                OnIntervalUpdate();
    
                //Allow for movement
                movement.ToggleMovement(true);
    
                //Mark gift as given for today
                relationship.giftGivenToday = true;
    
                //Remove the item from the player's hand
                InventoryManager.Instance.ConsumeItem(handSlot);
            };
    
            //Have the character reset their rotation after the conversation is over
            //onDialogueEnd += ResetRotation;
    
            bool isBirthday = RelationshipStats.IsBirthday(characterData);
    
            //The friendship points to add from the gift
            int pointsToAdd = 0;
            //Do the checks to determine which dialogue to put out
            switch(RelationshipStats.GetReactionToGift(characterData, handSlot.itemData))
            {
                case RelationshipStats.GiftReaction.Like:
                    dialogueToHave = characterData.likedGiftDialogue;
                    //80
                    pointsToAdd = 80;
                    if (isBirthday) dialogueToHave = characterData.birthdayLikedGiftDialogue;
                    break;
    
                case RelationshipStats.GiftReaction.Dislike:
                    dialogueToHave = characterData.dislikedGiftDialogue;
                    //-20
                    pointsToAdd = -20;
                    if (isBirthday) dialogueToHave = characterData.birthdayDislikedGiftDialogue;
                    break;
                case RelationshipStats.GiftReaction.Neutral:
                    dialogueToHave = characterData.neutralGiftDialogue;
                    //20
                    pointsToAdd = 20;
                    if (isBirthday) dialogueToHave = characterData.birthdayNeutralGiftDialogue;
    
                    break;
            }
            //Birthday multiplier
            if (isBirthday) pointsToAdd *= 8;
    
    
            RelationshipStats.AddFriendPoints(characterData, pointsToAdd);
    
            DialogueManager.Instance.StartDialogue(dialogueToHave, onDialogueEnd);
        }
    
        //Check if the character can be given a gift
        bool EligibleForGift()
        {
            //Reject condition: Player has not unlocked this character yet
            if (RelationshipStats.FirstMeeting(characterData))
            {
                DialogueManager.Instance.StartDialogue(DialogueManager.CreateSimpleMessage("You have not unlocked this character yet."));
                return false;
            }
    
            //Reject condition: Player has already given this character a gift today
            if (RelationshipStats.GiftGivenToday(characterData))
            {
                DialogueManager.Instance.StartDialogue(DialogueManager.CreateSimpleMessage($"You have already given {characterData.name} a gift today."));
                return false;
            }
    
    
            return true;
        }
    
        void OnFirstMeeting()
        {
            //Unlock the character on the relationships
            RelationshipStats.UnlockCharacter(characterData);
            //Update the relationship data
            relationship = RelationshipStats.GetRelationship(characterData);
        }
    
        void OnFirstConversation()
        {
            Debug.Log("This is the first conversation of the day");
            //Add 20 friend points
            RelationshipStats.AddFriendPoints(characterData, 20);
    
            relationship.hasTalkedToday = true;
        }
        #endregion
    }
    #17642
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    I just tried my old dialogue system on Ben, and it actually works. I then tried removing one dialogue condition after the other, and found that the problem occurs from the “Shop with 2 Hearts” dialogue, more specifically the “NPCRelationship_Ben.Hearts >= 2” condition. I therefore believe that I am missing something in that script. This was also the problem that prevented me from talking to Abby, so I’m confident that this is the cause. I hope you guys know what I’ve done wrong, so it can be fixed.

    #17650
    Jonathan Teo
    Level 18
    Moderator
    Helpful?
    Up
    0
    ::

    In that case the problem probably lies with how nested properties are being handled in the TryGetValueAsString or the GetNestedObject methods in the GameBlackboard script.

    #17652
    Raunsamsing
    Level 5
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    Well, I’ve earlier been given the entire “GameBlackboard” script by Terence here:

    [Part 27] Missing scripts in the video
    Could you provide me with the newest version of the script, if it has been changed since?
    #17700
    Jonathan Teo
    Level 18
    Moderator
    Helpful?
    Up
    0
    ::

    Hmmm… There shouldn’t be a difference. Are you trying this from loading in a new game after implementing the latest part, or are you doing this from continuing a loaded save before you changed it?

    Because after the first cutscene is loaded in, you already should have Ben’s relationship saved and the error should not trigger when you talk to Ben.

Viewing 20 posts - 1 through 20 (of 34 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: