Ren'Py AutoDesk renpy: How can I trigger a Dialogue chain when entering a location only the first time?

Is this kind of code adecuate for a visual novel?

  • Yes

    Votes: 2 100.0%
  • Kinda, could be better

    Votes: 0 0.0%
  • some of it, but needs a lot of work

    Votes: 0 0.0%
  • it's just rubbish.

    Votes: 0 0.0%

  • Total voters
    2

Emmerald Entertainment

Newbie
Game Developer
Aug 1, 2020
43
380
Hi, I have started using renpy pretty recently. And i am working on a VN. It has an explorable map, and an inventory, and 2 characters for now.

I have a class DIALOGUE(self, location, participant, chain, sequence, lbl, InitText)
Dialogue = []

a class CHAIN(self, participant, evnt, sequence, IsActive):
Chain = []

When you click on a character's icon, you trigger a certain chain label, showing all the dialogues in it's order of sequence. which is fine. But i want the chain to be triggered when you enter a location on the map for the first time, instead of being triggered by a click on a specific character.

In case it helps:

Game loop:
Code:
label start:
    $ y = renpy.input("what's your name?")
    $ Playing = True
    while Playing:
        window hide

        $ clickType = ""

        $ UIreturn = renpy.call_screen("mainUI")

        if clickType == "mapOpen":
            call MapNav

        if clickType == "subLocSelect":
            call SubLocNav

        if clickType == "CharacterClick":
            $ CharacterClick(location)
            if ChoiceList <> []:
                $ LabelToCall = renpy.display_menu(ChoiceList, interact=True, screen="choice")
                call expression LabelToCall

        if clickType == "Clicky":
            call expression UIreturn

        if clickType == "invOpen":
            pass
  return
My main UI():
Code:
screen mainUI():
    use BGIMAGE
    use topBar
    use clickies
    use Character_Screen
    use SubLocHud
My Top Bar:
Code:
screen topBar():
    imagebutton:
        xpos 0
        ypos 15
        focus_mask True
        hover "/ui/map/map_icon_hover.png"
        idle "/ui/map/map_icon.png"
        action SetVariable("clickType", "mapOpen"), Return(None)

my map screen():
Code:
screen townmap():
    add "/ui/map/mapbg.png"
    imagebutton:
        xpos 0
        ypos 0
        focus_mask True
        hover "/ui/map/map_icon_hover.png"
        idle "/ui/map/map_icon.png"
        action SetVariable("clickType", "mapCancel"), Return(None)

    for q in Scenes:
        $ TempName = "images/ui/map/map_" + q.name + "_icon.png"
        $ TempName1 = "images/ui/map/map_" + q.name + "_icon_hover.png"
        imagebutton:
            idle TempName
            hover TempName1
            focus_mask True
            action SetVariable("clickType", "mapSelect"), SetVariable("locationID", q.ID), Return(q.name)
The map Navigation:
Code:
label MapNav:
    $ UIreturn = renpy.call_screen("townmap")

    if clickType == "mapCancel":
        return

    if clickType == "mapSelect":
        $ location = UIreturn

    return


label SubLocNav:

    if clickType == "mapCancel":
        return

    if clickType == "subLocSelect":
        $ location = UIreturn
        

    hide SubLocHub()
    return
The functions:
Code:
init python:
    def BGDeclare():
        global location
        global BGimage
        BGimage = location.lower()
        BGimage = BGimage.replace(" ", "")
        Bgimage = BGimage + ".jpg"

    def CharacterClick(loc):
            global Dialogue
            global Chain
            global UIreturn
            global ChoiceList
            ChoiceList = []
            for q in Dialogue:
                if q.participant == UIreturn:
                    if q.location == loc or q.location == "":
                        ch = q.chain
                        sq = q.sequence
                        if sq == Chain[ch].sequence:
                            ChoiceList.append((q.InitText, q.lbl))
And the classes i think are most important for the question:
Code:
    class DIALOGUE(object):
        def __init__(self, location, participant, chain, sequence, lbl, InitText):
            self.location = location
            self.participant = participant
            self.chain = chain
            self.sequence = sequence
            self.lbl = lbl
            self.InitText = InitText

        @property
        def check(self):
            global location
            if self.participant == "":
                if self.location == location or self.location == "":
                    if self.sequence == Chain[self.chain].sequence or self.chain == -1:
                        return True
            return False

    Dialogue = []
    Dialogue.append(DIALOGUE("apothecary", "Tammara", 0, 0, "chain_0_0", ""))
    Dialogue.append(DIALOGUE("in_apothecary", "Tammara", 0, 1, "chain_0_1", "HI!"))



    class CHAIN(object):
        def __init__(self, participant, evnt, sequence, IsActive):
            self.participant = participant
            self.evnt = evnt
            self.sequence = sequence
            self.IsActive = IsActive

        def next(self):
            self.sequence += 1

    Chain = []
    Chain.append(CHAIN("Tammara",0, 0, True))


As I said i am pretty new, but i can say that the game works up to now. Even if i don't know if my code is the most adecuate. I would appretiate any kind of tips you think could help me.

Thanks :)
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,159
14,859
But i want the chain to be triggered when you enter a location on the map for the first time, instead of being triggered by a click on a specific character.
Then use a flag. You'll raise it once the first time in the said location is finished. And you'll trigger the dialog only if the flag is still lowered.

I would like to give a practical example, but it would be counter productive here, because your code is wrong from the start. You are over doing it way too much, trying to twist Ren'py to your logic, while you should adapt to Ren'py. This not being said negatively ; it's an observation, not a reproach.


Code:
        $ clickType = ""

        $ UIreturn = renpy.call_screen("mainUI")

        if clickType == "mapOpen":
            call MapNav

        if clickType == "subLocSelect":
            call SubLocNav
This way to do is outdated since around 10 years now.

Code:
    call screen mainUI
    if _return == "mapOpen":
        call MapNav
    elif _return == "subLocSelect":
        call SubLocNav

[...]
       action Return("mapCancel")
Of course, there's the problem due to the double value, but it's in fact a false problem :
Code:
    call screen mainUI
    $ action, param = _return.split("|")
    if action == "mapOpen":
        call MapNav
    elif action == "subLocSelect":
        call SubLocNav
    elif action == "Clicky":
        call expression param

[...]
       action Return("mapCancel|")
[...]
       action Return("Clicky|{}".format( WhateverValueGoesHere ) )

Code:
        if clickType == "CharacterClick":
            $ CharacterClick(location)
            if ChoiceList <> []:
                $ LabelToCall = renpy.display_menu(ChoiceList, interact=True, screen="choice")
                call expression LabelToCall
If you want conditional menus, just write conditional menus :
Code:
label whatever:
    [...]
    call whateverMenu
    [...]

menu whateverMenu:
    "some choice" if participant == "expected one" and location == "expected location":
        jump whateverLabel
    "some other choice" if participant == "expected one" and location == "expected location":
        jump anotherLabel
    [...]

label whateverLabel:
    [...]
    return
Then if you want to limit the number of entries in the menus, just discriminate beforehand :
Code:
label whatever:
    [...]
    if participant == "this participant"
        call whateverMenuThisParticipant
    elif participant == "that participant"
        call whateverMenuThatParticipant
    [...]

menu whateverMenuThisParticipant:
    "some choice" if location == "expected location":
        jump whateverLabel
    "some other choice" if location == "expected location":
        jump anotherLabel
    [...]

label whateverLabel:
    [...]
    return
If you want to get rid of the generic conditions, just get rid of the them :
Code:
label whatever:
    [...]
    call expression "menu{}{}".format( participant, location )
    [...]

menu menuThisParticipantThatLocation:
    "some choice":
        jump whateverLabel
    "some other choice":
        jump anotherLabel
    [...]

label whateverLabel:
    [...]
    return
And finally if you want an option to happen only once, just make it behave this way :
Code:
default doneOnce = False

label whatever:
    [...]
    call expression "menu{}{}".format( participant, location )
    [...]

menu menuThisParticipantThatLocation:
    "some choice" if doneOnce is False:
        $ doneOnce = True
        jump whateverLabel
    "some other choice":
        jump anotherLabel
    [...]

label whateverLabel:
    [...]
    return
And so on. Python is here to extend Ren'py's abilities, not to replace them. Basically speaking, the whole code you gave could be around 90% pure Ren'py, while you made it 99% Python.

By itself it doesn't matter this much, you code like you want. But by doing it this way, you deprive yourself to all the extended abilities that are available with the Ren'py statements, but not, or not easily, with the Python equivalents. And in top of that you highly increase the bug possibilities, since you try to recreate by yourself something tested and validated by millions of players over the years.
 

Emmerald Entertainment

Newbie
Game Developer
Aug 1, 2020
43
380
okay, I realize that i don't quite know what is written in python or renpy. My code is a mix of many different tutorials i found effective. Do you know of any threads or tutorials to understand that difference? Or the most basic things i must be missing as a newbie?

Thank you so much for your answer, it really helps :)
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,159
14,859
Do you know of any threads or tutorials to understand that difference? Or the most basic things i must be missing as a newbie?
Well, there's the obvious start, . It's not always easy to understand, but it's a really good start. Don't necessarily read it from start to end, the main page point to the essential and can easily be used as filter.
By example, I talked a lot about the menus, if you want to know more, then you look at the documentation's main page, see "The Ren'Py Language -> In-games Menus", and it's obvious that you'll surely find interesting things there.
There's also few interesting guides and how-to here, but honestly I don't remember their names. I just know that I wrote one, you can find the link in my signature. But it's not the only interesting one.
 

Emmerald Entertainment

Newbie
Game Developer
Aug 1, 2020
43
380
Thanks for the answer! i have kept working on the code, and after you comment, and reading, like you said, a lot of the offitial documentation, i have made a lot of changes to the code.

I solved the main issue that I was asking about here.

If you feel like taking a look and giving me a comment about it, here is my new code. Doesn't give me any problems yet.

Main script:

Code:
default locationID = 0
define mc = Character("[y]", color="#E0DAB4")
define ApFirstTime = 0
define herbsInt = 0
define herbs = False
define Tamm1 = True

define tam = Character("Tammara", color="#E0DAB4")

label start:
    scene home
    "this is your house"
    $ y = renpy.input("what's your name?")
    "you can explore the town"
    call screen MainUI

    pause
    return
My screens:
Code:
screen MainUI():
    use topBar
    use sublocations
    use objects
    use Character_Screen
    

screen townMap():
    modal True
    add "/ui/map/mapbg.png"
    for q in Places:
        $ TempName = "images/ui/map/map_" + q.name + "_icon.png"
        $ TempName1 = "images/ui/map/map_" + q.name + "_icon_hover.png"
        imagebutton:
            idle TempName
            hover TempName1
            focus_mask True
            action [Hide("townMap"), SetVariable("locationID", q.ID), Jump(q.name)]

screen sublocations():
    for q in subPlaces:
        $ TempName = "/UI/subLocIcons/" + q.name + "_icon.png"
        $ TempName1 = "UI/subLocIcons/" + q.name + "_icon_hover.png"
        if q.parent == locationID:
            imagebutton:
                yalign 0.0
                xalign 0.0
                focus_mask True
                idle TempName
                hover TempName1
                action [Jump(q.name), Hide("sublocations"), Return(None)]

Classes:
Code:
class place(object):
        def __init__(self, name, ID, unLocked):
            self.name = name
            self.ID = ID
            self.unLocked = unLocked

        @property
        def subplaces(self):
            outlist = []
            for q in SUBP:
                if q.parent == self.ID:
                    outlist.append(q.ID)
            return outlist

    Places = []
    Places.append(place("home", 0, True))
    Places.append(place("lei_house", 1, True))
    Places.append(place("apothecary", 2, True))
    Places.append(place("sanctuary", 3, True))
    Places.append(place("port", 4, True))
    Places.append(place("hideout", 5, True))
    Places.append(place("shrine", 6, True))
    Places.append(place("cave", 7, True))
    Places.append(place("longhouse", 8, True))

    class subplace(object):
        def __init__(self, ID, name, parent):
            self.name = name
            self.ID = ID
            self.parent = parent

    subPlaces = []
    subPlaces.append(subplace(9, "in_apothecary", 2))
    subPlaces.append(subplace(10, "in_cave2", 7))
    subPlaces.append(subplace(11, "in_cave3", 10))

class Clicky(object):
        def __init__(self, locationID, isActive, icon, hove, func):
            self.locationID = locationID
            self. isActive = isActive
            self.icon = icon
            self.hove = hove
            self.func = func

    Objects = []
    Objects.append(Clicky(3, True, "/UI/objects/herbs_icon.png", "/UI/objects/herbs_icon_hover.png", "herbs_00"))
    Objects.append(Clicky(6, True, "/UI/objects/shrine_icon.png", "/UI/objects/shrine_icon_hover.png", "shrine_00"))
    Objects.append(Clicky(11, True, "/UI/objects/cave_icon.png", "/UI/objects/cave_icon_hover.png", "cave_00"))


    class npc(object):
        def __init__(self, name, surname, locationID):
            self.name = name
            self.surname = surname
            self.locationID = locationID


    NPC = []
    NPC.append(npc("Tammara", "townie", 9))
    NPC.append(npc("Kissa", "magic", 3))
    NPC.append(npc("DaddyShark", "townie", 4))
Location labels:
Code:
image MC = "/UI/npcs/MC.png"
image tamm = "UI/npcs/Tammara_npc.png"
label home:
    $ locationID = 0
    scene home
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return

label lei_house:
    $ locationID = 1
    scene lei_house
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return

label apothecary:
    $ locationID = 2
    scene apothecary

    if ApFirstTime == 0:
        show MC:
            xalign 0.2
        mc "Man… I haven’t been here in a while. I wonder if Tamara remains as eccentric as I remember."
        mc "I have to admit, she's gotta be the best apothecary and healer in the entire island."
        mc "I even think I heard Chanchi say a couple of times that grandfather would visit her often before and after his trips. So certainly if he trusted her then I should too."
        mc "Given her status in the village she surely knows something about this Loko character I need to find out about."
        $ ApFirstTime += 1
        hide MC
        jump apothecary

    else:
        pass

    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return

label sanctuary:
    $ locationID = 3
    scene sanctuary
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return

label port:
    $ locationID = 4
    scene port
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return

label hideout:
    $ locationID = 5
    scene hideout
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return

label shrine:
    $ locationID = 6
    scene shrine
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return

label cave:
    $ locationID = 7
    scene cave
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return
Character labels:
Code:
label Tammara:
    if Tamm1 == True:
        menu:
            "Here are the plants":
                if herbsInt >= 1:
                    jump .gotherbs
                else:
                    show tamm:
                        xalign 0.8
                    tam "You don't have what i asked for"
                jump in_apothecary

            "Do you know a man called Loko?":
                if herbsInt >= 1:
                    show tamm:
                        xalign 0.8
                    tam "I only heard of him from your grandpa, a mysterious man for sure."
                    jump in_apothecary
                else:
                    show tamm:
                        xalign 0.8
                    tam "I told you i would answer your questions when you brought me those herbs"
                    jump in_apothecary

        label .gotherbs:
            show tamm:
                xalign 0.8
            show MC:
                xalign 0.2
            mc "I believe these are the herbs that you wanted?"
            tam "Wonderful! You have proven yourself to be quite useful. I’m happy to know I can trust you."
            mc "It’s no problem really, that was pretty easy."
            tam "That’s because you are very capable. Why don’t we step to the back and I teach you those mixtures I promised?"
            mc "Yes! Thank you!  .... But what about my questions?"
            tam "Yes, yes. I almost forget. What would you like to know?"
            mc "Do you know a person named Loko?"
            tam "You know, your grandpa used to bring me all kinds of materials, books and recipes from many different places. I actually owe a lot of my knowledge and skill to him."
            tam "I think you should look into visiting the Island of Illusions. It’s just south from here. Your grandfather did speak of visiting it often"
            tam "But be warned, it’s not a place anyone can enter. As the names describes, there are things that most men don’t understand, and they can lead to permanent injuries or even death."
            mc "If you say so, I trust you. I will prepare myself the best I can."
            tam "And now that I know you will venture into such dangerous places by your self, all the more reason to teach you a couple healing aids that you can make with some common herbs."
            tam "Follow me this way."
            pause

            jump Tam_0

    if Tamm1 == False:
        tam "If you find anything interesting, come see me."
        tam "Even though you could come see me anyway.."
        jump in_apothecary
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,159
14,859
Thanks for the answer!
You're welcome.


If you feel like taking a look and giving me a comment about it, here is my new code. Doesn't give me any problems yet.
I can have missed something, but right now I only see cosmetic changes. They'll not have impact on the game itself, just can possibly ease a little your own works :


Code:
screen townMap():
    modal True
    add "/ui/map/mapbg.png"
    for q in Places:
        $ TempName = "images/ui/map/map_" + q.name + "_icon.png"
        $ TempName1 = "images/ui/map/map_" + q.name + "_icon_hover.png"
        imagebutton:
            idle TempName
            hover TempName1
            focus_mask True
            action [Hide("townMap"), SetVariable("locationID", q.ID), Jump(q.name)]
I can't test right now, so I don't guaranty that it will works with the loop, but normally you can get rid of the two temporary variables :
Code:
screen townMap():
    modal True
    add "/ui/map/mapbg.png"
    for q in Places:
        imagebutton:
            idle ( "images/ui/map/map_" + q.name + "_icon.png" )
            hover ( ""images/ui/map/map_" + q.name + "_icon_hover.png" )
            focus_mask True
            action [Hide("townMap"), SetVariable("locationID", q.ID), Jump(q.name)]
Anyway with a small renaming of "map_[...]_icon.png" into "map_[...]_icon_idle.png" you can benefit from the auto proprerty of the :

Code:
screen townMap():
    modal True
    add "/ui/map/mapbg.png"
    for q in Places:
# if the first correction don't works
#        $ TempName = "images/ui/map/map_" + q.name + "_icon_%s.png"
        imagebutton:
#            if the first correction don't works
#            auto TempName
            auto ( "images/ui/map/map_" + q.name + "_icon_%s.png" )
            focus_mask True
            action [Hide("townMap"), SetVariable("locationID", q.ID), Jump(q.name)]

Code:
label home:
    $ locationID = 0
    scene home
    call screen MainUI
    $ renpy.pause(delay=None, hard=True)
    return
Code:
label home:
    $ locationID = 0
    scene home
    call screen MainUI
    pause
    return
The statement would have exactly the same behavior. In fact even $ renpy.pause() should do the same, but using the statement in place of the Python equivalent is here more beneficial ; a question of optimization made by Ren'py.

Side note:
But careful. While pause without parameter is an effective pause, when you add a value, like by example pause 1, something weird happen. It stop to be a pause, and become a transition that will wait. Which mean that if the player have decided to skip the transitions, the pause will be skipped.
Credit to Nottravis for this discovery.


Code:
label apothecary:
[...]

    if ApFirstTime == 0:
[...]

        jump apothecary

    else:
        pass
The else part is not needed here. Doing nothing if "ApFirstTime" isn't at 0 is already what would happen.
 
  • Like
Reactions: Shadow Fiend