Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

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
thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#1 Post by thexerox123 »

So, I'm trying to make a custom turn-based battle system for my game. I have point-and-click navigation screens that are displayed using a label with renpy.pause(hard=True).

Image

My issue occurs when I try to use an attack. The sequence of events is this: it determines whether the attack hits or misses, what effect the attack has, and then should display a message accordingly with battle_popup. Then another message with battle_popup saying it's the enemy's turn, what attack they use, and its effect. Then it's the player's turn again.

The problem is, the timer in the battle_popup screen is ignored, as the next message using the battle_popup screen immediately replaces it. So instead of seeing the series of messages going from the result of your attack through the enemy turn, it only displays the very last popup from the series of if statements in the perform_attack method. (So you just get the effect of the enemy's attack, and no other messages.)

I cannot add renpy.pause after the messages, because it is already in use. I can't use label control, because I need to pass the enemy variable back to the check to progress to the enemy's turn. I've tried time.sleep, but that stops everything for the allotted time and displays no message. I've tried a while loop, but that just freezes my game.

How can I approach this? I'm not sure in this circumstance how to have it display the messages sequentially over a set time rather than just jumping to the end.

Is there a way that I can use global variables to be able to use label control without losing the information that I'm passing between methods?

I currently have one tester battle accessible through an existing screen:

Code: Select all

            elif area == "NightForest":
                vbox:
                    imagebutton:
                        idle "images/Enemies/Taffyblob.png"
                        hover Transform("images/Enemies/Taffyblob.png", matrixcolor=TintMatrix("#10e91a"))
                        xpos 1100 ypos 490 
                        action [SetVariable("canclickscreen", False),SetVariable("activebattle", True),Hide("nightnav", _layer="nav"),Hide("green_inventory_button", _layer="invent"),Play("music", battlemusic),Show("battleon", enemy=taffyblob, x=1100, y=490)]
            
BattleSystem.rpy:

Code: Select all

python:

    def hptotal():

        return caffeine + sugar + snack + festive + delicious + spice


init:
    image lightningborder:
        "images/Battle/LightningH.png"
        xalign 1.0
        linear 1 xalign 0.0
        repeat

    image lightningborderrev:
        "images/Battle/LightningH.png"
        xalign 0.0 yalign 1.05
        linear 1 xalign 1.0
        repeat

screen battleon(enemy, x, y):
    add "images/Battle/BattleBlue.png"
    vbox:
        add "images/Enemies/" + enemy.name + ".png"
        xpos 800
    add "lightningborder"

    add "images/Battle/BattleGreenName.png"
    add "images/Battle/Battle.png"
    add "images/Battle/TaffyblobName.png"

    add "lightningborderrev"

    vbox:
        imagebutton:
            idle "images/bg/bg Transparency.png"
            action [Hide("battleon"),Show("battler", enemy=enemy),Show("battlestats", enemy=enemy),Show("combatant", enemy=enemy, x=x, y=y)]



screen battler(enemy):


    vbox:
        imagebutton:
            idle "images/Battle/AttackButton.png"
            hover Transform("images/Battle/AttackButton.png", matrixcolor=TintMatrix("#10e91a"))
            action [Play("sound", menubutt),Hide("battler"),Show("battleattack", enemy=enemy)]
            focus_mask True

    vbox:
        imagebutton:
            idle "images/Battle/AbilitiesButton.png"
            hover Transform("images/Battle/AbilitiesButton.png", matrixcolor=TintMatrix("#10e91a"))
            action [Play("sound", menubutt),Hide("battler"),Show("battleabilities", enemy=enemy)]
            focus_mask True

    vbox:
        imagebutton:
            idle "images/Battle/ItemsButton.png"
            hover Transform("images/Battle/ItemsButton.png", matrixcolor=TintMatrix("#10e91a"))
            action [Play("sound", menubutt),Hide("battler"),Show("battleitems", enemy=enemy)]
            focus_mask True
    vbox:
        imagebutton:
            idle "images/Battle/RunAwayButton.png"
            hover Transform("images/Battle/RunAwayButton.png", matrixcolor=TintMatrix("#10e91a"))
            action [Play("sound", menuback),SetVariable("activebattle", False),Hide("battler"),Hide("battlestats"),Hide("combatant"),Show("nightnav", _layer="nav"),Show("green_inventory_button", _layer="invent"),Play("music", forestchill)]
            focus_mask True

screen combatant(enemy, x, y):
    vbox:
        add "images/Enemies/" + enemy.name + ".png"
        xpos x ypos y

    bar:
        value enemy.hp  # Bind the bar value to the enemy's HP attribute
        range enemy.max_hp
        xpos x + 60 # Adjust the x-position of the HP bar
        ypos y - 50  # Adjust the y-position of the HP bar
        xsize 200  # Set the width of the HP bar
        ysize 50  # Set the height of the HP bar
        # left_bar "#ffffffaa"  
        # right_bar "#FFFF0099"

screen battlestats(enemy):
    vbox:
        add "images/Battle/StatsBox.png"
    hbox:
        text "EXP" color "#ffffff"
        text "      "
        text "CAFFEINE" color "#ffffff"
        text "    "
        text "SUGAR" color "#ffffff"
        text "    "
        text "SNACK" color "#ffffff"
        text "    "
        text "FESTIVE" color "#ffffff"
        text "    "
        text "DELICIOUS" color "#ffffff"
        text "    "
        text "SPICE" color "#ffffff"
        text "         "
        text "HP" color "#ffffff" size 40
        xpos 535 ypos 890

    hbox:
        text "[exp]" color "#ffffff" size 30
        xpos 555 ypos 960
    hbox:
        text "[caffeine]" color "#ffffff" size 40
        xpos 720 ypos 950
    hbox:
        text "[sugar]" color "#ffffff" size 40
        xpos 900 ypos 950
    hbox:
        text "[snack]" color "#ffffff" size 40
        xpos 1050 ypos 950
    hbox:
        text "[festive]" color "#ffffff" size 40
        xpos 1220 ypos 950
    hbox:
        text "[delicious]" color "#ffffff" size 40
        xpos 1410 ypos 950
    hbox:
        text "[spice]" color "#ffffff" size 40
        xpos 1590 ypos 950
    hbox:   
        text "[hp]" color "#ffffff" size 60
        xpos 1750 ypos 940


screen battleattack(enemy):
    vbox:
        add "images/Battle/BattleMenu.png"


    vbox:
        xpos 100 ypos 200
        for attack in attacks:
            textbutton attack['name'] action [
                Hide("battleattack"),
                Show("battler", enemy=enemy),
                Function(attack['function'], enemy=enemy),
                Jump("sortout")
            ]

    vbox:
        add "images/Battle/AttackButton.png"

    vbox:
        imagebutton:
            idle "images/Battle/BackButton.png"
            hover Transform("images/Battle/BackButton.png", matrixcolor=TintMatrix("#10e91a"))
            action [Play("sound", menuback),Hide("battleattack"),Show("battler", enemy=enemy)]
            focus_mask True


screen battleabilities(enemy):
    vbox:
        add "images/Battle/BattleMenu.png"


    vbox:
        add "images/Battle/AbilitiesButton.png"
        ypos -220

    vbox:
        imagebutton:
            idle "images/Battle/BackButton.png"
            hover Transform("images/Battle/BackButton.png", matrixcolor=TintMatrix("#10e91a"))
            action [Play("sound", menuback),Hide("battleabilities"),Show("battler", enemy=enemy)]
            focus_mask True

screen battleitems(enemy):
    vbox:
        add "images/Battle/BattleMenu.png"

    vbox:
        xpos 70 ypos 200
        for item in foster_inv.inv:
            if item[0].battle:  # Check if item is a battle item
                textbutton "[item[0].name]" action (item[0].act)  # Use the item's action
    vbox:
        add "images/Battle/ItemsButton.png"
        ypos -440

    vbox:
        imagebutton:
            idle "images/Battle/BackButton.png"
            hover Transform("images/Battle/BackButton.png", matrixcolor=TintMatrix("#10e91a"))
            action [Play("sound", menuback),Hide("battleitems"),Show("battler", enemy=enemy)]
            focus_mask True


screen battle_popup(message):
    zorder 5000
    layer "invent"
    frame:
        style_group "invstyle"
        hbox:
            text message
    timer 2.0 action Hide("battle_popup")
Attacks.rpy:

Code: Select all

init python:

    def kick_attack(enemy):
        hit_probability = 0.8 + luck - enemy.agility
        if random.random() < hit_probability:
            if enemy.attribute == "Sludge":
                dodge = 0 
                renpy.play("audio/SFX/Sludge.mp3", "sound")
                renpy.show_screen("battle_popup", "Your foot gets stuck in the sludge!")
            else:
                damage_dealt = 2
                enemy.take_damage(damage_dealt)
                renpy.show_screen("battle_popup", f"You Kick {enemy.name} for {damage_dealt} damage!.")
        else:
            renpy.show_screen("battle_popup", f"You try to kick {enemy.name}, but miss.")

        if enemy.is_alive():
            renpy.show_screen("battle_popup", "Enemy turn!")
            enemy.perform_attack()

    attacks = [
        {
            'name': "Kick",
            'function': kick_attack,
            'damage': 2  # Added missing ':' and corrected key name
        }
    ]
        
Bestiary.rpy:

Code: Select all

init python:    

    import random    

    # Define the Enemy class
    class Enemy:
        def __init__(self, name, hp, max_hp, defense, agility, attacks, attribute, flee):
            self.name = name
            self.hp = 0
            self.max_hp = 0
            self.defense = 0
            self.agility = 0
            self.attacks = attacks or []  # List to store enemy's attacks    
            self.attribute = attribute
            self.flee = flee

        def set_stats(self, hp, max_hp, defense, agility):
            self.hp = hp
            self.max_hp = max_hp
            self.defense = defense    
            self.agility = agility

        def take_damage(self, damage):
            self.hp -= max(0, damage - self.defense)
            if self.hp < 0:
                self.hp = 0    

        def is_alive(self):
            return self.hp > 0    

        def perform_attack(self):
            global sugar, spice, festive, caffeine, delicious, snack
            # Filter attacks based on conditions/rules
            available_attacks = [attack for attack in self.attacks if self.can_use_attack(attack)]    

            # Select a random attack from the list of available attacks
            selected_attack = random.choice(available_attacks)    

            # Perform the selected attack
            renpy.show_screen("battle_popup", f"{self.name} performs {selected_attack['name']}!")    

            if 'sugar' in selected_attack:
                sugar -= selected_attack['sugar']
                renpy.show_screen("battle_popup", f"-{selected_attack['sugar']} sugar!")
            if 'spice' in selected_attack:
                spice -= selected_attack['spice']
                renpy.show_screen("battle_popup", f"-{selected_attack['spice']} spice!")
            if 'festive' in selected_attack:
                festive -= selected_attack['festive']
                renpy.show_screen("battle_popup", f"-{selected_attack['festive']} festive!")
            if 'caffeine' in selected_attack:
                caffeine -= selected_attack['caffeine']
                renpy.show_screen("battle_popup", f"-{selected_attack['caffeine']} caffeine!")
            if 'delicious' in selected_attack:
                delicious -= selected_attack['delicious']
                renpy.show_screen("battle_popup", f"-{selected_attack['delicious']} delicious!")
            if 'snack' in selected_attack:
                snack -= selected_attack['snack']
                renpy.show_screen("battle_popup", f"-{selected_attack['snack']} snack!")
            if 'heal' in selected_attack:
                self.hp += selected_attack['heal']
                renpy.show_screen("battle_popup", f"Heals for {selected_attack['heal']}!")
            if 'defense' in selected_attack:
                self.defense += selected_attack['defense']
                renpy.show_screen("battle_popup", f"Defense boosted by {selected_attack['defense']}!")




            # Apply animation if available
            if 'animation' in selected_attack:
                self.play_animation(selected_attack['animation'])    

            # Apply sound effect if available
            if 'sound_effect' in selected_attack:
                self.play_sound_effect(selected_attack['sound_effect'])    

            renpy.jump("sortout")

        def can_use_attack(self, attack):
            if 'rule' in attack:
                return attack['rule'](self)
            else:
                return True  # Default to allowing the attack if no rule specified    

        def play_animation(self, animation_name):
            # Simulate playing animation (replace with actual implementation)
            print(f"Playing animation: {animation_name}")    

        def play_sound_effect(self, sound_effect_name):
            # Simulate playing sound effect (replace with actual implementation)
            print(f"Playing sound effect: {sound_effect_name}")    

    def pull_rule(enemy):
        return enemy.hp <= 5    


    # Define enemy data with attacks including rules
    taffyblob_data = {
        'name': "Taffyblob",
        'hp': 10,
        'max_hp': 10,
        'defense': 0,
        'agility': 0,
        'attacks': [
            {'name': "Stretch", 'sugar': 1},
            {'name': "Pull", 'heal': 3, 'rule': pull_rule},
            {'name': "Harden", 'defense': 2}
        ],
        'attribute': "Sludge",
        'flee': "sugar <= 0"
    }    

    # Create an instance of an enemy using the defined data
    taffyblob = Enemy(**taffyblob_data)    

    # Set specific stats for the Taffyblob enemy
    taffyblob.set_stats(hp=10, max_hp=10, defense=0, agility=0)    
And a bit elsewhere in my code that is relevant:

Code: Select all

label sortout:
    if activebattle == True:
        $ hp = caffeine + sugar + snack + delicious + festive + spice
        $ renpy.restart_interaction()
        $ renpy.pause(hard=True)
Any advice/insight would be greatly appreciated! :)

User avatar
m_from_space
Eileen-Class Veteran
Posts: 1009
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#2 Post by m_from_space »

So my way to show some message to the player while not wanting the player to continue, would be to call a modal screen and use a timer to automatically return from that screen.

Code: Select all

screen show_message(msg, t=3.0)
    modal True
    text "[msg]" align (0.5, 0.5)
    timer t action Return()

label start:
    "Hey, welcome to the game!"
    call screen show_message("Sorry, I have to interrupt you, you have to wait 3 seconds for no reason!")
    "Alright, you have control again."
    call screen show_message("Not anymore for a long time...", 60.0)
Does this make sense? You could of course also combine specific messages as part of labels here:

Code: Select all

label three_messages:
    call screen show_message("Hello!")
    call screen show_message("My name is...")
    call screen show_message("Eileen.")
    return

label start:
    call three_messages
    e "Sorry, I had a hiccup."
/edit
I just realized this is my 1000th message in this forum. Wow, congratz to myself. :lol:

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#3 Post by thexerox123 »

m_from_space wrote: Thu May 09, 2024 12:11 pm So my way to show some message to the player while not wanting the player to continue, would be to call a modal screen and use a timer to automatically return from that screen.

Code: Select all

screen show_message(msg, t=3.0)
    modal True
    text "[msg]" align (0.5, 0.5)
    timer t action Return()

label start:
    "Hey, welcome to the game!"
    call screen show_message("Sorry, I have to interrupt you, you have to wait 3 seconds for no reason!")
    "Alright, you have control again."
    call screen show_message("Not anymore for a long time...", 60.0)
Does this make sense? You could of course also combine specific messages as part of labels here:

Code: Select all

label three_messages:
    call screen show_message("Hello!")
    call screen show_message("My name is...")
    call screen show_message("Eileen.")
    return

label start:
    call three_messages
    e "Sorry, I had a hiccup."
/edit
I just realized this is my 1000th message in this forum. Wow, congratz to myself. :lol:
Congrats on the 1000th post! :mrgreen:

Adding modal doesn't help, as it's not the player progressing through, but Python progressing to the next line of the method. renpy.pause(hard=True) is already in use during all of this to keep the player from progressing the Renpy script. (I've tried modal True for both the screen and the timer, and it still just shows the final message but none of the preceding ones)

And if I jump to a label, I lose the 'enemy' arg that I've been passing between screens and methods, so I'm not sure how to get around that problem. And I can't pass the variable to the label, because renpy.jump doesn't allow for passing args, from what I can tell.

I guess my mistake was deciding to do control flow for this game by way of the imagebuttons and methods rather than labels with a gameloop. But at this point, I feel like it would be way more work to refactor the whole thing than to figure out a workaround for this situation. Cause other than this (admittedly major) problem, I have a lot of different parts of it working well.

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

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#4 Post by jeffster »

thexerox123 wrote: Thu May 09, 2024 12:37 pm Adding modal doesn't help, as it's not the player progressing through, but Python progressing to the next line of the method. renpy.pause(hard=True) is already in use during all of this to keep the player from progressing the Renpy script. (I've tried modal True for both the screen and the timer, and it still just shows the final message but none of the preceding ones)

And if I jump to a label, I lose the 'enemy' arg that I've been passing between screens and methods, so I'm not sure how to get around that problem. And I can't pass the variable to the label, because renpy.jump doesn't allow for passing args, from what I can tell.

I guess my mistake was deciding to do control flow for this game by way of the imagebuttons and methods rather than labels with a gameloop. But at this point, I feel like it would be way more work to refactor the whole thing than to figure out a workaround for this situation. Cause other than this (admittedly major) problem, I have a lot of different parts of it working well.
In Ren'Py we use "show screen" when that screen should show something in parallel with the rest of the game process.

If we want the game process to remain on a certain screen then we use "call screen".

Then we don't need "renpy.pause" "to keep the player from progressing the Renpy script", because we stay on the called screen until we return back from it.
And if I jump to a label, I lose the 'enemy' arg that I've been passing between screens and methods
The 'enemy' arg can be set as a normal variable. You can pass it to screens and methods, and inside screens you can set it with SetVariable.
If the problem is solved, please edit the original post and add [SOLVED] to the title. 8)

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

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#5 Post by jeffster »

PS. Example:

Code: Select all

label start_battle:
    # Initialize the battle
    python:
        en = Enemy("elf")
        pl = Player()

label battle_show_round:
    call screen battle(en)
    if not battle_finished:        # conditions like (en.hp > 0 and pl.hp > 0 and nobody_flees)
        jump battle_show_round

label do_whatever_after_battle:
    #...

label called_from_battle:
    # Do something here
    #...
    if not battle_finished:
        jump battle_show_round
    else:
        jump do_whatever_after_battle
If the problem is solved, please edit the original post and add [SOLVED] to the title. 8)

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

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#6 Post by jeffster »

PPS. It's possible to show several messages at the same time, either as a flat list of messages, or as a queue where each message would appear and disappear by its individual timer (and perhaps each message should be either the 1st in the queue, or it should be shown at least 0.5 sec after the previous message appeared).
If the problem is solved, please edit the original post and add [SOLVED] to the title. 8)

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#7 Post by thexerox123 »

jeffster wrote: Thu May 09, 2024 10:41 pm In Ren'Py we use "show screen" when that screen should show something in parallel with the rest of the game process.

If we want the game process to remain on a certain screen then we use "call screen".

Then we don't need "renpy.pause" "to keep the player from progressing the Renpy script", because we stay on the called screen until we return back from it.
And if I jump to a label, I lose the 'enemy' arg that I've been passing between screens and methods
The 'enemy' arg can be set as a normal variable. You can pass it to screens and methods, and inside screens you can set it with SetVariable.
This is a very helpful explanation -- I've never quite grasped the difference between call screen and show screen (as may be obvious from my approach).

Though I think I'm too far in to refactor it all to use call screen at this point. And one question re: call vs show, for future reference... if I'm using call screen, can I then still show screens on top of that? My navigation/point and click systems use a lot of different layered screens.

So it looks like I have to figure out the variable method! I'll take a closer look at your example. :)

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

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#8 Post by jeffster »

thexerox123 wrote: Fri May 10, 2024 12:41 pm if I'm using call screen, can I then still show screens on top of that? My navigation/point and click systems use a lot of different layered screens.
Yes. Call one main screen, and there we can additionally both use screens (like Main Menu uses the navigation screen) and show screens. Also we can show screen elements conditionally (e.g. instead of show/hide some screen, set variable/flag that would control the presence of some button etc.).
If the problem is solved, please edit the original post and add [SOLVED] to the title. 8)

giorgi1111
Newbie
Posts: 16
Joined: Sat May 04, 2024 10:40 pm
Contact:

Re: Yo Dawg, I Heard You Like Pauses - Screen Timer + renpy.pause

#9 Post by giorgi1111 »

You must use renpy.pause near actions not after messages
Something like this: action (attack), attackmsg or msg can be in attack, then pause, then jimp enemy turn.
I fully changed renpy battle engine one vs one battle it has what you need. Msgs: player attacked enemy used skill something player used potion enemy attack player defend and so on
I see your examples now . Use pause in label after each call i think it will wait even you call labal threemessages

Post Reply

Who is online

Users browsing this forum: BadMustard, Google [Bot]