I need some help with RenPy programing.

John Walker Black

Member
Game Developer
Feb 17, 2018
235
439
A few months back I started working on a project with the help of a Programmer but he had to abandon the project due to his hectic college curriculum. I would like to continue the project myself but am limited on my python programming skills and hoping someone here can help.

What I am trying to accomplish is a random Fail or Success chance event based on the character's skill level, which increases the chance of success as the character's skill increases.(Stealth , Persuasion.) And would like to incorporate it into my current BaseStats script "if Possible" that I am using for many data options in the game.

Here is the script I am using.
Code:
init python:

    class BaseStatsObject(object):
        """
        Base class for defaulting stats and integrating with the store.

        Designed to be extended by just overloading the constants


        Example of extended class

        class EnemyStats(BaseStatsObject):

            # Set the store.{prefix}.character_id value
            STORE_PREFIX = "enemy_stats"

            # Boolean toggle for validation - defaults both True
            VALIDATE_VALUES = True
            COERCE_VALUES = False

            STAT_DEFAULTS = {
                'element' : 'earth',
                'hp' : 50,
                'mp' : 40,
                'rarity' : 0.075,
            }

        """

        STORE_PREFIX = "character_stats"
        VALIDATE_VALUES = True
        COERCE_VALUES = True

        STAT_DEFAULTS = {}

        def __init__(self, id, **kwargs):
            """
            Initialize values from store or kwargs or default

            @param id: A unique id to use in the store. Generally set to
            the Character reference to allow cross object lookups

            @param **kwargs: Setup values that are not default
            """

            if not isinstance(id, basestring):
                id = str(id) # should raise if not stringable

            self.__dict__['_id'] = id

            self.run_optional_method( '__pre_init__', id, **kwargs )

            store_name = "{prefix}.{suffix}".format(
                prefix = type(self).STORE_PREFIX,
                suffix = self.__dict__['_id'] )

            setattr(store, store_name, {})

            self.__dict__['_store'] = getattr(store, store_name)

            # We use:
                # Store value
                # else kwargs value
                # else default value

            for key, value in kwargs.items():

                if key not in self.__dict__['_store']:

                    setattr(self, key, value)

            for key, value in type(self).STAT_DEFAULTS.items():

                if key not in self.__dict__['_store']:

                    setattr(self, key, value)

            self.run_optional_method( '__post_init__', id, **kwargs )


        def run_optional_method(self,
                                method_type='post_init',
                                *args,
                                **kwargs):
            """
            Run a method of the object if it exists
            """
            try:
                getattr( self, self.__dict__[ method_type ] )( *args,
                                                               **kwargs )
            except:
                pass


        def get_validated_value(self, key, value):
            """
            Return a value after validating where applicable
            """

            if not type(self).VALIDATE_VALUES:
                return value

            if not key in self.__dict__:
                return value

            default_type = type( self.__dict__[key] )

            if isinstance(value, default_type):
                return value

            if type(self).COERCE_VALUES:
                try:
                    return default_type(value)
                except:
                    pass

            raise TypeError, "Supplied value '{0}' for key '{1}' does not " \
                             "match the existing '{2}'".format(
                                value,
                                key,
                                default_type)


        def __setattr__(self, key, value):

            value = self.get_validated_value(key, value)

            self.__dict__[key] = value

            # Anything not recognized as an attribute of object
            # is placed into the store

            if key not in dir(object):

                self.__dict__['_store'][key] = value


        def __getattr__(self, key):

            try:

                return self.__dict__['_store'][key]
            
            except:
            
                if key in self.__dict__:

                    return self.__dict__[key]
                
                else:

                    try:

                        # try the character object
                        value = getattr(
                                    getattr( character, self._id ),
                                             key )

                        if key != 'name':
                        
                            return value

                        # substitute the name (for interpolation/translations)
                        return renpy.substitutions.substitute(value)[0]

                    except:
                        
                        pass
 
            return super(BaseStatsObject, self).__getattr__(key)


        def __getattribute__(self, key):

            # Check if the attribute is an @property first

            v = object.__getattribute__(self, key)

            if hasattr(v, '__get__'):

                return v.__get__(None, self)

            # Try the store if the attribute is not in base object

            if key not in dir(object):

                try:
                
                    return self.__dict__['_store'][key]
                
                except:
                
                    pass

            return super(BaseStatsObject, self).__getattribute__(key)
    

        def __setstate__(self, data):
            self.__dict__.update(data)


        def __getstate__(self):
            return self.__dict__


        def __delattr__(self, key):
            del self.__dict__[key]
and the initiate:
Code:
init python:

    import math

    class CharacterStats(BaseStatsObject):

        # Set the store.{prefix}.character_id value
        # STORE_PREFIX = "character_stats"

        # Boolean toggle for validation - defaults both True
        # VALIDATE_VALUES = False
        # COERCE_VALUES = False

        STAT_DEFAULTS = {
            'gender' : 'f',
            'age' : 0,
            'location' : 'Brooklyn',
            'affection' : 0,
            'anger' : 0,
            'corruption' : 0,
            'jealous' : 0,
            'feel':'fine',
            'skill' : 0,
            'persuade':0,
            'stealth':0,
            'libido' : 0,
            'pref' : 0,
            'cloth': 0,
            'swim': 0,
            'lingerie': 0,
            'fetish': 0,
            'nude': 0,
            'masterbate': 0,
            'porn': 0,
            'tlove' : 'Acquainted',
            'tanger' : 'Happy',
            'tskill' : 'Novice',
            'tpref' : 'Straight',
            'size':'6.0',
            'title':'Title',
            'name':'Name',
            'fname':'Name',
            'nick':'Name',
            'pname':'Name',
            'gay':0,
            'loan':0.0,
            'inter1':0.02,
            'inter2':0.006,
            'who':'Name',
            'status':'status',
            'status2':'status2',
            'partner':0,
            'partner2':0,
            'mlocation':'location',
            'kiss':0,
            'grope':0,
            'handjob':0,
            'titjob':0,
            'footjob':0,
            'blowjob':0,
            'finger':0,
            'licking':0,
            'vaginal':0,
            'anal':0,
            'blackmail':0,
            'gift':0,
            'hire':'False',
            'date':'False',
            'owed':0,
            'strong':0,
I am also using a random weighted choice script too. to reduce the tiresome game grinding so I need to make sure it doesn't cause errors (or maybe there is a way to combine them)
here is what that script looks like.

Code:
label choice_Jump(*choice_targets):
    # *args can be lists/tuples:
    # [ 'labelname' (string), 5 (integer weight) ]
    # [ 'labelname' (string) ] # given weight 1
    #  'labelname' (string) # expanded to [ 'labelname', 1 ]

    # build a list of weighted choices
    $ choice_jumps = [g for h in [
               [k[0]]*k[1] for k in [
                [j,1] if isinstance(j, basestring)
                else j if len(j)>1
                else [j[0],1] for j in choice_targets]
               ]
              for g in h]
              
    # choose one from the list
    $ choice_target = choice_jumps[renpy.random.randint(0,len(choice_jumps)-1)]
    # could use " $ choice_target = renpy.random.choice( choice_jumps ) " instead
    
    ## comment out these to avoid the debug dialogue
    #"Jumps: [choice_jumps]"
    #"Goto: [choice_target]"
    
    jump expression choice_target
I use this statement to call the choice jump script:

label marsleepTwo:
call choice_Jump( ['marCatchOne',2], ['marCatchThree', 2], 'marCatchTwo' ) from _call_choice_Jump_14

Any help to figure out how I can accomplish this while using the Base Stats would be greatly appreciated. Or if it can not be done maybe some help with some kind of choice jump that is weighted by the characters skill level.

Thanks in advanced.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,382
15,290
[Note: It's case like this that make me hate the fact that you can't anymore quote OP :( ]

Python:
#   class BaseStatsObject(object):
   class BaseStatsObject(renpy.python.RevertableObject):

        def __init__(self, id, **kwargs):
            if not isinstance(id, basestring):
                id = str(id) # should raise if not stringable

#            self.__dict__['_id'] = id
            self._id = id

            self.run_optional_method( '__pre_init__', id, **kwargs )

#            store_name = "{prefix}.{suffix}".format(
#                prefix = type(self).STORE_PREFIX,
#                suffix = self.__dict__['_id'] )
            store_name = "{}_{}".format( self.STORE_PREFIX, self._id )

#  No, no and no !!!! It's ridiculously plain stupid to create variables 
# with a dot in their name but that aren't objects.method !!!
# "character_stats.WHATEVER" should refer to the "WHATEVER" 
# attribute of the "character_stats" object, never to a plain variable !
# Reason why I replaced it by an underscore.
            setattr(store, store_name, {})

#            self.__dict__['_store'] = getattr(store, store_name)
            self._store = getattr(store, store_name)

            # We use:
                # Store value
                # else kwargs value
                # else default value
# While default -> kwargs -> store would have been simpler
# And would have make you benefit from the 
# /get_validated_value/ method right from the start.
#            for key, value in kwargs.items():
#                if key not in self.__dict__['_store']:
#                    setattr(self, key, value)
#            for key, value in type(self).STAT_DEFAULTS.items():
#                if key not in self.__dict__['_store']:
#                    setattr(self, key, value)

            for k in self.STAT_DEFAULTS:
                setattr( self, k, self.STAT_DEFAULTS[k] )
            for k in kwargs:
                setattr( self, k, self.kwargs[k] )
# Note that this is in fact *totally* useless, since the value
# will *always* be {}.
            for k in self._store:
                setattr( self, k, self.self._store[k] )

            self.run_optional_method( '__post_init__', id, **kwargs )

        def run_optional_method(self,
                                method_type='post_init',
                                *args,
                                **kwargs):
#            try:
#                getattr( self, self.__dict__[ method_type ] )( *args,
#                                                               **kwargs )
#            except:
#                pass
            if hasattr( self, method_type ):
                getattr( self, method_type )( *args, **kwargs )


        def get_validated_value(self, key, value):

#            if not type(self).VALIDATE_VALUES:
            if not self.VALIDATE_VALUES:
                return value

#            if not key in self.__dict__:
            if not hasattr( self, key ):
                return value

            default_type = type( self.__dict__[key] )

            if isinstance(value, default_type):
                return value

#            if type(self).COERCE_VALUES:
            if self.COERCE_VALUES:
                try:
                    return default_type(value)
                except:
                    pass

            raise TypeError, "Supplied value '{0}' for key '{1}' does not " \
                             "match the existing '{2}'".format(
                                value,
                                key,
                                default_type)


        def __setattr__(self, key, value):

            value = self.get_validated_value(key, value)

#            self.__dict__[key] = value
            super( BaseStatsObject, self ).__setattr__( key, value )

            # Anything not recognized as an attribute of object
#            # is placed into the store
            # is duplicated into the store
# which will lead to save breaking if it happen that what you 
# set is a code attribute and not a value one.

            if key not in dir(object):
#                self.__dict__['_store'][key] = value
                self._store[key] = value


        def __getattr__(self, key):
# What an over complicated thing :/
#            try:
#               return self.__dict__['_store'][key]
#            except:
#                if key in self.__dict__:
#                    return self.__dict__[key]
#                else:
#                    try:
#                        # try the character object
#                        value = getattr(
#                                    getattr( character, self._id ),
#                                             key )
#                        if key != 'name':
#                            return value
#                        # substitute the name (for interpolation/translations)
#                        return renpy.substitutions.substitute(value)[0]
#                    except:
#                        pass
#             return super(BaseStatsObject, self).__getattr__(key)

               if key in self._store:
                   return self._store[key]

# It should have been done like this, *but*
# /__getattr__/ is called *only* when the attribute don't 
# exist ; therefore /key/ will *never* be in /__dict__/.
#               if key in self.__dict__:
#                   return self.__dict__[key]

               # try the character object
# Will never works, there's no local variables named /character/
#              value = getattr( getattr( character, self._id ), key )
# I assume it meant /store.character/
               if hasattr( store, "character" ) and hasattr( store.character, self._id ):
                  value = getattr( getattr( store.character, self._id ), key, None )

                  if key != 'name':
                      return value
                  # substitute the name (for interpolation/translations)
                  return renpy.substitutions.substitute(value)[0]

             return super(BaseStatsObject, self).__getattr__(key)


        def __getattribute__(self, key):
# What an over complicated thing :/
#            # Check if the attribute is an @property first
#            v = object.__getattribute__(self, key)
#            if hasattr(v, '__get__'):
#                return v.__get__(None, self)
#            # Try the store if the attribute is not in base object
#            if key not in dir(object):
#                try:
#                    return self.__dict__['_store'][key]
#                except:
#                    pass
            if key in self._store:
                return self._store[key]

            return super(BaseStatsObject, self).__getattribute__(key)

# It's the default behavior
#        def __setstate__(self, data):
#            self.__dict__.update(data)

# It's the default behavior
#        def __getstate__(self):
#            return self.__dict__

# It's the default behavior
#        def __delattr__(self, key):
#            del self.__dict__[key]
This being corrected, what is the interest of _store and it being an external dict ?
  1. You assign the value both in it and in the object ;
  2. You only delete the value in the object, not in it ;
  3. You anyway took care of the update at load time, while it wasn't necessary.
Either you only use it and never assign the attribute and its value to the object, or you assign the attribute and its value to the object, and don't need it.
But doing both is just an opened gate to game breaking situations.

From what I get of intent behind the code, something as simple as :
Python:
init python:
   class BaseStatsObject(renpy.python.RevertableObject):

        VALIDATE_VALUES = True
        COERCE_VALUES = True
        STAT_DEFAULTS = {}

        def __init__(self, id, **kwargs):
            self._id = str(id) if not isinstance(id, basestring) else id

            if hasattr( self,  '__pre_init__' ): getattr( self,  '__pre_init__' )( *args, **kwargs )

            for k in self.STAT_DEFAULTS:
                setattr( self, k, self.STAT_DEFAULTS[k] )

            for k in kwargs:
                setattr( self, k, self.kwargs[k] )

            if hasattr( self,  '__post_init__' ): getattr( self,  '__post_init__' )( *args, **kwargs )


        def get_validated_value(self, key, value):
            if not self.VALIDATE_VALUES or not hasattr( self, key ):
                return value

            default_type = type( self.__dict__[key] )

            if isinstance(value, default_type):
                return value

            if self.COERCE_VALUES:
                try:
                    return default_type(value)
                except:
                    pass

            raise TypeError, "Supplied value '{0}' for key '{1}' does not " \
                             "match the existing '{2}'".format(
                                value,
                                key,
                                default_type)

        def __getattr__(self, key):
               # try the character object
# I assume it meant /store.character/
               if hasattr( store, "character" ) and hasattr( store.character, self._id ):
                  value = getattr( getattr( store.character, self._id ), key, None )
                  return value if key != 'name' else renpy.substitutions.substitute(value)[0]

               return super(BaseStatsObject, self).__getattr__(key)

# Not declaring the object like this is the reason why it wasn't saved.
# Doing it like this is why the [icode]_store[/icode] dict and it's /store/ 
# counter part is absolutely not needed.
default myVar = BaseStatsObject( WHATEVER PARAMETERS YOU WANT )
Would do exactly the same thing, without the over complications.


Code:
label choice_Jump(*choice_targets):
For once I advice to use Python in place of Ren'py here. Especially since it's just Python inside the label.
Code:
init python:
    def choice_Jump( *choice_targets):
        choice_jumps = [g for h in [
               [k[0]]*k[1] for k in [
                [j,1] if isinstance(j, basestring)
                else j if len(j)>1
                else [j[0],1] for j in choice_targets]
               ]
              for g in h]
        
         return renpy.random.choice( choice_jumps )     

label whatever:
    jump expression choice_Jump( WHATEVER PARAMETER YOU WANT )

What I am trying to accomplish is a random Fail or Success chance event based on the character's skill level, which increases the chance of success as the character's skill increases.
Python:
init python:
    def weightedSuccess( char, attr, success, weights=[] ):
        stat = getattr( char, attr, 0 )

        # For each step, the number to have for a success will be 
        # lowered by the value associated to this step
        for step, w in weights:
            if step > stat: break
            success -= w

        # By default I used a random pick between 1 and 20
        return random.randint( 1, 20 ) < success

default mc = CharacterStats( WHATEVER )

label whatever:
    "You try to charm your way out of this mess."
    if weightedSuccess( mc, "charm", 10, [ (5, 2 ), ( 10, 2 ), ( 15, 2 ) ] ):
        "You did it !"
        [WHATEVER HAPPEN]
    else:
        "You failed."
        [WHATEVER HAPPEN]
It will be the equivalent of :
Code:
success = 10
if mc.charm > 5: success = 8
elif mc.charm > 10: success = 6
elif mc.charm > 15: success = 4

Side note: Did it on the fly in far to be ideal condition, I can have made few errors/typos, but it's the spirit.
 

John Walker Black

Member
Game Developer
Feb 17, 2018
235
439
@anne O'nymous
Thank you very much for your reply I see now how I will be able to accomplish what Im trying to do. And for the advice concerning the base object script that I am currently using.