Replace word in Renpy

Rb.Elliot

Member
May 16, 2018
119
37
is there any way to replace a specific word in renpy commend console?
Ex:
i wanna change "sister" word to "xyz"
this word mention like thousand time in a game, also 100s of scripts for each scene, so it's impossible to change one by one. i wanna change all at once.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,499
8,035
That's why you use variables.

-edit-
However, to answer your question, It depends on the text editor/IDE you are using.

With Visual Studio it is possible with CTRL+SHIFT+F I believe... (Not really sure tbh... Only used it once).
With Atom:

I just listed two but you will need to specify which editor you are using...
 
  • Like
Reactions: Rb.Elliot

TCMS

Quote my posts if you want an answer
Donor
Former Staff
Aug 5, 2016
5,797
30,903
this word mention like thousand time in a game, also 100s of scripts for each scene, so it's impossible to change one by one. i wanna change all at once.
Open all rpy files with notepad++.
CTR+H
Use these settings

Click the replace All in all Opened Documents
CTRL + Shift + S

Explanation:
CTRL + H -> replace menu
Match whole world only -> if your word might have variants that have more letters you wouldn't want the program to change them (e.g You're changing the word "var" you wouldn't want the word "variant" to be changed)
Wrap around unticked -> if the images reference the word you're changing and you have this option ticked it will fuck up the images.
CTRL + Shift + S -> save all files
 

wurg

Active Member
Modder
Apr 19, 2018
705
1,654
You can use regular expressions for a game that is completed, there is a way to do this, I can't explain how it works completely. Look at the incest mod for Freeloading family, at the bottom there is a structure to replace single words in the game, one for another, when they appear. @bossapplesauce and @anne O'nymous can explain this far better than I can. I'll attach a small example that I made for a game. The main part of the one I made is the top part to replace some French lines that were left in the game, the bottom was included to make the top part work. For some reason you need both even if one will never comes true ( the bottom part in mine ). The bottom part will look for 'lydia' anywhere in the dialog and replace it with the variable 'friend_name'. @Winterfire can probably explain the block of code also.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,499
8,035
Yeah, Regex... I would use that for string editing or to avoid certain input (like allowing only numbers), I would not really use it for something that can be replaced only once by the dev manually.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,963
16,203
USER=25819]@bossapplesauce[/USER] and @anne O'nymous can explain this far better than I can.
It rely on the callback, which is called each time a line of text will be displayed, either as dialog line or as menu.
Taking your code (simplified and corrected) as explanation :
Code:
init python:
    # Here are defined the string that will be completely changed.
    # Before the ':' is the original string, and after it is the replacement one.
    # So, when the game want to display, "je dis des choses", it's "I hope
    # everything will be ok between me and [sister_name]." that will be 
    # effectively shown to the player. Note that the presence of 
    # "[sister_name]" imply that the string substitution will be performed
    # AFTER the call to the /callback/, not before.
    hardChanged = {
        "Je dis des choses.":"I hope everything will be ok between me and [sister_name].",
        "Ha c'est la sista ":"It's [sister_name]",
        "{i}Je ne regrette rien de ce qui c'est passé blalal.":"{i}I do not regret anything that happened.",
        "Cool, j'vais me poser sur l'canap": "Neither do I, that's a relief, I need to sit down for a bit."
        }

    # Here is the /callback/ itself. It take the text to display as parameter,
    # and MUST return the string that will be displayed.
    def TwoWeeks (text):

        # This is an internal function, it can only be called from inside the
        # /TwoWeeks/ function. Doing so help to keep it clean.
        # This function is called by "renpy.re.sub", and pass the result of
        # the RegEx as parameter.
        # Then, he return the value to replace according to the value
        # effectively found. See bellow.
        def lang (t):
            # If the keyword found is /lydia/, it will be replaced by the
            # value of /friend_name/.
            if t.group(0) == "lydia": return friend_name
           # If the keyword is inside the given list (so either /sister/ or
           # /sista/), it will be replaced by the value of /sister_name/.
            if t.group(0) in [ "sister", "sista"] : return sister_name

        # Here the /callback/ will first test if the string to display exist
        # in the /hardChanged/ dictionary defined above. If yes, it will
        # return the replacing value.
        if text in hardChanged:
            return hardChanged[text] 
        # If not, the /callback/ will search for a list of keyword expressed
        # as a RegEx. If one of the keyword is found, the function /lang/
        # will be called to perform the replacement before returning the
        # string. Else the string will be returned as it.
        else:
            # The list of keyword is what inside the parenthesis. So,
            # here the callback will replace /lydia/, /sister/ and /sista/.
            return renpy.re.sub(r'(?:lydia|sister|sista)', lang, text)

    # Finally, here we tell to Ren'py to use the /TwoWeeks/ function as
    # callback.
    config.say_menu_text_filter = TwoWeeks
Now, for what @FallenDuke want, it's this code :
Code:
init python:

    def noSister(text):
        def lang (t):
            if t.group(0) in [ "sister", "sis"] : return sister_name

        return renpy.re.sub(r'(?:sister|sis)', lang, text)

    config.say_menu_text_filter = noSister

define sister_name = "xyz"

Yeah, Regex... I would use that for string editing or to avoid certain input (like allowing only numbers), I would not really use it for something that can be replaced only once by the dev manually.
The regEx's implementation is less effective in Python than in Perl, or even Ruby (while being clearly designed for optimized regEx), still it's fast enough for being transparent and not affecting the game speed. This even taking in count that it use re.sub instead of working directly with a re object ; which imply that the regEx is recompiled every single time a line is displayed. On a seven years old computer, the regEx used in @bossapplesauce 's incest patch for Freeloading Family take less than 0.2 milliseconds to understand that none of the 16 keywords defined are in an average string, against around 0.05 milliseconds when one is present.

This said, beyond the speed problem, there's a practical reason to use this callback, and so a regEx, in place of hard replacement directly on the source : here, games are released update by update.
With the callback, just put it in a rpy file, and put this file inside the game directory, it will works every times. This while a hard change generally imply that you must start by extracting each file from the rpa archive, eventually un.rpyc the source, then edit each one of the rpy files to make the change.
For an average game, count more or less 15 minutes for this. So, the game need 4 500 000 lines displayed for the callback method to be slower on a seven years old computer ; without talking about the fact that it imply less manipulations coming from the player.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,499
8,035
I would still consider it bad practice, all IDE seem to have an option to replace a word in the whole project/file at once manually.
Even with updates, doing a manual replace would not take too long even if imho devs should put those things in a variable.

I did not know the exact numbers but I am actually impressed that it is that fast, in a visual novel such delay would not be noticeable so realistically it makes no difference even if I would still consider it an "ugly fix".

-edit-
Oh wait, I misunderstood.
You mean that the patch is needed only once rather than having to update the patch everytime... Yeah, if that is the case, it does make sense then.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,963
16,203
I would still consider it bad practice, [...]
It's because you come from C/C++ world (if I remember correctly what you said once). I past most of my life using Perl, where something like :
Code:
for( $var )
{
    /^this$/ && do{ something(); last; };
    /^that$/ && do{ otherthing; last; };
   error();
}
is most of the time (it need more than just two entries) faster, and more readable, than :
Code:
if( $var eq "this" )
{ something(); }
elsif( $var eq "that" )
{ otherthing();
else
{ error(); }

I did not know the exact numbers but I am actually impressed that it is that fast,
Didn't found a more generic benchmark, but you can look at . It focus on a flaw of Perl implementation, so a single kind of regEx ; but by chance it's exactly the one used here. It clearly demonstrate why regEx are massively used in Perl, and that the Python implementation isn't really effective.
Yet, between 25 and 80 micro second to perform a basic "FOO | BAR" motif is fast enough ; you can also see that globally the time needed tend to stay constant. False positive string (seem to be, but in fact isn't) don't really take more time depending of the place where the false positive is located. While founding the second motif or the first one don't really imply more times.
You can also see that the way the regEx is wrote have an effective impact on the time that will be needed.

This said, those data apply for script languages. It's probably slower with compiled language, because the regEx implementation is generally a more basic. An effectively optimized one would need more works to be integrated to the code.


You mean that the patch is needed only once rather than having to update the patch everytime...
Yes, that's what I mean.
 

Rb.Elliot

Member
May 16, 2018
119
37
thanks a TON :love: really appreciate it. some of was easy n some of then was little bit tricky. but, in the end got them all.
thank you all, it was really helpful also educational!!
 
  • Like
Reactions: anne O'nymous

yoyomistro

Engaged Member
Jan 15, 2017
2,939
4,026
anne O'nymous Is there anyway to include a hyphen in a replace function? I've been trying for the past half-hour and it's driving me up the fucking wall. IDK if I can write the in a way that excludes the regex, but this is the only method I've found that's effective for what I'm doing... I got this script directly from a response from PyTom to someone else.
Code:
init python:
    import re
    def word_replace(text):
        """
        take a text and replace words that match the key in a dictionary
        with the associated value, return the changed text
        """

        def translate(match):
            word = match.group(0)
            return replace_dict.get(word, word)
      
        return re.sub(r"\b[A-Za-z_]\w*\b", translate, text)

# create a dictionary of key_word:replace_with pairs
    replace_dict = {
    "stepmother" : "mother",
    "stepsister" : "sister",
    "stepsisters" : "sisters",
    "stepbrother" : "brother",
    "step-mother" : "mother",
    "step-sister" : "sister",
    "step-sisters" : "sisters",
    "step-brother" : "brother",
    "stepmom" : "mom",
    "stepsis" : "sis",
    "stepbro" : "bro",
    "step-mom" : "mom",
    "step-sis" : "sis",
    "step-bro" : "bro"
    }

define config.say_menu_text_filter =  word_replace
So basically it works for my purposes except for the hyphenated ones... I've tried escaping it like \- or \\-, placing it at the beginning or the end of the range, honestly I'm not great with regex so that's probably why I'm getting stuck.
 
  • Like
Reactions: alex76dvd and Phnx

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,963
16,203
anne O'nymous Is there anyway to include a hyphen in a replace function?
Yes, all characters can be used.

But I guess that your problem come from the RegEx. The problem you face come from the fact that \w mean "any letter and number", what mean a-z, A-Z, 0-9 and _ ; so what don't include the hyhpen.

I'm way more familiar with Perl RegEx, always struggle with Python's ones, had a fucking hard day, and struggle with the persistent file... So, not the best condition to answer ;) But normally this RegEx should works : r"\b[A-Za-z_][-\w]*\b"
 
  • Like
Reactions: yoyomistro

yoyomistro

Engaged Member
Jan 15, 2017
2,939
4,026
Yes, all characters can be used.

But I guess that your problem come from the RegEx. The problem you face come from the fact that \w mean "any letter and number", what mean a-z, A-Z, 0-9 and _ ; so what don't include the hyhpen.

I'm way more familiar with Perl RegEx, always struggle with Python's ones, had a fucking hard day, and struggle with the persistent file... So, not the best condition to answer ;) But normally this RegEx should works : r"\b[A-Za-z_][-\w]*\b"
Thanks, I took out the \w* and the \b and it didn't work, but your configuration did. I always appreciate your expertise, man.