Ren'Py Avoid an infinite loop of points with rollback

whoisLUNA

Newbie
Game Developer
Dec 24, 2019
25
94
Hi everyone

Right now I have a project that has a point system based on the choices the player makes.

The player has two options and one of them awards a point that is stored in a variable (I think). The game has the rollback feature enabled and my intention is not to remove it. The problem is that if the player uses the rollback and selects again the option that gives the point it is still added to the variable, which is not practical for the game.

I know that if I use the variables of the style "$ variable_x += 1" when the rollback is used that point is "eliminated", but the problem is that as it is a same system for several characters and with different variables I have a code that facilitates me to make the obtaining of points. The code is the following:

Code:
init python:
    class Actor():
        def __init__(self, character, name, affection, slotA, slotB):
            self.c = character
            self.name = name
            self.affect = affection
            self.slotA = slotA
            self.slotB = slotB

        def affection_up(self, amount): 
            self.affect += amount
            renpy.notify(f"{self.name} affection +{amount}")
            if self.affect < 0:   ### (this is to define the point limit, not related to the actual problem)
                self.affect = 0
            elif self.affect > 15:
                self.affect = 15
And inside the game code I have something like this:
Code:
 menu:
        "Option A":
            $ renpy.fix_rollback()
            "Ok. No problem"

        "Option B":      ### (The right one)
            $ renpy.fix_rollback()
            $ NAME.affection_up(1)      ### This line is the one that gives the point
            NAME.c "Oh... ha ha... I missed you too!"
The code works fine but the problem arises if the player makes rollback and chooses that option again because it continues adding points, which throughout the game is something that would cause a problem and I would like to know if there is a way to avoid this because I do not want to use the "$ variable_x += 1" style variables because of the lack of practicality that can generate me on the way.

Greetings.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,967
16,215
Code:
            self.c = character
[...]
            NAME.c "Oh... ha ha... I missed you too!"
Let me guess, you learned Ren'Py through tutorials found on the "Coding With B and E" youtube channel ? Do not follow their tutorials, they are pure piece of shit that lead to at least useless code, at worse bugged one.


As for your problem, it only happen if you declare your character with define NAME = Actor( [...] ).
What mean that the point being stacking up is the least of your concern. An object created that way will not be saved. Therefore, all the points will be lost the instant the player close the game, while they'll stay at their current value if he load a save made hours before or restart the game.

But if you declare it correctly, with default NAME = Actor( [...] ), then all your issues disappear.
Not only the object will be saved, and therefore the points will always be accurate when the player load, even if he closed the game in between, but also the object will be rollback compliant, and the point will be removed when the player rollback.

Of course, all this is explained in the documentation ( Versus ), but will not be obvious if some idiots tell you that you are , what is absolutely not the case here.
 

whoisLUNA

Newbie
Game Developer
Dec 24, 2019
25
94
But if you declare it correctly, with default NAME = Actor( [...] ), then all your issues disappear
Omg, it makes perfect sense.

Yes, I plead guilty to learning that particular system with those tutorials, it seemed to fit what I needed and I considered it the best option.

Clearly it makes all the sense in the world what you mention and I'm embarrassed I didn't think of that solution. Plus I've seen several of your responses in other threads and I definitely trust your wisdom more, thank you indeed.

Now, if it is possible, making that change from define to default do you think it is a good idea to continue with that point system or do you think there is a better alternative to that?

Greetings
 
  • Like
Reactions: Twistty

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,967
16,215
Now, if it is possible, making that change from define to default do you think it is a good idea to continue with that point system or do you think there is a better alternative to that?
Hmm...

Strictly speaking, there's no issue in using an object to regroup the information regarding a character. I would even advice for this if there's really many information regarding that character.
But the way it's done here, I'm absolutely not fan of it. Partly because it's confusing, as you seen yourself, and partly because there's redundant information and useless ones.

I know that there's an issue when you have two separate objects for the character, one for Ren'Py (Character), and one for the information and/or stats.
Creators are often facing this and wonder how they'll name each one. They often opt for something like "firstname" and "firstname_stats", what feel a bit too much. But there's another solution.
Variable names are case dependent. MyVar isn't the same than myvar by example. Then, why not just opt for the simplest solution ?
Python:
define ANNA = Character( "Anna" [...] )
default anna = Actor( [...] )
Since it's either all uppercase or all lowercase, there's no risk of syntax error. If it was "Anna" versus "anna", you could mistype, forgetting the leading uppercase. And you could pass hours looking at your code, without noticing that small error.
But when it's the whole name that change case, you'll be less likely to mistype it, and you would notice it way faster.

Starting there, Actor do not anymore need this "c" attribute.

It also don't need the "name" attribute, that is redundant and deprive you (and the player) from some freedom.
All Character object already have a "name" attribute (ANNA.name would return "Anna").
And the value returned is always accurate, even if the player changed the name in a way or another. Or if yourself changed it.



More globally, my advice is to stay as simple as possible... As long as you understand the logic behind that simplicity.

By example, if the only information you need is the name of the character and that affection stat, there's no need for an object. Something like this would do the same, without really being more difficult to use:
Python:
init python:

    #  Provide a default value for the increment/decrement.
    def affect( name, step=1 ):
        # A security for you. If the variable do not exist, you'll know it.
        if not hasattr( store, name + "_affect" ):
            raise AttributeError( "There's no affection stats for this character" )

        #  Get the current affection value. It's not really necessary to store it in a
        # variable. It just permit to not have to write the "getattr" part every time.
        value = getattr( store, name + "_affect" )

        #  Change the value.
        value += step

        #  If the value is too low, stay at the lowest possible value
        if value < 0:
            value = 0
        #  Same if the value is too high
        elif value > 15:
            value = 1
        
        # Finally, store the new value
        setattr( store, name + "_affect", value )


default anna_affect = 0

label whatever:
    menu:
        "Option A":
            "Ok. No problem"

        "Option B":
            $ affect( "anna", 1 )
            #  Like there's a default value for "change", it could also be wrote like this
            # $ affect( "anna" )
            #  The affection would automatically be raised by the default value, therefore by 1
            ANNA "Oh... ha ha... I missed you too!"

        "Option C":
            $ affect( "anna", -1 )
            ANNA "What ? You're a jerk !!!"
By itself, the code is more complex, but using it isn't, and in the end it's what matters.

Simplest do not mean "short", "optimized", and do not even regard the "hidden code". What need to be simplest is what you'll use the most.

And, in top of that, there's also a question of preference. Strictly speaking, in terms of simplicity affect( "anna", -1 ) is equivalent to anna.affection( -1 ). But you can prefer to put the name first ("I'll change anna's affection"), or prefer to put it after ("I'll change the affection value for Anna").
It's just a question of phrasing, but it have its importance, because it's the translation of your internal logic. And if you have to constantly goes against it, it's tiring...



Side note:
The code of the function can be shorter thanks to min() and max(). I'll give it as example, but do not advice for its use at first.
Python:
init python:

    def stats( name, step=1 ):
        if not hasattr( store, name + "_affect" ):
            raise AttributeError( "There's no affection stats for this character" )

        # All in one:
        # - Add the change
        # - Get the maximal value between 0 and the affection
        # If it's below 0 it will be 0
        # - Get the minimal value between 15 and the affection
        # If it's above 15, it will be 15
        setattr( store, name + "_affect", max( 0, min( 15, getattr( store, name + "_affect" ) + step ) ) )
One line in place of seven, but way more complex to understand at first sigh. And understanding is the most important point.
 

whoisLUNA

Newbie
Game Developer
Dec 24, 2019
25
94
I really appreciate the time you have given me for these examples, thank you very much!

The way you explain it I understand it and I think it would be a better alternative for my code. I will find a way to adapt it to my code. :)
 
  • Like
Reactions: anne O'nymous