init python: def shop_dress_sortfilter(item, sortby="Price (Asc)", filtering=None): # Always sort alphabetically first. item = sorted(item, key=lambda x: natsort_key(x.name)) if sortby == "Price (Asc)": item.sort(key=lambda x: x.price, reverse=False) elif current_sorting == "Price (Desc)": item.sort(key=lambda x: x.price, reverse=True) if sortby == "Lewdness (Asc)": item.sort(key=get_outfit_score, reverse=False) elif current_sorting == "Lewdness (Desc)": item.sort(key=get_outfit_score, reverse=True) return item label shop_dress: $ gui.in_context("shop_dress_menu") return label shop_dress_menu: python: current_sorting = "Price (Asc)" category_items = {"hermione": hermione.outfits, "tonks": tonks.outfits, "cho": cho.outfits, "luna": luna.outfits, "astoria": astoria.outfits, "susan": susan.outfits} current_category = "hermione" store_cart = set() menu_items = shop_dress_sortfilter((x for x in category_items.get(current_category, []) if (x.unlocked == False and x.price > 0 and x not in store_cart)), current_sorting) current_item = next(iter(menu_items), None) parcel_callbacks = [] show screen shop_dress() label .after_init: $ renpy.dynamic(__choice = ui.interact()) if __choice[0] == "category": $ current_category = __choice[1] $ menu_items = shop_dress_sortfilter((x for x in category_items.get(current_category, []) if (x.unlocked == False and x.price > 0 and x not in store_cart)), current_sorting) $ current_item = next(iter(menu_items), None) elif __choice[0] == "buy": show screen blktone with d3 if states.env.gold < __choice[1].price: gen "(I don't have enough gold.)" ("base", xpos="far_left", ypos="head") else: if len(store_cart) < 5: $ renpy.call("purchase_outfit", __choice[1]) play sound "sounds/money.ogg" $ states.env.gold -= __choice[1].price $ store_cart.add(__choice[1]) $ menu_items = shop_dress_sortfilter((x for x in category_items.get(current_category, []) if (x.unlocked == False and x.price > 0 and x not in store_cart)), current_sorting) $ current_item = next(iter(menu_items), None) if len(store_cart) < 5: maf "Anything else?" else: maf "That was your fifth order, sir, I'm afraid it will have to be your last one." else: maf "I'm sorry luv but that's as much as you can order for now." hide screen blktone with d3 elif __choice == "sort": if current_sorting == "Price (Asc)": $ current_sorting = "Price (Desc)" elif current_sorting == "Price (Desc)": $ current_sorting = "Lewdness (Asc)" elif current_sorting == "Lewdness (Asc)": $ current_sorting = "Lewdness (Desc)" elif current_sorting == "Lewdness (Desc)": $ current_sorting = "Price (Asc)" $ menu_items = shop_dress_sortfilter([x for x in category_items.get(current_category, []) if bool(x.unlocked == False and x.price > 0 and not x in store_cart)], current_sorting) else: # Close if len(store_cart) < 5: show screen blktone with d3 menu: maf "Are you finished shopping, dearie?" "-Yes, I'm done-": pass "-Not yet-": hide screen blktone with d3 jump .after_init if store_cart: $ transit_time = len(store_cart)+1 $ packaging_fee = 45 + ( (len(store_cart)-1) * 20 ) menu: maf "If you pay extra, I could hire a bunch of elves to speed things up..." "\"Fine. ([packaging_fee] gold)\"" if states.env.gold >= packaging_fee: $ states.env.gold -= packaging_fee $ transit_time = int(transit_time/2) "\"Fine. ([packaging_fee] gold)\"" (style="disabled") if states.env.gold < packaging_fee: maf "Sorry luv, but it appears you have no gold left." "-No thanks-": pass hide screen shop_dress hide screen blktone $ _tmp = "tomorrow" if transit_time == 1 else f"in about {transit_time} days" maf "You can expect a parcel [_tmp]." # Executes callbacks upon receival of the parcel. $ curry = renpy.curry(execute_callbacks)(parcel_callbacks) if parcel_callbacks else None $ parcel_callbacks = [] $ Parcel(contents=[(k, 1) for k in store_cart], wait=transit_time, func=curry).send() return else: gen "Nothing has caught my eye I'm afraid." ("base", xpos="far_left", ypos="head") maf "Maybe next time." return jump .after_init screen shop_dress(): tag shop_dress zorder 15 modal True add "gui_fade" if renpy.mobile: use close_button_background use close_button fixed: if settings.get("animations"): at gui_animation use shop_dress_menu() use shop_dress_menuitem() screen shop_dress_menu(): tag shop_menu zorder 15 style_prefix "shop" default icon_bg = gui.format("interface/achievements/{}/iconbox.webp") default icon_frame = Frame(gui.format("interface/frames/{}/iconframe.webp"), 6, 6) default panel = gui.format("interface/frames/{}/panel_left.webp") default highlight = gui.format("interface/achievements/{}/highlight_left_b.webp") window: pos (150, 90) xysize (207, 454) background panel use invisible_button() vbox: pos (6, 6) for category in category_items.keys(): if get_character_unlock(category): $ icon = Fixed(icon_bg, Frame( Transform(f"interface/icons/head/{category}.webp", fit="contain"), xysize=(42, 42), offset=(3, 3)), "interface/achievements/glass_iconbox.webp") vbox: textbutton category: style "empty" xysize (195, 48) text_align (0.6, 0.5) text_xanchor 0.5 text_size 20 foreground icon hover_background highlight selected_background highlight selected (current_category == category) action Return(["category", category]) add gui.format("interface/frames/{}/spacer_left.webp") vbox: style_prefix gui.theme('achievements_filters') pos (6, 384) button action None textbutton "Sort by: [current_sorting]" action Return("sort") screen shop_dress_menuitem(): tag shop_menuitem zorder 16 style_prefix "shop" default icon_size = (144, 288) default icon_frame = Frame(gui.format("interface/frames/{}/iconframe.webp"), 6, 6) default panel = gui.format("interface/frames/{}/panel.webp") window: pos (367, 37) xysize (560, 501) background panel use invisible_button() text "Shop" size 22 xalign 0.5 ypos 65 if current_item: frame: xalign 0.5 ypos 412 vbox: xalign 0.5 add gui.format("interface/achievements/{}/highlight.webp")# pos (112, 375) add gui.format("interface/achievements/{}/spacer.webp")# pos (120, 398) text "[current_item.desc]" size 12 yoffset 6 text "[current_item.name]" xalign 0.5 ypos 3 size 16 $ frame = Frame(gui.format("interface/frames/{}/iconframe.webp"), 6, 6) textbutton "Buy": style "inventory_button" background frame xalign 0.95 action Return(["buy", current_item]) vpgrid: rows 1 xspacing 5 yspacing 2 draggable True mousewheel "horizontal" scrollbars "horizontal" xmaximum 512 ypos 106 xalign 0.5 at transform: mesh True for item in menu_items: $ icon = Transform(item.image, crop=(215, 0, 680, 1200), mesh=True, gl_pixel_perfect=True) $ is_modded = item.is_modded() $ is_affordable = bool(states.env.gold >= item.price) button: style "shop_outfit_button" xysize icon_size background Transform(icon, xsize=144, ysize=288, fit="contain", anchor=(0.5, 1.0), align=(0.5, 1.0), yoffset=-6) selected (current_item == item) action SetVariable("current_item", item) add icon_frame if is_affordable: text "{color=#daa520}G{/color} [item.price]" xalign 0.5 ypos 10 color "#ffffff" outlines [ (1, "#000", 0, 0) ] style "shop_outfit_text" else: text "{color=#daa520}G{/color} {color=#ff0000}[item.price]{/color}" xalign 0.5 ypos 10 color "#ffffff" outlines [ (1, "#000", 0, 0) ] style "shop_outfit_text" if config.developer: $ outfit_score = get_outfit_score(item) text "{color=#fff}score{/color} [outfit_score]" align (0.1, 0.98) color "#ffffff" outlines [ (1, "#000", 0, 0) ] size 8 hbox: offset (5, -5) align (0.0, 1.0) if is_modded: text "M" color "#00b200" style shop_window is empty style shop_outfit_button is empty: foreground None hover_foreground "#ffffff80" selected_foreground "#ffffff40" activate_sound "sounds/click.ogg" style shop_outfit_button_text is default: size 14 style shop_outfit_text: size 20 # Functions (Initialised after wardrobe) init 5 python in clothing_store: get_icon = renpy.store.wardrobe.get_icon # Simple Proxy since both interfaces share the same code def buy(): renpy.play("sounds/money.ogg") pass def change_section(section): scope = renpy.get_screen("clothing_store_interface").scope scope["selected_section"] = section scope["selected_item"] = None scope["character"] = get_character_object(section) def add_remove_outfit(outfit): scope = renpy.get_screen("clothing_store_interface").scope cart = scope["cart"] if outfit in cart: # Remove the outfit from the cart if it's already there and there are more than one item cart ^= {outfit} elif len(cart) < 5: # Add the outfit to the cart if it's not already there and there are less than five items cart |= {outfit} else: renpy.notify("You can only have five items in your cart.") renpy.sound.play("sounds/fail.ogg") def select(outfit): scope = renpy.get_screen("clothing_store_interface").scope scope["selected_item"] = outfit renpy.restart_interaction() def exit(): scope = renpy.get_screen("clothing_store_interface").scope renpy.play("sounds/curtain_close.ogg") # Handle exit animation scope["navigation_last_frame_atl"] = renpy.store.navigation_last_frame_hide scope["navigation_atl"] = renpy.store.wardrobe_hide scope["character_atl"] = renpy.store.wardrobe_character_hide scope["navigation_exit"] = True # Reset states renpy.restart_interaction() # Context label clothing_store_interface(inter_pause=True): $ disable_game_menu() play sound "sounds/curtain_open.ogg" if inter_pause: # Ensures all irrelevant screens are hidden before capturing the surface tree with Pause(0.2) call screen clothing_store_interface $ enable_game_menu() # jump expression f"{states.active_girl}_requests" jump main_room_menu # Interface screen clothing_store_interface(): layer "interface" zorder 0 style_prefix "wardrobe" default navigation_atl = wardrobe_show default character_atl = wardrobe_character_show default last_frame = (screenshot.capture() or screenshot.image) default navigation_last_frame_atl = navigation_last_frame_show default navigation_exit = False default character = get_character_object("tonks") default selected_section = "tonks" default selected_item = None default cart = set() add last_frame at navigation_last_frame_atl add "gui_fade_both" at gui_fade if navigation_exit: timer 0.4 action Return() if selected_item: add selected_item.image align (1.0, 1.0) zoom 0.6 at character_atl frame: style "empty" at navigation_atl vbox: style_prefix "navigation_tabs" pos (540, 470) at navigation_tabs_show textbutton (_("Remove from cart") if selected_item in cart else _("Add to cart")) action Function(clothing_store.add_remove_outfit, selected_item) sensitive selected_item at navigation_tabs textbutton _("Checkout") action clothing_store.buy at navigation_tabs null height 35 textbutton _("Exit") action clothing_store.exit keysym "K_ESCAPE" at navigation_tabs frame: at navigation_atl vbox: # Sections hbox: spacing 0 button: add clothing_store.get_icon("tonks") xysize (64, 64) tooltip _("tonks") action Function(clothing_store.change_section, "tonks") selected (selected_section == "tonks") null height 3 add "frame_spacer" xsize 500 xalign 0.5 null height 3 # Outfit List vpgrid: cols 5 spacing 4 mousewheel True scrollbars "vertical" for outfit in (x for x in reversed(character.outfits) if (not x.unlocked and x.price > 0)): button: style "store_item_rectangular_button" add outfit.button.child if outfit in cart: add Transform(Text("🛒"), align=(1.0, 1.0), size=(24, 24)) action Function(clothing_store.select, outfit) selected (selected_item == outfit) style store_item_rectangular_button is wardrobe_item_button: background Transform("wheelmenu_frame_vertical", xysize=(96,168)) hover_background At(Transform("wheelmenu_frame_opaque_vertical", xysize=(96,168)), wheelmenu_hover_anim) selected_background Fixed(Transform("wheelmenu_frame_vertical", xysize=(96,168)), At(Transform("interface/achievements/glow.webp", align=(0.5, 0.5), size=(96, 96), alpha=0.5), rotate_circular)) selected_hover_background Fixed(At(Transform("wheelmenu_frame_vertical", xysize=(96,168)), wheelmenu_hover_anim), At(Transform("interface/achievements/glow.webp", align=(0.5, 0.5), size=(96, 96), alpha=0.5), rotate_circular)) selected_foreground None