How to add and use new ConversationTags for conversation strings, to have unique conversations for certain characters?

Users who are viewing this thread

So for one of my mods which adds new Lords, I want to give them all their own dialogue lines instead of just using the default ones so I can more fully develop their character and make them unique from other characters in the game. For starters I am trying to edit a specific character's first meeting line which is displayed the first time the player meets them. These are located among other lines, in SandBox/ModuleData/voice_strings.xml, with tags for when the conversation should take place. Here is an example for a first meeting:

XML:
<string id="str_context_line.first_meeting_cu" text="{=B05S6Fw4}Who are you, and what do you want?">

    <tags>

        <tag tag_name="FirstMeetingTag" />

        <tag tag_name="VoiceGroupPersonaCurtUpperTag" />

    </tags>

This one is for a first meeting ("FirstMeetingTag") where the character is curt and has an upper voice register ("VoiceGroupPersonaCurtUpperTag").

The tags are defined in TaleWorlds.CampaignSystem.Conversation.Tags, for example here is the FirstMeetingTag:

C#:
using System;

namespace TaleWorlds.CampaignSystem.Conversation.Tags
{
    public class FirstMeetingTag : ConversationTag
    {
        public override string StringId
        {
            get
            {
                return "FirstMeetingTag";
            }
        }

        public override bool IsApplicableTo(CharacterObject character)
        {
            return Campaign.Current.ConversationManager.CurrentConversationIsFirst;
        }
    }
}

As far as I can tell, tags are only referenced in one other location in ConversationTagList within TaleWorlds.Core. Here each tag has a string field such as:
public const string VoiceGroupPersonaCurtUpperTag = "VoiceGroupPersonaCurtUpperTag";

The class also contains a string List which gets populated with all the strings by the class' one method, Contains(string tagId). The method returns a bool of whether or not the string tagId exists in the class/the list:

C#:
        internal static bool Contains(string tagId)
        {
            if (ConversationTagList._tagIdList == null)
            {
                ConversationTagList._tagIdList = new List<string>();
                foreach (FieldInfo fieldInfo in typeof(ConversationTagList).GetFields(24))
                {
                    if (fieldInfo.IsLiteral && !fieldInfo.IsInitOnly && fieldInfo.FieldType == typeof(string))
                    {
                        ConversationTagList._tagIdList.Add((string)fieldInfo.GetValue(null));
                    }
                }
            }
            return ConversationTagList._tagIdList.Contains(tagId);
        }

I'm not exactly sure what calls this method, or the individual ConversationTags isApplicableTo method - as I can't find a reference to any of these anywhere else. I attempted to add my own ConversationTag for a specific character from my mod, as well as patch the ConversationTagList Contains method so that it will return a positive for "ValdyrTag" since I can't add a field directly to the class:

C#:
using System.Collections.Generic;
using System.Reflection;
using TaleWorlds.CampaignSystem;
using TaleWorlds.Core;
using TaleWorlds.MountAndBlade;
using TaleWorlds.CampaignSystem.Conversation.Tags;
using HarmonyLib;

namespace JotnarOfSturgiaSonsOfFenrir
{
    public class Main : MBSubModuleBase
    {
        protected override void OnSubModuleLoad()
        {
            base.OnSubModuleLoad();
            new Harmony("ThisIsSovereign.Bannerlord.JotnarOfSturgiaSonsOfFenrir").PatchAll();

            InformationManager.DisplayMessage(new InformationMessage("Jotnar of Sturgia - Sons of Fenrir 1.0.0 Loaded"));
        }
    }

    public class ValdyrTag : ConversationTag
    {
        public override string StringId
        {
            get
            {
                return "ValdyrTag";
            }
        }

        public override bool IsApplicableTo(CharacterObject character)
        {
            return character.GetName().ToString() == "Valdyr";
        }
    }

    [HarmonyPatch(typeof(ConversationTagList), "Contains")]
    public class JotnarConversationTagList
    {
        private static List<string> _tagIdList;

        public static bool Prefix(ref bool __result, string tagId)
        {
            if(tagId == "ValdyrTag")
            {
                __result = true;

                return false;
            }


            if (_tagIdList == null)
            {
                _tagIdList = new List<string>();
                foreach (FieldInfo fieldInfo in typeof(ConversationTagList).GetFields())
                {
                    if (fieldInfo.IsLiteral && !fieldInfo.IsInitOnly && fieldInfo.FieldType == typeof(string))
                    {
                        _tagIdList.Add((string)fieldInfo.GetValue(null));
                    }
                }
            }

            __result = _tagIdList.Contains(tagId);

            return false;
        }
    }
}

I also added a FirstMeeting line using ValdyrTag to voice_strings.xml:

XML:
<string id="str_context_line.first_meeting_valdyr" text="{=*}Valdyr Test">
    <tags>
        <tag tag_name="FirstMeetingTag" />
        <tag tag_name="ValdyrTag" weight="+5" />
    </tags>
</string>

However, when meeting the character for the fist time in-game, one of the Vanilla strings is still displayed. I've gone as far as to use Harmony patches to patch out the character's name from being eligible for any of the other tags he was being assigned down to the point where it would display the DefaultTag first meeting message which is not supposed to appear unless the character has nothing else eligible:

XML:
<string id="str_context_line.first_meeting" text="{=!}Yes? Do I know you? (Default - should not appear)">
    <tags>
        <tag tag_name="DefaultTag" />
        <tag tag_name="FirstMeetingTag" />
    </tags>
</string>

Things I've determined:
  • The string id isn't making the message ineligible to be displayed, I replaced the default string with the name "str_context_line.first_meeting_valdyr" and the game still chooses that for DefaultTag occurrences.
  • I tried ValdyrTag with and without weight="+5", which from what I've seen in other lines in the file is used to give certain tags more weight to prioritize a certain string over another when a certain character has multiple matching strings.
  • I attempted to ad DefaultTag to the string I added, and placed it above the original DefaultTag/FirstMeetingTag message, and it was still not selected.
From what I can tell, I just don't think my tag is actually being applied to my character - and I've tried various logic for determining if a character is eligible for the tag in the ValdyrTag class. Even going as far as making the IsApplicableTo(CharacterObject character) method simply return true all of the time so every character should get the tag.

Am I missing something here? Am I going about this the wrong way, or is there an easier way to do this? Any help would be greatly appreciated.
 
Hello,

I'm not sure if it could be the cause of your problem but I just noticed this strange piece of code in TaleWorlds.Core.GameTextManager :

C#:
private void LoadFromXML(XmlDocument doc)
{
    ...
    XmlAttributeCollection attributes = childNodes[i].Attributes;
    if (attributes != null)
    {
        int weight = 1;
        XmlAttribute xmlAttribute = attributes["weight"];
        GameTextManager.ChoiceTag item = new GameTextManager.ChoiceTag(attributes["tag_name"].Value, weight);
        list.Add(item);
    }
    ...
}

I started C# last week so I might miss something there but...
The XmlAttribute "weight" doesn't seem used at all. Instead, all tags get a default weight of 1 regardless of what was written in the XML...

If someone more experienced could confirm this, it might have a pretty big impact in choosing which lines are selected in conversations. :/

Edit : I detailed the problem in this bug report : https://forums.taleworlds.com/index...is-not-using-weight-of-tags-correctly.423685/
 
Last edited:
Upvote 0
Back
Top Bottom