how can i make the dialog window pop up after clicking on a 3d character?

Discuss modding questions and implementation details.
Post Reply
Ax31
Posts: 8
Joined: Sat Jul 11, 2020 4:48 am

how can i make the dialog window pop up after clicking on a 3d character?

Post by Ax31 »

Hi, i've been kicking my head for the last couple of days with unity, i'm facing a number of issues(like climbing on top of noc with collisions xD) and foreseeing even more, but for now i'd like to try my hand at doing just what the title says. As of now the only way i can talk to them is by clicking beetween their legs, which takes some guessing, i found the script Daggerfall TalkWindow but i have no idea how to bring it up after clicking on a mesh, can anyone shed some light on the subject?

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by l3lessed »

So, from a quick look at the base code, this is how it works. It uses a 3 step process to first grab the npc you clicked using a raycast detector, figure out what mode to activate, and then executes said mode (here it is talking/dialogue).

Step 1: You click, it raycast to the click, pulls the npc object it hit, and assigns it to an npc object.

Code: Select all

 // Fire ray into scene
if (InputManager.Instance.ActionComplete(InputManager.Actions.ActivateCenterObject))
{
...... Bunch of missing activator checks I removed to save on post space.
            
	// Check for static NPC hit
	StaticNPC npc;
	if (NPCCheck(hit, out npc))
	{
            	ActivateStaticNPC(hit, npc);
	}          
}
Now it knows what npc you clicked by grabbing it from the raycast hitinfo connected to your mouse click.

Step 2: Figure out what activate action to take based on the players click mode:

Code: Select all

void ActivateStaticNPC(RaycastHit hit, StaticNPC npc)
        {
            // Do not activate static NPCs carrying specific non-dialog actions as these usually have some bespoke task to perform
            // Note: currently only ShowText and ShowTextWithInput NPCs are excluded
            // Examples are guard at entrance of Daggerfall Castle and Benefactor and Sheogorath in Mantellan Crux
            DaggerfallAction action = npc.GetComponent<DaggerfallAction>();
            if (action &&
                (action.ActionFlag == DFBlock.RdbActionFlags.ShowTextWithInput ||
                 action.ActionFlag == DFBlock.RdbActionFlags.ShowText))
                return;

            switch (currentMode)
            {
                case PlayerActivateModes.Info:
                    PresentNPCInfo(npc);
                    break;
                case PlayerActivateModes.Grab:
                case PlayerActivateModes.Talk:
                case PlayerActivateModes.Steal:
                    if (hit.distance > StaticNPCActivationDistance)
                    {
                        DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway);
                        break;
                    }
                    StaticNPCClick(npc);
                    break;
            }
        }
Now we know which npc was clicked and that Dialogue mode was active, we can finish with step 3.

Step 3: Use the above npc object to pull all the required npc information and assign it to TalkManager script object class. The TalkManager object class, once it has all the npc information assigned to it, will handle the rest of the dialogue for you. As you can see below though, you have to pull and assign the proper npc information to the TalkManager object for the proper dialogue to show.

Code: Select all

// Player has clicked on a static NPC
        void StaticNPCClick(StaticNPC npc)
        {
            // Do nothing if no NPC passed or fade in progress
            // Quest machine does not tick while fading (to prevent things happening while screen is black)
            // But this can result in player clicking a quest NPC before quest state ticks after load and breaking quest
            if (!npc || DaggerfallUI.Instance.FadeBehaviour.FadeInProgress)
                return;

            // Store the NPC just clicked in quest engine
            QuestMachine.Instance.LastNPCClicked = npc;

            // Handle quest NPC click and exit if linked to a Person resource
            QuestResourceBehaviour questResourceBehaviour = npc.gameObject.GetComponent<QuestResourceBehaviour>();
            if (questResourceBehaviour && TriggerQuestResourceBehaviourClick(questResourceBehaviour))
            {
                return;
            }

            // Do nothing further if a quest is actively listening on this individual NPC
            // This NPC not reserved as a Person resource but has a WhenNpcIsAvailable action listening on it
            // This effectively shuts down several named NPCs during main quest, but not trivial to otherwise determine appropriate access
            // TODO: Try to find a good solution for releasing listeners when the owning action is disabled
            if (QuestMachine.Instance.HasFactionListener(npc.Data.factionID))
                return;

            // Get faction data.
            FactionFile.FactionData factionData;
            TalkManager talkManager = GameManager.Instance.TalkManager;
            if (GameManager.Instance.PlayerEntity.FactionData.GetFactionData(npc.Data.factionID, out factionData))
            {
                UserInterfaceManager uiManager = DaggerfallUI.Instance.UserInterfaceManager;
                FactionFile.FactionData buildingFactionData = new FactionFile.FactionData();
                if (!playerEnterExit.IsPlayerInsideBuilding ||
                    !GameManager.Instance.PlayerEntity.FactionData.GetFactionData(playerEnterExit.BuildingDiscoveryData.factionID, out buildingFactionData))
                    buildingFactionData.ggroup = (int)FactionFile.GuildGroups.None;

                Debug.LogFormat("faction id: {0}, social group: {1}, guild: {2}, building faction: {3}, building guild: {4}", npc.Data.factionID,
                    (FactionFile.SocialGroups)factionData.sgroup, (FactionFile.GuildGroups)factionData.ggroup, buildingFactionData.id, (FactionFile.GuildGroups)buildingFactionData.ggroup);

                // Check if the NPC offers a guild service.
                if (Services.HasGuildService(npc.Data.factionID))
                {
                    FactionFile.GuildGroups guildGroup = (FactionFile.GuildGroups)buildingFactionData.ggroup;
                    if (guildGroup == FactionFile.GuildGroups.None)
                    {   // Use NPC guild group if building has none (e.g. Temple buildings of divine faction)
                        guildGroup = (FactionFile.GuildGroups)factionData.ggroup;
                        // Don't popup guild service menu when holy order NPC isn't in a divine faction building. (bug t=1238)
                        // Don't popup guild service menu when TG spymaster NPC isn't in a faction building. (bug t=2037)
                        if (guildGroup == FactionFile.GuildGroups.HolyOrder && !Temple.IsDivine(buildingFactionData.id) ||
                            factionData.id == (int)GuildNpcServices.TG_Spymaster && buildingFactionData.id == 0)
                        {
                            talkManager.TalkToStaticNPC(npc, false, factionData.id == (int)GuildNpcServices.TG_Spymaster);
                            return;
                        }
                    }
                    // Popup guild service menu.
                    uiManager.PushWindow(UIWindowFactory.GetInstanceWithArgs(UIWindowType.GuildServicePopup, new object[] { uiManager, npc, guildGroup, playerEnterExit.BuildingDiscoveryData.factionID }));
                }
                // Check if this NPC is a merchant.
                else if ((FactionFile.SocialGroups)factionData.sgroup == FactionFile.SocialGroups.Merchants)
                {
                    // Custom merchant service registered?
                    if (Services.HasCustomMerchantService(npc.Data.factionID))
                        uiManager.PushWindow(UIWindowFactory.GetInstanceWithArgs(UIWindowType.MerchantServicePopup, new object[] { uiManager, npc, DaggerfallMerchantServicePopupWindow.Services.Sell }));
                    // Shop?
                    else if (RMBLayout.IsShop(playerEnterExit.BuildingDiscoveryData.buildingType))
                    {
                        if (RMBLayout.IsRepairShop(playerEnterExit.BuildingDiscoveryData.buildingType))
                            uiManager.PushWindow(UIWindowFactory.GetInstanceWithArgs(UIWindowType.MerchantRepairPopup, new object[] { uiManager, npc }));
                        else
                            uiManager.PushWindow(UIWindowFactory.GetInstanceWithArgs(UIWindowType.MerchantServicePopup, new object[] { uiManager, npc, DaggerfallMerchantServicePopupWindow.Services.Sell }));
                    }
                    // Bank?
                    else if (playerEnterExit.BuildingDiscoveryData.buildingType == DFLocation.BuildingTypes.Bank)
                        uiManager.PushWindow(UIWindowFactory.GetInstanceWithArgs(UIWindowType.MerchantServicePopup, new object[] { uiManager, npc, DaggerfallMerchantServicePopupWindow.Services.Banking }));
                    // Tavern?
                    else if (playerEnterExit.BuildingDiscoveryData.buildingType == DFLocation.BuildingTypes.Tavern)
                        uiManager.PushWindow(UIWindowFactory.GetInstanceWithArgs(UIWindowType.Tavern, new object[] { uiManager, npc }));
                    else
                        talkManager.TalkToStaticNPC(npc, false);
                }
                // Check if this NPC is part of a witches coven.
                else if ((FactionFile.FactionTypes)factionData.type == FactionFile.FactionTypes.WitchesCoven)
                {
                    uiManager.PushWindow(UIWindowFactory.GetInstanceWithArgs(UIWindowType.WitchesCovenPopup, new object[] { uiManager, npc }));
                }
                // TODO - more checks for npc social types?
                else // if no special handling had to be done for npc with social group of type merchant: talk to the static npc
                {
                    talkManager.TalkToStaticNPC(npc, false);
                }
            }
            else // if no special handling had to be done (all remaining npcs of the remaining social groups not handled explicitely above): default is talk to the static npc
            {
                talkManager.TalkToStaticNPC(npc, false);
            }
        }
                    
However, the very first thing I am wondering is does the 3d models have some form of a hit mesh setup and connected to the engine properly? The engine has to be able to detect the raycast hit to pull the proper npc information and use it with the TalkManager script class. I'm not a 3d modeler, so past knowing it needs to detect it in the code, I can't help you anymore.

There is a number of things that can go wrong here. Start with the hit detection to ensure the engine is even picking up that you're clicking an npc. If it is, then check how it is assigning the npc object and ensure it is using the TalkManager object class script correctly. The code above is how it is currently done.

Hope this helps.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

Ax31
Posts: 8
Joined: Sat Jul 11, 2020 4:48 am

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by Ax31 »

wow, this seems very complete, thank you!

the 3d npcs' share the name with the textures the system uses to instance them and have the generate colliders box ticked, if you run toward them, you can climb them, so i'm assuming it should be able to distinguish them, then again, if that was the case it would already work off the bat, right? what trows me off is that i can bring the dialog up by clicking beetween their legs, my idea was finding the code you posted and maybe do some adjustments, i'm going to take a better look at your post and see what's up.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by l3lessed »

Okay, if it appears by clicking between their legs, that means there is a hit mesh detection on the 3d model, it just isn't setup properly. For some reason, it is only between their legs. There is a few ways to resolve this. The easiest is would be to just take the current hit box system and use it. The current system just creates a giant pill shaped hitbox around the entity, be it player or enemy.

It looks like, for whatever reason, the hitbox pill is not sizing properly with the model, which forces you to click between their legs.

I'm trying to track down in the code where the actual hitbox is setup for the entities to show you it, but I can't find it.

What I would do is load the model/entity in the engine builder, while it is running, turn on gizmo's so you can see the hit boxes, and see if I'm correct; there should be a hit detection box showing where you're clicking and getting dialogue to appear. If so, you just need to find a way to resize the hitbox for the entity, so it scales properly with the model. I imagine this was setup manually before, since they were using 2d sprites. Since we transitioned to 3d models, it probably is confusing the base engine, which was never built for 3d model scaling for npcs/player. The script may need reworked to resize hitboxes for models? I don't know, but I'll keep digging.

The proper way would be to connect the engine raytrace/hit detection to the models physics hit detection mesh, but again, outside the coding side of it, I'm not 100% sure how this is done or if it can be or even is worth the time.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by l3lessed »

Okay, I found it for you. I can technically fix the script, if I had the model and everything ready to go.

What is happening is what I thought. The original engine is setup to pull the enemy 2d billboard and grab a predefined size measurement from it. From there, it uses it to setup the 2d billboards 3d mesh renderer/detection.

Since we are now using 3d models, it cannot pull this value, so it defaults the hitbox to whatever the default value is, thus making it small and between their legs.

Honestly, this seems like it would be an easy fix to get the old school pill box meshes to show and work again. I would need to only pull the 3d model size and then mess with it until I found a proper formula to resize the mesh renderer to the 3d model size. This would recreate the pill box mesh of the classic around the 3d model.

The long term best solution is the rebuild the mesh renderer code and use the proper 3d meshes build with the model so hit detection is as accurate to the model as possible. This I'm not nearly as familiar with and would have to research more.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by l3lessed »

Here is where it pulls the npc entity properties from the original source and sets up the entity in the world based on this, including its height (I'm almost positive the height value is what also controls the hitbox size, thus why it is at their feet.

Code: Select all

 /// <summary>
        /// Sets up enemy based on current settings.
        /// </summary>
        public void ApplyEnemySettings(MobileGender gender)
        {
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;
            Dictionary<int, MobileEnemy> enemyDict = GameObjectHelper.EnemyDict;
            MobileEnemy mobileEnemy = enemyDict[(int)EnemyType];
            if (AlliedToPlayer)
                mobileEnemy.Team = MobileTeams.PlayerAlly;

            // Find mobile unit in children
            DaggerfallMobileUnit dfMobile = GetMobileBillboardChild();
            if (dfMobile != null)
            {
                // Setup mobile billboard
                Vector2 size = Vector2.one;
                mobileEnemy.Gender = gender;
                dfMobile.SetEnemy(dfUnity, mobileEnemy, EnemyReaction, ClassicSpawnDistanceType);

                // Setup controller
                CharacterController controller = GetComponent<CharacterController>();
                if (controller)
                {
                    // Set base height from sprite
                    size = dfMobile.Summary.RecordSizes[0];
                    controller.height = size.y;

                    // Reduce height of flying creatures as their wing animation makes them taller than desired
                    // This helps them get through doors while aiming for player eye height
                    if (dfMobile.Summary.Enemy.Behaviour == MobileBehaviour.Flying)
                        // (in frame 0 wings are in high position, assume body is  the lower half)
                        AdjustControllerHeight(controller, controller.height / 2, ControllerJustification.BOTTOM);

                    // Limit minimum controller height
                    // Stops very short characters like rats from being walked upon
                    if (controller.height < 1.6f)
                        AdjustControllerHeight(controller, 1.6f, ControllerJustification.BOTTOM);

                    controller.gameObject.layer = LayerMask.NameToLayer("Enemies");
                }

                // Setup sounds
                EnemySounds enemySounds = GetComponent<Game.EnemySounds>();
                if (enemySounds)
                {
                    enemySounds.MoveSound = (SoundClips)dfMobile.Summary.Enemy.MoveSound;
                    enemySounds.BarkSound = (SoundClips)dfMobile.Summary.Enemy.BarkSound;
                    enemySounds.AttackSound = (SoundClips)dfMobile.Summary.Enemy.AttackSound;
                }
Here is where it assigns the physics hit detection to the entity and creates the pillbox hit box:

Code: Select all

                    controller.gameObject.layer = LayerMask.NameToLayer("Enemies");
This is where I think the issue is. I think the layer is being applied, but now that it is applying it to a 3D Model, it uses different sizes/measurements for the layer (That is, the 3D Models require a larger base value in the code because of how it renders the 3D model).

So, the issue is it grabs the original height value and sets up the entity based on the 2d texture measurements and render system of the base original engine; but, then in comes the 3D model, with completely differing render method in the newer unity render engine, and now the layer/hit box gets resized based on the 3D model and new render engine system, and then we get the smaller hit box.

I think this because the odd thing is the size is a set record, so no matter what, it can pull a height value. It doesn't actually need the billboard texture to get it. Thus, why you still get a hit box, but a very small one.

Code: Select all

                    // Set base height from sprite
                    size = dfMobile.Summary.RecordSizes[0];
                    controller.height = size.y;
Either way, this is where the issue is most likely cropping up script/code wise.

Easiest way to test this would be to go into the base script file for this code and force override the size value and see what this does to the hitbox.

This may create some hit detection bugs for the controller.height object for certain enemy entities. Not sure, would have to try it and see.
Last edited by l3lessed on Mon Jul 20, 2020 6:06 pm, edited 1 time in total.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

Ax31
Posts: 8
Joined: Sat Jul 11, 2020 4:48 am

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by Ax31 »

the green square box, which i'm assuming is this object you mention doesn't show up, weirdly enough merging the prefab into one object makes it appear, i'm replying now to maybe stop you from continuing your search as it may be for nothing.

so far this worked for one npc, i haven't tried the other one i made, i was having issues getting the game to work after importing both models, and what seemed to work was splitting the first one into pieces, the second one worked after who knows what, on my las attempt before giving up. the biggest problem is the .fbx extension, autodesk keeps changing it to occlude the actual workings of it so other products have less of a fighting chance, which makes the exporting process a nightmare, it took me three times longer to actually import them properly as it took to make them, i guess i'll have to give obj a shot, but then there's no armatures export for animation, which bring even more trouble.
at this rate, making over a hundred statics seems like a waste of time, and that's only the statics, let alone everything else.

l3lessed
Posts: 1403
Joined: Mon Aug 12, 2019 4:32 pm
Contact:

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by l3lessed »

Thanks.

Like I said, not a modeler. Glad you found it was an issue with how they were being setup and not the code/scripts itself so far.
My Daggerfall Mod Github: l3lessed DFU Mod Github

My Beth Mods: l3lessed Nexus Page

Daggerfall Unity mods: Combat Overhaul Mod

Enjoy the free work I'm doing? Consider lending your support.

Ax31
Posts: 8
Joined: Sat Jul 11, 2020 4:48 am

Re: how can i make the dialog window pop up after clicking on a 3d character?

Post by Ax31 »

thank YOU! your posts will still help clarify a lot of thing i'll need to know to proceed further down the line.

Post Reply