Dialogue and choices
Lines, moods, pronouns, choices, text input and staging people in the room.
Lines
luaGame:Narrate("The rain has not stopped all day.")
Game:Say(Actor, "Happy", "You came! I was starting to think you wouldn't.")
Game:Say(Player, "Flirty", "I wouldn't miss it.")
Game:Narrate(text): narration, no speaker. Underlives's narration is written in the first person, from the player's point of view ("I", "my").Game:Say(person, mood, text): a line spoken byperson. The dialogue box shows their name and portrait, and their face takes the mood's expression while the line is on screen.moodmay benil.
Each line waits for the player to advance it (or for the auto-advance timer, depending on the player's setting). Lines are kept in the scene's history panel.
Moods
| Mood | ||
|---|---|---|
Neutral |
Happy |
Flirty |
Angry |
Furious |
Sad |
Crying |
Surprised |
Shocked |
Scared |
Pain |
Embarrassed |
Wink |
Evil |
Kiss (same as Flirty) |
An unknown mood shows a neutral face.
Moods are data: CharGen/moods.json maps each one to Genesis 8 expression morphs and their weights. Your mod's own CharGen/moods.json adds moods or replaces one by name:
json{
"moods": { "Smug": { "eCTRLSmileFullFace_HD": 0.4, "eCTRLGlare_HD": 0.3 } },
"aliases": { "Proud": "Smug" }
}
The mood list is fixed for now; mods cannot add moods or change which expression a mood uses yet.
Names and pronouns
Build lines from the people in them, so they work for anyone. Write <Name.token> in the text:
lualocal Partner = Game:getPerson("partner")
Game:Narrate("<Partner.name> looks up from <Partner.his> phone.")
-- "Mia looks up from her phone." / "Tom looks up from his phone."
Game:Say(Partner, nil, "Tell <Player.him> I said hi.")
Name is any variable of your scene that holds a person: a local (Partner above), Player, Actor, CurrentCompanion. A tag whose name is not a person (<b>, <Nobody.name>) stays as written. Game:fillText(text) does the same for text that is not a line (a notification, a panel). The same tokens work as a function call:
luaGame:Narrate(Actor:name() .. " looks up from " .. Actor:GetToken("p") .. " phone.")
Game:Say(Actor, nil, "Call me your " .. Actor:GetGenderedWord("boyfriend", "girlfriend") .. " one more time.")
Actor:GetToken(token):
| Token | He | She |
|---|---|---|
S / s (or He / he) |
He / he | She / she |
O / o (or Him / him) |
Him / him | Her / her |
P / p (or His / his) |
His / his | Her / her |
PP / pp |
His / his | Hers / hers |
R / r (or Himself / himself) |
Himself / himself | Herself / herself |
name, name_last |
First name, last name | |
relationship |
What the player is to this person ("son", "niece"…) | |
callplayer |
What this person is to the player ("mom", "cousin"…) | |
Major |
The subject they study or teach | |
word1_or_word2 |
word1 |
word2 |
The last row means any a_or_b token works: Actor:GetToken("mister_or_miss"). Tokens are case-sensitive (S capitalises, s does not). Pronouns follow the person's presented gender.
Actor:GetGenderedWord(male, female) is the same choice as a function call.
A face without a line
A line's mood is put on the speaker's face when the line shows. Between lines, Actor:mood(name)
does the same on anyone, and holds it until the next mood or line: a reaction shot in a cutscene,
someone listening, a face that changes before the words do.
luap:mood("Shocked") -- the hand lands; nothing is said
Game:Hold(0.6)
p:mood("Furious", 0.4) -- reached in a blend of 0.4 s instead of at once
Game:Say(p, "Furious", "Hands. Off.")
Beyond the recipes: p:face({ eCTRLAngry_HD = 0.6, eCTRLSmileFullFace_HD = 0.3 }, 0.3) composes
a face from any expression morphs; p:alive(0.5) adds the face's own life on top (breath, eyelids,
brows; 0 is still); p:blink() blinks once, p:blink(false) stops blinking (a stare, a corpse).
Eyes are directed too: p:lookAt(other) turns the neck, the head and the eyes to another person
and keeps them there; p:lookAt("camera") looks into the lens wherever each shot puts it;
p:lookAt({0, 0, 0.9}) at a point of the stage frame (metres from the player's feet);
p:lookAt(target, { seconds = 0.5, weight = 1, max = 60, eyes = true }) sets the ease, how far
the head follows, its cap in degrees, or moves the eyes alone. p:lookAt() lets go.
p:turnTo(target) turns the body; p:freeze(true) holds it on its frame; p:animSpeed(0.5)
slows its clips; p:walkTo({ x, y }, "idle_f") walks to a stage point, and p:walkTo("piece:window") (or any piece:<match>) to the spot in front of that piece, half a metre off it.
The names are the moods of CharGen/moods.json (Neutral, Happy, Flirty, Angry, Sad,
Surprised, Shocked, Scared, Furious, Crying, Pain, Evil, Embarrassed, Bored,
Irritated, Hurt, Unconscious…); an unknown name is Neutral. A mod adds or changes recipes
in its own CharGen/moods.json.
Choices
Register the options, then ask:
luaGame:Narrate("What do I say?")
Game:Choice(0, nil, "Tell the truth")
Game:Choice(1, Player:getStat("interpersonal") > 40, "Talk my way out of it")
Game:Choice(2, nil, "Say nothing")
local pick = Game:GetChoice()
if pick == 0 then
-- …
elseif pick == 1 then
-- …
end
Game:Choice(index, condition, text, options?):indexis whatGetChoicereturns if this option is picked.conditionisnil(always available) or a true/false value; an option whose condition is false is not shown.Game:GetChoice()shows the options, waits, and returns the index of the pick.- Calling
GetChoice()again without registering new options returns the same answer, so you can test it in several places. - If every option at a choice point is hidden, the scene closes and the log names your file. Always leave one unconditional option.
Greyed options and odds
A fourth argument tells the player more:
luaGame:Choice(0, nil, "Talk your way in", { odds = 0.6, tooltip = "Charm against the bouncer" })
Game:Choice(1, Player:hasItem("vip_card"), "Flash a VIP card", { reason = "You have no VIP card" })
Game:Choice(2, nil, "Walk away")
reason: when the condition is false, the option is shown greyed with the reason beside it (and on hover) instead of hidden. It cannot be picked and takes no number key.odds: a chance of success,0.6or60, printed after the text: "Talk your way in (60%)". Showing it is your promise: roll the same number (Game:Random(1, 100) <= 60).tooltip: hover text for any option.
Menus that loop
luarepeat
Game:Choice(0, nil, "Ask about the job")
Game:Choice(1, not askedFamily, "Ask about the family")
Game:Choice(9, nil, "Leave")
local pick = Game:GetChoice()
if pick == 1 then
askedFamily = true
Game:Say(Actor, "Sad", "We don't really talk anymore.")
end
until pick == 9
Built-in pickers
These register their own options and wait, like GetChoice:
| Call | Asks the player to… | Read the answer with |
|---|---|---|
Game:selectNPC(tag) |
pick one of their contacts (or "Nobody") | Game:getSelectedNPC(), an invalid person for "Nobody" |
Game:chooseContact() |
same, legacy name | Game:getSpecific("chooseContact") |
Game:chooseMajor() then Game:setMajor() |
pick a university major | the player's major is set |
Text input
lualocal name = Actor:enterFirstName() -- the player types a first name for Actor
local nick = Actor:enterCustom("nickname")
local shop = Game:enterBusinessName()
Each waits for the player to type and returns the text.
Staging people
People in a scene are real people in the room, not cut-outs.
| Call | Effect |
|---|---|
Actor:show(), Actor:hide() |
Put the person in the scene or take them out |
Actor:moveToPerson(other) |
Walk up to other and face them |
Actor:moveToPersonStand(other, cm) |
Walk to cm centimetres from other (0 = their spot) |
Game:faceEachOther(a, b) |
Turn two people towards each other |
Actor:animate("smoke") |
Play an activity (see Animations); no argument returns to idle |
Game:AnimatePair(a, b, "Kissing") |
A paired animation (a kiss, a hug) without starting a sex scene |
Actor:closeEyes(true) |
Hold the eyes shut; false opens them |
Actor:dress(), Actor:strip(), Actor:stripOne("Top") |
Change what they wear. See People. |
Phone calls and thoughts
Game:secondScreen(person) puts a person "on the phone": their lines still show their portrait, but show() will not place them in the room. Game:secondScreen() with no argument ends it for everyone. Game:secondScreenIfHidden(person) does it only if they are not already visible.
lualocal mom = Game:getRelative("Parent")
Game:secondScreen(mom)
Game:Say(mom, "Happy", "Sweetie! You never call.")
Game:secondScreen()
Changing place
Game:setBackground("park") (or Game:moveTo("park")) moves the scene, and the player, to a place of that venue type. Game:GetCurrentLocation() returns the current venue tag. Game:isAtHome() is true at the player's home.
On-screen messages
Game:ShowMessage("Saved!", 3) shows a short message that does not wait for the player; the second argument is seconds (default 5). Useful for feedback from libraries and UI scripts too.