Random selection custom text tag for procedural text
Posted: Fri Mar 04, 2022 3:34 pm
Inspired by the excellent Tracery, I wrote a custom text tag that lets you specify a series of options inline in dialogue or narration that will be randomly chosen when they're displayed. Random choices can be nested or even included in substitutions, letting you do some pretty complicated stuff including rudimentary L-systems.
I don't do any special processing for unusual tags, so this probably won't work with stuff like {nw} or other tags mentioned on the custom tags page.
Note that there's currently an issue with self-voicing where Ren'Py's automatic TTS won't run custom tags. This will be fixed soon, but even with that change you may see that text-to-speech will show a different randomly-generated quip than is displayed on screen. To fix both these issues, I made a pretty hacky monkey-patch that will make self-voicing consistently read the same thing as is displayed on-screen. No guarantees that this works for all situations.
The Code
How to Use and Examples
Put the {randtext} around a group of options separated by pipe (|) symbols, like so: The dress is {randtext}blue|white|yellow|black{/randtext}.
You can include empty options, or make an option more common by repeating it: I think therefore I am{randtext}hungry|hungry||am not{/randtext}.
You can nest random text: This episode is brought to you by {randtext}the letter {randtext}A|B|C{/randtext}|the number {randtext}1|2|3{/randtext}|viewers like you{/randtext}.
Randtext can include substitutions, which can in turn include random text and substitutions of their own. If you want to use nested substitution, make sure you're on a recent version of Ren'Py and apply the !i flag to substitutions.
A simple example for some eerie ambience, generating text like "A haunting song drifts, muffled, from a nearby building.":
I don't do any special processing for unusual tags, so this probably won't work with stuff like {nw} or other tags mentioned on the custom tags page.
Note that there's currently an issue with self-voicing where Ren'Py's automatic TTS won't run custom tags. This will be fixed soon, but even with that change you may see that text-to-speech will show a different randomly-generated quip than is displayed on screen. To fix both these issues, I made a pretty hacky monkey-patch that will make self-voicing consistently read the same thing as is displayed on-screen. No guarantees that this works for all situations.
The Code
Code: Select all
def randtext_tag(tag, argument, contents):
randtext_depth = 0
options = []
curr_option = []
for content in contents:
if content[0] == renpy.TEXT_TAG:
if content[1] == "randtext":
randtext_depth += 1
elif content[1] == "/randtext":
randtext_depth -= 1
curr_option.append(content)
elif content[0] == renpy.TEXT_TEXT:
if randtext_depth == 0:
if '|' in content[1]:
splits = content[1].split('|')
for index, split in enumerate(splits):
if len(split) > 0:
curr_option.append((renpy.TEXT_TEXT, split))
else:
curr_option.append((renpy.TEXT_TEXT, ""))
if index == 0 or index < len(splits) - 1:
options.append(curr_option)
curr_option = []
else:
curr_option.append(content)
else:
curr_option.append(content)
if len(curr_option) > 0:
options.append(curr_option)
choice = renpy.random.choice(options)
return choice
config.custom_text_tags["randtext"] = randtext_tag
def monkey_tts_decorator(method):
def monkey_tts(self):
real_text = self.text
fake_text = ""
for t_type, t_text in self.tokens:
if t_type == renpy.text.textsupport.TEXT:
fake_text += t_text
elif t_type == renpy.text.textsupport.TAG:
fake_text += "{" + t_text + "}"
elif t_text == renpy.text.textsupport.PARAGRAPH:
fake_text += "\n"
self.text = [fake_text]
ret = method(self)
self.text = real_text
return ret
return monkey_tts
renpy.text.text.Text._tts = monkey_tts_decorator(renpy.text.text.Text._tts)
Put the {randtext} around a group of options separated by pipe (|) symbols, like so: The dress is {randtext}blue|white|yellow|black{/randtext}.
You can include empty options, or make an option more common by repeating it: I think therefore I am{randtext}hungry|hungry||am not{/randtext}.
You can nest random text: This episode is brought to you by {randtext}the letter {randtext}A|B|C{/randtext}|the number {randtext}1|2|3{/randtext}|viewers like you{/randtext}.
Randtext can include substitutions, which can in turn include random text and substitutions of their own. If you want to use nested substitution, make sure you're on a recent version of Ren'Py and apply the !i flag to substitutions.
A simple example for some eerie ambience, generating text like "A haunting song drifts, muffled, from a nearby building.":
A more complicated random breakfast, generating text like "You arrive downstairs and discover blueberry muffins set out. Theresa enters shortly.":"A {randtext}haunting|strange|familiar|catchy|charming|cheery{/randtext} {randtext}melody|song|chant|rhythm|hymn|tune{/randtext} {randtext}echoes from somewhere in the distance|drifts on the wind|plays from a nearby window|catches your ear from somewhere nearby|is barely audible|drifts, muffled, from a nearby building{/randtext}."
define random_breakfast_food = "{randtext}spiced rice pudding|thick slabs of toast with {randtext}butter and jam|marmalade|scrambled eggs|cured fish|soft cheese{/randtext}|eggs over fish and onions|{randtext}bacon|sausage|mushrooms{/randtext} and eggs|fruit salad and cottage cheese|a {randtext}cheese|ham and cheese|mushroom and cheese|spinach and cheese{/randtext} omelet|{randtext}fruit-studded|blueberry|bran|nut-filled{/randtext} muffins|crepes with {randtext}fresh fruit|fruit preserves|hazelnut spread{/randtext}|a hash of potatoes and {randtext}corned beef|sausage|chipped beef|shredded pork{/randtext}|eggs {randtext}Benedict|Florentine|Cochon|Mornay|Royale{/randtext}|a mess of meats, eggs, tomatoes, baked beans, mushrooms, and toast|{randtext}thoroughly-dressed|lightly-seasoned|nut-topped|fruit-topped{/randtext} oatmeal|{randtext}pancakes|waffles|hotcakes{/randtext} with {randtext}syrup|honey|whipped cream|chocolate sauce|fruit compote{/randtext}|muesli with {randtext}berries|almonds|walnuts|tinned peaches{/randtext}{/randtext}"
"Theresa {randtext}is already in the dining room|is right behind you|enters with you{/randtext}. You {randtext}greet her before you|give her a nod and|exchange pleasantries and|push in her seat before you{/randtext} {randtext}sit at a table set with|prepare to eat your|are served{/randtext} [random_breakfast_food]."