Codes: Censoring Nudity (the YAGS way)

One of the things I’ve been more aware of recently is nudity in games, and particularly how that plays (or doesn’t play) with payment processors.

A lot of games, to stay on the safe side, have released their games in a “censored” mode and then released patches to re-enable nudity. I’m not sure how other people do it, but my goal was to make it as simple as possible for users (one file to replace) but also give myself a lot of control over changes.

As always, I don’t pretend that this is the right or best way to do things… only that it works for me.

First off, you have to identify what file(s) and game declarations have nudity or will be affected by censoring. In my case, I have both nude sprites and nudity in my CGs, located in /images/chnew and /images/cg, as well as LayeredImage declarations for my CGs.

We start by defining a new rpy file ndpatch.rpy to hold the aspects that require and control patching. Because of the way my sprites work, the only other thing I need in here is the CG definitions*. We also include some additional constants, for use later.

## Is nudity allowed at all
define nudityIsAllowed = True

##  is for uncensored, c is for censored
define nudityverending = ""

## Align the game with the patch version where necessary
## Update expectedndpatchversion in options.rpy
define ndpatchversion = 1

layeredimage person_cg:
    always:
        "cg/person_base.jpg"
    if persistent.bodyhair_person:
        "cg/person_hair.png"
    if not persistent.nudes:
        "cg/person_censor.png"

Next, we define a separate rpa file for everything related to nudity (the sprites, the CGs, and the ndpatch.rpy file itself). This should come first in the build.classify calls since each file will be placed into an archive based on the order the rules are declared, in options.rpy. We also add a corresponding constant that will not be built into ndpatch.rpa.

## Match the patch version in ndpatch.rpy
define expectedndpatchversion = 1

init python:
    build.archive("ndpatch", "all")
    
    # Nudity
    build.classify("game/ndpatch.rpyc", "ndpatch")
    build.classify("game/images/chnew/**", "ndpatch")
    build.classify("game/images/cg/**", "ndpatch")

    # ...other archive and classify calls...

Now, we make a copy of the entire game project. In that copy, we edit the rpy file to reflect censored nudity.

## Is nudity allowed at all
define nudityIsAllowed = False

##  is for uncensored, c is for censored
define nudityverending = "c"

## Align the game with the patch version where necessary
## Update expectedndpatchversion in options.rpy
define ndpatchversion = 1

layeredimage person_cg:
    always:
        "cg/person_base.jpg"
    if persistent.bodyhair_person:
        "cg/person_hair.png"

And then we also make sure, in the censored version of the project, we merge the censor in the CGs with the base layer (so even the raw game assets don’t have nudity that can be uncovered via unarchiving the rpa file later). Similarly, any sprite image files that have nudity in them must be edited in the censored version of the project to remove or obscure nudity.**

Technically, this is enough to get censoring working. You would simply build a distribution of the game via the censored version of the project, build a second distribution via the uncensored version, and provide the ndpatch.rpa file from the uncensored version for download. The images will be replaced in the game with uncensored versions when the ndpatch.rpa file is replaced, since they are part of the ndpatch.rpa file.

However, we can do a little better.

First, you can use the nudityverending constant to change how the version of your game displays, to make it obvious whether it’s got nudity or not. For example:

screen about():
...stuff...
text "Version [config.version!t][nudityverending]"
...stuff...

Second, your game may later need to change in a way that makes an older ndpatch.rpa incompatible. For example, if you add a new sprite expression, someone with an older ndpatch.rpa file that tries to use it against a newer game build will see errors when running the game. Similarly, if you delete an asset, someone with a newer ndpatch.rpa file will see errors with an older game build.

This is where the ndpatchversion and expectedndpatchversion constants come in handy. You can ensure they are equal when the game starts up and show an error or warning if they don’t match. For example:

label start:
if ndpatchversion != expectedndpatchversion:
    "Your ndpatch.rpa file is the wrong version for this build of the game."
    "Please download the latest patch and game from URL."
    return

and

screen main_menu():
    fixed:
        if ndpatchversion > expectedndpatchversion:
            text "{size=50}{color=#000}Your ndpatch.rpa is newer than the build of this game (v[config.version]). Please download the latest version of the game before patching.{/color}{/size}" xsize 500 xanchor 0.0 yanchor 0.0 pos(550, 100)
        elif ndpatchversion < expectedndpatchversion:
            text "{size=50}{color=#000}Your ndpatch.rpa too old for use with this build of the game (v[config.version]). Please download the latest version of the patch and try again.{/color}{/size}" xsize 500 xanchor 0.0 yanchor 0.0 pos(550, 100)

which then lets you bump both constants whenever you make a breaking change (like adding or removing a sprite asset or cg).

Finally, you can use the nudityIsAllowed constant inside the game itself anywhere that you want to handle differently in a censored vs uncensored game. For example, I change the warning that shows at the beginning of the game to reflect explicit vs non-explicit nudity.

There’s probably a way to make this work with a single Renpy project that builds both a ZIP distribution and a separate ndpatch.rpa file. But it seemed easier to me to just duplicate the project. But because of this, you probably want to do this work when your game is otherwise done, or you’ll potentially have to make bug fixes in both copies of the game project.


* Yes, I could have left the CG declarations outside of ndpatch.rpy and simply censored the base image, but that would require me to have a cg/person_censor.png file in both versions of the game, and that felt inefficient for me.

** Identifying and editing relevant images is where most of the work is, really. If you just want to flat out replace images, you don’t even need an ndpatch.rpy file. Simply declaring an ndpatch.rpa archive in options.rpy, and including all image files that will need to be censored in that archive, would be enough.

Fic: Why Not?

Hey, look. It’s another fic! (See all of them here.)

There’s a lot of things, in terms of backstory with the other characters and things that you don’t see, that I have specced out in my mind. I thought it would be interesting to present them in text form to fill in some of the gaps, as it were, in the main YAGS storyline.

Needless to say, these pieces will be highly spoilerific, so I recommend you do not read them unless you have finished the game at least once. (Likewise, they may not make as much sense if you haven’t played the game at least once.)

Seriously. Spoilers ahead! Do not continue if you haven’t played the game!

Additional warning: This fic is highly explicit, and NSFW, and all of that stuff. R18+, NC-17, XXX, etc, etc. 

Continue reading “Fic: Why Not?”

YAGS Fanart!

Via @soulsoftea comes the first ever piece of YAGS fanart (Adam, Elliot, and Steve), and via @wolfscade comes piece #2 (chibi Adam!), and I am both blown away by how amazing they are, and touched by the effort put in.

Ahhhh I am so excited to see these.

The placeholder sprites have been unexpectedly popular, so I’m actually going to carry them through in the final game as an unlockable sprite option. But seeing them together with finished Adam in the first piece (much like the game in its current state) is just ridiculous and awesome.

Also now I want to commission more characters in chibi form…

Hannah pencils, and the last CG preview

When it rains, it pours!

In addition to a finished Nikhil, I also received preliminary sketches for Hannah from @stollcomics today. Here she is pictured next to Adam, which I always find amusing because she’s canonically a foot shorter than him.

Additionally, @soulsoftea has started work on the final CG for the game! A preview of it is below the cut, for spoilers (as always), and I kind of feel like it’s the best one yet.

Continue reading “Hannah pencils, and the last CG preview”