Replies: 1 comment 1 reply
-
|
For dialogue, what if we had a tool kind of like the map editor that visualizes the json data and makes it a bit more intuitive. Dialogue is tough, because at the end of the day it is a ton of strings and branching paths and there's no way around it, and by nature it's messy. I think it just comes down to having an intuitive front end to create all the data. Honestly, my mind is just now thinking of a system where it looks a lot like the dialogue window we have in game, just with editor options so it feels natural, with it kind of giving you the feedback right there: here's exactly what it looks like in game. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Preamble
First of all, I just want to express gratitude that any of this is even possible. The modding potential of bright nights is evidently growing exponentially with the lua support.
For context, this is all coming from a week long modding adventure. It's not online, and not sure if/when it will be. It's adult themed, so I'm not gonna be posting much details about that here for obvious reasons.
I can read the c++ code kinda, but I code in C# most of the time. Honestly I have very little experience in lua (clearly) but it's meant to be simple. C++ is another beast entirely.
I might attempt to set myself up do builds and commits, but I've already shunted my life a bit much with this project as is.
I hope this document finds itself valuable.
NPCs
This is feedback I think will be the most valuable. While the cursed animation work I've done is clearly niche and frankly meme-worthy, it's clear that people are already interested in/working on NPC support for LUA.
Despite their potential, npcs just lack a lot of support.
Here's a list of functionality I found myself lacking and recreating:
Spawn NPC at Given Location
This will likely require adding a c++ binding for the npc class type.
At the moment, my project uses a mission to spawn them, which adds many complications.
An honorable mention to my project's predecessor, which used an activate function of an item to spawn npcs. Really awkward, but I bet that person actually touches grass occasionally.
A dishonorable mention to traps, which can be triggered to do a similar action to missions (updating the map generation dynamically). This worked, but just placing the trap under the npc didn't.
I had to spawn a monster, force it to move on the trap, then instakill it. No death method was invisible, silent, and left no messages. Suddenly the mission spawn seems sane. It did work though, but I scrapped it.
It was funny having npcs just explode out of gore. Even the vanish death effect prints a message for the player to see.
The disappears.Could have teleported it underground, but still problematic for many reasons.
Only upside was the code side cleanup. Missions are awkward to deal with. I haven't yet done testing, but you can clear missions through speaker effects, so I'd hope that prevents missions clogging your missions up.
Despawn NPC
At the moment, all you can do is force kill them. It'd be nice if we could just bucket them somewhere and grab them via id.
Obviously a permanent despawn would be easier to implement, but just shelving them would be great.
Get NPC from ID
I know better than to assume something would be simple, but honestly, shouldn't it be? There's gotta be a globally accessible array of NPCs somewhere.
On NPC Spawn Hook
No matter what method, when an npc spawns, I want a hook for it. Honestly, give us methods for monsters and creatures as well.
on_creature_spawn_hook is honestly good enough. Better than iterating over every entity in a radius every turn.
On NPC every turn hook
The ultimate option. same with the spawn hook, on_creature_turn_hook would be just fine and give more options.
Dialogue
Honestly? The current system is just terrible feeling to work with. Largely this is due to the ways it deals with conditionals and nesting.
Nesting checks and having a "yes" and "no" output after is awful. u_has_var has FOUR MANUAL PARAMETERS, only 2 of which really matter as far as I can tell.
Why can't I just set a variable by name with value? Why can't I check if u_has_var without specifying the value I expect to find?
What if I want to set a variable for an npc nickname, but I need to check if we have one? I need another variable to verify that, since I don't know what the nickname might be set to.
Why do conditionals incite violence within the common masses?
I'd love to see some massive improvements, but honestly I see why that's unlikely to happen. I considered suggesting a completely new implementation, but nothing good and simple is written in c++.
Twine is html, and yarn spinner is c#. Ink is c# too, but I don't know much about it. Looking at them could be good inspiration, though.
Making dialogue comfortable to make is super important. There seems to be so much of it, yet so little.
Also, lua really doesn't have many interactions with dialogue. The on_effect_added and related hooks are your best bet, but I'm already on 47 effects for largely dialogue purposes.
There are some piecemeal ideas I have for the current system, though.
TRIGGER_LUA_HOOK for Dialogue
Triggering a lua hook from speaker effects and effects from responses would be a massive help, honestly. Bonus points if we can pass arbitrary data through the effect like:
Then, in your lua function, params.val would return "shake stick at god {10}". Obviously you could do whatever you wanted with that. Really helps with cross-talk between lua and dialogue. I know you can just use an on_character_effect_added hook, but it's cumbersome.
EDIT:
I have no idea how viable this would be, but it would be excellent if we could do a dialogue function call.
{ "type": "talk_topic", "id": "TALK_FRIEND", "dynamic_line": [ { "my_custom_lua_callback":true, "yes": "text a", "no": "text b", } ], "responses": [ ] }, { "type": "lua_callback", "id": "my_custom_lua_callback", "return_type": "bool" }Not sure if return_type would be feasible, but even if it only does true/false checks, it'd be great.
Dialogue Helper Types
This may take some explaining, but the basic idea is this:
We have a type of "talk_topic" that the json parser uses to grab the dialogue info, yeah?
Well in my opinion, the biggest issue with the dialogue system is how nested everything has to be. Even the source code shows this, just look at this snippet from TALK_COMMON_ALLY at the time of writing:
{ "id": "TALK_COMBAT_ENGAGEMENT", "type": "talk_topic", "dynamic_line": { "and": [ { "npc_engagement_rule": "ENGAGE_NONE", "no": { "npc_engagement_rule": "ENGAGE_CLOSE", "no": { "npc_engagement_rule": "ENGAGE_WEAK", "no": { "npc_engagement_rule": "ENGAGE_HIT", "no": { "npc_engagement_rule": "ENGAGE_FREE_FIRE", "no": { "npc_engagement_rule": "ENGAGE_NO_MOVE", "no": "*will engage all enemies.", "yes": "*will engage enemies close enough to attack without moving." }, "yes": "*will engage distant enemies without moving." }, "yes": "*will engage enemies you attack." }, "yes": "*will engage weak enemies." }, "yes": "*will engage nearby enemies." }, "yes": "*will not engage enemies." }, " What should <mypronoun> do?" ] },All for a glorified switch statement. It's not even an especially egregious example.
The solution? Add a few types that we can build up.
{ "id": "u_different_gender_than_npc", "type": "conditional_snippet", "condition":{ "or": [ { "and": [ "u_female", "npc_male" ] }, { "and": [ "u_male", "npc_female" ] } ] }, }, { "id": "u_same_gender_as_npc", "type": "conditional_snippet", "condition":{ "not": { "conditional_snippet":"u_different_gender_than_npc" } }, },Not 100% that second one would be valid json, but you get the idea.
This would be reusable. This would save on code reuse, it would clean up nesting, it'd be great. Only downsides I see is performance and it might be dependent on the order of json loading.
But the vast majority of these would only be inherited in the same file.
This snippet from npcs.md here shows something I'd like conditional_snippet to do:
Because "npc_female" and "npc_male" just straight up return true or false, they can be used here in a way that bypasses doing the "yes" and "no" lines.
That looks so much cleaner than what it otherwise would be. The definition for "u_different_gender_than_npc" is already somewhat awkward to parse as is, at least for me.
Another example:
{ "id": "generic_gendered_messages", "type": "dialogue_snippet", "text": { "u_male_npc_male": ["text a","text b"], "u_male_npc_female": ["text c","text d"], "u_female_npc_male": ["text e","text f"], "u_female_npc_female": ["text g","text h"], } }, { "dynamic_line": [ "generic text", "generic_gendered_messages", ] },Again, the biggest thing is that we're not doing so much nesting. Is this less performant? Probably. But dialogue performance is unlikely to be the biggest pain point.
I've seen that there's someone working on implementing jsonc formatting, which would also help a little.
Initiate Dialogue via Lua
This function should be able to specify the talk_topic we use. All you gotta do is pass in a string, right? Worse case, a new talk_topic binding has to be made.
It would still be useful without being able to specify the talk_topic. I know you can talk at range in the game already, and it isn't required that the player initiate. TALK_MUG is a thing.
Set NPC starting dialogue
Optional bool to make it permanent?
You can already change the class of the npc through dialogue, though it's barely documented, and I don't quite know how it effects things. But an NPC's class can overwrite it's starting dialogue. Still awkward.
Toggle Dialogue Window Visibility
Not the biggest deal, but custom ui during dialogue would be nicer if the dialogue window got hidden, but you could be kept in dialogue,
Alternatively, if you can just initiate dialogue through lua again, this wouldn't be quite as useful.
on_before_dialogue_hook and on_dialogue_ended hook
The on_before_dialogue_hook would be especially useful if we had the ability to set the npc starting dialogue.
EDIT: I should clarify that you can simply use a speaker effect on a new talk topic like so:
{ "type": "talk_topic", "id": [ "TALK_RADIO", "TALK_LEADER", "TALK_FRIEND", "TALK_STOLE_ITEM", "TALK_MISSION_DESCRIBE_URGENT", "TALK_SEDATED", "TALK_WAKE_UP", "TALK_MUG", "TALK_STRANGER_AGGRESSIVE", "TALK_STRANGER_SCARED", "TALK_STRANGER_WARY", "TALK_STRANGER_FRIENDLY", "TALK_STRANGER_NEUTRAL", "TALK_SHELTER", "TALK_CAMP_OVERSEER" ], "speaker_effect": [ { "effect": [ { "npc_add_effect":"on_npc_dialogue"}, ], }, ], "responses": [ { "text": "", "topic": "TALK_NONE", "condition": { "not": { "or": [ "u_male", "u_female" ] } }, }, ], }, { "type": "effect_type", "id": "on_npc_dialogue", "flags": [ "EFFECT_LUA_ON_ADDED" ], },I'm sure this could be made a little smaller, but this shows just how much boiler plate can be needed for such a simple thing.
Of note, this triggers the dialogue before the dialogue window appears. I haven't confirmed just what parts of the dialogue system are initialized at this stage, largely because there's very little you can do to interface with the dialogue system.
For example, if you changed the NPC's traits using this hook, would the dialogue system have some incorrect values somewhere?
I'm not certain if there's a clean way to trigger on the ending of dialogue. Doing the same thing with the "TALK_DONE" id doesn't work.
Graphics
Every step of my project's animation implementation is awkward and monstrous. It is incredibly funny and incredibly cursed.
But it does work. It uses the mutation_overlay system. Each animation has 3 stages that I can swap between. There are 11 animations.
Each animation has layers, to account for character traits (let's be adults about this).
Since each stage and layer requires an individual mutation for the mutation_overlay to hook to, my mod currently has 315 mutations just for the animations.
And by god, the json files. So much copy, paste, find and replace. I wrote lua code just for Aesprite to assist in the process.
The animation mutation def files + the mod_tileset.json are actually larger in filesize than the tileset png. It's a 1600 x 4000 image.
I am aware I am abusing a system in ways it is not meant to support. But I do think there are legit use cases for such things.
Assuming we actually want to support anything I've done, or what else it might enable, there's a few things here that I want to discuss.
"Lua Pause"
A lot of options for doing interactions from the user involve pausing the game loop entirely. All of the popup queries pause the timer that animations use.
Even if I had a different way of displaying animations other than using mutation overlays (yikes), It's likely not insignificant to make them play during these pauses.
On that note:
Custom UI Visuals
The mutation overlay method might be the most horrifying thing I've coded. It clogs up the debug list of mutations, and if other mods have to iterate over that list, it's gonna be awful.
It has bad interactions with layering. It isn't fixed to the screen. It kicked my dog.
I know it might be a lot to ask, but I'd love if we could just set up arbitrary arrays of tile data for animations and just display them in screen space, somehow...
It's probably the biggest stretch in this document, but enabling truly custom UI would be great.
Having a simplified system for adding UI elements using built in resources like text and boxes would also be fine. As simple as making the stylish rating from DMC possible as a lark or something would enable a lot for modders.
Tileset Colors
This is most evident for the appearance traits, like hair and skin, but that's a lot of tiles to maintain as a tileset author.
I suggest a novel tile type that allows a method of setting the color. The simplest method would be to just include a color input, and it would use the tileset's luminance and multiply it with the color.
A more robust but expensive option would be an implementation of LUTs, or look-up textures.
Miscellaneous
Coordinate system clarity; Either via documentation or awkward methods, I never could figure out how to convert between coordinate systems.
Better Lua documentation on parameter inputs specifically. I know that improving documentation is an ongoing effort. Honestly might do some of that myself.
The appearance traits for hair are twofold: color, and hairstyle. But each combo is it's own trait. In the mutation window at character creation, you can see that.
Perhaps we could dynamic generate them somehow? That doesn't solve the character creator problem.
If you wanted to add a bunch of hairstyles and hair colors, the number of mutations grows much quicker than the amount of content added.
This has been my hyper-fixation for a few days, and I really wish I had more time to contribute more directly. If I can make the time, I certainly will.
But for now, I hope that this document helps anyone with goals to improve this wonderful game, or was at least interesting in some way.
EDIT: I have made discoveries.
It turns out you can interact with npc dialogue variables. It's just very poorly documented.
I'll lay out the relevant snippets:
Reference source snippet:
Reference JSON
Lua functions that get the variable:
It turns out that you don't need the "type" and "context" fields for npc_add_var and such. The code to store it can handle empties.
Now you could, but it slightly complicates something that's already foggy. That just means you need to concatenate "npc_talk_var_" to the front of your var name on the lua side, and it just works. I'm gonna test if the u_has_var version works by injecting the value on the lua side with:
This is incredible news. Stopgap for now, and easy improvements for the source later. You could format the variable name however you like and do string splits to get paths and stuff oooh.
Here's some utility functions that may help.
Beta Was this translation helpful? Give feedback.
All reactions