IQuestStateListener

Announcements, support questions, and discussion for the Dialogue System.
Ultroman
Posts: 39
Joined: Thu Sep 17, 2020 7:47 am

IQuestStateListener

Post by Ultroman »

The relationship between QuestStateDispatcher and QuestStateListener, which is just the OnChange function, screams interface. It would decouple the complex and versatile dispatcher completely from the QuestStateListener. I say this because I would like to use the QuestStateDispatcher with a custom QuestStateListener script, which will function quite differently from yours so overriding isn't enough. All I really need is for the QuestStateDispatcher to trust me that I'll give it an OnChange function to work with :)

Making it an interface looks to be a non-destructive change, compared to e.g. changing it so the QuestStateDispatcher instead has an event which the QuestStateListener (or whatever custom script) can just hook up to, which would also decouple it, instead of the current listener-registration concept.
User avatar
Tony Li
Posts: 21959
Joined: Thu Jul 18, 2013 1:27 pm

Re: IQuestStateListener

Post by Tony Li »

Hi,

I'm working on version 2.2.12 right now, but a bigger 2.3 is just down the road. I'll add IQuestStateListener to it.
Ultroman
Posts: 39
Joined: Thu Sep 17, 2020 7:47 am

Re: IQuestStateListener

Post by Ultroman »

Thanks a lot!
Any kind of ballpark ETA? Just so I know if I need to implement my own temporary solution for now ;)
User avatar
Tony Li
Posts: 21959
Joined: Thu Jul 18, 2013 1:27 pm

Re: IQuestStateListener

Post by Tony Li »

Version 2.2.12 is being released on Monday.

Version 2.3's target release date is October 26.

If you implement something in the meantime and don't mind sharing it, please do so. I may incorporate it as-is or make the interfaces and signatures the same even if the code ends up different under the hood.
Ultroman
Posts: 39
Joined: Thu Sep 17, 2020 7:47 am

Re: IQuestStateListener

Post by Ultroman »

Will do!
Ultroman
Posts: 39
Joined: Thu Sep 17, 2020 7:47 am

Re: IQuestStateListener

Post by Ultroman »

Well, this is as simple as it gets. I don't really care where you put the interface since my script should easily be changed to instead reference yours after you update. It could be handled better, though. Optimally, the responsibility for determining whether the change is relevant to the listener should lie with the listener, since the listener is the one with the information about which quest it is listening to. Perhaps OnChange() should take the questName variable as parameter, but that's costly. I do see that a bunch of function calls and passing of parameters are saved with your method, and my new GetQuestName() is just yet another function call. I don't see a better way to do it, while retaining the use of your [QuestPopup(true)] tag and not using an abstract class (which would be more than a little restrictive).

One could make QuestName a property, but that's just hidden function calls.

Changes to QuestStateDispatcher:
2020-10-08 06_39_51-Window.png
2020-10-08 06_39_51-Window.png (43.54 KiB) Viewed 393 times
Changes to QuestStateListener:
2020-10-08 06_39_29-Window.png
2020-10-08 06_39_29-Window.png (21.27 KiB) Viewed 393 times
2020-10-08 06_39_06-Window.png
2020-10-08 06_39_06-Window.png (10.38 KiB) Viewed 393 times
Attachments
Quests.zip
(2.31 KiB) Downloaded 26 times
Ultroman
Posts: 39
Joined: Thu Sep 17, 2020 7:47 am

Re: IQuestStateListener

Post by Ultroman »

I've run into a wall here. I would like my new QuestStateListener to instead listen for changes to quest entries, but when I follow the call stack, it ends in QuestLog with this line:

Code: Select all

Lua.RegisterFunction("UpdateQuestIndicators", null, typeof(QuestLog).GetMethod("UpdateQuestIndicators", new[] { typeof(string) }));
...which indicates to me that it's something I have to call manually on my conversation nodes in order to update the Quest Indicators, BUT you don't have that in your demo scenes, so where is UpdateQuestIndicators() called? I'm asking, because I think it is only called when the quest state is changed, which makes sense, but I want to trigger my script for each change in quest entry states. So I guess I have to build my own QuestEntryStateDispatcher, as well, but I can't figure out how you trigger yours :)

I think I found the other end of the "black box" here:

Code: Select all

public static void InformQuestStateChange(string questName)
{
	DialogueManager.instance.BroadcastMessage(DialogueSystemMessages.OnQuestStateChange, questName, SendMessageOptions.DontRequireReceiver);
}
...and I think I want to instead have my script be triggered by this one:

Code: Select all

public static void InformQuestEntryStateChange(string questName, int entryNumber)
{
	DialogueManager.instance.BroadcastMessage(DialogueSystemMessages.OnQuestEntryStateChange, new QuestEntryArgs(questName, entryNumber), SendMessageOptions.DontRequireReceiver);
}
Ultroman
Posts: 39
Joined: Thu Sep 17, 2020 7:47 am

Re: IQuestStateListener

Post by Ultroman »

You know what? This is a bit dumb. It's all because I thought a purely C# approach seemed less of a hassle and less rigid, than setting up a bunch of C# functions to check for things like "x of some item in the player's inventory", which there will be a lot of, and then register them as Lua-functions in order to use them in conditions for both conversation-nodes AND QuestStateListeners. But I guess this is the route the asset intends, right? That I have some QuestManager script which has a reference to all the things involved in quests and has functions for checking my custom conditions on these things, and has the responsibility of setting Quest Entry states and Quest states to "success" when the conditions are met.

It's probably easier if I just explain what I want:
World of Warcraft, basically.
- Exclamation Point when the quest is "unassigned" and its initial conditions are met.
- Question Mark when the quest is "success", or alternatively when the quest is "active" and all or certain quest entries have a state of "success". The reason why I say "certain", is because I have "Talk to <questgiver>" quest entries on all my quests, which do not have to be in state "success" to be handed in (I was intending to use a Custom Field to denote this on the quests that have such a dummy-entry and handle it in a custom QuestStateListener). I could just set their state to "success" to begin with, but I don't want them to look like they're done. I guess I can handle that in the UI somehow, but I'd rather do it here.

The first problem is with the Exclamation Point. I don't want the Exclamation Point shown for an "unassigned" quest if its conditions aren't met. I have conditions set on the init-node of the conversation for each quest, so I basically have to duplicate my conditions onto a QuestStateListener for each quest on each questgiver, in order to "sync up" the Quest State Indicators. It seems like we're missing the state "ineligible", in order for "unassigned" to be a sure-fire check for the Exclamation Point.

The second problem, In the same vein, it sounds like "success" should indicate that the quest has been completed but not handed in yet, and then "done" should mean that the quest has been handed in. Then the check for whether to show the Question Mark becomes as easy as state == "success".

This raises complexity a bit, though, as switching from "ineligible" to "unassigned" and from "active" to "success" would require more "listening" and/or custom code. But having QuestStateListeners for most quests is already going to get pretty taxing very quickly with all those listeners being needlessly checked after they're done and before they're even eligible.

You know what would be cool? If I could link a condition to a specific conversation node and use its condition. Then I only have to define both sets of conditions once. Then my QuestStateListener script simply becomes:
- QuestName (string)
- Eligibility Condition (normal condition fold-out, but I can also pick a Conversation Node and say whether the condition being true or false denotes eligibility)
- Hand In Condition (normal condition fold-out, but I can also pick a Conversation Node and say whether the condition being true or false means it can be handed in)

This would also mean I wouldn't have to do anything special to ignore my dummy-quest-entries, since the conversation node can handle that, and if I get my **** together and register some Lua functions for my quest-checks, they can handle those, too.
Last edited by Ultroman on Thu Oct 08, 2020 1:51 pm, edited 1 time in total.
User avatar
Tony Li
Posts: 21959
Joined: Thu Jul 18, 2013 1:27 pm

Re: IQuestStateListener

Post by Tony Li »

Hi,

Quests can also be in the "grantable" state. (In the API: PixelCrushers.DialogueSystem.QuestState.Grantable)

You can use "unassigned" to mean ineligible and "grantable" to mean eligible (i.e., NPC can grant quest to player).

If you want to consolidate the eligibility condition so the NPC's conversation and the quest indicator can both check the condition from the same source, add a custom field to your quest template. Here's an example quest in which I've added a field named Eligibility:

questEligibilityField.png
questEligibilityField.png (19.3 KiB) Viewed 365 times

I put Lua code in the field. (In this example, I used a Lua function from the Opsive Ultimate Inventory System integration.)

You could write a C# function that checks the value of this field:

Code: Select all

public bool IsQuestEligible(string quest)
{
    return Lua.IsTrue(DialogueLua.GetQuestField(quest, "Eligibility").asString);
    // ^ Maybe also check if the quest state is grantable?
}
If you register that function with Lua, you can use it in conversations, too. For example, you could set a dialogue entry's Conditions field to:
  • Conditions: IsQuestEligible("Return Ring")
Ultroman
Posts: 39
Joined: Thu Sep 17, 2020 7:47 am

Re: IQuestStateListener

Post by Ultroman »

Thanks! That's brilliant! And I can also use this method to create a generic "check if all quest entries are success" function to use as a hand-in condition.

Two problems left, then.

I have a bunch of delivery quests, so I'll have to change the state of the quest entries between being "active" and "success" depending on whether the player currently has the required items in their inventory. I can set up my own scripts to update those, BUT then I also need to update the Quest Indicators by calling QuestLog.UpdateQuestIndicators("questName") every time I change the state of an entry, right? Since the hand-in condition checks the states of its entries. This is the way?

Second problem. This is all fine until you have more than one quest "active" or "grantable" with the NPC. Which one takes priority for the Quest Indicator when there are several QuestStateListener components on the same NPC? Optimally, the Question Mark would have priority, but it looks to me like it's just whichever quest last had its state updated. I think one would have to iterate over all available quests and do checks, in order to really determine which indicator should be shown.
Post Reply