# 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: import functools get_icon = renpy.store.wardrobe.get_icon # Simple Proxy since both interfaces share the same code parcel_callbacks = [] def checkout(): scope = renpy.get_screen("clothing_store_interface").scope cart = scope["cart"] renpy.call_screen_in_new_context("clothing_store_checkout", cart) def checkout_confirm(): scope = renpy.get_screen("clothing_store_checkout").scope transit_time = scope["transit_time"] cart = scope["cart"] renpy.play("sounds/money.ogg") scope["navigation_atl"] = renpy.store.gui_modal_hide scope["fade_atl"] = renpy.store.wardrobe_fade_hide renpy.call_in_new_context("clothing_store_checkout", transit_time, cart) curry = renpy.curry(renpy.store.execute_callbacks)(parcel_callbacks) if parcel_callbacks else None renpy.store.Parcel(contents=[(i, 1) for i in cart], wait=transit_time, func=curry).send() scope["navigation_exit"] = True exit() def checkout_cancel(): scope = renpy.get_screen("clothing_store_checkout").scope scope["navigation_atl"] = renpy.store.gui_modal_hide scope["fade_atl"] = renpy.store.wardrobe_fade_hide scope["navigation_exit"] = True renpy.restart_interaction() def change_section(section): scope = renpy.get_screen("clothing_store_interface").scope scope["character"] = renpy.store.get_character_object(section) scope["selected_section"] = section scope["selected_item"] = next(outfits(section), None) 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.sound.play("sounds/fail.ogg") renpy.call_in_new_context("clothing_store_cart_full") def select_outfit(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["information_atl"] = renpy.store.wardrobe_fade_hide scope["character_atl"] = renpy.store.wardrobe_character_hide scope["navigation_exit"] = True # Reset states renpy.restart_interaction() @functools.cache def characters(): # Filter out side characters from the dolls list return [doll for doll in renpy.store.states.dolls if doll != "hooch"] def outfits(section): scope = renpy.get_screen("clothing_store_interface").scope character = scope["character"] # Sort the outfits by price in ascending order sorted_outfits = sorted((x for x in reversed(character.outfits) if not x.unlocked and x.price > 0), key=lambda outfit: outfit.price) return iter(sorted_outfits) def discuss(outfit): renpy.store._skipping = True renpy.suspend_rollback(False) renpy.block_rollback() renpy.call_in_new_context("clothing_store_discuss", outfit) renpy.store._skipping = False def wheelmenu(outfit): scope = renpy.get_screen("clothing_store_interface").scope character = scope["character"] cart = scope["cart"] exit_action = renpy.store.Function(renpy.hide_screen, "wheelmenu") select_action = renpy.store.Function(select_outfit, outfit) add_remove_action = renpy.store.Function(add_remove_outfit, outfit) d = { _("select"): (renpy.store.Text("✅", align=(0.5, 0.5)), [exit_action, select_action]) } if outfit in cart: d[_("Remove from cart")] = (renpy.store.Text("🛒", align=(0.5, 0.5)), [exit_action, add_remove_action]) else: d[_("Add to cart")] = (renpy.store.Text("🛒", align=(0.5, 0.5)), [exit_action, add_remove_action]) btns = renpy.store.create_wheelmenu(d) renpy.play("sounds/qubodup-click1.ogg") renpy.show_screen("wheelmenu", btns, pos=None, close_action=exit_action) def character_in_cart(character): scope = renpy.get_screen("clothing_store_interface").scope cart = scope["cart"] for outfit in cart: if outfit.char.name == character: return True return False @functools.cache def mannequin(name): # Name used for cache ref only scope = renpy.get_screen("clothing_store_interface").scope character = scope["character"] return renpy.store.Transform(character.body.image, matrixcolor=renpy.store.SaturationMatrix(0.0)) # 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) $ clothing_store.parcel_callbacks = [] # Clears the parcel callbacks upon the next call to screen clothing_store_interface 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 "store" default navigation_atl = wardrobe_show default information_atl = wardrobe_fade_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 = next(clothing_store.outfits(selected_section), None) default cart = set() $ total_price = sum([item.price for item in cart]) $ total_gold = states.env.gold-total_price 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 information_atl add "gui_fade_bottom" vbox: xpos 550 yoffset -10 yalign 1.0 xsize 350 label selected_item.name align (0.0, 1.0) add "frame_spacer" xsize 350 text selected_item.desc align (0.0, 1.0) vbox: pos (972, 468) vbox: xoffset -36 text _("[states.env.gold]G") xalign 1.0 text _("-[total_price]G") xalign 1.0 add "frame_spacer" xsize 100 if total_gold >= 0: text _("[total_gold]G") color "#0ead00" xalign 1.0 else: text _("[total_gold]G") color "#ad0000" xalign 1.0 label _("G") + str(selected_item.price) xoffset -50 style "store_pricetag_label" else: add clothing_store.mannequin(selected_section) align (1.0, 1.0) zoom 0.6 at character_atl frame: style "empty" at navigation_atl vbox: style_prefix "navigation_tabs" pos (540, 300) at navigation_tabs_show textbutton _("Discuss") action Function(clothing_store.discuss, selected_item) sensitive (selected_item != None) style "navigation_tabs_button_special" at navigation_tabs textbutton (_("Remove from cart") if selected_item in cart else _("Add to cart")) action Function(clothing_store.add_remove_outfit, selected_item) selected (selected_item in cart) sensitive (selected_item != None) style "store_tabs_button_add_remove" at navigation_tabs textbutton _("Checkout") action clothing_store.checkout sensitive (states.env.gold >= total_price and cart and (selected_item != None)) 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 for i in clothing_store.characters(): button: xysize (64, 64) add clothing_store.get_icon(i) xysize (64, 64) if clothing_store.character_in_cart(i): add Transform(Text("🛒"), align=(1.0, 1.0), size=(24, 24)) tooltip i action Function(clothing_store.change_section, i) selected (selected_section == i) null height 3 add "frame_spacer" xsize 500 xalign 0.5 null height 3 if not selected_item: text _("SOLD OUT!") size 96 at transform: align (0.5, 0.5) rotate -45 # Outfit List vpgrid: cols 5 spacing 4 mousewheel True scrollbars "vertical" for outfit in clothing_store.outfits(selected_section): button: style "store_rectangular_button" add outfit.button.child label "G[outfit.price]" style "store_pricetag_label_small" if outfit in cart: add Transform(Text("🛒"), align=(1.0, 1.0), size=(24, 24)) if renpy.android: action Function(clothing_store.wheelmenu, outfit) else: action Function(clothing_store.select_outfit, outfit) alternate Function(clothing_store.wheelmenu, outfit) selected (selected_item == outfit) screen clothing_store_checkout(cart): modal True layer "interface" zorder 0 style_prefix "frame" default navigation_atl = gui_modal_show default fade_atl = wardrobe_fade_show default navigation_exit = False add "gui_fade_both" at fade_atl default fast_delivery = False default fast_delivery_fee = 45 + ( (len(cart)-1) * 20 ) default cart = cart $ total_price = sum([item.price for item in cart]) + (fast_delivery_fee if fast_delivery else 0) $ total_gold = states.env.gold-total_price $ can_afford = states.env.gold >= total_price $ transit_time = (len(cart)+1)//2 if fast_delivery else len(cart)+1 if navigation_exit: timer 0.4 action Return() frame: at navigation_atl label _("Checkout") vbox: xminimum 496 hbox: style "store_checkout_hbox" for item in cart: button: xysize (96, 168) style "store_rectangular_button" add item.button.child label "G[item.price]" style "store_pricetag_label_small" action NullAction() textbutton _("Fast Delivery") action ToggleScreenVariable("fast_delivery", True, False) style "store_checkbox_button" text _("Delivery Time: [transit_time] day(s)") color "#ffffff" xalign 0.0 style "store_checkbox_button_text" vbox: style_prefix "store_checkout" text _("[states.env.gold]G") xalign 1.0 text _("-[total_price]G") xalign 1.0 add "frame_spacer" xsize 200 xalign 1.0 text _("[total_gold]G") color ("#0ead00" if can_afford else "#ad0000") xalign 1.0 null height 20 hbox: xalign 0.5 yalign 1.0 textbutton _("Confirm") action clothing_store.checkout_confirm sensitive (can_afford) textbutton _("Cancel") action clothing_store.checkout_cancel style store_rectangular_button is wardrobe_item_button: xysize (96, 168) 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 style store_frame is wardrobe_frame style store_button is wardrobe_button style store_text is wardrobe_text: color "#FFFFFF" style store_label_text is wardrobe_text: color "#EA8D60" style store_pricetag_label: background Image("gui/creamy_pumpkin_pie/store/price_tag.png", oversample=3) style store_pricetag_label_text: anchor (1.0, 0.5) text_align 1.0 pos (110, 30) color "#000000" outlines [(2, "#8BA5D9", 1, 1)] hinting "bytecode" size 24 style store_pricetag_label_small: align (0.0, 1.0) xysize (68, 28) background Frame(Image("gui/creamy_pumpkin_pie/store/price_tag.png", oversample=6), 20, 10, tile=False, fit="contain") style store_pricetag_label_small_text: anchor (1.0, 0.5) text_align 1.0 pos (50, 16) color "#000000" outlines [(1, "#8BA5D9", 1, 1)] hinting "bytecode" size 14 style store_tabs_button_add_remove is navigation_tabs_button: ysize 35 left_padding 15 right_padding 30 selected_right_padding 30 background Frame(Transform(Image("gui/creamy_pumpkin_pie/book/book_tab.png", oversample=4), matrixcolor=HueMatrix(180.0)), 0, 0, 80, 0, tile=False) selected_background Frame(Transform(Image("gui/creamy_pumpkin_pie/book/book_tab.png", oversample=4), matrixcolor=HueMatrix(275.0)), 0, 0, 80, 0, tile=False) insensitive_background Frame(Transform(Image("gui/creamy_pumpkin_pie/book/book_tab.png", oversample=4), matrixcolor=SaturationMatrix(0.0)), 0, 0, 80, 0, tile=False) activate_sound "sounds/woosh2.ogg" selected_activate_sound "sounds/woosh.ogg" style store_tabs_button_add_remove_text is navigation_tabs_button_text: selected_xoffset 0 style store_checkout_hbox: fit_first True spacing 4 xalign 0.5 style store_checkout_vbox: xalign 0.5 margin (10, 10) style store_checkout_text is store_text: size 36 style store_checkbox_button is wardrobe_checkbox_button style store_checkbox_button_text is wardrobe_checkbox_button_text