Tutorial How-to: Easy string patching

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,110
14,783
The question start to be recurrent: I'm making a game with Ren'py, and I want to help the creation of a patch letting me easily change the way characters are named.

There's more than one solution, but most of them come with their cons. Among them you can :
  • Use the translation feature.

    It's simple and all what's needed is already in Ren'py, but it need that you "translate" the whole string, even if there's only one word to change in it.​
  • Use some RegEx to perform string replacement.

    It's efficient, but not really simple. Even if you use a list to ease your work, you still have to define each string and their replacement. Plus it can have some weird side effects. If you made an error when writing the RegEx, or if you don't limited it correctly, it can change part of a word, because it looks like the word/sentence you wanted to change.​


Here I'll propose you another approach of the problem. One which is more dynamics and probably easier to use. All the process is based on the use of a generic Python class and the dynamic text inclusion of Ren'py.
It works in three steps :

  • Define your class :

    You don't have permission to view the spoiler content. Log in or register now.
    You don't have permission to view the spoiler content. Log in or register now.
  • Create the objects associated to this class :

    You'll need one object by character affected by the change. For the example, let's say that you have two characters :
    Code:
    define m = Character( "Laura" )
    define s = Character( "Tina" )
    To keep it easy to remember, you'll keep the letter and create two objects in your game:
    You don't have permission to view the spoiler content. Log in or register now.
    You don't have permission to view the spoiler content. Log in or register now.
    Now someone can create a patch which will automatically overwrite the objects you created and change the names everywhere in your game, with only three lines in a "patch.rpy" file :
    You don't have permission to view the spoiler content. Log in or register now.
    You don't have permission to view the spoiler content. Log in or register now.
  • Use Ren'py dynamic text inclusion when writing your dialog.

    By example, you can have these lines of dialog :
    Code:
    label somePartOfYourGame:
       "I can't, she's [mNames.fullFormal] !"
       "[mNames.Informal], come here please."
       "[sNames.Formal] ? She's [sNames.informal] and [sNames.fullFormal], why ?"
    Without the patch, the game will display :
    I can't, she's my teacher !
    Laura, come here please.
    Tina ? She's a buddy and my classmate, why ?
    But with the patch, the player will see this :
    I can't, she's my mother !
    Laura, come here please.
    Tina ? She's a slut and my sister, why ?

It's not clearly seen in the example, but you aren't limited to "formal", "informal" and "fullFormal". You can use the names you want and define as many as you want. Plus, if you put the first letter of this "name" in upper case, the text returned will also have it's first letter in upper case. So, by just writing this :
Code:
    mName = DynamicNames( fullFormal = "my teacher" )
You'll be able to have in your game both, mNames.fullFormal, which will return "my teacher", and mNames.FullFormal, which will return "My teacher". All this without any need to define it twice.



Now, you are ready to make your game using fully dynamics names, with a lot of names, or sentences, depending of the context. Then, with a single file and without editing a single line of your own code, anyone will be able to patch your game and change these "fully dynamics names".

But here come a problem. You are a good writer, there's a lot of things to change and it will make a really big line to write behind "mNames = DynamicNames(". Therefore, I made the works for you...

Attached to this message, you'll find an archive. In this archive you'll find a "script.rpy" file as example, and a file named "00DynamicNames.rpy", to put in your "[project name]\game" folder among all your game's code. This file will add the "defineDynamicName" and "define_dynamic_name" statements to Ren'py language. Use the one you prefer and write things like this :

Code:
init:
    defineDynamicName mName:
        formal "teacher"
        informal mGetName
        fullFormal "my teacher"
    defineDynamicName sName:
        formal sGetName
        informal "a budy"
        fullFormal "my classmate"
Like above, anyone will be able to patch your game just by writing a "patch.rpy" file which will have these "defineDynamicName" statements at an higher init level.
It will use the advanced class I've shown above, with a bonus side. You can now easily extend it by adding a line in the "dynamicName" block:

Code:
init:
    defineDynamicName mName:
        formal "teacher"
        informal mGetName
        fullFormal "my teacher"
        mcIs "one of my student"
    defineDynamicName sName:
        formal sGetName
        informal "a budy"
        fullFormal "my classmate"
Now, Laura have a new dynamic string, "mcIs". Repeat each time your writing make you add one thing to change...

In addition, it also add the "dynamicName" and "dynamic_name" statements, to use in a label this time, if you want "mName" and "sName" objects to be saved. But I do NOT recommend you to do this ; it will force the player to restart the game each time you add something.
By keeping the values defined at init time, you'll keep your game up to date, whatever it's a regular version or a patched one, and whatever time have past since the last version played by the player. It will also make it easier for a player to revert to an unpatched version by just removing the patch file.


Note:
Please don't misunderstood me. It appear that my examples make it look like you're trying to create an incest patch to work around the guideline of a certain site. It's obviously not my intent, nor yours, I know it. Who could want to do this kind of things, especially in a pirate site like this one...



If you found this useful, consider giving a like as thanks and/or encouragement to write more.


You don't have permission to view the spoiler content. Log in or register now.
 

Afflyction

Member
Game Developer
May 7, 2017
190
613
The question start to be recurrent: I'm making a game with Ren'py, and I want to help the creation of a patch letting me easily change the way characters are named.

There's more than one solution, but most of them come with their cons. Among them you can :
  • Use the translation feature.

    It's simple and all what's needed is already in Ren'py, but it need that you "translate" the whole string, even if there's only one word to change in it.​
  • Use some RegEx to perform string replacement.

    It's efficient, but not really simple. Even if you use a list to ease your work, you still have to define each string and their replacement. Plus it can have some weird side effects. If you made an error when writing the RegEx, or if you don't limited it correctly, it can change part of a word, because it looks like the word/sentence you wanted to change.​


Here I'll propose you another approach of the problem. One which is more dynamics and probably easier to use. All the process is based on the use of a generic Python class and the dynamic text inclusion of Ren'py.
It works in three steps :

  • Define your class :

    You don't have permission to view the spoiler content. Log in or register now.
    You don't have permission to view the spoiler content. Log in or register now.
  • Create the objects associated to this class :

    You'll need one object by character affected by the change. For the example, let's say that you have two characters :
    Code:
    define m = Character( "Laura" )
    define s = Character( "Tina" )
    To keep it easy to remember, you'll keep the letter and create two objects in your game:
    You don't have permission to view the spoiler content. Log in or register now.
    You don't have permission to view the spoiler content. Log in or register now.
    Now someone can create a patch which will automatically overwrite the objects you created and change the names everywhere in your game, with only three lines in a "patch.rpy" file :
    You don't have permission to view the spoiler content. Log in or register now.
    You don't have permission to view the spoiler content. Log in or register now.
  • Use Ren'py dynamic text inclusion when writing your dialog.

    By example, you can have these lines of dialog :
    Code:
    label somePartOfYourGame:
       "I can't, she's [mNames.fullFormal] !"
       "[mNames.Informal], come here please."
       "[sNames.Formal] ? She's [sNames.informal] and [sNames.fullFormal], why ?"
    Without the patch, the game will display :

    But with the patch, the player will see this :​

It's not clearly seen in the example, but you aren't limited to "formal", "informal" and "fullFormal". You can use the names you want and define as many as you want. Plus, if you put the first letter of this "name" in upper case, the text returned will also have it's first letter in upper case. So, by just writing this :
Code:
    mName = DynamicNames( fullFormal = "my teacher" )
You'll be able to have in your game both, mNames.fullFormal, which will return "my teacher", and mNames.FullFormal, which will return "My teacher". All this without any need to define it twice.



Now, you are ready to make your game using fully dynamics names, with a lot of names, or sentences, depending of the context. Then, with a single file and without editing a single line of your own code, anyone will be able to patch your game and change these "fully dynamics names".

But here come a problem. You are a good writer, there's a lot of things to change and it will make a really big line to write behind "mNames = DynamicNames(". Therefore, I made the works for you...

Attached to this message, you'll find an archive. In this archive you'll find a "script.rpy" file as example, and a file named "00DynamicNames.rpy", to put in your "[project name]\game" folder among all your game's code. This file will add the "defineDynamicName" and "define_dynamic_name" statements to Ren'py language. Use the one you prefer and write things like this :

Code:
init:
    defineDynamicName mName:
        formal "teacher"
        informal mGetName
        fullFormal "my teacher"
    defineDynamicName sName:
        formal sGetName
        informal "a budy"
        fullFormal "my classmate"
Like above, anyone will be able to patch your game just by writing a "patch.rpy" file which will have these "defineDynamicName" statements at an higher init level.
It will use the advanced class I've shown above, with a bonus side. You can now easily extend it by adding a line in the "dynamicName" block:

Code:
init:
    defineDynamicName mName:
        formal "teacher"
        informal mGetName
        fullFormal "my teacher"
        mcIs "one of my student"
    defineDynamicName sName:
        formal sGetName
        informal "a budy"
        fullFormal "my classmate"
Now, Laura have a new dynamic string, "mcIs". Repeat each time your writing make you add one thing to change...

In addition, it also add the "dynamicName" and "dynamic_name" statements, to use in a label this time, if you want "mName" and "sName" objects to be saved. But I do NOT recommend you to do this ; it will force the player to restart the game each time you add something.
By keeping the values defined at init time, you'll keep your game up to date, whatever it's a regular version or a patched one, and whatever time have past since the last version played by the player. It will also make it easier for a player to revert to an unpatched version by just removing the patch file.


Note:
Please don't misunderstood me. It appear that my examples make it look like you're trying to create an incest patch to work around the guideline of a certain site. It's obviously not my intent, nor yours, I know it. Who could want to do this kind of things, especially in a pirate site like this one...



If you found this useful, consider giving a like as thanks and/or encouragement to write more.


You don't have permission to view the spoiler content. Log in or register now.
This will help a lot there, nicely done and much appreciated. Somebody get this man a steak!
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,110
14,783
This will help a lot there, nicely done and much appreciated.
Don't hesitate to tell me if there's a problem.
Ren'py sometimes change a lots of things from a version to another. I tried to make the code as compatible as possible, but I can have missed a silent change.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,110
14,783
[coming from another thread]
Minor bug in the example? If I leave the "return" in, here:
Code:
init 1:
    defineDynamicName eName:
        formal ( "ms. ", getLauraName )
        informal getLauraName
        mcIs "one of my student"
        private "honey"
        relationWithMc "against school's rules"
        error "I'm the only one with this part"
    return
it throws an error
Code:
[...]
Exception: Unexpected return during the init phase.
Oop's, that's what you get when you start with a "label" to test everything then fallback to "init". Don't know why it haven't thrown the error when I tried it. Will correct this right now and downloadable the file in the first page. Will add the corrected file here (don't have the right to edit OP in this section).


Also having some issues running this in my own game, but I'm probably just doing something stupid, so will figure that one out :D
What issue ? And with what version of ren'py ?
It's not necessarily you who's doing something wrong. Each time a new version of Ren'py is released, there's a lot of undocumented change and bug correction. All the part related to user defined statements have had a change in almost all version since 6.99.12. I tried to take all them in account, but I can have missed one.
 
Last edited:
  • Like
Reactions: YaYa_UnTIN2

Afflyction

Member
Game Developer
May 7, 2017
190
613
I'm getting this error

I'm sorry, but an uncaught exception occurred.

While running game code:
File "game/script.rpy", line 17, in script
init python:
File "game/script.rpy", line 23, in <module>
bNames = DynamicNames( formal=bGetName, informal=bGetName, fullFormal="my friend" )
File "game/00DynamicNames.rpy", line 69, in __init__
v = eval( kwargs[k] )
TypeError: eval() arg 1 must be a string or code object
here is my code

Code:
 init python:
         def bGetName():
             return b.name
         def sGetName():
             return s.name

         bNames = DynamicNames( formal=bGetName, informal=bGetName, fullFormal="my friend" )
         sNames = DynamicNames( formal=sGetName, informal=sGetName, fullFormal="my friend" )
i most likely got it wrong somewhere
 

a meme

Member
Sep 26, 2017
274
259
I'm getting this error



here is my code

Code:
 init python:
         def bGetName():
             return b.name
         def sGetName():
             return s.name

         bNames = DynamicNames( formal=bGetName, informal=bGetName, fullFormal="my friend" )
         sNames = DynamicNames( formal=sGetName, informal=sGetName, fullFormal="my friend" )
i most likely got it wrong somewhere
Not a python user nor a programmer, but probably the () brackets after bGetName that are missing.
 

a meme

Member
Sep 26, 2017
274
259
it gives me an invalid syntex error with it gone
Try spaces before and after the = sign. In Ruby that gives you random errors as well.

Also make sure that your b.names are assigned a value. You declared them, but that's usually a null-type variable, not a string.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,110
14,783
I most likely got it wrong somewhere
Yes, because you use the python syntax. Prefer the Ren'py one, it's way simpler and it will work.
Basically the error happen because when using the Ren'py syntax, bGetName is passed as a string, which is later changed as a variable name by "eval".

Code:
 init:
    python:
         def bGetName():
             return b.name
         def sGetName():
             return s.name

    defineDynamicName bNames:
        formal bGetName
        informal bGetName
        fullFormal "my friend"
    defineDynamicName sNames:
        formal sGetName
        informal sGetName
        fullFormal "my friend"
This will work.

There's an ambiguity (and a typo) on the explanation but like I can't edit the first message :(
 

Afflyction

Member
Game Developer
May 7, 2017
190
613
Yes, because you use the python syntax. Prefer the Ren'py one, it's way simpler and it will work.
Basically the error happen because when using the Ren'py syntax, bGetName is passed as a string, which is later changed as a variable name by "eval".

Code:
 init:
    python:
         def bGetName():
             return b.name
         def sGetName():
             return s.name

    defineDynamicName bNames:
        formal bGetName
        informal bGetName
        fullFormal "my friend"
    defineDynamicName sNames:
        formal sGetName
        informal sGetName
        fullFormal "my friend"
This will work.

There's an ambiguity (and a typo) on the explanation but like I can't edit the first message :(
Ahh, so much better. thanks so much again!
 

Studio Errilhl

Member
Game Developer
Oct 16, 2017
315
235
I've been using this, but I was wondering if it's in any way possible to utilize the renpy.input() function and use that with this code?

I've done a couple tries, but I'm not entirely sure how I would get it to work properly.

Basically, I have a user-input for the MCs name. However, I would like to be able to modify the assignments for that characters interactions.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,110
14,783
Basically, I have a user-input for the MCs name. However, I would like to be able to modify the assignments for that characters interactions.
Oops, forgot to talk about this ? It's the job of tuples. When you wrote something like :
Code:
    def getMcName():
          return mcName

    defineDynamicName mName:
        mcIsForHer ( "my dear ", getMcName )
Then mName.mcIsForHer will be the combination of "my dear " and the value returned by getMcName(). Obviously you can go as far as you want with that and have something like :

Code:
    defineDynamicName mName:
        someValue ( "my dear ", getMcName, anotherSub, "some text", yetAnotherSub )
So it let you have both user defined values and also random one. By example you can have something like this :
Code:
    import random

    def randomHot():
         return random.choice( [ "hot", "exciting", "so beautiful" ] )

    defineDynamicName mName:
        momIsHot ( "mom, you're ", randomHot )
Which will randomly return "mom, you're hot", "mom, you're exciting" and "mom, you're so beautiful".

The example is dumb since you don't really need a tuple for a thing as simple, but well, it's to show the spirit.
 

Studio Errilhl

Member
Game Developer
Oct 16, 2017
315
235
Thanks for the clarification! :)
However, I'm still a bit unsure as to how I would do this with a renpy.input().

Would I use a regular renpy.input and just import that into the def... although I'm not entirely sure how that would work, since the input isn't defined when the def is read in...

Also, I was wondering how difficult it would be to also make an ALL CAPS version of the iterator.

Currently, I guess this is what picks whether or not it's with a capitalized first char:
Code:
return aValue if ord( aName[:1] ) >= 97 else aValue[:1].upper() + aValue[1:]
If it possible to enter an elif there that can account for all letters in the call-argument being capitalized? Something like
Code:
pName.FORMAL


I figured out the change of code:
Added this on the __init__:
Code:
self._alls[k[:1].upper() + k[1:].upper()] = v
and this on the __getattr__:
Code:
if ord(aName[:1]) >= 97:
                return aValue
            elif (65 < ord(aName[:1]) < 90) and (65 < ord(aName[-1:]) < 90):
                return aValue.upper()
            else:
                return aValue[:1].upper() + aValue[1:]
Not entirely sure it's the most efficient way of writing it, but it seems to work fine
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,110
14,783
Would I use a regular renpy.input and just import that into the def... although I'm not entirely sure how that would work, since the input isn't defined when the def is read in...
You said it's for mc's name, right ? So the best way to do it is probably this one :
Code:
init python:
   def mcName()
       return mc.name

label inputName:
   $ mc = Character( "MC" )
   $ mc.name = renpy.input( "what's your name", "default name" )
And use defineDynamicName like above.

In fact I used two trick here ; not sure that trying to explain them at 01:54 AM is a right idea, but I'll try.
Firstly I made it so every attribute of mName (and other objects created by defineDynamicName) are seen like scalar variable. This way, they can be used as intended, "blabla [mName.someAttribute]". Ren'py will think that he address a direct value. But in fact it's a function which will generate the returned value each time it will be called. This mean that everytime Ren'py will need it, the value will be computed again, and so always up to date.
The second trick is related to the way Python works with its functions. "functionName" return a reference to the code, while "functionName()" execute the code and return its "return value" if there's one.

If you combine the two, you'll have something which look like a scalar variable, but which will return a computed value always up to date according to the change you made elsewhere in your code.
So, at anytime you can change "mc.name", the function mcName() will always return the actual value. Before the input, it will return the default "MC" value. After the input, it will return the value defined by the user. And if at some time the user go to the console and change this value, then mcName() will return this new value.

You can try it this way :
Code:
init python:
    testValue = "something"

    def myTest():
        return testValue

init:
    defineDynamicName t:
        test myTest  

label start:
    "Default value is : [t.test]"
    $ testValue = "oh, it changed !"
    "Now the value is... [t.test]"
    $ testValue = renpy.input( "Please enter something", "" )
    "You typed : [t.test]"
    "Test done"
    return
Obviously it will work exactly in the same way if you use myTest inside a tuple instead of using it a single value like I did here.
 

Studio Errilhl

Member
Game Developer
Oct 16, 2017
315
235
Thanks! I edited my question above (with another question, really) with something I think I found a solution for. Could you just check it and see if it's horrible, or if it will work as intended?
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,110
14,783
Thanks! I edited my question above (with another question, really) with something I think I found a solution for. Could you just check it and see if it's horrible, or if it will work as intended?
Alright, keep in mind that it's now 2:35AM here :D

The spirit is here, and it should works. Well, except that it should be "aName[:-1]", you put the "-1" on the wrong part.
Another way to do it is this one :
Code:
        def __getattr__( self, aName ):
            if aName == "_alls": return super( DynamicNames, self).__getattribute__( aName )
            switch = aName[:1] == "_"
            if switch is True: aName = aName[1:]
            if not aName in self._alls: return "I FORGET TO SET THIS"
            aValue = ""
            for atom in self._alls[aName]: aValue += atom() if callable( atom ) else atom
            if ord( aName[:1] ) >= 97: return aValue[:1].upper() + aValue[1:]
            elif switch is True:       return aValue.upper()
            else:                      return aValue
It works like this :
t.myString -> Return the default value
t.MyString -> Return the value with a capitalized first letter
t._myString -> Return the value all in uppercase.

Both yours and mine do the same thing, and yours is way better because simpler...
I'll add it, once I can edit OP, and probably use my "_" prefix approach to do something else ; capitalized initial for each words can perhaps be useful.


Edit: Er... what I said ? No, the -1 was at the right place !!! alright, I go to sleep right now :)
 

Studio Errilhl

Member
Game Developer
Oct 16, 2017
315
235
Yeah, I'll keep mine for now, as it's (as you said) simpler, and I can use the exact same names as before, just capitalizing them, which sort of tells you how they work just by reading the code. But thanks for the update, the code you provided is helpful in that it's teaching me new things :)
 
  • Like
Reactions: anne O'nymous