init python: class book_readable_class(object): def __init__(self, title, contents=(), **kwargs): self.title = title self.page = 0 self.overflow=None self.title = title self.contents = list(contents) # list of (title, text) pairs self.__dict__.update(**kwargs) self.npages = len(self.contents) @property def maxpage(self): # maximum value for the `page` attribute if self.npages %2: return self.npages-1 return self.npages-2 def open(self, page=0): self.page = max(0, min(page, self.maxpage)) self.refresh() return def close(self): self.page = 0 renpy.hide_screen("book_menu") return def next(self): self.page = min(self.page+2, self.maxpage) self.refresh() return def prev(self): self.page = max(self.page-2, 0) self.refresh() return def refresh(self): page_title, page_text = self.contents[self.page] page_text = page_text[:880] if self.npages > self.page+1: next_page_title, next_page_text = self.contents[self.page-1] next_page_text = next_page_text[:880] else: next_page_title, next_page_text = None, None renpy.show_screen("book_menu", page=self.page, npages=self.npages, title=self.title, page_title=page_title, page_text=page_text, next_page_title=next_page_title, next_page_text=next_page_text, page_overflow=self.overflow, ) return def append(self, page_title, page_text): self.contents.append((page_title, page_text)) self.npages = len(self.contents) return class diary_class(book_readable_class): def __init__(self, *args, dictionary, **kwargs): """ `dictionary` is a dict containing two types of entries: - for normal text pages, id: (title, text). `text` may contain interpolation fields, such as {code}. When that's the case, there must be an 2nd-type entry for "code" in this dict. `title` may be None. - for interpolation codes, code: text. `text` is the text to be used for the interpolation. Alternatively, `dictionary` may be the name of a store variable containing the actual dictionary (that's better when not in a testing phase, for pickling/saving/updating reasons) """ super().__init__(*args, **kwargs) self.dictionary_ = dictionary # str id : either (title, text) or text self.entry_ids = set() # str id @property def dictionary(self): rv = self.dictionary_ if isinstance(rv, str): rv = getattr(renpy.store, rv) return rv # append still exists, so that you can manually add pages without going through the dictionary def diary_append(self, id, day=None, **branches): """ Adds a page to the diary. `id` is the key of the event that just happened, in the dictionary. `day` sets the day number for the entry, defaulting to the current day. `branches` is a dict of {sub-event id : code} for every happened sub-event specializing event `id`. the specified codes will be looked for in the dictionary, and the original entry of id `id` will be formatted by associating {sub-event id} with dictionary[code]. If `code` is not a valid entry in the dict, the `code` value itself will be interpolated instead, or nothing if v is a false value (like None). If the page for the key `id` contains interpolation fields, it is a mistake to not specify all interpolation fields in `branches`. It is benign to specify keys which are not interpolation fields. """ if id in self.entry_ids: return dictionary = self.dictionary page_title, page_text = dictionary[id] if branches: branches = {k : dictionary.get(v, v or "") for k, v in branches.items()} page_text = page_text.format(**branches) if day is None: day = game.day if page_title: page_title = "Day {}\n{}".format(day, page_title) else: page_title = "Day {}".format(day) self.append(page_title, page_text) self.entry_ids.add(id) label book_handle(book=None): $ book.open() play sound "sounds/bookopen.ogg" show screen book_animator("book_page_next", 0.5) # use an ATL sort-of-Fade instead, and With label .after_init: $ renpy.dynamic(__choice = ui.interact()) if __choice == "next": $ book.next() play sound "sounds/pageflip.ogg" show screen book_animator("book_page_next", 0.5) with d1 elif __choice == "prev": $ book.prev() play sound "sounds/pageflip.ogg" show screen book_animator("book_page_prev", 0.5) with d1 elif __choice == "back": $ book.open() play sound "sounds/pageflipback.ogg" show screen book_animator("book_page_start", 0.5) with d1 elif __choice == "Close": $ book.close() play sound "sounds/bookclose.ogg" return jump .after_init screen book_menu(page, npages, title, page_title, page_text, next_page_title, next_page_text, page_overflow): # page is 0-based # npages is the number of pages, so max(page) is npages-1 tag book_menu zorder 30 button style "empty" action NullAction() add Color("#000", alpha=0.5) add "interface/book/book_open.webp" frame: style "empty" pos (280, 130) xsize 250 ysize 300 text page_title ypos -20 size 16 xalign 0.5 textalign .5 text page_text size 12 ypos 40 text str(page+1) bold True xalign 0.5 ypos 350 size 11 frame: style "empty" xpos 600 ypos 130 xsize 250 ysize 300 if next_page_title is not None: text next_page_title ypos -20 size 16 xalign 0.5 textalign .5 if next_page_text is not None: text next_page_text size 12 ypos 40 text str(page+2) bold True xalign 0.5 ypos 350 size 11 if page+2 < npages: # Next imagebutton: pos (721, 100) idle Transform("interface/book/hover.webp", alpha=0) hover "interface/book/hover.webp" action Return("next") elif page > 0: # Fast Back to start imagebutton: pos (721, 100) idle "interface/book/back.webp" hover "interface/book/back.webp" action Return("back") # Previous imagebutton: pos (242, 100) idle Transform("interface/book/hover.webp", xzoom=-1.0, alpha=0) hover Transform("interface/book/hover.webp", xzoom=-1.0, alpha=1) action Return("prev") use close_button screen book_animator(img, timer): tag animator zorder 31 add img button style "empty" action [Hide("book_animator"), With(Dissolve(0.05))] timer timer action [Hide("book_animator"), With(Dissolve(0.1))] image book_page_next: contains: "interface/book/book_open.webp" contains: #"interface/book_of_secrets/book_anim_01.webp" #pause.1 "interface/book/page_02.webp" pause.08 "interface/book/page_03.webp" pause.08 "interface/book/page_04.webp" pause.08 "interface/book/page_05.webp" pause.08 "interface/book/page_06.webp" pause.08 "interface/book/page_07.webp" pause.08 "blank" image book_page_prev: contains: "interface/book/book_open.webp" contains: xoffset 40 #"interface/book_of_secrets/book_anim_01.webp" #pause.1 Transform("interface/book/page_02.webp", xzoom=-1.0) pause.08 Transform("interface/book/page_03.webp", xzoom=-1.0) pause.08 Transform("interface/book/page_04.webp", xzoom=-1.0) pause.08 Transform("interface/book/page_05.webp", xzoom=-1.0) pause.08 Transform("interface/book/page_06.webp", xzoom=-1.0) pause.08 Transform("interface/book/page_07.webp", xzoom=-1.0) pause.08 "blank" image book_page_start: contains: "interface/book/book_open.webp" contains: "interface/book/reverse_01.webp" pause.07 "interface/book/reverse_02.webp" pause.07 "blank" repeat 3 #book_page_max was too slow