init python: from collections import namedtuple AchievementKind = namedtuple("AchievementKind", ("category", "name", "description", "icon", "secret")) AchievementKind.title = AchievementKind.name define achievements_db = { # id : (categoryname, title, description, icon, secret) "unlockher": AchievementKind("Characters", "Granger Danger", "Awarded for unlocking Hermione Granger.", "interface/icons/head/hermione.webp", False), "unlockcho": AchievementKind("Characters", "Chang Dynasty", "Awarded for unlocking Cho Chang.", "interface/icons/head/cho.webp", False), "unlocklun": AchievementKind("Characters", "Looney Tunes", "Awarded for unlocking Luna Lovegood.", "interface/icons/head/luna.webp", False), "unlockast": AchievementKind("Characters", "Green Peas", "Awarded for unlocking Astoria Greengrass.", "interface/icons/head/astoria.webp", False), "unlockton": AchievementKind("Characters", "Nymphadoreador", "Awarded for unlocking Nymphadora Tonks.", "interface/icons/head/tonks.webp", False), "overwhored": AchievementKind("Characters", "Overwhored", "Awarded for fully corrupting Hermione.", "interface/icons/head/hermione.webp", False), "unlocksus": AchievementKind("Characters", "Boner", "Awarded for unlocking Susan Bones.", "interface/icons/head/susan.webp", False), "unlocksna": AchievementKind("Characters", "Strictly colleagues", "Awarded for unlocking Severus Snape.", "interface/icons/head/snape.webp", False), "mirror": AchievementKind("Mirror", "Mirror, mirror on the wall..", "Awarded for unlocking the Room of Requirement.", "images/rooms/room_of_requirement/mirror_hover.webp", False), "gold": AchievementKind("General", "Gold Digger", "Awarded for having 10,000 gold in total.", "interface/icons/gold.webp", False), "drunkard": AchievementKind("General", "Drunken Master", "Awarded for collecting 25 bottles of wine.", "interface/icons/wine.webp", True), "workaholic": AchievementKind("General", "Workaholic", "Awarded for completing five full reports.", "interface/icons/generic_scroll.webp", False), "fireplace": AchievementKind("General", "Feel the Heat", "Awarded for lighting the fireplace 5 times or more.", "images/rooms/main_room/fireplace/fireplace_idle.webp", True), "peta": AchievementKind("General", "I think I forgot something...", "Awarded for not feeding the bird for 50 days.... \nYou monster.\n{size=-4}Disclaimer: No real nor fictional animals were harmed in the process.{/size}", "images/rooms/main_room/phoenix/phoenix_01.webp", True), "petpal": AchievementKind("General", "Regular stroking", "Awarded for petting the bird 25 times.", "images/rooms/main_room/phoenix/phoenix_01.webp", False), "postman": AchievementKind("Cardgame", "Poster Boy", "Awarded for buying all posters from the token shop.", "interface/icons/agrabah_poster.webp", False), "hats": AchievementKind("Cardgame", "Mad Hatter", "Awarded for buying all hat decorations from the token shop.", "interface/icons/icon_gambler_hat.webp", False), "daddy": AchievementKind("Characters", "Who's your daddy?", "Awarded for letting Hermione call you a {size=-5}(sugar){/size} daddy.", "interface/icons/head/hermione.webp", True), "pantiesfap": AchievementKind("Characters", "I sneezed on them...", "Awarded for rubbing one out on Hermione's panties.", "characters/genie/chibis/jerk_off/02.webp", False), "pantiesfapcho": AchievementKind("Characters", "Exercise is important", "Awarded for rubbing one out on Cho's panties.", "characters/genie/chibis/jerk_off/02.webp", False), "bros": AchievementKind("Characters", "Bros before hoes", "Awarded for becoming best pals with Snape.", "interface/icons/head/snape.webp", False), "knock": AchievementKind("Characters", "*Knock* *knock*", "Awarded for telling Hermione to go away during her introductory events.", "images/rooms/main_room/door/door_idle.webp", True), "decorator": AchievementKind("Cardgame", "Decorator", "Awarded for decorating the office for the first time.", "interface/icons/stag_trophy.webp", False), "flashback": AchievementKind("Cardgame", "Flashback", "Awarded for retelling what actually happened...", "interface/icons/cards.webp", True), "start": AchievementKind("General", "Welcome to Hogwarts!", "Awarded for finishing the intro.", "interface/icon.webp", False), "export": AchievementKind("General", "Sharing is caring", "Awarded for exporting an outfit through the wardrobe menu.", "interface/wardrobe/icons/load.webp", False), "Credits": AchievementKind("General", "New game, who this?", "Awarded for checking out the Credits Menu.", "interface/icons/silver_scroll.webp", False), "Cardwin": AchievementKind("Cardgame", "Time to duel", "Awarded for winning your first Card game duel.", "interface/icons/cards.webp", False), "puzzle": AchievementKind("General", "Down the hatch!", "Awarded for wasting a bottle of unbelievably rare phoenix tears by drinking it.", "interface/icons/item_potion.webp", True), "ending": AchievementKind("General", "Bittersweet Farewell", "Awarded for reaching the original ending.", "interface/icons/silver.webp", True), #1.37 HG achievements "busted": AchievementKind("Characters", "BUSTED!", "Awarded for getting busted by Hermione when busting a nut.", "interface/icons/head/hermione.webp", False), "herstrip": AchievementKind("Characters", "Dance lessons", "Awarded for having Hermione dance naked in front of you... and Snape.", "interface/icons/head/hermione.webp", False), "herkiss": AchievementKind("Characters", "First Kiss", "Awarded for having Hermione make out with you-- r... cock...", "interface/icons/head/hermione.webp", False), "hertits": AchievementKind("Characters", "Boobs Lover", "Awarded for sticking it between Hermione's fun bags.", "interface/icons/head/hermione.webp", False), "headlib": AchievementKind("Characters", "Head Librarian", "Awarded for releasing your seed in Hermione's mouth.", "interface/icons/head/hermione.webp", False), "nerdgasm": AchievementKind("Characters", "Nerdgasm", "Awarded for doing the deed with Hermione.", "interface/icons/head/hermione.webp", False) } init python hide: for name in achievements_db: achievement.register(name) # keep the achievements from earlier versions if isinstance(persistent.achievements, dict): # id : [categoryname, title, description, unlocked, icon, secret] for k, v in persistent.achievements.items(): if v[3]: achievement.grant(k) del persistent.achievements init python: class Achievements(object): """ Useless class, can't be rolled out because of pickle and save compatibility. """ @staticmethod def validate(): """Check if icons are loadable at init""" for i in achievements_db.values(): if not renpy.loadable(i.icon): raise IOError(repr(i.icon)) status = staticmethod(achievement.has) @staticmethod def unlock(id, silent=False): if _in_replay: return if not achievement.has(id): achievement.grant(id) if not silent: renpy.play('sounds/achievement.ogg') __popup_stack.append(id) lock = staticmethod(achievement.clear) def achievement_sortfilter(lst, sortby="A-z", filtering=None): """ Takes a list/iterable of achievement ids, returns a list of achievement ids """ if filtering == "Locked": lst = (x for x in lst if not achievement.has(x)) elif filtering == "Unlocked": lst = filter(achievement.has, lst) elif filtering == "Secret": lst = (x for x in lst if achievements_db[x].secret is True) # Always sort alphabetically first. lst = sorted(lst, key=lambda x: natsort_key(achievements_db[x].title)) if sortby == "z-A": lst.sort(key=lambda x: natsort_key(achievements_db[x].title), reverse=True) elif sortby == "Unlocked": lst.sort(key=lambda x: not achievement.has(x)) elif sortby == "Locked": lst.sort(key=achievement.has) return lst default achievements = Achievements() ### init python: # intentionaly not a define nor a default __popup_stack = [] config.after_default_callbacks.append(__popup_stack.clear) config.always_shown_screens.append("achievement_main") screen achievement_main(): layer "interface" vbox: for id index id in __popup_stack[:3]: use achievement_window(msg=achievements_db[id].title, title="Achievement unlocked!", icon=achievements_db[id].icon) timer 6 action Function(__popup_stack.remove, id) label popup(msg="", title="", icon=None, xpos=0, ypos=60, sound=True, soundfile='sounds/achievement.ogg'): if sound: play sound soundfile hide screen achievement_window show screen achievement_window(msg=msg, title=title, icon=icon, xpos=xpos, ypos=ypos) return screen achievement_window(msg="", title="", icon=None, xpos=0, ypos=60): tag popup_window layer "interface" frame: style "empty" at popup_animation(time=5.0, xx=-410) pos (xpos, ypos) xsize 410 ysize 96 add gui.format("interface/achievements/{}/box.webp") if icon: frame: style "empty" pos (6, 6) xsize 84 ysize 84 $ image_zoom = crop_image_zoom(icon, 84, 84) if 'head' in icon: add image_zoom align (0.5, 1.0) yoffset -1 else: add image_zoom align (0.5, 0.5) add "interface/achievements/glass.webp" frame: style "empty" xpos 96 xsize 314 vbox: ypos 12 spacing 10 xalign 0.5 text title size 18 xalign 0.5 text msg size 14 xalign 0.5 timer 6.0 action Hide("achievement_window") transform rotate_circular(t=7): on show, appear, start: subpixel True rotate 0 linear t rotate 360 repeat #################################### ############# Menu ################# #################################### define achievement_categories_sorted = ("All", "General", "Characters", "Cardgame", "Mirror") define items_shown = 36 init python: class __SetCategory(Action): def __init__(self, category): self.category = category def __call__(self): global current_category global number_unlocked current_category = self.category if current_category == "All": category_items = achievements_db else: category_items = filter((lambda x: current_category==achievements_db[x].category), achievements_db) __regen(category_items) number_unlocked = len(tuple(filter(achievement.has, menu_items))) def __regen(category_items=achievements_db): global menu_items global menu_items_length global current_page global current_item menu_items = achievement_sortfilter(category_items, current_sorting, current_filter) menu_items_length = len(menu_items) current_page = 0 current_item = next(iter(menu_items), None) renpy.restart_interaction() label achievement: $ gui.in_context("achievement_menu") jump main_room_menu label achievement_menu(xx=150, yy=90): $ renpy.dynamic( current_page = 0, current_category = achievement_categories_sorted[0], current_filter = None, current_sorting = "Unlocked", menu_items = achievement_sortfilter(achievements_db, "Unlocked", None), number_unlocked = len(tuple(filter(achievement.has, achievements_db))) ) $ renpy.dynamic( menu_items_length = len(menu_items), current_item = next(iter(menu_items), None), ) call screen achievements(xx, yy) return screen achievements(xx, yy): tag achievements zorder 15 modal True # add "gui_fade" if renpy.mobile: use close_button_background use close_button fixed: pos (xx, yy) if settings.get("animations"): at gui_animation use achievement_menu() use achievement_menuitem() screen achievement_menu(): window: style "empty" style_prefix gui.theme('achievements') xysize (207, 454) use invisible_button() add gui.format("interface/achievements/{}/panel_left.webp") vbox: style_prefix gui.theme('achievements_categories') pos (6, 41) for category in achievement_categories_sorted: vbox: textbutton category: selected (current_category == category) action __SetCategory(category) add gui.format("interface/achievements/{}/spacer_left.webp") vbox: style_prefix gui.theme('achievements_filters') pos (6, 384) if current_filter is None: textbutton "Show: All" action [CycleVariable("current_filter", (None, "Locked", "Unlocked", "Secret")), __regen] else: textbutton "Show: [current_filter]" action [CycleVariable("current_filter", (None, "Locked", "Unlocked", "Secret")), __regen] textbutton "Sort by: [current_sorting]" action [CycleVariable("current_sorting", ("A-z", "z-A", "Unlocked", "Locked")), __regen] screen achievement_menuitem(): window: style "empty" pos (217, -53) xysize (560, 507) use invisible_button() add "interface/achievements/star.webp" add gui.format("interface/achievements/{}/panel.webp") text "Achievements" size 22 xalign 0.5 ypos 65 text "Unlocked: [number_unlocked]/[menu_items_length]" size 12 pos (24, 70) # Page counter if menu_items_length > items_shown: hbox: xanchor 1.0 pos (540, 24) spacing 5 add "interface/page.webp" yanchor 0.5 ypos 53 text str(current_page+1)+"/"+str(int(math.ceil(menu_items_length/items_shown))) ypos 44 size 16 vbox: pos (570, 186) spacing 10 imagebutton: idle gui.format("interface/frames/{}/arrow_up.webp") if not current_page <= 0: hover image_hover(gui.format("interface/frames/{}/arrow_up.webp")) action SetVariable("current_page", current_page-1) # TODO: in 8.2, replace with IncrementVariable("current_page", -1) imagebutton: idle Transform(gui.format("interface/frames/{}/arrow_up.webp"), yzoom = -1.0) if current_page < math.ceil((menu_items_length-1)/items_shown)-1: hover Transform(image_hover(gui.format("interface/frames/{}/arrow_up.webp")), yzoom = -1.0) action SetVariable("current_page", current_page+1) # TODO: in 8.2, replace with IncrementVariable("current_page") # Add items grid 9 4: style "empty" pos (24, 113) spacing 10 for it_item in menu_items[current_page*items_shown:(current_page+1)*items_shown]: $ it_item_data = achievements_db[it_item] $ it_item_unlocked = achievement.has(it_item) frame: style "empty" xsize 48 ysize 48 add gui.format("interface/achievements/{}/iconbox.webp") if current_item and current_item == it_item: add "interface/achievements/glow.webp" align (0.5, 0.5) zoom 0.105 alpha 0.7 at rotate_circular if it_item_unlocked or not it_item_data.secret: $ image_zoom = crop_image_zoom(it_item_data.icon, 42, 42, not it_item_unlocked) else: $ image_zoom = crop_image_zoom("interface/achievements/secret.webp", 35, 35, True) add image_zoom: xalign .5 if it_item_data.category == "Characters" and (it_item_unlocked or not it_item_data.secret): yalign 1. yoffset -3 else: yalign .5 button: style gui.theme("overlay_button") background "interface/achievements/glass_iconbox.webp" xsize 48 ysize 48 action SetVariable("current_item", it_item) if it_item_data.secret and not it_item_unlocked: tooltip "???" else: tooltip it_item_data.name if current_item: $ current_item_data = achievements_db[current_item] $ current_item_unlocked = achievement.has(current_item) frame: style "empty" xsize 96 ysize 96 pos (24, 375) add gui.format("interface/achievements/{}/icon_selected.webp") if current_item_unlocked or not current_item_data.secret: $ image_zoom = crop_image_zoom(current_item_data.icon, 84, 84, not current_item_unlocked) else: $ image_zoom = crop_image_zoom("interface/achievements/secret.webp", 70, 70, True) add image_zoom: xalign .5 if current_item_data.category == "Characters" and (current_item_unlocked or not current_item_data.secret): yalign 1. yoffset -7 else: yalign .5 add "interface/achievements/glass_selected.webp" pos (6, 6) add gui.format("interface/achievements/{}/highlight.webp") pos (112, 375) add gui.format("interface/achievements/{}/spacer.webp") pos (120, 398) hbox: spacing 5 xalign 0.5 text current_item_data.name ypos 380 size 16 xoffset 45 if current_item_unlocked: add "interface/unlocked_True.webp" xoffset 45 ypos 377 else: add "interface/unlocked_False.webp" xoffset 45 ypos 377 hbox: pos (132, 407) xsize 410 if current_item_unlocked or not current_item_data.secret: text current_item_data.description size 12 else: text "???" size 12 # Category styles style achievements_categories_button is empty: xysize (195, 16) style dark_achievements_categories_button: hover_background "interface/achievements/gray/highlight_left.webp" selected_background "interface/achievements/gray/highlight_left.webp" style light_achievements_categories_button: hover_background "interface/achievements/gold/highlight_left.webp" selected_background "interface/achievements/gold/highlight_left.webp" style achievements_categories_button_text is text: xalign 0.5 outlines [] # style dark_achievements_categories_button_text: # take dark_button_text # style light_achievements_categories_button_text: # take light_button_text # Filter styles style achievements_filters_button is empty: xysize (195, 32) style dark_achievements_filters_button: hover_background "#7d75aa40" style light_achievements_filters_button: hover_background "#e3ba7140" style achievements_filters_button_text is default: align (0.5, 0.5) size 12 outlines []