Ren'Py [solved]how to improve this character schedule code

rayminator

Engaged Member
Respected User
Sep 26, 2018
3,041
3,140
how to improve this code character schedule

what I need is able to disable a character and to change a single characters time and for images naming in a number or a pose_name into image name

this a repeat my original post here
what told me to learn mose Python but can't find nothing on it I don't know if he/she misspelled it or not

I have tried what anne O'nymous have posted but it given a error

this code the way it should but there is only one problem is the image

image would named....
mom_bedroom.png

number or a pose_name into image name
mom_bedroom_0.png or mom_bedroom_sleeping.png

I know that I need to add a variable so right now I can have one image per character in a position in a location but can't change the position but can change the location

Hope you understand I'm not great at this the code is below if I can''t get this to work well I do what I did in my other game by using imagebutton with true/false statements

now how does this codes work this will only show one image
images/avatar/mom/mom_sc_Office.png
images/avatar/{pull_the_folders_name_of_character}/{character_name}_{{location}}.png
self.fmt_str = "avatar/{name}/{name}_{{}}.png".format(name=name)

default.rpy
Python:
"""
npc_schedule is a dict { (time: int, location: str): (npc_name: str, lbt: str} }
npcs is a dict { npc_name: NPC } used to get an NPC by its name.
"""
default npc_schedule = {}
default npcs = {}
default time = 0 #when this is change the character will change

label Characters_Variables:

    # (time, "location"): "lbt"
    $ NPC.create("mom", nice_name="Mom", schedule={
            (0, "sc_Office"): "test", #sleeping #this will be a problem is at
            (1, "sc_Office"): "test", #changing #this will be a problem is at
            (2, "sc_Office"): "test", #looking out the window #this will be a problem is at
            (3, "sc_Office"): "test", #watching tv
    })
class.rpy
Python:
init python:
    class NPC:
        @classmethod
        def create(cls, name, nice_name, schedule):
            """
            schedule: a dict {(time: int, location: str): lbt: str}
                value will be converted to tuple (name, lbt) during creation
            """
            npcs[name] = NPC(name, nice_name, NPC.__key)
            for key, value in schedule.iteritems():
                if key not in npc_schedule:
                    npc_schedule[key] = [ ]
                npc_schedule[key].append( (name, value) )

        __key = object() # To disallow accidental creation
        def __init__(self, name, nice_name, key):
            assert(create_key == NPC.__key)
            self.nice_name = nice_name
            self.fmt_str = "avatar/{name}/{name}_{{}}.png".format(name=name)

        @property
        def avatar(self):
            global location
            avtr = self.fmt_str.format(location)
            if renpy.loadable(avtr):
                return avtr
          
    class School(object):
        def __init__(self, name, unlocked):
            self.name = name
            self.unlocked = unlocked

    sch = []
    sch.append(School("sc_Hallway", True))
    sch.append(School("sc_Classroom 1", True))
    sch.append(School("sc_Classroom 2", True))
    sch.append(School("sc_Classroom 3", True))
    sch.append(School("sc_Bathroom", True))
    sch.append(School("sc_Gym", True))
    sch.append(School("sc_Pool", True))
    sch.append(School("sc_shower", True))
    sch.append(School("sc_Office", True))

    location = sch[7].name
character_screen.rpy
Python:
screen character_screen():
    $ npc_present = npc_schedule.get( (time, location), [ ] )

    for name, lbt in npc_present:
        $ npc = npcs[name]
        imagebutton:
            idle npc.avatar
            hover npc.avatar
            focus_mask True
            action Call(lbt) #calls labels
here three images I can't name then this way mom_sc_Office.png now can I
test1.png test2.png test3.png
 
Last edited:

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,296
3,952
I'm not sure of all the code but there's a syntax error in the first block - there is an extra double quotes.

Code:
(3, "livingroom"): " "test", #watching tv
Regarding your question of how to modify the avatar code to include a pose:

This defines the initial format string and the character name is subsituted in to two places
Code:
self.fmt_str = "avatar/{name}/{name}_{{}}.png".format(name=name)
And it is used like this, as source for a second call to the format method to add the location
Code:
          avtr = self.fmt_str.format(location)
If you want to add a pose as well then modify like this:
Code:
self.fmt_str = "avatar/{name}/{name}_{{location}}_{{pose}}.png".format(name=name)
Code:
          avtr = self.fmt_str.format(location=location, pose=pose)
However I'm not sure how you can inject the pose "number" of the character into the screen at runtime. I guess it wasn't much help in the end.
 
Last edited:
  • Like
Reactions: rayminator

rayminator

Engaged Member
Respected User
Sep 26, 2018
3,041
3,140
I'm not sure of all the code but there's a syntax error in the first block - there is an extra double quotes.

Code:
(3, "livingroom"): " "test", #watching tv
Regarding your question of how to modify the avatar code to include a pose:

This defines the initial format string and the character name is subsituted in to two places
Code:
self.fmt_str = "avatar/{name}/{name}_{{}}.png".format(name=name)
And it is used like this, as source for a second call to the format method to add the location
Code:
          avtr = self.fmt_str.format(location)
If you want to add a pose as well then modify like this:
Code:
self.fmt_str = "avatar/{name}/{name}_{{location}}_{{pose}}.png".format(name=name)
Code:
          avtr = self.fmt_str.format(location=location, pose=pose)
However I'm not sure how you can inject the pose "number" of the character into the screen at runtime. I guess it wasn't much help in the end.
thanks for the help

I can put the pose into a list like so
default pose = ["sleeping", "standing", "sitting"]

this part would be something like so

Python:
screen character_screen():
    $ npc_present = npc_schedule.get( (time, location, pose), [ ] )
and this part would be like

Python:
"""
npc_schedule is a dict { (time: int, location: str, pose: str ##(str/int)##): (npc_name: str, lbt: str} }
npcs is a dict { npc_name: NPC } used to get an NPC by its name.
"""
default npc_schedule = {}
default npcs = {}
default time = 0

label Characters_Variables:

    # (time, "location"): "lbt"
    $ NPC.create("mom", nice_name="Mom", schedule={
            (0, "bedroom", "sleeping"): "test", #sleeping #this will be a problem is at
            (1, "bedroom", "Changing"): "test", #changing #this will be a problem is at
            (2, "bedroom", "standing"): "test", #looking out the window #this will be a problem is at
            (3, "livingroom", "Sitting"): "test", #watching tv
    })
it's the class is where to put the pose into

this part here what you have is kinda wrong and right

Python:
self.fmt_str = "avatar/{name}/{name}_{{location}}_{{pose}}.png".format(name=name)
location is controlled by a different class or a global already
that's why has put the code like this I think it because it's a string

Python:
self.fmt_str = "avatar/{name}/{name}_{{}}.png".format(name=name)
 
Last edited:

moist-

New Member
Mar 9, 2022
1
0
The main problem is that you are using the wrong data structure for your problem. A mapping (dictionary, key-value pairs) would only let you hardcode the time so the schedule would be pretty rigid. For example, you would have to jump from the event to event. I guess this is OK if you want the day to be divided in "morning" events, "evening" events, "night" events, etc; and have the player move from a time-frame to another. I think Summer Time uses this approach.
I tried to give you a solution you could use right away using a mapping but it would require dealing with many other issues and I don't think it quite fits what you want to do.

The usual approach for this problem is to use a linked list (objects who reference other objects); each object storing a date and an event. That way you could use the time of the day to decide what event should be triggered at a given time. An event object would store the details of the event like the avatar, location, and whatnot.
Python:
class Event:
    """
    Data class to store params for a virtual event
    """
    def __init__(self, location: str, character: "NPC", variation: int):
        """
        :param location:
            string naming the location where the event should happen
        :param character:
            NPC object to who this event is related to
        :param variation:
            variation of the avatar we should use for the event
        """
        self.location = location
        self.character = character
        # this could be a string with the name of the event
        # so we could name the images "bedroom_sleeping" or "livingroom_watching_tv"
        self.variation = variation

        self.img_qualname = f"avatar/{character.name}/{location}{variation}"
        # I'm not sure if renpy supports fstrings
        # so feel free to use this one if it gives a SyntaxError
        #
        # self.img_qualname = (
        #    "avatar/{character_name}/{location}{variation}"
        #    .format(
        #        character_name=character.name,
        #        location=location,
        #        variation=str(variation),
        #    )
        # )
        # NOTE I removed the "_"s to make the names less complicated



class Notice:
    """
    'Circular', double linked list data structure to organize virtual events.
    """

    def __init__(
        self, date: int, event: Event, following: "Notice"=None, previous: "Notice"=None
    ):
        """
        :param time:
           date is an integer greater than 0 not greater than 24. It represent an hour of the day.
        :param event:
            Event to be sheduled
        """
        self.date = date
        self.event = event

        self.following = following
        self.previous = previous

    def insert(self, notice: "Notice"):
        """
        Insert new event into the linked list.
        """
        if notice.date > self.date:
            if self.following is None:
                # case: last event
                #
                # [ self ] [ (None) ]
                self.following = notice
                notice.previous = self
                return
            # delegate
            #
            # [ self ] ... [ notice ]
            self.following.insert(notice)
            return
        # <
        # it's still before the previous
        if not self.previous:
            # case: first
            # [ (None) ] [ self ]
            self.previous = notice
            notice.following = self
            return
        # there is a previous fix it
        # fix previous
        #
        # [ prev ] > notice <  [ self ] [ following ]
        self.previous.following = notice
        notice.previous = self.previous

        # fix following
        self.previous = notice
        notice.following = self

    def event_for(self, date):
        """
        Get the event for a given date.
        11PM >= [ sleep ] > 8AM >= [ wake up ] > 12PM >= [ ... ]
        """
        notice = self
        _old_notice = None

        while True:
            if notice is None or notice.date > date:
                break
            _old_notice = notice
            notice = notice.following

        if _old_notice is None:
            # case: date < first element of the linked list
            #  we return the last element
            #
            # it would better to make our linked list circular
            #(the last element refernces the first one)
            # but this works too
            while notice.following:
                notice = notice.following
            _old_notice = notice

        return _old_notice


class NPC:
    def __init__(
        self, name: str, nice_name: str, schedule: Notice = None
    ):
        self.name = name
        self.nice_name = nice_name
        self.schedule = schedule

    @property
    def location(self):
        """
        Get the current location of the NPC
        """
        # location depends of the current event for NPCs
        global TIME
        time = TIME % 24

        notice = self.schedule.event_for(time)

        return notice.event.location

    @property
    def avatar(self):
        """
        Get the correct avatar path for the event we are currently executing.
        """
        # get the hour of the day
        global TIME
        time = TIME % 24

        notice = self.schedule.event_for(time)
        avtr = notice.event.img_qualname
        if renpy.loadable(avtr):
            return avtr
        return "images/avatar/empty.png"


TIME = 7
def test_simulation():
    global TIME
    """
    Funcional test
    """
    ## Initialization
    # NPCs
    mom = NPC(name="mom", nice_name="Mom")

    # create the events
    events = (
        (Event(location="bedroom", variation=0, character=mom), 23),  # goes to sleep at 11PM
        (Event(location="bedroom", variation=2, character=mom), 12),
        (Event(location="livingroom", variation=0, character=mom), 18),  # watching TV
    )

    # schedule the events
    # the first event initializes the Schedule, the order matters for this so
    # don't add the last event to be executed first
    wake_up_event = Event(location="bedroom", variation=1, character=mom)
    schedule = Notice(event=wake_up_event, date=8)  # wakes up at 8 AM
    for event, date in events:
        schedule.insert(Notice(event=event, date=date))
    # add the schedule to the NPC
    mom.schedule = schedule

    ## Simulation
    # let time be 7 AM, still sleeping
    assert "avatar/mom/bedroom0" == mom.avatar

    # suppose the player triggered an event
    # time passes
    TIME += 2
    # 9 AM
    # still in the bedroom but the avatar is different
    assert "avatar/mom/bedroom1" == mom.avatar

    # another event is triggered; time passes
    TIME += 10
    # 7 PM now the NPC is at the livingroom
    assert "livingroom" == mom.location

    # player goes to sleep
    TIME += 12
    # 7 AM again
    assert "avatar/mom/bedroom0" == mom.avatar

if __name__ == "__main__":
    test()
Here is a sample solution that should work for your problem. I'm not sure if this could be implemented directly in renpy but if the engine lets you define objects in Python it should just work.
 

rayminator

Engaged Member
Respected User
Sep 26, 2018
3,041
3,140
The main problem is that you are using the wrong data structure for your problem. A mapping (dictionary, key-value pairs) would only let you hardcode the time so the schedule would be pretty rigid. For example, you would have to jump from the event to event. I guess this is OK if you want the day to be divided in "morning" events, "evening" events, "night" events, etc; and have the player move from a time-frame to another. I think Summer Time uses this approach.
I tried to give you a solution you could use right away using a mapping but it would require dealing with many other issues and I don't think it quite fits what you want to do.

The usual approach for this problem is to use a linked list (objects who reference other objects); each object storing a date and an event. That way you could use the time of the day to decide what event should be triggered at a given time. An event object would store the details of the event like the avatar, location, and whatnot.
Python:
class Event:
    """
    Data class to store params for a virtual event
    """
    def __init__(self, location: str, character: "NPC", variation: int):
        """
        :param location:
            string naming the location where the event should happen
        :param character:
            NPC object to who this event is related to
        :param variation:
            variation of the avatar we should use for the event
        """
        self.location = location
        self.character = character
        # this could be a string with the name of the event
        # so we could name the images "bedroom_sleeping" or "livingroom_watching_tv"
        self.variation = variation

        self.img_qualname = f"avatar/{character.name}/{location}{variation}"
        # I'm not sure if renpy supports fstrings
        # so feel free to use this one if it gives a SyntaxError
        #
        # self.img_qualname = (
        #    "avatar/{character_name}/{location}{variation}"
        #    .format(
        #        character_name=character.name,
        #        location=location,
        #        variation=str(variation),
        #    )
        # )
        # NOTE I removed the "_"s to make the names less complicated



class Notice:
    """
    'Circular', double linked list data structure to organize virtual events.
    """

    def __init__(
        self, date: int, event: Event, following: "Notice"=None, previous: "Notice"=None
    ):
        """
        :param time:
           date is an integer greater than 0 not greater than 24. It represent an hour of the day.
        :param event:
            Event to be sheduled
        """
        self.date = date
        self.event = event

        self.following = following
        self.previous = previous

    def insert(self, notice: "Notice"):
        """
        Insert new event into the linked list.
        """
        if notice.date > self.date:
            if self.following is None:
                # case: last event
                #
                # [ self ] [ (None) ]
                self.following = notice
                notice.previous = self
                return
            # delegate
            #
            # [ self ] ... [ notice ]
            self.following.insert(notice)
            return
        # <
        # it's still before the previous
        if not self.previous:
            # case: first
            # [ (None) ] [ self ]
            self.previous = notice
            notice.following = self
            return
        # there is a previous fix it
        # fix previous
        #
        # [ prev ] > notice <  [ self ] [ following ]
        self.previous.following = notice
        notice.previous = self.previous

        # fix following
        self.previous = notice
        notice.following = self

    def event_for(self, date):
        """
        Get the event for a given date.
        11PM >= [ sleep ] > 8AM >= [ wake up ] > 12PM >= [ ... ]
        """
        notice = self
        _old_notice = None

        while True:
            if notice is None or notice.date > date:
                break
            _old_notice = notice
            notice = notice.following

        if _old_notice is None:
            # case: date < first element of the linked list
            #  we return the last element
            #
            # it would better to make our linked list circular
            #(the last element refernces the first one)
            # but this works too
            while notice.following:
                notice = notice.following
            _old_notice = notice

        return _old_notice


class NPC:
    def __init__(
        self, name: str, nice_name: str, schedule: Notice = None
    ):
        self.name = name
        self.nice_name = nice_name
        self.schedule = schedule

    @property
    def location(self):
        """
        Get the current location of the NPC
        """
        # location depends of the current event for NPCs
        global TIME
        time = TIME % 24

        notice = self.schedule.event_for(time)

        return notice.event.location

    @property
    def avatar(self):
        """
        Get the correct avatar path for the event we are currently executing.
        """
        # get the hour of the day
        global TIME
        time = TIME % 24

        notice = self.schedule.event_for(time)
        avtr = notice.event.img_qualname
        if renpy.loadable(avtr):
            return avtr
        return "images/avatar/empty.png"


TIME = 7
def test_simulation():
    global TIME
    """
    Funcional test
    """
    ## Initialization
    # NPCs
    mom = NPC(name="mom", nice_name="Mom")

    # create the events
    events = (
        (Event(location="bedroom", variation=0, character=mom), 23),  # goes to sleep at 11PM
        (Event(location="bedroom", variation=2, character=mom), 12),
        (Event(location="livingroom", variation=0, character=mom), 18),  # watching TV
    )

    # schedule the events
    # the first event initializes the Schedule, the order matters for this so
    # don't add the last event to be executed first
    wake_up_event = Event(location="bedroom", variation=1, character=mom)
    schedule = Notice(event=wake_up_event, date=8)  # wakes up at 8 AM
    for event, date in events:
        schedule.insert(Notice(event=event, date=date))
    # add the schedule to the NPC
    mom.schedule = schedule

    ## Simulation
    # let time be 7 AM, still sleeping
    assert "avatar/mom/bedroom0" == mom.avatar

    # suppose the player triggered an event
    # time passes
    TIME += 2
    # 9 AM
    # still in the bedroom but the avatar is different
    assert "avatar/mom/bedroom1" == mom.avatar

    # another event is triggered; time passes
    TIME += 10
    # 7 PM now the NPC is at the livingroom
    assert "livingroom" == mom.location

    # player goes to sleep
    TIME += 12
    # 7 AM again
    assert "avatar/mom/bedroom0" == mom.avatar

if __name__ == "__main__":
    test()
Here is a sample solution that should work for your problem. I'm not sure if this could be implemented directly in renpy but if the engine lets you define objects in Python it should just work.
thanks but no thank. that would be changing my code completely

my game is set by screens setup that's why I have lbt that leads to labels

and my game has more then one character not just mom

and no your code is not similar to summertime sega not even close

my code works just fine it just need some tuning

test of my code
screenshot0015.png

and is the image that's being shown name mon_sc_office.png list of location and class

mom_sc_Office.png


class.rpy

Code:
default location = ""
class School(object):
        def __init__(self, name, unlocked):
            self.name = name
            self.unlocked = unlocked

    sch = []
    sch.append(School("sc_Hallway", True))
    sch.append(School("sc_Classroom 1", True))
    sch.append(School("sc_Classroom 2", True))
    sch.append(School("sc_Classroom 3", True))
    sch.append(School("sc_Bathroom", True))
    sch.append(School("sc_Gym", True))
    sch.append(School("sc_Pool", True))
    sch.append(School("sc_shower", True))
    sch.append(School("sc_Office", True))
 
Last edited:

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,296
3,952
oh god, I can hear it in my head....

Captain..... CAVEMAAAAANNNN!!!!!
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,283
I'm not sure of all the code but there's a syntax error in the first block - there is an extra double quotes.

Code:
(3, "livingroom"): " "test", #watching tv
It's not a syntax error. Look closer to the code:
Python:
 $ NPC.create("mom", nice_name="Mom", schedule={
            (0, "sc_Office"): "test",
            (1, "sc_Office"): "test",
            (2, "sc_Office"): "test",
            (3, "sc_Office"): "test",
    })
(3, "livingroom") is a "schedule" dictionary key, and "test" is the value for this key. Therefore the ":" is mandatory here.



Python:
"""
npc_schedule is a dict { (time: int, location: str): (npc_name: str, lbt: str} }
npcs is a dict { npc_name: NPC } used to get an NPC by its name.
"""
default npc_schedule = {}
default npcs = {}
[/quote]

The indexation is wrong.
It would be easier to have [icode](name, location, time): lbt[/icode].


It's a bit too early after the holidays, but normally the code should looks like:
[code=python]
    class NPC:
        [...]

        def __init__(self, name, nice_name, key):
            assert(create_key == NPC.__key)
            self.nice_name = nice_name
            #self.fmt_str = "avatar/{name}/{name}_{{}}.png".format(name=name)
            self.name = name


        @property
        def isPresent(self):
            return ( self.name, store.location, store.time ) in store.npc_schedule

        @property
        # return either /None/ or "avatar/[self.name]/[self.name]_[store.location]_[POSITION]_[SOMETHING_ELSE]"
        def avatar(self):
# Assume that you tested /isPresent/ first
            avtr = "avatar/{0}/{0}_{1}_{2}_{3}.png".format( self.name, store.location, POSITION, SOMETHING_ELSE )
            if renpy.loadable(avtr):
                return avtr

        @property
        def label(self):
            # Assume that you tested /isPresent/ first
            if renpy.has_label( store.npc_schedule[( self.name, store.location, store.time )]:
                return store.npc_schedule[( self.name, store.location, store.time )]

what lead to a screen that would looks like:
Python:
screen character_screen():

    # Browse all the NPCs
    for n in npcs:
        # If it's present, and the label exist, display the sprite.
        if n.isPresent and n.label:
            imagebutton:
                idle n.avatar
                hover n.avatar
                focus_mask True
                action Call( n.label )
 
  • Angry
Reactions: osanaiko

rayminator

Engaged Member
Respected User
Sep 26, 2018
3,041
3,140
just update
anne O'nymous I accidentally added a " when I was posting this
(3, "livingroom"): " "test", #watching tv

i figure it out in different way

it's similar to my code

class.rpy
Python:
class NPC(object):
        def __init__(self, name, niceName, location, hours, minutes, lbt=""):
            self.name = name
            self.niceName = niceName
            self.location = location
            self.hours = hours
            self.minutes = minutes
            self.lbt = lbt

        @property
        def avatar(self):
            global hours
            global minutes
            global location
            avtr = "avatar/{}_{}_{}_{}.png".format(self.name, str(hours), str(minutes), location)


            if renpy.loadable(avtr):
                return avtr
            else:
                return "avatar/empty.png"
character_screen.rpy
Python:
screen charcater_screen():
    for q in NPCS:
        if q.location == location:
            imagebutton:
                idle q.avatar
                hover q.avatar
                focus_mask True
                action SetVariable("mainUI", False), Call(q.lbt)
default.rpy
Python:
label InitialiseVariables:

    $ NPCS.append(NPC("mom", "Mom", "livingroom", 12, 0, lbt="test"))
    return
 
  • Like
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,283
[something]
You know, there's something name "other languages than English". And it happen that in some of them, colon have a different name, either "double something" or "two something". So yes, I haven't fully recovered from the holidays, and misread, mixing between two languages. What a shame, really, I'll go hang myself when I'll found the time for this.
But well, thanks for the reaction, one more is probably better than none more.
 
  • Red Heart
Reactions: osanaiko

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,296
3,952
Haha, I'm sorry to be so cruel to you mr/ms anne. It was my turn to be the grumpy one.

And now i've gone down the rabbit hole of punctuation in various euro-languages

Guillemets, la dos puntos, comillas, gansefusschen (Geese Feet lol!)
 
Last edited:
  • Like
Reactions: anne O'nymous