Some classes don't default on new game start [solved]

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
GNVE
Regular
Posts: 44
Joined: Wed Sep 12, 2018 4:11 pm
Completed: ShSt - Bad Day
Projects: ShSt - Afterparty, Collings University
itch: https://gnve.itch.io
Contact:

Some classes don't default on new game start [solved]

#1 Post by GNVE »

So I'm having a weird issue with classes in Ren'Py, My time class will reset to default when I go to the main menu while another class doesn't. I made sure that every class instance is called as default. I have included what I hope is relevant code (but omitted a lot for readability).
The time class works as expected and when I restart it goes back to it's 0 state while the build screen keeps whatever was last in the game (e.g. 10 built offices where there should be 0). saving and loading seems to work as expected for both classes as far as I can tell.

below part of the timeclass.

Code: Select all

init python:
   class timeclass:    #adding in EOD modules
        standards = {'nextday':{'Monday':'Tuesday','Tuesday':'Wednesday','Wednesday':'Thursday','Thursday':'Friday','Friday':'Saturday','Saturday':'Sunday','Sunday':'Monday'},
                    'nextmonth':{'January':'February', 'February':'March', 'March':'April', 'April':'May', 'May':'June', 'June':'July', 'July':'August', 'August':'September', 'September':'October', 'October':'November', 'November':'December', 'December':'January'},
                    'monthdays': {'January':31,'February':28,'March':31,'April':30,'May':31,'June':30,'July':31,'August':31,'September':30,'October':31,'November':30,'December':31},
                    'nextseason':{'spring':'summer', 'summer':'autumn', 'autumn':'winter', 'winter':'spring'}}

        def __init__(self, dayN=0, dateday=1, dayname='Saturday', hour=8, minute=0, period='morning', month='April', simseason='spring', vnseason='spring', simyear=0, vnyear=0, lastcheck=99):
            self.dayN = dayN
            self.dateday = dateday  
            self.dayname = dayname
            self.hour = hour
            self.minute = minute
            self.period = period
            self.month = month      #Only used for special holiday events like christmas, carnival etc
            self.simseason = simseason
            self.vnseason = vnseason
            self.simyear = simyear
            self.vnyear = vnyear
            self.lastcheck = lastcheck

        def timeinc(self, newmin):
            if newmin > 0:
                self.hour += newmin // 60
                self.minute += newmin % 60

            while self.minute >= 60:
                self.minute -= 60
                self.hour += 1

            while self.hour >= 24:
                self.hour -= 24
                self.EOD()

            if self.hour < 6:
                if self.period != 'night':
                    maps.decidecurrent()
                    self.period = 'night'
                    self.lastcheck = 0
            elif self.hour < 12:
                if self.period != 'morning':
                    maps.decidecurrent()
                    self.period = 'morning'
                    self.lastcheck = 0
            elif self.hour < 18:
                if self.period != 'afternoon':
                    maps.decidecurrent()
                    self.period = 'afternoon'
                    self.lastcheck = 0
            else:
                if self.period != 'evening':
                    maps.decidecurrent()
                    self.period = 'evening'
                    self.lastcheck = 0
            
            if self.lastcheck == 99:
                maps.decidecurrent()
                self.lastcheck = 0
Here the commonhandler class that has functions that are shared among other classes and functions (yeah I know parenting is a thing but I never claimed I could actually program...)

Code: Select all

    
    class commonhandlerclass:
        def __init__(self, store={}):
            self.store = store

        def building(self, OID, amount, what, remove=False): #building new buildings/offices/classrooms etc
            OID = getattr(store, OID)
            if what == None:
                if remove == False:
                    OID.money -= OID.space['unallocated']['icps'] * amount * OID.mod['constructioncost']
                    OID.store['timers']['building'].append(('unallocated',OID.space['unallocated']['T'],amount))
                else:
                    OID.money = OID.base['removalcost'] * amount * OID.mod['constructioncost'] * 10
                    OID.space['unallocated']['total'] -= amount

            else:
                if remove == False:
                    size = OID.space[what]['size'] * amount
                    cost = OID.space[what]['icps'] * size * OID.mod['constructioncost']
                    
                    if OID.space['unallocated']['total'] >= size:
                        OID.space['unallocated']['total'] -= size
                        OID.money -= cost
                        OID.store['timers']['building'].append((what, OID.space[what]['T'],amount))
                else:
                    size = OID.space[what]['size'] * amount
                    cost = OID.base['removalcost'] * size * OID.mod['constructioncost']
                    
                    OID.space['unallocated']['total'] += size
                    OID.space[what]['total'] -= amount
                    OID.money -= cost

        def maintenance(self, OID):
            OID = getattr(store, OID)
            temp = []
            for i in OID.store['timers']['building']:
                if i[1] <= 0:
                    OID.space[i[0]]['total'] += i[2]
                else:
                    temp.append((i[0],i[1]-1,i[2]))
            
            OID.store['timers']['building'] = temp
then part of the campus class where buildings are stored:

Code: Select all

    class campusclass: #population = students + workers
        def __init__(self, OID='campus', name='Turner University', money=250, corruption=0, prestige=-1000, space={}, base={}, mod={}, store={}, stats={}):
            self.OID = OID
            self.name = name
            self.money = money
            self.corruption = corruption
            self.prestige = prestige
            self.space = space
            self.base = base
            self.mod = mod
            self.store = store
            self.stats = stats

            if not 'timers' in store: store['timers'] = {'building':[]}
            if not 'removalcost' in base: base['removalcost'] = 75
            if not 'constructioncost' in mod: mod['constructioncost']= 1
            if not 'total' in space: space['total'] = 0
            if not 'unallocated' in space: space['unallocated'] = {'total':0, 'T':15,'icps':1500, 'ccps':1.0}
            if not 'cabinetoffice' in space: space['cabinetoffice'] = {'total':0, 'seats':1, 'size':10, 'T':2, 'icps':1000.0, 'ccps':10.0, 'ccpo':10.0, 'pop':'cabinet', 'demand':1.0} #cost per size vs cost per occupant negative numbers give income.
            if not 'supportoffice' in space: space['supportoffice'] = {'total':0, 'seats':5, 'size':25, 'T':3, 'icps':100.0, 'ccps':1.5, 'ccpo':1.0, 'pop':'admin', 'demand':1.0} #pop means who uses this, demand is how likely to use/want this. If demand is not met happiness will drop
            if not 'toilet' in space: space['toilet'] = {'total':0, 'seats':9, 'size':10, 'T':5, 'icps':200.0, 'ccps':2.5, 'ccpo':5.0, 'pop':'population', 'demand':0.063}
            if not 'storage' in space: space['storage'] = {'total':0, 'seats':2, 'size':10, 'T':1, 'icps':20.0, 'ccps':1.1, 'ccpo':1.0, 'pop':'janitor', 'demand':1.0} 

        def EOD(self):
            commonhandler.maintenance(self.OID)
then how the classes are called:

Code: Select all

default time = timeclass()
default commonhandler = commonhandlerclass()
default campus = campusclass()
The timescreen:

Code: Select all

screen TSTtimeSCR():
    $renpy.retain_after_load()
    $dayN = (store.time.dayN +14) % 52
    $time = store.time.displaytime()
    $date = store.time.dayname + ' ' + str(store.time.dateday) + ' ' + store.time.month + ' ' + str(store.time.vnyear)
    $currentperiod = store.time.period
    $currentvnseason = store.time.vnseason
    $currentsimseason = store.time.simseason
    $currentsimyear = store.time.simyear
    vbox:
        hbox:
            text "It is currently [time], [date].\nIt is currently a fine [currentvnseason] [currentperiod].\nThe simulation is currently in week [dayN] year [currentsimyear] and it's currently [currentsimseason]."
        hbox:
            button:
                action Function(store.time.timeinc, 6)
                text "increase 6 minutes"
        hbox:
            button:
                action Function(store.time.timeinc, 60)
                text "increase 60 minutes"
        hbox:
            button:
                action Function(store.time.timeinc, 600)
                text "increase 600 minutes"
        hbox:
            button:
                action Function(store.time.timeinc, 6000)
                text "increase 6.000 minutes"
        hbox:
            button:
                action Function(store.time.timeinc, 60000)
                text "increase 60.000 minutes"
        hbox:
            button:
                action ToggleScreen("TSTtimeSCR"), ToggleScreen("TSToverlaySCR")
                text "return"
and finally the buildscreen:

Code: Select all

screen TSTbuildSCR():
    $renpy.retain_after_load()
    $unallocated = globals()['campus'].space['unallocated']['total']
    $CO = globals()['campus'].space['cabinetoffice']['total']
    $SO = globals()['campus'].space['supportoffice']['total']

    vbox:
        hbox:
            text "you currently have [CO] cabinetoffices and [SO] supportoffices. there is [unallocated] space left"
        hbox:
            button:
                action Function(store.time.timeinc, 1440)
                text "next day"
        hbox:
            button:
                action Function(store.commonhandler.building, 'campus', 200, None)
                text 'build 200m2 of building space.'
        hbox:
            button:
                action Function(store.commonhandler.building, 'campus', 1, 'cabinetoffice')
                text 'build a cabinetoffice'
        hbox:
            button:
                action Function(store.commonhandler.building, 'campus', 5, 'supportoffice')
                text 'build 5 supportoffices'
        hbox:
            button:
                action ToggleScreen("TSTbuildSCR"), ToggleScreen('TSToverlaySCR')
                text 'back'
Last edited by GNVE on Fri May 17, 2024 4:10 pm, edited 1 time in total.

jeffster
Veteran
Posts: 485
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: Some classes don't default on new game start

#2 Post by jeffster »

Ren'Py variables are normally in "store" namespace, right? Why would you use

Code: Select all

globals()['campus']
Shouldn't it be store.campus?

Another question:

Code: Select all

default time = timeclass()

screen TSTtimeSCR():
    $time = store.time.displaytime()
Wasn't store.time defined as timeclass()?
And then you assign store.time.displaytime() to store.time?
Or am I missing something?

In general, try to make data handling as simple as possible.

E.g. why use this:

Code: Select all

    class campusclass:
        def __init__(...blah blah...):
            #...
            if not 'timers' in store: store['timers'] = {'building':[]}
            #...and the like

default campus = campusclass()
instead of simple

Code: Select all

    class campusclass:
        def __init__(...blah blah...):
            #...

default campus = campusclass()
default timers = {'building':[]}
#...and the like
Refactor the code to make it simpler, then it will be easier to read and understand it.

PS. Honestly I don't understand what happens when you create "campusclass()" instance, where store={} is a default parameter of __init__(), and there you assign store['timers']. Will that "store" (the default parameter) exist at all after __init__() finishes?
If the problem is solved, please edit the original post and add [SOLVED] to the title. 8)

GNVE
Regular
Posts: 44
Joined: Wed Sep 12, 2018 4:11 pm
Completed: ShSt - Bad Day
Projects: ShSt - Afterparty, Collings University
itch: https://gnve.itch.io
Contact:

Re: Some classes don't default on new game start

#3 Post by GNVE »

jeffster wrote: Fri May 17, 2024 4:27 am Ren'Py variables are normally in "store" namespace, right? Why would you use

Code: Select all

globals()['campus']
Shouldn't it be store.campus?
thought I had replaced them all. It's a leftover. so yeah it should have been.
I initially used globals() after having a problem and googling for a solution I came across this. But I learned the store.classinstance is better.
jeffster wrote: Fri May 17, 2024 4:27 am Another question:

Code: Select all

default time = timeclass()

screen TSTtimeSCR():
    $time = store.time.displaytime()
Wasn't store.time defined as timeclass()?
And then you assign store.time.displaytime() to store.time?
Or am I missing something?
Eh I don't understand the question. displaytime() is just a function of the timeclass (it makes sure the 24h clock looks like 08:00 rather then 8:0)
jeffster wrote: Fri May 17, 2024 4:27 am In general, try to make data handling as simple as possible.

E.g. why use this:

Code: Select all

    class campusclass:
        def __init__(...blah blah...):
            #...
            if not 'timers' in store: store['timers'] = {'building':[]}
            #...and the like

default campus = campusclass()
instead of simple

Code: Select all

    class campusclass:
        def __init__(...blah blah...):
            #...

default campus = campusclass()
default timers = {'building':[]}
#...and the like
Refactor the code to make it simpler, then it will be easier to read and understand it.

PS. Honestly I don't understand what happens when you create "campusclass()" instance, where store={} is a default parameter of __init__(), and there you assign store['timers']. Will that "store" (the default parameter) exist at all after __init__() finishes?
Please note I am not a programmer working kinda at or above my skill level trying to make a management game.

As stated I have only copied code relevant rather then entire classes. I have started over 3 times making the code easier (for me at least) to understand.
I felt it was best to keep the timers with the class they are part of. (there will be faculty classes, government classes etc. all with their own timers.)

the reason for the if not statements is that I started out with half a page of text to create a class instance. This was part of the explanation another programmer gave me on how to create classes in a better way.

what happens is that after the __init__ finishes the store should look something like:

Code: Select all

store{
'timers':{'building':[], 'setup':[]},
'job':{stuff},
'FTE':{stuff}
etc...}

jeffster
Veteran
Posts: 485
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: Some classes don't default on new game start

#4 Post by jeffster »

GNVE wrote: Fri May 17, 2024 9:52 am what happens is that after the __init__ finishes the store should look something like:

Code: Select all

store{
'timers':{'building':[], 'setup':[]},
'job':{stuff},
'FTE':{stuff}
etc...}
OK, here:

(1) "store" is the name of the main Ren'Py namespace. It's best to not use that name for your data to avoid confusion. Name it "storage" or "stock" or something.

(2) In __init__() function, if you assign things like

Code: Select all

    store['timers'] = {'building':[]}
then "store" here is a local variable. It won't exist after the function finishes.

(And that's good, otherwise you would be addressing global "store" variable (the namespace I think?), and you could end up doing God knows what with it).

If you wanted to have "store" related to the "campusclass" instance, assign self.store.

(3) If you don't plan to create instances of "campusclass" with non-empty "store" parameter, then drop those if's.

(4) Use capitalized class names, because that's a universal convention and it helps readability.

To summarize, that class definition should be:

Code: Select all

init python:

    class Campus:
        # population = students + workers

        def __init__(self, OID='campus', name='Turner University', money=250, corruption=0, prestige=-1000, space={}, base={}, mod={}, stock={}, stats={}):
            self.OID = OID
            self.name = name
            self.money = money
            self.corruption = corruption
            self.prestige = prestige
            self.space = space
            self.base = base
            self.mod = mod
            self.stock = stock
            self.stats = stats

            if not 'timers' in stock:
                self.stock['timers'] = {'building':[]}

            if not 'removalcost' in base:
                self.base['removalcost'] = 75

            if not 'constructioncost' in mod:
                self.mod['constructioncost']= 1

            if not 'total' in space:
                self.space['total'] = 0

            if not 'unallocated' in space:
                self.space['unallocated'] = {'total':0, 'T':15,'icps':1500, 'ccps':1.0}

            if not 'cabinetoffice' in space:
                # cost per size vs cost per occupant negative numbers give income.
                self.space['cabinetoffice'] = {'total':0, 'seats':1, 'size':10, 'T':2, 'icps':1000.0, 'ccps':10.0, 'ccpo':10.0, 'pop':'cabinet', 'demand':1.0}

            if not 'supportoffice' in space:
                # pop means who uses this, demand is how likely to use/want this. If demand is not met happiness will drop
                space['supportoffice'] = {'total':0, 'seats':5, 'size':25, 'T':3, 'icps':100.0, 'ccps':1.5, 'ccpo':1.0, 'pop':'admin', 'demand':1.0}

            if not 'toilet' in space:
                self.space['toilet'] = {'total':0, 'seats':9, 'size':10, 'T':5, 'icps':200.0, 'ccps':2.5, 'ccpo':5.0, 'pop':'population', 'demand':0.063}

            if not 'storage' in space:
                self.space['storage'] = {'total':0, 'seats':2, 'size':10, 'T':1, 'icps':20.0, 'ccps':1.1, 'ccpo':1.0, 'pop':'janitor', 'demand':1.0} 

        def EOD(self):
            commonhandler.maintenance(self.OID)

default campus = Campus()
Or even simpler, if you don't plan to define Campus instances using non-empty parameters like "space" etc.:

Code: Select all

init python:

    class Campus:
        # population = students + workers

        def __init__(self, OID='campus', name='Turner University', money=250, corruption=0, prestige=-1000):
            self.OID = OID
            self.name = name
            self.money = money
            self.corruption = corruption
            self.prestige = prestige

            self.space = {
                'total': 0,
                'unallocated': {'total': 0, 'T': 15, 'icps': 1500, 'ccps': 1.0},

                # cost per size vs cost per occupant negative numbers give income:
                'cabinetoffice': {'total': 0, 'seats': 1, 'size': 10, 'T': 2, 'icps': 1000.0, 'ccps': 10.0, 'ccpo': 10.0, 'pop': 'cabinet', 'demand': 1.0},

                # pop means who uses this, demand is how likely to use/want this. If demand is not met happiness will drop
                'supportoffice': {'total': 0, 'seats': 5, 'size': 25, 'T': 3, 'icps': 100.0, 'ccps': 1.5, 'ccpo': 1.0, 'pop': 'admin', 'demand': 1.0},

                'toilet': {'total': 0, 'seats': 9, 'size': 10, 'T': 5, 'icps': 200.0, 'ccps': 2.5, 'ccpo': 5.0, 'pop': 'population', 'demand': 0.063},

                'storage': {'total': 0, 'seats': 2, 'size': 10, 'T': 1, 'icps': 20.0, 'ccps': 1.1, 'ccpo': 1.0, 'pop': 'janitor', 'demand': 1.0}
                }

            self.base = {
                'removalcost': 75,
                }

            self.mod = {
                'constructioncost': 1,
                }

            self.stock = {
                'timers': {'building':[]},
                'setup': [],
                }

            self.stats = {}

        def EOD(self):
            commonhandler.maintenance(self.OID)

default campus = Campus()

Code: Select all

default time = timeclass()

screen TSTtimeSCR():
    $time = store.time.displaytime()
Eh I don't understand the question. displaytime() is just a function of the timeclass (it makes sure the 24h clock looks like 08:00 rather then 8:0)
I think when you assign "$ time" you actually rewrite "store.time". I.e. first time was an instance of timeclass(), and then you rewrite it with a string. Even if it doesn't seem to give an immediate error, don't do that. Use some other variable name, like "disptime" or something:

Code: Select all

default time = timeclass()

screen TSTtimeSCR():
    $ disptime = store.time.displaytime()
Also note side effects when assigning values inside a screen:
https://renpy.org/doc/html/screens.html#python
If the problem is solved, please edit the original post and add [SOLVED] to the title. 8)

GNVE
Regular
Posts: 44
Joined: Wed Sep 12, 2018 4:11 pm
Completed: ShSt - Bad Day
Projects: ShSt - Afterparty, Collings University
itch: https://gnve.itch.io
Contact:

Re: Some classes don't default on new game start

#5 Post by GNVE »

jeffster wrote: Fri May 17, 2024 12:17 pm (1) "store" is the name of the main Ren'Py namespace. It's best to not use that name for your data to avoid confusion. Name it "storage" or "stock" or something.
Right, hadn't thought of that. I'll see what is a better name.
jeffster wrote: Fri May 17, 2024 12:17 pm Or even simpler, if you don't plan to define Campus instances using non-empty parameters like "space" etc.:

Code: Select all

init python:

    class Campus:
        # population = students + workers

        def __init__(self, OID='campus', name='Turner University', money=250, corruption=0, prestige=-1000):
            self.OID = OID
            self.name = name
            self.money = money
            self.corruption = corruption
            self.prestige = prestige

            self.space = {
                'total': 0,
                'unallocated': {'total': 0, 'T': 15, 'icps': 1500, 'ccps': 1.0},

                # cost per size vs cost per occupant negative numbers give income:
                'cabinetoffice': {'total': 0, 'seats': 1, 'size': 10, 'T': 2, 'icps': 1000.0, 'ccps': 10.0, 'ccpo': 10.0, 'pop': 'cabinet', 'demand': 1.0},
                
                #etc...

default campus = Campus()
Ah yes a combination of this and the if not statements is better depending on the use case. This generally isn't covered or isn't covered well in the tutorials I followed to pick up python. Often examples use short 2 line classes so it's hard to see what they are actually trying to teach. Sometimes you need a slightly larger class to get what they are trying to teach. I have run into issues before where things aren't thought or the lesson just doesn't land.
jeffster wrote: Fri May 17, 2024 12:17 pm I think when you assign "$ time" you actually rewrite "store.time". I.e. first time was an instance of timeclass(), and then you rewrite it with a string. Even if it doesn't seem to give an immediate error, don't do that. Use some other variable name, like "disptime" or something:
Oh wow, hadn't seen that. It is hard to see issues with my own code sometimes. Especially with a screen that is just intended to test if a function/class works as intended.
jeffster wrote: Fri May 17, 2024 12:17 pm Also note side effects when assigning values inside a screen:
https://renpy.org/doc/html/screens.html#python
Yeah I have read that before. As far as I understand it I try not to fall foul of it. The functions called in screens are just display functions or need confirmation by a button click.

GNVE
Regular
Posts: 44
Joined: Wed Sep 12, 2018 4:11 pm
Completed: ShSt - Bad Day
Projects: ShSt - Afterparty, Collings University
itch: https://gnve.itch.io
Contact:

Re: Some classes don't default on new game start

#6 Post by GNVE »

Seems that replacing the store inside the classes solved the issue I was having.

Post Reply

Who is online

Users browsing this forum: Semrush [Bot]