From 737ba3cf095934a2f17957282c9a149f69ec12d9 Mon Sep 17 00:00:00 2001 From: LoafyLemon Date: Thu, 14 Nov 2024 15:03:20 +0000 Subject: [PATCH] Clothing Store 2.0 - Parcel hooks and animations --- .../button_check_checked.png | 3 + .../creamy_pumpkin_pie/button_check_empty.png | 3 + game/scripts/gui/frame.rpy | 13 ++ game/scripts/shops/dress/chitchats.rpy | 27 ++- game/scripts/shops/dress/menu.rpy | 214 +++++++++++++++--- game/scripts/utility/engine.rpy | 13 ++ game/scripts/wardrobe/wardrobe.rpy | 17 +- 7 files changed, 249 insertions(+), 41 deletions(-) create mode 100644 game/gui/creamy_pumpkin_pie/button_check_checked.png create mode 100644 game/gui/creamy_pumpkin_pie/button_check_empty.png diff --git a/game/gui/creamy_pumpkin_pie/button_check_checked.png b/game/gui/creamy_pumpkin_pie/button_check_checked.png new file mode 100644 index 00000000..fff05fa6 --- /dev/null +++ b/game/gui/creamy_pumpkin_pie/button_check_checked.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ddb51fbc818563b10c58f1a3f81a6ec9cc0deab930fec4cb3f8625aba8c5ad9 +size 4561 diff --git a/game/gui/creamy_pumpkin_pie/button_check_empty.png b/game/gui/creamy_pumpkin_pie/button_check_empty.png new file mode 100644 index 00000000..33e397ec --- /dev/null +++ b/game/gui/creamy_pumpkin_pie/button_check_empty.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d6ea2d116ca1e04ab11fb4d3bd8e66c644d55e8854bfe84255a42eaab43b664 +size 440 diff --git a/game/scripts/gui/frame.rpy b/game/scripts/gui/frame.rpy index bb0096a5..baa97173 100644 --- a/game/scripts/gui/frame.rpy +++ b/game/scripts/gui/frame.rpy @@ -45,6 +45,18 @@ transform gui_modal_popup: on hide: easeout_back 0.4 zoom 0.0 alpha 0.0 +transform gui_modal_show: + subpixel True + zoom 0.0 + alpha 0.0 + easein_back 0.4 zoom 1.0 alpha 1.0 + +transform gui_modal_hide: + subpixel True + zoom 1.0 + alpha 1.0 + easeout_back 0.4 zoom 0.0 alpha 0.0 + image gui_fade_bottom = Frame(Image("gui/creamy_pumpkin_pie/fade_bottom.png", oversample=4), ysize=150, yalign=1.0) image gui_fade_both = Fixed(Frame(Image("gui/creamy_pumpkin_pie/fade_top.png", oversample=4)), Frame(Image("gui/creamy_pumpkin_pie/fade_bottom.png", oversample=4))) @@ -94,5 +106,6 @@ style frame_button is button: style frame_button_text is choice_text: align (0.5, 0.5) + insensitive_color "#888" image frame_spacer = Image("gui/creamy_pumpkin_pie/window_frame_spacer.png", oversample=4) diff --git a/game/scripts/shops/dress/chitchats.rpy b/game/scripts/shops/dress/chitchats.rpy index cc812e0d..105930a7 100644 --- a/game/scripts/shops/dress/chitchats.rpy +++ b/game/scripts/shops/dress/chitchats.rpy @@ -1,11 +1,9 @@ -label discuss_outfit(item): +label clothing_store_discuss(item): # # Hermione Granger # if item == her_outfit_maid: - $ maid_outfit_ITEM.owned = 1 - gen "I'd like to order a maid outfit." ("base", xpos="far_left", ypos="head") maf "A maid outfit, what on earth for? Surely the house elves are keeping your office tidy." gen "The what?" ("base", xpos="far_left", ypos="head") @@ -34,8 +32,6 @@ label discuss_outfit(item): maf "As you wish sweetie... It should be ready shortly." gen "Thank you." ("base", xpos="far_left", ypos="head") elif item == her_outfit_ball: - $ parcel_callbacks.append(renpy.curry(setattr)(ball_outfit_ITEM, "owned", 1)) - if not states.her.ev.yule_ball.e4_complete: gen "Could you make a dress for me?" ("base", xpos="far_left", ypos="head") maf "A dress? Do you mean something like a ball dress, or more burlesque?" @@ -1012,3 +1008,24 @@ label discuss_outfit(item): gen "Excellent." ("base", xpos="far_left", ypos="head") maf "I'll get it done as soon as I can." return + +label clothing_store_cart_full(): + random: + maf "That was your fifth order, sir, I'm afraid it will have to be your last one." + maf "Love, I only have two hands. Five outfits is the maximum I can carry." + maf "I'm sorry, but I don't think that's going to work. Five orders at once is enough for me." + maf "I'm afraid I can't do more than five outfits at a time." + maf "Headmaster, more than five orders at once? That's too much for the little ol' me!" + return + +label clothing_store_checkout(transit_time, cart): + $ _tmp = "tomorrow" if transit_time == 1 else f"in about {{number={transit_time}}} days" + maf "You can expect a parcel [_tmp]." + + if her_outfit_maid in cart: + $ maid_outfit_ITEM.owned = 1 + + if her_outfit_ball in cart: + $ clothing_store.parcel_callbacks.append(renpy.curry(setattr)(ball_outfit_ITEM, "owned", 1)) + + return diff --git a/game/scripts/shops/dress/menu.rpy b/game/scripts/shops/dress/menu.rpy index 95c28a90..a4268db3 100644 --- a/game/scripts/shops/dress/menu.rpy +++ b/game/scripts/shops/dress/menu.rpy @@ -286,12 +286,44 @@ # 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 - def buy(): + 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") - pass + + 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 @@ -310,8 +342,8 @@ init 5 python in clothing_store: # 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") + renpy.call_in_new_context("clothing_store_cart_full") def select_outfit(outfit): scope = renpy.get_screen("clothing_store_interface").scope @@ -327,12 +359,18 @@ init 5 python in clothing_store: # 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"] @@ -346,7 +384,7 @@ init 5 python in clothing_store: renpy.store._skipping = True renpy.suspend_rollback(False) renpy.block_rollback() - renpy.call_in_new_context("discuss_outfit", outfit) + renpy.call_in_new_context("clothing_store_discuss", outfit) renpy.store._skipping = False def wheelmenu(outfit): @@ -372,6 +410,21 @@ init 5 python in clothing_store: 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() @@ -379,6 +432,7 @@ label clothing_store_interface(inter_pause=True): 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() @@ -392,6 +446,7 @@ screen clothing_store_interface(): 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 @@ -415,39 +470,52 @@ screen clothing_store_interface(): 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, 428) + pos (540, 300) at navigation_tabs_show - textbutton _("Discuss") action Function(clothing_store.discuss, selected_item) 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) style "store_tabs_button_add_remove" at navigation_tabs - textbutton _("Checkout") action clothing_store.buy sensitive (states.env.gold >= total_price and cart) at navigation_tabs + 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 - if selected_item: - vbox: - at character_atl - 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") xalign 1.0 - else: - text _("[total_gold]G") color "#ad0000" xalign 1.0 - - label _("G") + str(selected_item.price) xoffset -50 - frame: at navigation_atl @@ -456,10 +524,13 @@ screen clothing_store_interface(): hbox: spacing 0 - for i in states.dolls: + 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) @@ -467,6 +538,10 @@ screen clothing_store_interface(): 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 @@ -478,7 +553,7 @@ screen clothing_store_interface(): button: style "store_rectangular_button" add outfit.button.child - label "G[outfit.price]" style "store_label_small" + label "G[outfit.price]" style "store_pricetag_label_small" if outfit in cart: add Transform(Text("🛒"), align=(1.0, 1.0), size=(24, 24)) @@ -489,6 +564,63 @@ screen clothing_store_interface(): 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) @@ -504,10 +636,13 @@ style store_button is wardrobe_button style store_text is wardrobe_text: color "#FFFFFF" -style store_label: +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_label_text: +style store_pricetag_label_text: anchor (1.0, 0.5) text_align 1.0 pos (110, 30) @@ -516,12 +651,12 @@ style store_label_text: hinting "bytecode" size 24 -style store_label_small: +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_label_small_text: +style store_pricetag_label_small_text: anchor (1.0, 0.5) text_align 1.0 pos (50, 16) @@ -543,3 +678,18 @@ style store_tabs_button_add_remove is navigation_tabs_button: 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 diff --git a/game/scripts/utility/engine.rpy b/game/scripts/utility/engine.rpy index a0195fb6..9d133e93 100644 --- a/game/scripts/utility/engine.rpy +++ b/game/scripts/utility/engine.rpy @@ -1,3 +1,5 @@ +# This file contains functions, utilities, and hacks for the Ren'py engine. Sometimes a dev must do something to get it to work with Ren'py. :) + python early hide: import functools @@ -383,3 +385,14 @@ init -100 python: def __call__(self): renpy.set_return_stack([]) renpy.call(self.label, *self.args, **self.kwargs) + + # Allows calling screens in new contexts directly. + @renpy.pure + def _call_screen_in_new_context(screen, *args, **kwargs): + return renpy.call_in_new_context("__call_screen_in_new_context", screen, *args, **kwargs) + + renpy.call_screen_in_new_context = _call_screen_in_new_context + +label __call_screen_in_new_context(screen, *args, **kwargs): + call screen expression screen pass (*args, **kwargs) + return diff --git a/game/scripts/wardrobe/wardrobe.rpy b/game/scripts/wardrobe/wardrobe.rpy index 92bda718..46923d11 100644 --- a/game/scripts/wardrobe/wardrobe.rpy +++ b/game/scripts/wardrobe/wardrobe.rpy @@ -772,10 +772,10 @@ style wardrobe_viewport: style wardrobe_checkbox_button: padding (6, 4) - hover_background Frame(Image("gui/creamy_pumpkin_pie/book/book_select.png", oversample=4), 20, 0, 20, 0, tile=False) - foreground Transform(Image("gui/creamy_pumpkin_pie/book/book_button_check_empty.png", oversample=4), xpos=6, yalign=0.5) - selected_foreground Transform(Image("gui/creamy_pumpkin_pie/book/book_button_check_checked.png", oversample=4), xpos=6, yalign=0.5) - insensitive_foreground Transform(Image("gui/creamy_pumpkin_pie/book/book_button_check_empty.png", oversample=4), alpha=0.5, xpos=6, yalign=0.5) + # hover_background Frame(Image("gui/creamy_pumpkin_pie/book/book_select.png", oversample=4), 20, 0, 20, 0, tile=False) + foreground Transform(Image("gui/creamy_pumpkin_pie/button_check_empty.png", oversample=4), xpos=6, yalign=0.5) + selected_foreground Transform(Image("gui/creamy_pumpkin_pie/button_check_checked.png", oversample=4), xpos=6, yalign=0.5) + insensitive_foreground Transform(Image("gui/creamy_pumpkin_pie/button_check_empty.png", oversample=4), alpha=0.5, xpos=6, yalign=0.5) style wardrobe_checkbox_button_text is wardrobe_button_text: first_indent 24 @@ -798,6 +798,15 @@ transform wardrobe_hide: alpha 1.0 easeout 0.4 xpos -1080 alpha 0.0 +transform wardrobe_fade_show: + alpha 0.0 + easein 0.4 alpha 1.0 + +transform wardrobe_fade_hide: + events False + alpha 1.0 + easeout 0.4 alpha 0.0 + transform wardrobe_character_show: xoffset 1080 easein 0.4 xoffset 0