[solved] Rollback/Custom Class/Function call

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
Ragnarok does VN
Newbie
Posts: 7
Joined: Sat Aug 06, 2016 2:56 pm
Projects: Shapes
Contact:

[solved] Rollback/Custom Class/Function call

#1 Post by Ragnarok does VN » Sat Aug 06, 2016 4:01 pm

Hi everyone!

While I am new to Ren'Py specifically and Python generally I am used to object orientated programming, so naturally I applied my knowledge to a little project.

Everything compiles fine, but the rollback feature, which I really want to use, creates some undesireable outcomes.
At a base level, variable values aren't rolled back.

The 'exact' code, that causes my distress is:

Code: Select all

$ story.relationship_circle.face.negative.minor()
#...
chars.circle "sometext"
When I roll back to it, the custom function simply gets called again, but the increment

Code: Select all

def minor(self):
    self._minor += 1
isn't reverted. Essentially each time I scroll up/down the function gets called again and won't be reverted

The whole construct is initialised as

Code: Select all

define chars.circle = Character("a name", kind=nvl, color="#ffaaaa") #bonus initialisation for completeness
# ...
define story.relationship_circle = AvoidableMeter()
I think I am simply not aware of the limits the rollback feature implies to coding, so please enlighten me.
EDIT: I am also able to reproduce the behaviour on each call of a method of a Meter instance, e.i. each time "I step" to the function call, it will get performed, not matter if basic continue, rollback or rollforward

On a side notice, the function call and the say statement above are separated by a call and an if-else construct, although I don't think that it affects the situation.

The relevant class definition is this:

Code: Select all

init -100 python:
    
    class Meter:
        def __init__(self):
            self._trivial = 0
            self._minor = 0
            self._major = 0
            self._key = 0
            self._critical = 0
            self._keyevents = []
            self._criticalevents = []
        
        def trivial(self):
            self._trivial += 1
            
        def minor(self):
            self._minor += 1
            
        def major(self):
            self._major += 1
            
        def key(self, eventname):
            self._key += 1
            self._keyevents.append(eventname)
            
        def critical(self, eventname):
            self._critical += 1
            self._criticalevents.append(eventname)
    
    class FacettableMeter:
        def __init__(self):
            self.positive = Meter()
            self.negative = Meter()
            
    class AvoidableMeter:
        def __init__(self):
            self.avoid = FacettableMeter()
            self.face = FacettableMeter()
Last edited by Ragnarok does VN on Sun Aug 07, 2016 11:14 am, edited 1 time in total.

User avatar
trooper6
Lemma-Class Veteran
Posts: 3612
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Rollback/Custom Class/Function call - undiserable outcom

#2 Post by trooper6 » Sat Aug 06, 2016 10:11 pm

Your Meter class should inherit from the object class. I am currently in a theatre lobby during intermission, so I can't quote code at you, but if you look at my post in this thread, it will show you how to do that: viewtopic.php?f=8&t=39256&p=420729&hili ... ry#p420729
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

Ragnarok does VN
Newbie
Posts: 7
Joined: Sat Aug 06, 2016 2:56 pm
Projects: Shapes
Contact:

Re: Rollback/Custom Class/Function call - undiserable outcom

#3 Post by Ragnarok does VN » Sun Aug 07, 2016 9:30 am

So I tried what you suggested, which means each of the Meter classes directly inherits from object now.

It didn't fix the problem.

I also tried to increment the attribute directly, like suggested in the linked thread. It looks like this now:

Code: Select all

$ story.relationship_circle.avoid.negative._trivial += 1
That didn't fix it either.
Then I tried to let just the "Meter" class inherit from object, not the other ones, the literal suggestion. Which changed nothing like expected. I reverted it to let all xMeter classes inherit from object.

I then suspected nvl-mode to be the culprit, but it turned out the bug also persists in adv.

I tried the regular save feature, but it seems no values of the whole construct get saved.

Also, maybe I was wrong with thinking that the "call [label]"-"return" statement tree wasn't a problem, here is an overview:

Code: Select all

## relevant content of script.rpy

label start:
    
    # ... some other call we always return from ...
    
    call line_introduction
    
    $ result = str(story.relationship_circle)
    narrator "story.relationship_circle = [result]" ## Tag: result (for this discussion)

    # ... some more str(story.x) ...

    return

################################
## relevant content of line_introduction.rpy

label line_introduction:

    # ... some other call we always return from ...

    call line_introduction_credit_question

    return

# ... some code ...

label line_introduction_credit_question:

    # ... some dialog ....

    if trigger.credit_granted:
        # ... other WIP call, currently not used
    else:
        call line_introduction_credit_not_granted
    
    return

# ... some code ...

label line_introduction_credit_not_granted:

    # ... some dialog ....
    
    menu:
        chars.line "question"
        
        "option 1":
            nvl clear
            
            chars.line "say option 1" ## tag: option 1 (for this discussion)
            $ story.relationship_circle.avoid.negative.trivial() ## tag: option 1 fcall A
            $ story.relationship_circle.face.negative.minor() ## tag: option 1 fcall B
            
            call line_introduction_for_better_or_worse(positivereaction=False)
        
        "option 2":
            nvl clear
            
            chars.line "say option 2" ## tag: option 2
            $ story.relationship_circle.face.negative.minor() ## tag: option 2 fcall
            
            call line_introduction_for_better_or_worse(positivereaction=False)
        
        "option 3":
            nvl clear
             
            chars.line "say option 3" ## tag: option 3
            $ story.relationship_circle.avoid.negative.trivial() ## tag: option 3 fcall
            
            call line_introduction_for_better_or_worse(positivereaction=True)
          
    return

label line_introduction_for_better_or_worse(positivereaction=True):
    if positivereaction:
        chars.circle "some positive reaction" ## tag: reaction positive
    else:
        chars.circle "some negative reaction" ## tag: reaction negative
    
    return
The behaviour is as follows:
  • If I rollback from result to reaction positive, then rollforward to result again, story.relationship_circle.avoid.negative._trivial is incremented once, likely by option 3 fcall
  • If I rollback from result to reaction positive, then rollback to option3 result and then rollforward 2 times to result again, story.relationship_circle.avoid.negative._trivial is incremented twice, again by 2 option 3 fcalls
  • The other paths show the same behaviour, as expected
For completeness, here are the full xMeter classes:

Code: Select all

init -100 python:
    
    class Meter(object):
        def __init__(self):
            self._trivial = 0
            self._minor = 0
            self._major = 0
            self._key = 0
            self._critical = 0
            self._keyevents = []
            self._criticalevents = []
            
        def __str__(self):
            return "Meter{{trv="+str(self._trivial)+", mnr="+str(self._minor)+", mjr="+str(self._major)+", key="+str(self._key)+", crt="+str(self._critical)+"}}"
        
        def trivial(self):
            self._trivial += 1
            
        def minor(self):
            self._minor += 1
            
        def major(self):
            self._major += 1
            
        def key(self, eventname):
            self._key += 1
            self._keyevents.append(eventname)
            
        def critical(self, eventname):
            self._critical += 1
            self._criticalevents.append(eventname)
        
        def result(self):
            return self._trivial * 1 + self._minor * 3 + self._major * 7 + self._key * 15 + self._critical * 31
    
    class FacettableMeter(object):
        def __init__(self):
            self.positive = Meter()
            self.negative = Meter()
            
        def __str__(self):
            return "FacettableMeter{{pos="+str(self.positive)+", neg="+str(self.negative)+"}}"
        
        def result(self):
            return self.positive.result()-self.negative.result()
            
    class AvoidableMeter(object):
        def __init__(self):
            self.avoid = FacettableMeter()
            self.face = FacettableMeter()
            
        def __str__(self):
            return "AvoidableMeter{{fce="+str(self.face)+", avd="+str(self.avoid)+"}}"
            
        def result(self):
            return self.face.result()-self.avoid.result()

User avatar
PyTom
Ren'Py Creator
Posts: 15426
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Rollback/Custom Class/Function call - undiserable outcom

#4 Post by PyTom » Sun Aug 07, 2016 10:27 am

If you want an object to be rolled-back, you have to created it using a default statement, not a define statement.

Define runs at init time, and objects reachable from variables that have been created at init time are not rolled back. Default runs at game-start time, and hence those objects participate in rollback.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
"Silly and fun things are important." - Elon Musk
Software > Drama • https://www.patreon.com/renpytom

Ragnarok does VN
Newbie
Posts: 7
Joined: Sat Aug 06, 2016 2:56 pm
Projects: Shapes
Contact:

Re: Rollback/Custom Class/Function call - undiserable outcom

#5 Post by Ragnarok does VN » Sun Aug 07, 2016 11:13 am

Thanks a lot! It runs perfectly now.

Post Reply

Who is online

Users browsing this forum: Google [Bot], QtPaTT