Tech Demo using Cythera style interactive dialogs

Started by llamazing, January 25, 2017, 09:13:09 AM

Previous topic - Next topic
I'm not expecting anyone to have heard of the game Cythera, which is a mac exclusive tile from the late 90s. It had an interesting dialog system where certain words said by NPCs would be highlighted, and the player could ask about them go into more depth on that topic. I've discussed this type of dialog system previously in this topic.

I've put together a quick little sample quest that tries to replicate the dialog system used by Cythera. It replicates the dialog text of three characters from the game (one of which is fairly complicated and a good testbed to push my dialog script to its limits).

When talking to NPCs, pretty much all of them respond to the key words "name", "job" and "bye" (to close the dialog). Beyond that, certain key words are highlighted in blue denoting that the player can take the conversation further by asking about those topics. The player can either type the keyword into a text box or select them from a running list by using the mouse.

A little quirk to be aware of is that only the first 5 letters of whatever the player types in are considered (it can be less than 5 letters too, depending on the word). This makes it easy for "fish", "fishing" or "fisherman" to all give the same response. Note that while Cythera only ever considered the first 4 letters entered, I decided to bump the limit up to 5 for a little more versatility. I don't think that it would ever be necessary to use more than 5 letters, though.

See if you can figure out how to complete this short little quest. I will say that there are two possible solutions. No items are required, and there are not any enemies.

Installation instructions:
The Solarus ALTTP resource pack is required for this quest. Confirmed to work with version 1.5.6, which can be found here. Download and extract the ALTTP resource pack. Then download my quest and extract it to the the same directory as the resource pack. For any duplicate files between the two, the ones from my quest should overwrite the ones from the resource pack.

Feel free to borrow and or modify the scripts for your own usage. Note that most of the images and sounds included in the quest actually come from Cythera and should not be used. If I end up going further with this project, it will ultimately not use any Cythera assets or text.

This demo quest has the following features:
  • Interactive dialog system
    • NPC portraits
    • clickable hyperlink text
    • text input
    • dynamic dialog content that depends on a multitude of factors
  • Preserve a log of NPC dialog for later reference
  • Objective tracking
  • Day/night cycle that darkens the map at night
    • lluminating light sources at night
    • Different music between night & day
    • NPC movements synchronized with the time
    • sleep in a bed to advance time
  • Read-only console that prints notifications for the player
  • Various UI elements with mouse/keyboard interaction

The quest can be downloaded from its github project page


January 25, 2017, 09:13:36 AM #2 Last Edit: January 28, 2017, 05:51:32 AM by llamazing
Script features and how to use them

Interactive Dialogs

The interactive dialogs are the primary feature of this quest. The dialog window has 4 sections arranged vertically. The top one is for the NPC the player is talking to. The second one is for an additional NPC who can be involved in the discussion (like in Majora's Mask, where Link could be having a conversation with Anju in the Stock Pot Inn and in walks the postman, who interjects himself into the conversation -- my quest does not make use of this feature). The third section is for dialog from the player. This usually involves one-word responses that the player types in to continue the conversation, but it can also include pre-written paragraphs of text. The fourth and bottom section is for narrative text that can describe the setting or actions that characters take during the conversation.

dialogs.dat contains the dialog text content for ease of translation. Each section of the dialog window can contain up to 4 lines of text (except for the bottom section, which can only have up to 2 lines). Which section of the dialog the text gets added to is designated by using the $1 through $4 characters at the beginning of a paragraph, where omitting the special characters is the same as $1 and beginning a paragraph with # is the same as $4. If the paragraph has more text than will fit, it gets continued once the player clicks with the mouse or presses a key. Empty lines are used for paragraph breaks.

$s and $v are used for placeholder text that can change dynamically. $s is for strings (usually from strings.dat) that would need to be translated if a different language were used. $v is for number values, and thus don't require translation.

topic_rules.lua is what maps out the different dialog entries into a coherent conversation. Every NPC with spoken dialog has an entry in topic_rules.lua, along with a list that links words submitted by the player with the dialogs.dat entry to display in response. Note that the words listed in topic_rules.lua are not the exact text the player types into the dialog, but are a key to lookup the text from strings.dat so that it can be translated to the player's native language. There are also special entries for GREETING, which corresponds to the dialog to start the conversation off when the player first interacts with an NPC, as well as the special entry UNKNOWN for when the player enters text for which the NPC does not have a set response.

The script offers quite a bit of flexibility in setting up various conditions for determining which dialog to show. The dice game that can be played with the bartender at the inn is a good example of what can be achieved with the use of custom functions. Note that the dice game comes directly from Cythera, as does nearly all of the NPC dialog used in the quest.

Finally, the player can preserve a record of what is said by NPCs in the journal sidebar window for review at a later time. The journal text gets saved to a separate text file in the same directory as the save game data. I did not include a mechanism to remove journal entries (which didn't seem necessary given how short the quest is). The game_manager.lua script manages the loading and saving of the journal records.

Passage of time/NPC Movements/Night and Day cycle

The flow of time is essential for both NPC movements and the night overlay which darkens the screen at night. game_clock.lua manages the flow of time, which works by modifying the map metatable so that whenever a map gets loaded, the game clock is started (and updates every in-game minute) using a looping timer. Whenever a map is finished, the game clock is stopped with the current time saved in memory for when the next map is loaded, resuming the clock again.

The rate of time is set for 12 minutes real-time per in-game day, or 0.5 seconds real-time per in-game minute. I probably would have made an in-game day take twice as long for a real quest, but I didn't want the player to have to sit around too long for the purposes of this demo quest.

NPCs have a movement speed of 8 pixels per in-game minute, which works out to 1 tile (16 pixels) every second. NPC movements are scripted using path movements, which works out nicely since each in-game minute corresponds to one entry (step) in the path table associated with the path movement.

NPC movements are defined in the events directory, where each NPC has a file in that directory. The file lists a schedule for the NPC throughout the day, where at any given time the NPC is either stationary at some location, or moving from one location to another. The file also defines where on the map each of these locations can be found, as well as the path movements needed to get from one location to another.

One of the interesting things about the script is that an NPC can move from one map to another. Theoretically the NPC could even exist on multiple maps at the same time, but for this quest I hide the NPC at all other maps while present on a given map so that the NPC is only ever on one map at a given time.

game_events.lua manages NPC movements. The way that the implementation works is that whenever a map is loaded, the locations of all NPCs have to be adjusted in accordance with the current time. For the case where an NPC is standing stationary at some location, this is easy. Otherwise the script has to calculate where in the middle of a path movement the NPC should be, move the NPC to the corresponding map location, and then assign a path movement for the remainder of the movement. Finally, every in-game minute the schedule for an NPC is checked to see if a new path movement should be started for the NPC.

With the current implementation, NPCs follow the exact same schedule every day (which was all that was necessary for the purposes of this quest). Eventually I would like the expand on this script allowing for schedules that change based on what day of the week it is, as well as allowing for randomized special events that can alter an NPC's schedule.

Another feature borrowed from Cythera is that the player can sleep in a bed to skip ahead in time quickly. The sleep dialog that pops up when the player interacts with a bed comes straight from Cythera, as does the feature that NPCs will kick the player out of their bed during the night.

night_overlay.lua is what darkens the screen at night. This works by creating one overlay to darken the screen using the multiply blend mode and another overlay for light sources using the add blend mode. The color to use for the darkening overlay (which corresponds to the ambient light level or how much to darken the screen), is defined in night_overlay.dat at hour increments during the twilight hours. Then every in-game minute the ambient light level is updated, smoothly transitioning between the defined colors.

The number of light sources present on a map also affects the ambient light level. This can be seen on the first floor of the inn. It will start to get darker at the start of the evening, then eventually the 3 braziers turn on, at which point there is a sudden increase in the ambient light level, which then continues to get darker and darker as the night progresses.

Also note that the sequence of colors used for the night overlay varies depending on the map, as defined by the custom map.lighting variable with possible values of "outside", "inside", "dim", and "dark". Outside maps have a slightly blueish hue on account of the moonlight, whereas inside maps use pure shades of grey for the transition to night. Dim and Dark would be used for a dungeon or underground cavern where the ambient is always dark and does not change from day to night (these are not used in my quest).

Similarly, it is possible have different music playing between day and night where the custom variables map.music.day and map.music.night call out the music file to use respectively. Note that in order to get this feature to work, I set the music to be played from the quest editor as <Same as before>. This ensures that the music won't get restarted when changing maps, flowing seamlessly.

UI Elements

I created a library of scripts solely for the purpose of creating UI elements (clickable buttons, text fields, scrollbars, frame images, etc.). They can be found in the scripts/lib/uix directory. The name UIX is short for UI Express, which is what I was calling the master script to load the individual scripts for each UI subelement. Originally I was using require to load all of the UI subelement scripts needed in a given script. So if I had a script that created a window with buttons, it would require both buttons.lua and frame.lua, for example. This became quite cumbersome as I not only had to keep track of which UI subelements were used in a given script, but also the list of require statements would grow to be quite long. So instead, any script that uses any UI subelements now simply requires the ui_express.lua script, which in turn requires all of the UI subelement scripts.

The UI subelement scripts currently make use of images from Cythera in order to replicate the look of Cythera's dialogs and menus. I intend to someday release the UIX library separately using public domain images instead, making it more suitable for use in other quests.


January 27, 2017, 06:38:54 PM #4 Last Edit: January 27, 2017, 10:57:11 PM by MetalZelda
I don't know that much of Cythera but nice work, very polished and nicely done

QuoteNPC movements synchronized with the time

I'm interested by this, how that work ? Is there a special algorythm applied to the NPC speed ?[/s]
EDIT: I found it

I've updated the 3rd post to describe the implementation of the major features and provide insight to how some of the scripts work.

Just curious, was anyone able to finish this quest (i.e. complete both objectives)?

I was intending to post some hints, but I don't see any way to do a spoiler tag on these forums (except perhaps making the font color the same color as the background). Any ideas?
Spoiler Test