Easy In-Line Profanity Filter (Text Tags Method)

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
Message
Author
User avatar
Zahdernia
Newbie
Posts: 2
Joined: Mon Feb 26, 2018 10:17 pm
Projects: Thyne
Organization: Thyne & Parasoul Productions
Deviantart: zahdernia
Github: zahdernia
Soundcloud: zahdernia
itch: zahdernia
Location: Thyne
Discord: @zahdernia
Contact:

Easy In-Line Profanity Filter (Text Tags Method)

#1 Post by Zahdernia »

A while back I was reading into custom text tags for text resizing and realised that they could be a brilliant alternative to using variables for word swapping and profanity toggling in dialogue. I wound up with an extremely simple yet extremely effective system that hasn't broken for me yet, so I thought I'd share it here in case it could be useful for anyone else.

Please note, this system is not for stopping your players from inputting expletives as names and the likes. This system is so that you can offer a squeaky-clean version of your narrative so that your parents or teachers or family-friendly-streamer-friends can play your game without spontaneously combusting.

The main ideas behind this method are as follows:
— Expletives can be sanitised on a string-by-string or dialogue-box basis (i.e. no "all instances of X are now Y")
— Single words can be replaced with whole phrases and vice-versa, offering general flexibility;
— Several "tiers" or "intensities" of expletives can be set in one self-closing tag (allowing for more granular settings, such as {clean|light|medium|heavy})
— If a player has seen a permutation of a line of dialogue, dialogue skipping won't catch or stop on a different permutation (which is a common problem of using if/else statements for profanity filters)

As a bonus, the logic in this module can be applied to other instances where one might need to swap out words. Think pronoun-verb agreement with custom pronouns ("[player.they] {verb=are|is} going to meet us there." — 0 for plural, 1 for singular) or very basic fictional language translation ("{altlang=LO cei ettei?|Are you okay?}" — 0 for incomprehensible, 1 for fluent) or countless other use-cases I haven't thought of.

Be aware, though, there are a few caveats:
This has not been tested with translations. I know very little about translations, but I have to assume it would work fine even with them? With my rudimentary understanding of how translations work, I can't really imagine any way it would fail, but as I have yet to experiment with it, I'd feel guilty not mentioning the lack of testing here.
If you're using a lot of intensity tiers, it may get a little unwieldy. There's only so long a line can get before it starts to look ridiculous. Further, if you have, say, 4 intensities ({clean|light|medium|heavy}) and two of them use the same expletive ({curse=x|y|y|z}), the identical expletives will still need to be written out as though they were unique. (Else, your medium tier will have your heavy tier's curse!)
While toggling the preference variable, it WILL affect the displayed history, but it will NOT affect the currently displayed dialogue until refreshed. That is to say, if you have a curse word on-screen and turn on the filter, that word will not be updated until you click forward and roll back, or roll back and move forward. Pretty sure this is due to how Ren'py loads things. If you have ideas on how to fix or mitigate this, please feel free to suggest them!

So! First things first, you'll likely want to either make a new file for this. (config_texttags.rpy, config_profanity.rpy, or even just profanityfilter.rpy are some name ideas; whatever works for you.)
Alternatively, you could add the following code to the bottom of screens.rpy, init.rpy if you've made that, or wherever else you may have your pre-existing custom text tags.

Here's the (heavily commented) code to create the custom text tag:

Code: Select all

default preferences.profanity = 1
    ## 0 should always represent clean speech.
    ## I personally default to 1 because it my "intended
    ##    experience" but you may decide to do differently.

init python: ## Profanity Filter Tags

    def profanity_tag(tag, argument):
        dialogue = argument.split("|") 
            ## This splits the string provided to the tag everywhere that | appears
            ##    and adds each separated chunk to the list.
            ## Always order your dialogue options from least-to-most severe.

        severity = min((len(dialogue)-1), preferences.profanity) 
            ## This compares how many items are in the dialogue list and how
            ##    intense you've set the global profanity level. It returns the lesser. 
            ## This ensures that if the severity is set to 4, but the list only goes
            ##    up to 2, it will return the most intense option (2), even if option 4
            ##    doesn't exist.
            ## This also ensures that if the severity is set to 0, but there are 4
            ##    levels of severity, it will return the clean version.
        
        return [(renpy.TEXT_TEXT, dialogue[severity])]
            ## This returns the list item that matches the severity as text.

    config.self_closing_custom_text_tags["curse"] = profanity_tag
        ## This is what actually creates the custom text tag.
        ## To use it in dialogue, you write it like this: {curse=clean|coarse}
        ##   If severity == 0, it will use clean; if severity >= 1, it will use coarse.

Additionally, you're going to need to add "curse" to gui.history_allow_tags. This makes it so that the curse actually appears in the game's dialogue history.
In a brand new project made in Ren'py 8.2.1, this is in screens.rpy on line 934. It'll look something like this once you're done:

Code: Select all

define gui.history_allow_tags = { "alt", "noalt", "rt", "rb", "art", "curse"}
And finally, in order for your player to select the severity they are comfortable with, you're going to want to add some sort of button or switch to your preferences() screen. Below I've attached three vboxes that each fit in quite nicely beneath the "skip" vbox. You only need one, so pick whichever one you prefer (or alternatively make your own):

Code: Select all


vbox: ## If you want to have one single button you can click to toggle the setting:
    style_prefix "check"
    label _("Other Settings")
    textbutton _("Profanity Filter") action ToggleVariable("preferences.profanity", 0, 1) 

vbox: ## If you want to have two different buttons to enable or disable the filter:
    style_prefix "radio"
    label _("Profanity Filter")
    hbox:
        textbutton _("Enabled") action SetVariable("preferences.profanity", 0)
        textbutton _("Disabled") action SetVariable("preferences.profanity", 1)

vbox: ## If you have a wider range of severity in your cursing (examples shown later):
    style_prefix "radio"
    label _("Profanity Severity")
    hbox:
    	textbutton _("Clean") action SetVariable("preferences.profanity", 0)
    	textbutton _("Light") action SetVariable ("preferences.profanity", 1)
    	textbutton _("Medium") action SetVariable ("preferences.profanity", 2)
    	textbutton _("Heavy") action SetVariable ("preferences.profanity", 3)

Ta-da! You should be good to go!

If you want to see a demonstration of how it works, here's a bit of code you can jump to from your start() label:

Code: Select all

label ProfanitySample():

    "This is an example of how the profanity filter works."

    "Because the preferences.profanity variable is set to [preferences.profanity], I say {curse=heck|hell} instead of {curse=something more... extreme|heck}."

    "You don't have to use the curse tag just with one word, though. You can put in whatever you need!"

    "For example, because we're using the {curse=clean|profane} version of this line of dialogue, I'll tell you this: {curse=it's great to want to have a clean version of your game. Just make sure you know why you're choosing to make one, and are doing it because it suits your game!|coarse language doesn't inherently change the value of a narrative. It's just a tool. Use it with intent!}"

    "Oh, and remember to check both versions when you playtest to make sure your dialogue looks as you expect."
    
    "Best of luck out there!"

    return 
And for some use-case examples:

Code: Select all

## An example of why you might not always want an expletive to be replaced with the same word.
eileen "{curse=Great|Crap}."
eileen "... Oh, {curse=gosh|crap}."

## This one is neat as its sanitary version isn't a word. 
## With profanity disabled, it reads as "Lori? You around?", and with it enabled, "Lori? You around, dummy?"
eileen "Lori? You around{curse=?|, dummy?}"

## An example for several levels of severity:
eileen "Oh my {curse=word|lord|God}!"

## An example with several levels of severity, some with shared words:
eileen "{curse=Golly|Gosh|Gosh|God}, I just don't know what to say!"

## And an example with several different max severity thresholds in the same line:
eileen "You can be {curse=a dummy|an idiot} sometimes, but I sure as {curse=sugar|heck|hell} won't abandon you for it."

And that's that! Hopefully this helps someone out there. 💖 If you notice any bugs or issues or have ideas on how to improve it, please please please say something! I am happy to learn!

Thank you for your time and interest!
Find Me Elsewhere
Oh, the worlds we can weave when we weave them together.

Post Reply

Who is online

Users browsing this forum: No registered users