Ren'Py "unren_noblock" is not working in Ren'py 8.2.0+ Versions - [Solved]

Twistty

The Happy Beaver
Respected User
Donor
Former Staff
Sep 9, 2016
4,183
37,999
The old "unren_noblock" that used to work is now... not working in ren'py 8.2.0+
They changed a lot in the "20" series it seems.

This worked on all older versions.
Python:
init 999 python:

    _preferences.skip_unseen = True
    renpy.game.preferences.skip_unseen = True
    renpy.config.allow_skipping = True
    renpy.config.fast_skipping = True

    #  Divide_By_Zero
    renpy.config.rollback_enabled = True
    renpy.config.hard_rollback_limit = 256
    renpy.config.rollback_length = 256

    def unren_noblock( *args, **kwargs ):
        return

    renpy.block_rollback = unren_noblock

    try:
        config.keymap['rollback'] = [ 'K_PAGEUP', 'repeat_K_PAGEUP', 'K_AC_BACK', 'mousedown_4' ]
        # Allows interruption of Hard Pause... USE '*' key on KEYPAD (NUMPAD)... (NOT 'SHIFT' + '8')
        config.keymap['dismiss_hard_pause'].append('K_KP_MULTIPLY')
    except:
        pass

    try:
        config.underlay[0].keymap['quickSave'] = QuickSave()
        config.keymap['quickSave'] = 'K_F5'
        config.underlay[0].keymap['quickLoad'] = QuickLoad()
        config.keymap['quickLoad'] = 'K_F9'
    except:
        pass
However, when used on the game Lust Bound it give these traceback errors attached below.

This seems to be the issue?
Python:
    def unren_noblock( *args, **kwargs ):
        return

    renpy.block_rollback = unren_noblock
As when these are #'ed out or removed - the rest works fine.

What noblock works in the new ren'py version now???

Thanks guys!!!
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,263
15,073
The old "unren_noblock" that used to work is now... not working in ren'py 8.2.0+
No, it's still working fine, it's the game that is broken.

Save files are build by pickling the current state and the rollback stack. Said rollback stack that is obviously not updated when the rollback is blocked, what also have an impact on the current state, making some variable stay in the "unchanged" state, and therefore not parts of what will be saved.
Obviously too, when you use "unren_noblock", you change this, and you'll end with Ren'Py wanting to save variables that it wouldn't have saved instead.

And it's precisely what the traceback is saying:
Exception: Could not pickle <module 'time' (built-in)>. perhaps renpy.game.log.log[0].stores['store']['time'] = <module 'time' (built-in)>)
previous interaction.
game variables.
variable name.
something that have absolutely no reason to be there.



There's a savable variable named "time" that host a copy of the time module. This is ridiculous and it shouldn't be. Import should happen at init time, period.
It's just a guess, but if you look at the code, you'll probably find something like $ import time.


I also guess that it's the reason why the rollback is disabled. Unable to understand the error, the dev found that workaround to hide it, instead of correcting his code.
 

Twistty

The Happy Beaver
Respected User
Donor
Former Staff
Sep 9, 2016
4,183
37,999
Thanks anne O'nymous ;)

Always nice to talk to you - even if it's not my own thread, as I have learned a lot, from your insights - over the years.
You actually earned your your Respected User badge - where as mine just showed up in my neighbors mail box and I stole it, borrowed it. Lol

As usually the case this "time" you are spot on.
(I cannot say "you are always right", - because that could be used against me, later - lmfao).

I looked - and found the "time" function in the script.rpy and screens.rpy files.
Seems the dev wants to play different intro videos based on the users real time?
screens.rpy
Python:
    style_prefix "main_menu"
    if hour < 19 and hour > 6:
        add Movie(play="images/anim/intro_loop.webm", channel="movie")
    else:
        add Movie(play="images/anim/intro_loop_night.webm", channel="movie")
And to do that - they added this below code in the script.rpy
Python:
IMPORT TIME--------------------------------------------------------------------
init:
    $ import time

    $ year, month, day, hour, minute, second, dow, doy, dst = time.localtime()
When they are disabled - my file posted in the OP, works fine.

So the mystery is solved.

However;
- How else could have the developer achieve the results that they intended, in a fashion that would not give the error posted in my traceback file?

Now I am sort of curious. :unsure:
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,263
15,073
(I cannot say "you are always right", - because that could be used against me, later - lmfao).
It would more surely be used against me ; "But ! Twistty said that you are always right..." :D


Python:
IMPORT TIME--------------------------------------------------------------------
init:
    $ import time

    $ year, month, day, hour, minute, second, dow, doy, dst = time.localtime()
[...]

However;
- How else could have the developer achieve the results that they intended, in a fashion that would not give the error posted in my traceback file?
Hmm, there's a bit more behind this...

Ren'Py's behavior at init time do not differ too significantly between init: and init python:. I tested with the version 8.2.0, and both way to import the time module do not lead to propagation into the rollback, nor to save issue. Yet there's no doubt that it's the cause of the issue.


My first thought was something like $ time = time somewhere, but it would have thrown an exception once you removed the importation of the module.
So, I downloaded the game to take a look by myself, and I found the culprit:
free_roaming.rpy:7 -> default time = "morning"

He use a variable named "time" while, by it's importation, the time module automatically create a variable named "time", where the said module will be stored.
By itself this will not lead to an error. Init blocks are processed before default statements, therefore the module is overrode by his own variable. And it wouldn't thrown an error if the module was used, because it follow the same principle than this classical error:
Python:
define mc = Character( "[mc]" )

label start:
   $ mc = renpy.input( "What's your name" )
Due to the way Ren'Py works, the two achieve to coexist, one in the AST that is already pre-processed, and the other in the game scope. And the same happen with the "time" variable, because it exist as module in the pre-processed python blocks, and as string variable in the game scope.
But in both cases, sometimes it happen that the two collides, and then, boom.


His error is to not know the basis of Python, and therefore to use "time" to name the variable where he store the time of the day. Something that file ridiculous since he also have a "time_of_day" integer that surely have the exact same role.

Being smart should fix the error:
Python:
init python hidden:
    import time

    year, month, day, store.hour, minute, second, dow, doy, dst = time.localtime()
This would make Ren'Py forget all the variables declared in the init bloc, except the only one he use, that is explicitly created in the game scope.


And being smarter:
define hour = renpy.time.localtime()[3]

Because the time module is already imported by Ren'Py's core, and therefore available from it. And there's no need to assign all the values to a variable when you only need one.



Be noted that all this mess have only one goal: present a different background for the main menu, depending if you play at day or night time...

What mean that he should have directly used renpy.time.localtime()[3] in his condition, because the player can want to go back to the main menu after some time, and who know, perhaps is it now night time, or day time for insomniacs.
 

Twistty

The Happy Beaver
Respected User
Donor
Former Staff
Sep 9, 2016
4,183
37,999
So, I downloaded the game to take a look by myself, and I found the culprit:....
Well, not totally surprised you took the "time" to do that, or answer this question (in your usual - thoughtful and straight foreword manner).
As you have done so for me, (and for many others), both past and present.
But I am always truly grateful for your insights, and knowledge. :geek:
Just though I would mention that.
(Ps. - I also find your humor, and sarcasm amusing; in your past posts that I have read, over the years) :ROFLMAO:

free_roaming.rpy:7 -> default time = "morning"
I saw that - when I searched using - but never gave it much notice?

However - init python hidden:
I can sort of understand. yaa beaver.
That makes sense.

This I have never seen before?
define hour = renpy.time.localtime()[3]
- As I though "time" was a python function?
And not sure how ren'py "imports" it? So?

Guess I need to go down the rabbit hole again....

I'll never be a programmer - but am curious by nature, and enjoy learning the how and why's of things.

Thanks ever so much again anne O'nymous - not just for solving my original issue, but helping me understand the underlying causes and issues - to the extent of my...lol.
Now I have something new to learn!!!

Cheers and best wishes;
Twistty
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,263
15,073
This I have never seen before?
define hour = renpy.time.localtime()[3]
- As I though "time" was a python function?
And not sure how ren'py "imports" it? So?
It's both simple and complicated.

Unlike elder script languages, or Ren'Py script, where you can split you code in many files that will form a single entity, each Python file act as a module(-like) that have his own scope and context.
Therefore, if you have something like:

main.py:
import secondary
secondary.py:
Code:
import time
def myFunction():
   [...]
The time module will only be imported in "secondary.py".

But in the same time, everything in "secondary.py" is available from "main.py" through secondary.. So, you can use "myFunction" from "main.py" with secondary.myFunction(). And like modules are stored in a variable at their name, the time module too is available, through secondary.time.

For Ren'Py, all its core is located behind the renpy module-like (it's not strictly a module but will act as it). And Ren'Py need the most basic modules ("os", "time", "re"). Therefore, it's not necessary to import them in your game, all are already available through renpy.os, renpy.time and renpy.re.
Not that importing a module more than once would be an issue ; Python only import it once, and after that create a variable at its name pointing to it. But as seen in the present case, it can permit to avoid variables name conflict to not do it.
 
  • Crown
Reactions: Twistty

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,263
15,073
define hour = renpy.time.localtime()[3]
I come back because awakening this morning I realized that the "how module works" was in fact addressing only half of the magic...

There's also the ()[3] that is a bit weird looking. By chance, here, it's more simple.

time.localtime() return a tuple like value. It can be assigned value by value as in the code you shown, like it can be assigned directly to one value, that will then be manipulated like a tuple:
Python:
now = time.localtime()
currentHour = now[3]
The trick rely on the fact that you can chain event accordingly to their result type.
It's a bit like math when you simplify an equation.

"now" appear in both line, once in the left side, once in the right one. Therefore you can simplify the code by using directly whatever is assigned to it, what lead to currentHour = time.localtime()[3].


The same apply to more complex, and possibly less explicit, chain of code:
Python:
a = [ "Hell", "there " ]
b = " ".join( a )
# Oops, we forgot a letter
c = b.replace( "Hell", "Hello" )
# And there's a trailing space that have nothing to do there
d = c.strip()
join() return a string. replace() is a string method, it can be applied to the result of join().
replace() return a string. strip() is a string method, it can be applied to the result of replace().

And then, your four lines, that need two intermediary variables, can become one that use no intermediary variables:
Python:
a = [ "Hell", "there " ]
d = " ".join( a ).replace( "Hell", "Hello" ).strip()
And you can goes to the infinite with that.

If you've a "separator" function that return the letter to use as separation between words, you can include it in the chain.
As long as it return a string, you can apply join() to it:
d = separator().join( a).replace( "Hell", "Hello" ).strip()


Edit: A format issue
 
Last edited:
  • Red Heart
Reactions: Twistty

Twistty

The Happy Beaver
Respected User
Donor
Former Staff
Sep 9, 2016
4,183
37,999
That's a great explanation!!!

Your not a closet uni professor by any chance? :unsure:
Because you have a real talent for explaining things!
Even "I" mostly get it now!
And the "I" - means your did a wonderful job explaining - trust me on this. lol

Edit:
So it's "kind of" "sort of" like this in it's basic logic?

Code:
# for making a tuple
my_tuple = (89, 32)
my_tuple_with_more_values = (1, 2, 3, 4, 5, 6)

# to concatenate tuples
another_tuple = my_tuple + my_tuple_with_more_values
print(another_tuple)
# (89, 32, 1, 2, 3, 4, 5, 6)

# getting a value from a tuple is similar to a list
first_val = my_tuple[0]
second_val = my_tuple[1]

# if you have a function called my_tuple_fun that returns a tuple,
# you might want to do this
my_tuple_fun()[0]
my_tuple_fun()[1]

# or this
v1, v2 = my_tuple_fun()


So next lesson - Harfbuzz - OpenType variable fonts. lol
Just kidding - but do find the shaper style property, named instance, and axis property sets fun to play around with.
Code:
define gui.text_font = "nunito.ttf"

define gui.text_instance = "light"

define gui.text_axis = {"width" : 125}

However: currently I am fairly happy that I can sometimes convert to/from Python 2 format to/from Python 3 f-strings, just for learning purposes.

Simple examples:
tmp_list = [i for i in xrange(0, z, 1)] to tmp_list = [*range(0, z, 1)]
or
print("Error: Invalid Mode - '%s'." % mode) to print("Error: Invalid Mode - '{}'.".format(mode))

I might have to research the code I want, but am able to visually see the difference - and know, what the issue may be; was not something I could do even 1 year ago.
 
Last edited:
  • Hey there
Reactions: rKnight

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,263
15,073
Your not a closet uni professor by any chance? :unsure:
Oh god... You've seen how little patience and high level of grumpy sarcasm I have ? Poor students who would have me as teacher ;)


Because you have a real talent for explaining things!
Even "I" mostly get it now!
And the "I" - means your did a wonderful job explaining - trust me on this. lol


So it's "kind of" "sort of" like this in it's basic logic?
Yep.


Simple examples:
tmp_list = [i for i in xrange(0, z, 1)] to tmp_list = [*range(0, z, 1)]
or
print("Error: Invalid Mode - '%s'." % mode) to print("Error: Invalid Mode - '{}'.".format(mode))
The second was already possible with Python 2.x, but Ren'Py stuck to the old "%" approach. Probably for understanding purpose since "%s", "%d" are common in C and therefore in many language. This while "{}" can be confusing, while leading to some headache, since it's used to mark Ren'Py's text tags.

As for the first one, I have mixed feelings. I often use shortcut like this, but in the same time it's relatively obscure, and therefore hard to understand for the reader.
It's like the fact that one line blocks don't need to be explicit blocks (at least in python blocks, Ren'Py language don't understand it):
Python:
if whatever is None:
    return
can perfectly be wrote:
if whatever is None: return

I used it a lot in my posts the first years, then now I tend to drop it. What matters at the end of the day isn't how short or efficient your code is, but how easily you'll still understand it in one month or more.


I might have to research the code I want, but am able to visually see the difference - and know, what the issue may be; was not something I could do even 1 year ago.
We all learn and, as strange as it can perhaps seem, this include me.
I don't always have the answers, sometimes I have to figure it out before providing the code that will save someone's life.
 
  • Yay, new update!
Reactions: Twistty

Twistty

The Happy Beaver
Respected User
Donor
Former Staff
Sep 9, 2016
4,183
37,999
Oh god... You've seen how little patience and high level of grumpy sarcasm I have ? Poor students who would have me as teacher ;)
You grumpy - perish that thought.
766.jpg :ROFLMAO: :LOL: :ROFLMAO:
What matters at the end of the day isn't how short or efficient your code is, but how easily you'll still understand it in one month or more.
Best lesson in the whole thread :cool:

Have a great week Anon.......