Ren'Py My code for the Period/Day/Week cycle is valid for a sandbox?

Miss Lizzy Anthus

Member
Game Developer
Nov 16, 2022
145
367
Hello, I m new in the game development, I felt like making a game when I saw my partner making his own game.

I wanted to know if anyone could tell me if my code is correct for a sandbox with a time of day, days, and weeks system. My partner helped me a little with the code, but he's doing a VN and not a sandbox so he's not sure if it will work.

I made a quick build but the rules forbid me to post a link because my account is too new. So I'll wait until I get more posts.

I used this thread for making the code.

This is my code :

You don't have permission to view the spoiler content. Log in or register now.

I already have a little problem. The save system is saving the variables of the period, days, and weeks ONLY if we change rooms. If I advance the time in the same room (same screen) the save system does not keep the variables. Apart from that everything seems to work perfectly.

The goal then is to add events according to time. For example, imagine a hotel, a character would be present in room 237 only on Friday night of week three. (That's why I make the car screen.)

In addition to that, the main quest system and a small secondary quest system with a quest log (I think I know how to code it) an inventory with money and items (I think I know how to do it too) a map system to move around (I think I know how to do it too) NO stats or grind.
And of course repeatable naughty events. (Same code as the car screen I suppose, but with a "jump/label" system for the dialogue instead of an "action Show".)

This is a very simplistic code, but is it valid?

Thank you very much.

*I used a corrector for my message, if it broke the code let me know and I will copy and paste it again.

View attachment 2022-11-17 01-06-00.webm
 
Last edited:

rayminator

Engaged Member
Respected User
Sep 26, 2018
3,130
3,194
# Period, Day, Week in the UI
define sPoD = ["Morning", "Noon", "Afternoon", "Evening", "Night"]
define sDoW = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ]
define sWeeK = ["First", "Second", "Third", "Fourth"]
change define to default when you save then load from the save file it go back to the default setting 0 for define

so here is what's going on when you start a new game started at morning-monday-first but when you change it to evening-monday-first and then save it then reload it. it will show this morning-monday-first

have to remember define will start at zero regardless if you changed this default PoD = 0 so renpy is reading sPoD a a define so it read as a 0 so if you used default it will save where you have changed it to
 
  • Like
Reactions: Miss Lizzy Anthus

gojira667

Member
Sep 9, 2019
325
320
I already have a little problem. The save system is saving the variables of the period, days, and weeks ONLY if we change rooms. If I advance the time in the same room (same screen) the save system does not keep the variables. Apart from that everything seems to work perfectly.
You should review the whole page, but in particular this should be what you are looking for: .
 
  • Like
Reactions: Miss Lizzy Anthus

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,226
change define to default when you save then load from the save file it go back to the default setting 0 for define
[...]
Look at her(?) code again. The variable declaration are correct.
The counters (DoW, PoW, etc) are declared with default, while it's only the constants arrays (sDoW, sPoW, etc) that are declared with define.


You should review the whole page, but in particular this should be what you are looking for: .
No, this wouldn't fix her issue. This is to keep the value as it is "right now in the game", not using the saved value. Therefore, during the tests it would looks like it's fixed, but it would just be an illusion.

Let's say that she save when PoD is at "5", if she load this save right now, the value will effectively be "5". But if she progress in the game, to a moment where PoD is now at "2", then loading the save will let them with PoD value still being "2". And if she quit Ren'py, then restart the game and load the save, then PoD value will be at "0".


I already have a little problem. The save system is saving the variables of the period, days, and weeks ONLY if we change rooms. If I advance the time in the same room (same screen) the save system does not keep the variables. Apart from that everything seems to work perfectly.
I'll look at the code later, not really the time for this right now, but I guess that it's a "screens are not interactions" issue.

Globally speaking, while you'll stay on a screen, Ren'Py will know that a value have changed, but it will act as if it's a temporary change. Therefore, when you play, Ren'Py use the new value, but when it will save, it's the previous value that will be saved.
Python:
label whatever:
    $ myVar = 42
    # Save between here (included)
    call screen whatever
    # and here (excluded), and /myVar/ will be saved with '42' as value.
    "boo"
    #  Save starting here, and /myVar/ will be saved with whatever value it had
    # when you returned from the screen.
Normally using after you changed the value should be enough to fix the issue. Something like this:
Python:
       textbutton ("Sleep"):
            background "#000000"
            text_color "#FFFFFF"
            # The change is here
            action [SetVariable("PoD", 0), SetVariable("DoW", DoW +1), Function( renpy.restart_interaction )]
            xcenter 0.5
But Ren'Py is a bit temperamental, and the notion of "interaction" is a bit too much on the abstract concept side, so I don't guaranty it.
If it's not enough to fix the issue, you'll need to exit the screen, advance the time, then come back to the screen. But at first sight your code don't seem designed for this.
 
  • Like
Reactions: gojira667

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,226
Back to now talk about the code itself. I'll starts by addressing the generic issues, and talk about the "does this works for a sandbox" part after.


Python:
#No rollback
define config.hard_rollback_limit = 0
$ renpy.block_rollback()
Two issues here.

Firstly it's a really bad idea to disable the rollback, especially globally.
If you need to disable the rollback for your code to works, then your code is broken in a way or another. Disabling the rollback should be something exceptional, that you do only for the purpose of your game.
Also, remember that Ren'Py is really easy to mod, in order to set back the rollback by example.

Secondly, the $ renpy.block_rollback() line is outside of everything (label, screen, init block, Python code).
This mean that Ren'Py will probably not proceed it, and if it do, it will surely not be when you expect it.
Except few statements (default, define, image, transform, style, and from memory it's all) all your code must be either in an init block (init: or init python:), in a label, or in a screen.


Python:
# Background for my rooms
# Room 1
image morning ="images/background/morning.png"
image noon = "images/background/noon.png"
image afternoon = "images/background/afternoon.png"
image evening = "images/background/evening.png"
image night ="images/background/night.png"

# Room 2
image morning2 ="images/background/morning2.png"
image noon2 = "images/background/noon2.png"
image afternoon2 = "images/background/afternoon2.png"
image evening2 = "images/background/evening2.png"
image night2 ="images/background/night2.png"

# interaction image
image car = "images/interactions/car.png"
image car_h = "images/interactions/car_h.png"
image car_zoom = "images/interactions/car_zoom.png"
image returnb = "images/gui_game/return.png"
image returnb_h = "images/gui_game/return_h.png"
Those statements are useless.

You declare the image as pure images (see below) and you give them the same name than their file.
As the , Ren'Py will automatically proceed to the declaration of any images located in the "images" directory and it's sub folders, creating an image having the same name than the file once stripped from its extension.
Said otherwise, Ren'Py already did exactly the same thing than all the image statement you used here.


Python:
# Room 1 Screen
screen station:
    zorder 1
    # Background of the room
    if PoD == 0:
        add "morning"

    if PoD == 1:
        add "noon"

    if PoD == 2:
        add "afternoon"

    if PoD == 3:
        add "evening"

    if PoD == 4:
        add  "night"
Here, there's two "should be better to".

Firstly you shouldn't use a suite of if like you did.
By chance they aren't conflicting with each others, but it's not a reason to do it. You should instead use the if/elif[/else] structure:
Python:
    if PoD == 0:
        add "morning"
    elif PoD == 1:
        add "noon"
    elif PoD == 2:
        add "afternoon"
    elif PoD == 3:
        add "evening"
    elif PoD == 4:
        add  "night"
Ren'Py/Python will automatically proceed each test until one match, then stop the series of test. This by opposition to your actual structure, where every test will always be proceeded.
If "PoD" value is equal to "0", with your code Ren'Py/Python will still test if "PoD" is equal to "1", then equal to "2", etc. This will my code if "PoD" is equal to "0", all the other tests will be skipped.

The second "should be better" come from an undocumented behavior of the add screen statement.
You can make it use an expression, and like you already have a list that make the correspondence between "PoD" and a string, this permit to replace all your if/add couple by a single line:
Python:
screen station:
    zorder 1
    # Background of the room
    add ( sPoD[PoD].lower() )
Then:
Python:
screen station2:
    zorder 1
    # Background of the room
    add ( sPoD[PoD].lower() + "2" )
And so on.

By itself using an if structure like you did isn't an issue. It can help to have a better view regarding what is happening "here". But in the same time it also drown you with information and this can lost you in the end.


Python:
label start:
    "Hello, welcome."
    show screen advanceTime
    show screen gui_ingame
    jump station

label station:
    show screen advanceTime
    show screen gui_ingame
    call screen station
Here, one useless part, and a "should be better to" issue.

The useless part is that you show the screens every time.
A screen will be shown until you explicitly ask Ren'Py to hide it. Therefore, this part could be:
Python:
label start:
    "Hello, welcome."
    show screen advanceTime
    show screen gui_ingame
    jump station

label station:
    call screen station
Both "advanceTime" and "gui_ingame" screens will still be displayed when you call the "station" screen, when the said screen is displayed, and after the screen have been displayed.

As for the "should be better to", it's related to the screen statement.
This statement permit to include a screen inside another one. And like both "advanceTime" and "gui_ingame" are expected to be seen most of the time, you could have used:
Python:
screen station1:
    zorder 1

    use advanceTime
    use gui_ingame  
    [...]

screen station2:
    zorder 1

    use advanceTime
    use gui_ingame  
    [...]
This would then make the show screen advanceTime and show screen gui_ingame definitively useless.


Now that this is said, you rely was too much on screens, and not enough on labels.

Take "station1" by example. This part is both good and bad:
Python:
    # interaction test, here only the Friday at evening first week
    if PoD == 3 and DoW == 4 and WeeK == 0:
        imagebutton:
            focus_mask True
            idle "car"
            hover "car_h"
            action ToggleScreen("car_zoom")
Good because yes, it's from the screen that you need to decide if the car is seen or not.
Bad because the car will not be seen just a one period, one day and one week. What mean that you'll have a lot of condition to define here, and it will soon be too much to effectively manage it. It would be better to rely on arguments past to the screen.

For my example, I changed the premise of the code, in order to have a better base for the demonstration.
I assumed that "station1" is an house garage. Therefore, the car is present outside of work hours, except for the Wednesday of the second week, because for some reason the car owner take his lunch at home. I also assumed that there's a bicycle, in order to have more than one item:
Python:
label station1:
    # Is the car present ?

    #  The variable exist only in this label.
    $ renpy.dynamic( "car" )

    # If it's morning or night, or eithert Saturday or Sunday.
    if PoD in [0, 4] and or DoW in [5, 6]:
        $ car = True
    # First exception, Wednesday of the second week, at lunch time.
    elif Week == 2 and PoD == 1 and DoW == 2:
        $ car = True
    # Any other times, the car is not present.
    else:
        $ car = False

    # Is the bicycle present ?

    $ renpy.dynamic( "bicycle" )

    # Yes, it's always present, it's your bicycle.
    $ bicycle = True

    #  And now call the screen, telling it what it should display or not:
    call screen station1( car, bicycle )  

screen station1( car, bicycle ):
   [...]

   # If asked to display the car, display it.
   if car:
        imagebutton:
            focus_mask True
            idle "car"
            hover "car_h"
            action ToggleScreen("car_zoom")

   #  Same for the bicycle.
   if bicycle:
        imagebutton:
            [...]
This shifts to the label the responsibility to know if something should be displayed or not.
It's important because:
  1. labels are a bit faster;
  2. screens are proceeded around 5 times by seconds (therefore the test would be performed 5 times by seconds);
  3. It keep the screens more readable;
  4. You can later simplify your labels to keep them readable.

Regarding it's a function that the given variable will be relevant only in this label, and the labels called from it (but not the label jumped). This permit you to use a lot of variable to define what should be seen in your screen, without those variables being still present elsewhere in the game.

Regarding the point 4 of the list above, you can make your label more compact, while still being readable:
Python:
label station1:
    # Is the car present ?

    $ renpy.dynamic( "car" )
    #  An external label will take care of the computations.
    call isCarPresent
    #  Get the value returned by the label, and store it
    $ car = _return

    # Is the bicycle present ?
    $ renpy.dynamic( "bicycle" )
    call isBicyclePresent
    $ bicycle = _return

    call screen station1( car, bicycle )

label isCarPresent:

    # If it's morning or night, or eithert Saturday or Sunday.
    if PoD in [0, 4] and or DoW in [5, 6]:
        return True
    # First exception, Wednesday of the second week, at lunch time.
    elif Week == 2 and PoD == 1 and DoW == 2:
        return True
    # Any other times, the car is not present.
    else:
        return False
In fact, designed this way, your code will be more readable, even without the comments.
call isCarPresent explicitly say what happen at this time. This while, if you need to make change in the conditions for the car to be present, you know that it will be in the label named "isCarPresent", and that this label will have nothing more than those conditions.

Relying on this approach instead of the "screen based" one you shown, it even more important that you intend to use this as part of an event system.
But, as you probably imagine, the example I gave is more suitable for recurring events than "one time" ones. So now that I explained how to shifts the computing to the label, and how to have simplified labels, I'll explain how to make all this works with none recurring events.

The trick is to not anymore rely on a flag (a boolean variable), but on a list that will contain the parts that have to be added in the screen:
Python:
label station1:
    #  By default, nothing will happen.
    $ renpy.dynamic( "events" )
    $ events = []

    call isCarPresent
    #  If the returned value is /True/, then the car is present,
    if _return:
        # therefore, add it to the list of events.
        $ events.append( "car" )

    call isBicyclePresent
    $ bicycle = _return
    if _return:
        $ events.append( "bicycle" )

    call screen station1( events )


screen station1( events ):
   [...]

   # If "car" is parts of the events, display the button.
   if "car" in events:
        imagebutton:
            focus_mask True
            idle "car"
            hover "car_h"
            action ToggleScreen("car_zoom")

   # If "bicycle" is parts of the events, display the button.
   if "bicycle" in events:
        imagebutton:
            [...]
Now, everything rely on an unique variable, what permit to extend the events without real constraints nor limits.


Not much to say regarding the rest of the code, well except your save issue that I addressed previously.
Normally, the use of renpy.restart_interaction should be enough, but if it isn't I'll tell you how to solve it ; or someone else will do it if I can't at this time.
 
  • Heart
  • Like
Reactions: osanaiko and Grap

Miss Lizzy Anthus

Member
Game Developer
Nov 16, 2022
145
367
Hello everyone, I apologize for not responding sooner and for not expressing my gratitude to those who have posted. I have been completely absorbed in my renders and lost track of everything else. I truly apologize for any inconvenience caused.

I will begin working on the code shortly and will return soon. I am grateful for the assistance provided.