diff --git a/lovely/better_calc.toml b/lovely/better_calc.toml index 9b6f27609..0e546d922 100644 --- a/lovely/better_calc.toml +++ b/lovely/better_calc.toml @@ -2154,7 +2154,7 @@ table.sort(G.playing_cards, function (a, b) return a.playing_card > b.playing_ca ''' payload = ''' if saveTable then - if saveTable.SMODS then + if saveTable.SMODS and saveTable.SMODS.last_hand then SMODS.last_hand = {scoring_hand = {}, full_hand = {}, scoring_name = saveTable.SMODS.last_hand.scoring_name} for _, v in ipairs({'scoring_hand','full_hand'}) do for _, card in ipairs(G.playing_cards) do diff --git a/lovely/booster.toml b/lovely/booster.toml index b735e7e11..09bb4a36d 100644 --- a/lovely/booster.toml +++ b/lovely/booster.toml @@ -10,34 +10,53 @@ priority = -10 [patches.pattern] target = "card.lua" pattern = ''' -if self.ability.name:find('Arcana') then - G.STATE = G.STATES.TAROT_PACK - G.GAME.pack_size = self.ability.extra - elseif self.ability.name:find('Celestial') then - G.STATE = G.STATES.PLANET_PACK - G.GAME.pack_size = self.ability.extra - elseif self.ability.name:find('Spectral') then - G.STATE = G.STATES.SPECTRAL_PACK - G.GAME.pack_size = self.ability.extra - elseif self.ability.name:find('Standard') then - G.STATE = G.STATES.STANDARD_PACK - G.GAME.pack_size = self.ability.extra - elseif self.ability.name:find('Buffoon') then - G.STATE = G.STATES.BUFFOON_PACK - G.GAME.pack_size = self.ability.extra - end +if self.ability.name:find('Arcana') then + G.STATE = G.STATES.TAROT_PACK + G.GAME.pack_size = self.ability.extra +elseif self.ability.name:find('Celestial') then + G.STATE = G.STATES.PLANET_PACK + G.GAME.pack_size = self.ability.extra +elseif self.ability.name:find('Spectral') then + G.STATE = G.STATES.SPECTRAL_PACK + G.GAME.pack_size = self.ability.extra +elseif self.ability.name:find('Standard') then + G.STATE = G.STATES.STANDARD_PACK + G.GAME.pack_size = self.ability.extra +elseif self.ability.name:find('Buffoon') then + G.STATE = G.STATES.BUFFOON_PACK + G.GAME.pack_size = self.ability.extra +end + +G.GAME.pack_choices = self.config.center.config.choose or 1 - G.GAME.pack_choices = self.config.center.config.choose or 1''' +if self.cost > 0 then + G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.2, func = function() + inc_career_stat('c_shop_dollars_spent', self.cost) + self:juice_up() + return true end })) + ease_dollars(-self.cost) +else + delay(0.2) +end''' match_indent = true position = "at" payload = """ +G.GAME.pack_choices = math.min((self.ability.choose or self.config.center.config.choose or 1) + (G.GAME.modifiers.booster_choice_mod or 0), self.ability.extra and math.max(1, self.ability.extra + (G.GAME.modifiers.booster_size_mod or 0)) or self.config.center.extra and math.max(1, self.config.center.extra + (G.GAME.modifiers.booster_size_mod or 0)) or 1) +if self.cost > 0 then + G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.2, func = function() + inc_career_stat('c_shop_dollars_spent', self.cost) + self:juice_up() + return true end })) + ease_dollars(-self.cost) +else + delay(0.2) +end booster_obj = self.config.center if booster_obj and SMODS.Centers[booster_obj.key] then - G.STATE = G.STATES.SMODS_BOOSTER_OPENED SMODS.OPENED_BOOSTER = self + SMODS.enter_state(SMODS.STATES.BOOSTER_OPENED, nil, {from_hold = true, leave_shop_sign = true, leave_HUD_blind = true}) end -G.GAME.pack_choices = math.min((self.ability.choose or self.config.center.config.choose or 1) + (G.GAME.modifiers.booster_choice_mod or 0), self.ability.extra and math.max(1, self.ability.extra + (G.GAME.modifiers.booster_size_mod or 0)) or self.config.center.extra and math.max(1, self.config.center.extra + (G.GAME.modifiers.booster_size_mod or 0)) or 1) -""" +""" # Reordered to maintain vanilla timing # Card:open # Adds modifier for size of booster @@ -89,40 +108,14 @@ payload = ''' SMODS.calculate_context({modify_booster_card = true, booster = self, card = card, index = i}) ''' -# Game:set_globals -[[patches]] -[patches.regex] -target = "globals.lua" -pattern = '''(?[\t ]*)self\.STATES = \{''' -position = "after" -payload = ''' - - SMODS_BOOSTER_OPENED = 999, - SMODS_REDEEM_VOUCHER = 998,''' -line_prepend = '$indent' - -# Game:update -[[patches]] -[patches.regex] -target = "game.lua" -pattern = '''(?[\t ]*)if self\.STATE == self\.STATES\.TAROT_PACK then''' -position = "before" -payload = ''' -if G.STATE == G.STATES.SMODS_BOOSTER_OPENED then - SMODS.OPENED_BOOSTER.config.center:update_pack(dt) -end - -''' -line_prepend = '$indent' - -# G.FUNC.can_skip_booster +# G.FUNCS.can_skip_booster # TODO customize whether pack can be skipped [[patches]] [patches.regex] target = "functions/button_callbacks.lua" pattern = '''(?[\t ]*)\(G\.STATE == G\.STATES\.PLANET_PACK or G\.STATE == G\.STATES\.STANDARD_PACK''' position = "at" -payload = '''(G.STATE == G.STATES.SMODS_BOOSTER_OPENED or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.STANDARD_PACK''' +payload = '''(G.STATE == SMODS.STATES.BOOSTER_OPENED or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.STANDARD_PACK''' # CardArea:draw() [[patches]] @@ -131,7 +124,7 @@ target = "cardarea.lua" pattern = "(self.config.type == 'deck' and self ~= G.deck) or" position = "before" payload = ''' -(self.config.type == 'hand' and state == G.STATES.SMODS_BOOSTER_OPENED) or''' +(self.config.type == 'hand' and SMODS.in_booster()) or''' match_indent = true # G.FUNCS.use_card @@ -141,7 +134,7 @@ target = "functions/button_callbacks.lua" pattern = "prev_state == G.STATES.SPECTRAL_PACK or prev_state == G.STATES.STANDARD_PACK or" position = "after" payload = ''' -prev_state == G.STATES.SMODS_BOOSTER_OPENED or''' +prev_state == SMODS.STATES.BOOSTER_OPENED or''' match_indent = true # CardArea:align_cards() @@ -150,7 +143,7 @@ match_indent = true target = "cardarea.lua" pattern = "if self.config.type == 'hand' and (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK) then" position = "at" -payload = "if self.config.type == 'hand' and (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) then" +payload = "if self.config.type == 'hand' and (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or SMODS.in_booster()) then" match_indent = true # CardArea:align_cards() @@ -159,7 +152,7 @@ match_indent = true target = "cardarea.lua" pattern = "if self.config.type == 'hand' and not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK) then" position = "at" -payload = "if self.config.type == 'hand' and not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) then" +payload = "if self.config.type == 'hand' and not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or SMODS.in_booster()) then" match_indent = true # Card:can_use_consumable() @@ -168,7 +161,7 @@ match_indent = true target = "card.lua" pattern = "if G.STATE == G.STATES.SELECTING_HAND or G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK then" position = "at" -payload = "if G.STATE == G.STATES.SELECTING_HAND or G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED then" +payload = "if G.STATE == G.STATES.SELECTING_HAND or G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED then" match_indent = true # G.FUNCS.use_card() @@ -183,7 +176,7 @@ if nc then play_sound('cardSlide2', nil, 0.3) dont_dissolve = true end -if (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) then""" +if (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SPECTRAL_PACK or SMODS.in_booster()) then""" match_indent = true # G.FUNC.use_card() @@ -203,7 +196,7 @@ target = "functions/button_callbacks.lua" pattern = '''if prev_state == G.STATES.TAROT_PACK then inc_career_stat('c_tarot_reading_used', 1) end''' position = 'at' match_indent = true -payload = '''if prev_state == G.STATES.SMODS_BOOSTER_OPENED and booster_obj.name:find('Arcana') then inc_career_stat('c_tarot_reading_used', 1) end''' +payload = '''if prev_state == SMODS.STATES.BOOSTER_OPENED and booster_obj.name:find('Arcana') then inc_career_stat('c_tarot_reading_used', 1) end''' # G.FUNC.use_card() [[patches]] @@ -212,7 +205,7 @@ target = "functions/button_callbacks.lua" pattern = '''if prev_state == G.STATES.PLANET_PACK then inc_career_stat('c_planetarium_used', 1) end''' position = 'at' match_indent = true -payload = '''if prev_state == G.STATES.SMODS_BOOSTER_OPENED and booster_obj.name:find('Celestial') then inc_career_stat('c_planetarium_used', 1) end''' +payload = '''if prev_state == SMODS.STATES.BOOSTER_OPENED and booster_obj.name:find('Celestial') then inc_career_stat('c_planetarium_used', 1) end''' # G.FUNC.use_card() [[patches]] @@ -220,16 +213,16 @@ payload = '''if prev_state == G.STATES.SMODS_BOOSTER_OPENED and booster_obj.name target = "functions/button_callbacks.lua" pattern = "(G.STATE == G.STATES.BUFFOON_PACK and G.STATES.BUFFOON_PACK) or" position = "before" -payload = "(G.STATE == G.STATES.SMODS_BOOSTER_OPENED and G.STATES.SMODS_BOOSTER_OPENED) or" +payload = "(G.STATE == SMODS.STATES.BOOSTER_OPENED and SMODS.STATES.BOOSTER_OPENED) or" match_indent = true -# G.FUNC.use_card() +# G.FUNC.draw_from_deck_to_hand() [[patches]] [patches.pattern] target = "functions/state_events.lua" pattern = "if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK) and" position = "at" -payload = "if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) and" +payload = "if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED) and" match_indent = true # Card:use_consumeable() @@ -239,8 +232,8 @@ target = "card.lua" pattern = '''(?[\t ]*)align = \(G\.STATE[\s\S]*and -0\.2 or 0},''' position = "at" payload = ''' -align = (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) and 'tm' or 'cm', -offset = {x = 0, y = (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) and -0.2 or 0},''' +align = (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or SMODS.in_booster()) and 'tm' or 'cm', +offset = {x = 0, y = (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or SMODS.in_booster()) and -0.2 or 0},''' line_prepend = '$indent' # G.FUNCS.use_card() @@ -462,7 +455,7 @@ target = 'card.lua' match_indent = true position = 'after' pattern = '''if self.shop_voucher then G.GAME.current_round.voucher = nil end''' -payload = '''G.STATE = G.STATES.SMODS_REDEEM_VOUCHER''' +payload = '''SMODS.enter_state(SMODS.STATES.REDEEM_VOUCHER, nil, {from_hold = true, leave_shop_sign = true, leave_HUD_blind = true, leave_data = true, no_sound = true})''' # G.FUNCS.use_card() # Consumables in areas other than the consumable don't count diff --git a/lovely/center.toml b/lovely/center.toml index 8e8a44b38..4fb183f8e 100644 --- a/lovely/center.toml +++ b/lovely/center.toml @@ -523,7 +523,6 @@ if G.booster_pack and not G.booster_pack.alignment.offset.py and ((not select_to ''' - # G.FUNCS.use_card() [[patches]] [patches.pattern] @@ -534,6 +533,7 @@ position = 'at' payload = ''' if not card.from_area then card.from_area = card.area end if card.area and (not nc or card.area == G.pack_cards) then card.area:remove_card(card) end''' + [[patches]] [patches.pattern] target = 'functions/button_callbacks.lua' diff --git a/lovely/fixes.toml b/lovely/fixes.toml index e03c94e54..7d7176e5a 100644 --- a/lovely/fixes.toml +++ b/lovely/fixes.toml @@ -78,7 +78,7 @@ target = 'functions/misc_functions.lua' pattern = "function save_run()" position = "after" payload = """ if G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SPECTRAL_PACK - or G.STATE == G.STATES.BUFFOON_PACK or G.STATE == G.STATES.STANDARD_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED then return end""" + or G.STATE == G.STATES.BUFFOON_PACK or G.STATE == G.STATES.STANDARD_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED then return end""" match_indent = true ## Set `G.your_collection.config.collection` to true in all cases diff --git a/lovely/game_state.toml b/lovely/game_state.toml new file mode 100644 index 000000000..a70724547 --- /dev/null +++ b/lovely/game_state.toml @@ -0,0 +1,263 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + + +# game.lua : Game:update() : inject SMODS.GameStates +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = ''' + if self.STATE == self.STATES.MENU then + self:update_menu(dt) + end +''' +position = 'after' +match_indent = true +payload = ''' +if SMODS.GameStates[self.STATE] then + SMODS.GameStates[self.STATE]:update(dt) +end +''' + +# game.lua : Game:start_run() +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' +self:prep_stage(G.STAGES.RUN, saveTable and saveTable.STATE or G.STATES.BLIND_SELECT) +''' +payload = ''' +self:prep_stage(G.STAGES.RUN, saveTable and saveTable.STATE or SMODS.STATES.BLIND_SELECT) +''' +# game.lua : Game:prep_stage() +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' +self.STATE = new_state or self.STATES.MENU +''' +payload = ''' +SMODS.enter_state(new_state or SELF.STATES.MENU) +''' + +# game.lua : Game:update_selecting_hand(dt) / Game:update_hand_played(dt) / etc. : Avoid deleting the shop in case it is held in the SMODS.state_stack +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = '''if self.shop then self.shop:remove(); self.shop = nil end''' +payload = '''''' +[[patches]] # Repatch Game:delete_run() +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'after' +pattern = '''if self.deck_preview then self.deck_preview:remove(); self.deck_preview = nil end''' +payload = '''if self.shop then self.shop:remove(); self.shop = nil end''' + + +# cardarea.lua : CardArea:draw() : update logic +[[patches]] +[patches.pattern] +target = 'cardarea.lua' +pattern = '''(self.config.type == 'hand' and ({[G.STATES.SHOP]=1, [G.STATES.TAROT_PACK]=1, [G.STATES.SPECTRAL_PACK]=1, [G.STATES.STANDARD_PACK]=1,[G.STATES.BUFFOON_PACK]=1,[G.STATES.PLANET_PACK]=1, [G.STATES.ROUND_EVAL]=1, [G.STATES.BLIND_SELECT]=1})[state]) or''' +position = 'at' +match_indent = true +payload = '''(self.config.type == 'hand' and ({[SMODS.STATES.SHOP]=1, [G.STATES.TAROT_PACK]=1, [G.STATES.SPECTRAL_PACK]=1, [G.STATES.STANDARD_PACK]=1,[G.STATES.BUFFOON_PACK]=1,[G.STATES.PLANET_PACK]=1, [SMODS.STATES.ROUND_EVAL]=1, [SMODS.STATES.BLIND_SELECT]=1})[state]) or''' + +# functions/button_callbacks.lua : G.FUNCS.use_card() : update logic +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = '''if card.ability.set == 'Booster' and not nosave and G.STATE == G.STATES.SHOP then''' +payload = '''if card.ability.set == 'Booster' and not nosave and G.STATE == SMODS.STATES.SHOP then''' +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = ''' +if G.shop and not G.shop.alignment.offset.py then + G.shop.alignment.offset.py = G.shop.alignment.offset.y + G.shop.alignment.offset.y = G.ROOM.T.y + 29 +end +if G.blind_select and not G.blind_select.alignment.offset.py then + G.blind_select.alignment.offset.py = G.blind_select.alignment.offset.y + G.blind_select.alignment.offset.y = G.ROOM.T.y + 39 +end +if G.round_eval and not G.round_eval.alignment.offset.py then + G.round_eval.alignment.offset.py = G.round_eval.alignment.offset.y + G.round_eval.alignment.offset.y = G.ROOM.T.y + 29 +end +''' +payload = ''' + +''' +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = ''' +if G.shop then + G.shop.alignment.offset.y = G.shop.alignment.offset.py + G.shop.alignment.offset.py = nil +end +if G.blind_select then + G.blind_select.alignment.offset.y = G.blind_select.alignment.offset.py + G.blind_select.alignment.offset.py = nil +end +if G.round_eval then + G.round_eval.alignment.offset.y = G.round_eval.alignment.offset.py + G.round_eval.alignment.offset.py = nil +end +''' +payload = ''' + +''' +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = '''G.STATE = prev_state''' +payload = ''' +if SMODS.GameStates[G.STATE] and SMODS.GameStates[G.STATE].exit_after_use_card then -- SMODS.STATES.BOOSTER_OPENED is handled by G.FUNCS.end_consumeable() below + SMODS.exit_state(nil, {no_shop_calc = true, leave_data = true, no_sound = true}, {state_override=prev_state}) +elseif not SMODS.GameStates[G.STATE] then + G.STATE = prev_state +end +''' + +# functions/button_callbacks.lua : G.FUNCS.end_consumeable() : update logic +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = ''' +if G.shop and G.shop.alignment.offset.py then + G.shop.alignment.offset.y = G.shop.alignment.offset.py + G.shop.alignment.offset.py = nil +end +if G.blind_select and G.blind_select.alignment.offset.py then + G.blind_select.alignment.offset.y = G.blind_select.alignment.offset.py + G.blind_select.alignment.offset.py = nil +end +if G.round_eval and G.round_eval.alignment.offset.py then + G.round_eval.alignment.offset.y = G.round_eval.alignment.offset.py + G.round_eval.alignment.offset.py = nil +end +''' +payload = ''' + +''' +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = '''G.STATE = G.GAME.PACK_INTERRUPT''' +payload = ''' +if SMODS.GameStates[G.STATE] and SMODS.GameStates[G.STATE].exit_after_end_consumable then + SMODS.exit_state(nil, {no_shop_calc = true, no_sound = true}, {state_override=G.GAME.PACK_INTERRUPT}) +elseif not SMODS.GameStates[G.STATE] then + G.STATE = G.GAME.PACK_INTERRUPT +end +''' + +# functions/state_events.lua : G.FUNCS.evaluate_round() : remove Blind:defeat() call -> moved to SMODS.STATES.BLIND's on_exit() +[[patches]] +[patches.pattern] +target = 'functions/state_events.lua' +match_indent = true +position = 'at' +pattern = ''' +G.E_MANAGER:add_event(Event({ + trigger = 'before', + delay = 1.3*math.min(G.GAME.blind.dollars+2, 7)/2*0.15 + 0.5, + func = function() + G.GAME.blind:defeat() + return true + end + })) +delay(0.2) +''' +payload = ''' +-- Blind:defeat() call moved to SMODS.STATES.BLIND's on_exit() +''' + +# functions/state_events.lua : end_round() : update logic +[[patches]] +[patches.pattern] +target = 'functions/state_events.lua' +match_indent = true +position = 'at' +pattern = ''' +G.STATE = G.STATES.ROUND_EVAL +G.STATE_COMPLETE = false +''' +payload = ''' +if #SMODS.state_queue == 0 then + SMODS.queue_state(SMODS.STATES.ROUND_EVAL) +end +SMODS.advance_state_queue() +''' +[[patches]] +[patches.pattern] +target = 'functions/state_events.lua' +match_indent = true +position = 'at' +pattern = ''' +if G.STATE == G.STATES.ROUND_EVAL then + win_game() + G.GAME.won = true + return true +end +''' +payload = ''' +win_game() +G.GAME.won = true +return true +''' + + +### SMODS.STATES.USE_CONSUMABLE + +# card.lua : Card:use_consumable() : inject SMODS.enter_state +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'after' +pattern = '''local used_tarot = copier or self''' +payload = ''' +SMODS.enter_state(SMODS.STATES.USE_CONSUMABLE, nil, {from_hold = true, leave_shop_sign = true, leave_HUD_blind = true, leave_data = true, no_sound = true}) +''' + +# card.lua : Card:can_use_consumeable() : update logic +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'at' +pattern = '''if G.STATE ~= G.STATES.HAND_PLAYED and G.STATE ~= G.STATES.DRAW_TO_HAND and G.STATE ~= G.STATES.PLAY_TAROT or any_state then''' +payload = '''if G.STATE ~= G.STATES.HAND_PLAYED and G.STATE ~= G.STATES.DRAW_TO_HAND and G.STATE ~= SMODS.STATES.USE_CONSUMABLE or any_state then''' + +# functions/button_callbacks.lua : G.FUNCS.use_card() : update logic +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = '''G.STATES.PLAY_TAROT''' +payload = '''SMODS.STATES.USE_CONSUMABLE''' \ No newline at end of file diff --git a/lovely/override_notice.toml b/lovely/override_notice.toml index b52820cc0..b439a4de1 100644 --- a/lovely/override_notice.toml +++ b/lovely/override_notice.toml @@ -225,6 +225,31 @@ position = "before" payload = "-- Function overridden by SMODS in src/overrides.lua" match_indent = true +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +pattern = "G.FUNCS.toggle_shop = function(e)" +position = "before" +payload = "-- Function overridden by SMODS in src/overrides.lua" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +pattern = "G.FUNCS.cash_out = function(e)" +position = "before" +payload = "-- Function overridden by SMODS in src/overrides.lua" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +pattern = "G.FUNCS.select_blind = function(e)" +position = "before" +payload = "-- Function overridden by SMODS in src/overrides.lua" +match_indent = true + + # src/ui.lua [[patches]] @@ -281,4 +306,4 @@ target = "functions/UI_definitions.lua" pattern = "function G.UIDEF.custom_deck_tab(_suit)" position = "before" payload = "-- Function overridden by SMODS in src/ui.lua" -match_indent = true +match_indent = true \ No newline at end of file diff --git a/lovely/poker_hand.toml b/lovely/poker_hand.toml index 8bb29183a..5ec2f7ed1 100644 --- a/lovely/poker_hand.toml +++ b/lovely/poker_hand.toml @@ -112,16 +112,4 @@ pattern = '''G.GAME.hands[text].played_this_round = G.GAME.hands[text].played_th position = "after" payload = '''G.GAME.hands[text].played_this_ante = G.GAME.hands[text].played_this_ante + 1''' match_indent = true -times = 1 - -# end_round() -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = '''G.GAME.voucher_restock = nil''' -position = "after" -payload = '''for k, v in pairs(G.GAME.hands) do - v.played_this_ante = 0 -end''' -match_indent = true times = 1 \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index ca499b2bd..8408f079a 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -1524,7 +1524,6 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. --]] update_pack = function(self, dt) if G.buttons then G.buttons:remove(); G.buttons = nil end - if G.shop then G.shop.alignment.offset.y = G.ROOM.T.y+11 end if not G.STATE_COMPLETE then G.STATE_COMPLETE = true @@ -1840,6 +1839,7 @@ SMODS.UndiscoveredCompat = { if self.modifies_draw then SMODS.Blinds.modifies_draw[self.key] = true end end } + SMODS.Blind:take_ownership('eye', { set_blind = function(self, reset, silent) if not reset then @@ -4013,6 +4013,11 @@ SMODS.UndiscoveredCompat = { text = '^' } + ------------------------------------------------------------------------------------------------- + ----- API IMPORT GameObject.GameState + ------------------------------------------------------------------------------------------------- + + assert(load(SMODS.NFS.read(SMODS.path..'src/game_objects/game_states.lua'), ('=[SMODS _ "src/game_objects/game_states.lua"]')))() ------------------------------------------------------------------------------------------------- ----- API IMPORT GameObject.DrawStep diff --git a/src/game_objects/game_states.lua b/src/game_objects/game_states.lua new file mode 100644 index 000000000..5c7f74ee1 --- /dev/null +++ b/src/game_objects/game_states.lua @@ -0,0 +1,861 @@ +SMODS.STATES = { + BOOSTER_OPENED = "BOOSTER_OPENED", + REDEEM_VOUCHER = "REDEEM_VOUCHER", + USE_CONSUMABLE = "USE_CONSUMABLE", + SHOP = "SHOP", + ROUND_EVAL = "ROUND_EVAL", + BLIND = "BLIND", + BLIND_SELECT = "BLIND_SELECT" +} +SMODS.default_state = SMODS.STATES.BLIND_SELECT + +SMODS.state_stack = {} + +function SMODS.get_current_state() + return #SMODS.state_stack > 0 and SMODS.state_stack[#SMODS.state_stack] +end + +function SMODS.push_to_state_stack(state, args) + table.insert(SMODS.state_stack, {state=state, args=args}) +end + +function SMODS.pop_from_state_stack(state, pop_duplicate) + if #SMODS.state_stack < 1 then return end + pop_duplicate = pop_duplicate == nil or pop_duplicate + local index = #SMODS.state_stack + while index > 0 and SMODS.state_stack[index].state == state do + table.remove(SMODS.state_stack, index) + index = index - 1 + if not pop_duplicate then + index = 0 + end + end +end + +function SMODS.get_next_held_state(allow_duplicate) + local index = #SMODS.state_stack - 1 + while index > 0 and SMODS.state_stack[index].state ~= SMODS.STATE do + if allow_duplicate or SMODS.state_stack[index].state ~= SMODS.STATE then + return SMODS.state_stack[index].state + end + index = index - 1 + end + return nil +end + +function SMODS.get_state_stack_index(state, exclude_latest) + for i, state_table in ipairs(SMODS.state_stack) do + if exclude_latest and i == #SMODS.state_stack then + return nil + end + if state_table.state == state then + return i + end + end + return nil +end + +function SMODS.clear_state_stack() + SMODS.state_stack = {} +end + +function SMODS.enter_state(new_state, enter_args, exit_args) + local current_state = SMODS.STATE or G.STATE + if current_state == new_state then return end + local next_held_state = SMODS.get_next_held_state() + enter_args = enter_args or {} + enter_args.old_state = current_state + local new_state_index = SMODS.get_state_stack_index(new_state) + if new_state_index then + if not enter_args.force_refresh then + enter_args.from_hold = true + end + end + exit_args = exit_args or {} + exit_args.new_state = exit_args.new_state or new_state + local current_state_index = SMODS.get_state_stack_index(current_state, true) + local exit_state_in_stack = not exit_args.from_hold and current_state_index -- If exit_args.from_hold is already true, this is irrelevant + if exit_state_in_stack then + exit_args.from_hold = true + end + if SMODS.GameStates[current_state] then + SMODS.GameStates[current_state]:on_exit(exit_args) + if not exit_args.from_hold or exit_state_in_stack then + SMODS.pop_from_state_stack(current_state) + end + end + G.STATE = new_state + if SMODS.GameStates[new_state] then + SMODS.STATE = new_state + if next_held_state ~= new_state or (exit_args.from_hold and not exit_state_in_stack) then + SMODS.push_to_state_stack(new_state, enter_args) + end + SMODS.GameStates[new_state]:on_enter(enter_args) + end +end + +function SMODS.exit_state(exit_args, enter_args, default) + local current_state = SMODS.STATE or G.STATE + local new_state + local next_held_state = SMODS.get_next_held_state() + default = default or {} + default.enter_args = default.enter_args or {} + if next_held_state then + new_state = next_held_state + else + new_state = default.state_override or SMODS.default_state + end + exit_args = exit_args or {} + exit_args.new_state = exit_args.new_state or new_state + if SMODS.get_state_stack_index(current_state, true) then + exit_args.from_hold = true + end + enter_args = enter_args or {} + enter_args.old_state = current_state + if SMODS.get_state_stack_index(new_state) then + if not enter_args.force_refresh then + enter_args.from_hold = true + end + end + if SMODS.GameStates[current_state] then + SMODS.GameStates[current_state]:on_exit(exit_args) + SMODS.pop_from_state_stack(current_state) + end + if #SMODS.state_stack < 1 then + G.STATE = nil + SMODS.STATE = nil + SMODS.enter_state(new_state, default.enter_args) + return + end + G.STATE = new_state + if SMODS.GameStates[G.STATE] then + SMODS.STATE = G.STATE + SMODS.GameStates[G.STATE]:on_enter(enter_args) + end +end + +SMODS.state_queue = {} + +function SMODS.queue_state(new_state, enter_args, exit_args) + SMODS.state_queue[#SMODS.state_queue+1] = {state = new_state, enter_args = enter_args, exit_args = exit_args} +end + +function SMODS.advance_state_queue(instant) + if #SMODS.state_queue < 1 then + return + end + local func = function() + local queue_table = table.remove(SMODS.state_queue, 1) + SMODS.enter_state(queue_table.state, queue_table.enter_args, queue_table.exit_args) + return true + end + if instant then + func() + else + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = func + })) + end +end + +function SMODS.clear_state_queue() + SMODS.state_queue = {} +end + + +local delete_run_clear_state_stuff = function () + SMODS.STATE = nil + SMODS.clear_state_stack() + SMODS.clear_state_queue() +end + +local _delete_run_ref = Game.delete_run +function Game:delete_run() + local ret = _delete_run_ref(self) + delete_run_clear_state_stuff() + return ret +end + +SMODS.GameStates = {} +SMODS.GameState = SMODS.GameObject:extend{ + set = 'GameState', + obj_table = SMODS.GameStates, + obj_buffer = {}, + required_parameters = { + 'key', + }, + inject = function (self, i) + + end, + on_enter = function (self, args) end, + on_exit = function (self, args) end, + on_load = function (self) end, + update = function (self, dt) end, + ease_background_colour = nil, -- function + exit_after_use_card = false, -- Used for consumable states like SMODS.STATES.REDEEM_VOUCHER + exit_after_end_consumable = false, -- Used for booster-like states like SMODS.STATES.BOOSTER_OPENED +} + +SMODS.GameState { + key = SMODS.STATES.BOOSTER_OPENED, + update = function (self, dt) + SMODS.OPENED_BOOSTER.config.center:update_pack(dt) + end, + exit_after_end_consumable = true, +} + +function SMODS.in_booster() + local held_state = SMODS.get_next_held_state() + return SMODS.STATE == SMODS.STATES.BOOSTER_OPENED or ((SMODS.GameStates[SMODS.STATE] or {}).holds_booster and held_state == SMODS.STATES.BOOSTER_OPENED) +end + +SMODS.GameState { + key = SMODS.STATES.REDEEM_VOUCHER, + exit_after_use_card = true, + holds_booster = true, +} + +SMODS.GameState { + key = SMODS.STATES.USE_CONSUMABLE, + on_enter = function (self, args) + if G.buttons then G.buttons:remove(); G.buttons = nil end + end, + exit_after_use_card = true, + holds_booster = true, +} + +SMODS.GameState { + key = SMODS.STATES.SHOP, + on_load = function () + G.shop = G.shop or UIBox{ + definition = G.UIDEF.shop(), + config = {align='tmi', offset = {x=0,y=G.ROOM.T.y+11},major = G.hand, bond = 'Weak'} + } + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = (function() + G.SHOP_SIGN.alignment.offset.y = -15 -- Counteracts the immediate event inside G.UIDEF.shop() + return true + end) + })) + + if G.load_shop_jokers then + G.shop_jokers:load(G.load_shop_jokers) + for k, v in ipairs(G.shop_jokers.cards) do + create_shop_card_ui(v) + if v.ability.consumeable then v:start_materialize(nil, true) end + for _kk, vvv in ipairs(G.GAME.tags) do + if vvv:apply_to_run({type = 'store_joker_modify', card = v}) then break end + end + end + G.load_shop_jokers = nil + end + + if G.load_shop_vouchers then + G.shop_vouchers:load(G.load_shop_vouchers) + for k, v in ipairs(G.shop_vouchers.cards) do + create_shop_card_ui(v) + v:start_materialize(nil, true) + end + G.load_shop_vouchers = nil + end + + if G.load_shop_booster then + G.shop_booster:load(G.load_shop_booster) + for k, v in ipairs(G.shop_booster.cards) do + create_shop_card_ui(v) + v:start_materialize(nil, true) + end + G.load_shop_booster = nil + end + end, + on_enter = function (self, args) + args = args or {} + G.CONTROLLER.locks.toggle_shop = nil + if args.force_refresh then + self:on_exit() + elseif args.from_hold then + if G.shop then + -- Extracted from G.FUNCS.use_card() + G.shop.alignment.offset.y = G.shop.alignment.offset.py or -5.3 + G.shop.alignment.offset.py = nil + G.SHOP_SIGN.alignment.offset.y = 0 + + if not args.no_shop_calc then + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.2, + blockable = false, + func = function() + if math.abs(G.shop.T.y - G.shop.VT.y) < 3 then + G.ROOM.jiggle = G.ROOM.jiggle + 3 + if not args.no_sound then play_sound('cardFan2') end + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'shop_start'}) + end + return true + end + end + })) + SMODS.calculate_context({starting_shop = true, from_hold = true}) + end + G.CONTROLLER:snap_to({node = G.shop:get_UIE_by_ID('next_round_button')}) + G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) + else + args.force_refresh = true + self:on_enter(args) + end + return + end + stop_use() + G.STATE_COMPLETE = true + ease_background_colour_blind(G.STATES.SHOP) + local shop_exists = not not G.shop + G.shop = G.shop or UIBox{ + definition = G.UIDEF.shop(), + config = {align='tmi', offset = {x=0,y=G.ROOM.T.y+11},major = G.hand, bond = 'Weak'} + } + -- Moved here from G.FUNCS.cash_out() + G.GAME.current_round.jokers_purchased = 0 + G.GAME.shop_free = nil + G.GAME.shop_d6ed = nil + ------- + G.E_MANAGER:add_event(Event({ + func = function() + G.shop.alignment.offset.y = -5.3 + G.shop.alignment.offset.x = 0 + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.2, + blockable = false, + func = function() + if math.abs(G.shop.T.y - G.shop.VT.y) < 3 then + G.ROOM.jiggle = G.ROOM.jiggle + 3 + if not args.no_sound then play_sound('cardFan2') end + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'shop_start'}) + end + local nosave_shop = nil + if not shop_exists then + if G.load_shop_jokers then + nosave_shop = true + G.shop_jokers:load(G.load_shop_jokers) + for k, v in ipairs(G.shop_jokers.cards) do + create_shop_card_ui(v) + if v.ability.consumeable then v:start_materialize() end + for _kk, vvv in ipairs(G.GAME.tags) do + if vvv:apply_to_run({type = 'store_joker_modify', card = v}) then break end + end + end + G.load_shop_jokers = nil + else + for i = 1, G.GAME.shop.joker_max - #G.shop_jokers.cards do + G.shop_jokers:emplace(create_card_for_shop(G.shop_jokers)) + end + end + + if G.load_shop_vouchers then + nosave_shop = true + G.shop_vouchers:load(G.load_shop_vouchers) + for k, v in ipairs(G.shop_vouchers.cards) do + create_shop_card_ui(v) + v:start_materialize() + end + G.load_shop_vouchers = nil + else + local vouchers_to_spawn = 0 + for _,_ in pairs(G.GAME.current_round.voucher.spawn) do vouchers_to_spawn = vouchers_to_spawn + 1 end + if vouchers_to_spawn < G.GAME.starting_params.vouchers_in_shop + (G.GAME.modifiers.extra_vouchers or 0) then + SMODS.get_next_vouchers(G.GAME.current_round.voucher) + end + for _, key in ipairs(G.GAME.current_round.voucher or {}) do + if G.P_CENTERS[key] and G.GAME.current_round.voucher.spawn[key] then + SMODS.add_voucher_to_shop(key) + end + end + end + + if G.load_shop_booster then + nosave_shop = true + G.shop_booster:load(G.load_shop_booster) + for k, v in ipairs(G.shop_booster.cards) do + create_shop_card_ui(v) + v:start_materialize() + end + G.load_shop_booster = nil + else + for i=1, G.GAME.starting_params.boosters_in_shop + (G.GAME.modifiers.extra_boosters or 0) do + G.GAME.current_round.used_packs = G.GAME.current_round.used_packs or {} + if not G.GAME.current_round.used_packs[i] then + G.GAME.current_round.used_packs[i] = get_pack('shop_pack').key + end + + if G.GAME.current_round.used_packs[i] ~= 'USED' then + local card = Card(G.shop_booster.T.x + G.shop_booster.T.w/2, + G.shop_booster.T.y, G.CARD_W*1.27, G.CARD_H*1.27, G.P_CARDS.empty, G.P_CENTERS[G.GAME.current_round.used_packs[i]], {bypass_discovery_center = true, bypass_discovery_ui = true}) + create_shop_card_ui(card, 'Booster', G.shop_booster) + card.ability.booster_pos = i + card:start_materialize() + G.shop_booster:emplace(card) + end + end + + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'voucher_add'}) + end + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'shop_final_pass'}) + end + end + end + + if not nosave_shop then SMODS.calculate_context({starting_shop = true}) end + G.CONTROLLER:snap_to({node = G.shop:get_UIE_by_ID('next_round_button')}) + if not nosave_shop then G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) end + return true + end + end + })) + return true + end + })) + end, + on_exit = function (self, args) + args = args or {} + G.CONTROLLER.locks.toggle_shop = true + if args.from_hold then + if G.shop and not G.shop.alignment.offset.py then + G.shop.alignment.offset.py = G.shop.alignment.offset.y + G.shop.alignment.offset.y = G.ROOM.T.y + 29 + if not args.leave_shop_sign then + G.SHOP_SIGN.alignment.offset.y = -15 + end + end + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.5, + func = function () + G.CONTROLLER.locks.toggle_shop = nil + return true + end + })) + return + end + stop_use() + if G.shop then + SMODS.calculate_context({ending_shop = true}) + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + G.shop.alignment.offset.y = G.ROOM.T.y + 29 + G.SHOP_SIGN.alignment.offset.y = -15 + return true + end + })) + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.5, + func = function() + G.shop:remove() + G.shop = nil + G.SHOP_SIGN:remove() + G.SHOP_SIGN = nil + G.CONTROLLER.locks.toggle_shop = nil + return true + end + })) + else + G.CONTROLLER.locks.toggle_shop = false + end + end, +} + +local end_round_ref = end_round +function end_round() + if SMODS.STATE ~= SMODS.STATES.BLIND then return end + return end_round_ref() +end + +SMODS.GameState { + key = SMODS.STATES.ROUND_EVAL, + on_enter = function (self, args) + args = args or {} + if args.force_refresh then + self:on_exit() + elseif args.from_hold then + if G.round_eval then + G.round_eval.alignment.offset.y = G.round_eval.alignment.offset.py + G.round_eval.alignment.offset.py = nil + else + args.force_refresh = true + self:on_enter(args) + end + return + end + stop_use() + G.STATE_COMPLETE = true + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + save_run() + ease_background_colour_blind(G.STATES.ROUND_EVAL) + G.round_eval = UIBox{ + definition = create_UIBox_round_evaluation(), + config = {align="bm", offset = {x=0,y=G.ROOM.T.y + 19},major = G.hand, bond = 'Weak'} + } + G.round_eval.alignment.offset.x = 0 + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + if G.round_eval.alignment.offset.y ~= -7.8 then + G.round_eval.alignment.offset.y = -7.8 + else + if math.abs(G.round_eval.T.y - G.round_eval.VT.y) < 3 then + G.ROOM.jiggle = G.ROOM.jiggle + 3 + play_sound('cardFan2') + delay(0.1) + G.FUNCS.evaluate_round() + return true + end + end + end})) + return true + end + })) + end, + on_exit = function (self, args) + args = args or {} + if args.from_hold then + if G.round_eval and not G.round_eval.alignment.offset.py then + G.round_eval.alignment.offset.py = G.round_eval.alignment.offset.y + G.round_eval.alignment.offset.y = G.ROOM.T.y + 29 + end + return + end + if G.round_eval then + G.round_eval.alignment.offset.y = G.ROOM.T.y + 15 + G.round_eval.alignment.offset.x = 0 + G.deck:shuffle('cashout'..G.GAME.round_resets.ante) + G.deck:hard_set_T() + delay(0.3) + G.GAME.current_round.discards_left = math.max(0, G.GAME.round_resets.discards + G.GAME.round_bonus.discards) + G.GAME.current_round.hands_left = (math.max(1, G.GAME.round_resets.hands + G.GAME.round_bonus.next_hands)) + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + if G.round_eval then + G.round_eval:remove() + G.round_eval = nil + end + return true + end + })) + ease_dollars(G.GAME.current_round.dollars) + G.E_MANAGER:add_event(Event({ + func = function() + G.GAME.previous_round.dollars = G.GAME.dollars + return true + end + })) + play_sound("coin7") + G.VIBRATION = G.VIBRATION + 1 + end + + ease_chips(0) + if G.GAME.round_resets.blind_states.Boss == 'Defeated' then + G.GAME.round_resets.blind_ante = G.GAME.round_resets.ante + G.GAME.round_resets.blind_tags.Small = get_next_tag_key() + G.GAME.round_resets.blind_tags.Big = get_next_tag_key() + end + reset_blinds() + delay(0.6) + end, +} + +SMODS.GameState { + key = SMODS.STATES.BLIND, + on_enter = function (self, args) + args = args or {} + if args.force_refresh then + self:on_exit() + elseif args.from_hold then + G.GAME.facing_blind = true + if args.leave_data then + save_run() + G.STATE = G.STATES.SELECTING_HAND + G.CONTROLLER:recall_cardarea_focus('hand') + return + end + local data = SMODS.state_stack[#SMODS.state_stack].data + ease_chips(data.chips) + G.GAME.blind:set_blind(G.P_BLINDS[data.blind_key], nil, true) + G.GAME.blind.chips = data.blind_chips + G.GAME.blind.chip_text = number_format(data.blind_chips) + ease_hands_played(G.GAME.current_round.hands_left - data.hands_left) + G.GAME.current_round.hands_played = data.hands_played + ease_discard(G.GAME.current_round.discards_left - data.discards_left) + G.GAME.current_round.discards_used = data.discards_used + local hand_cards = SMODS.get_cards_by_sort_ids(data.hand_cards, {G.playing_cards}) + local discarded_cards = SMODS.get_cards_by_sort_ids(data.discarded_cards, {G.playing_cards}) + for _, pcard_sort_id in ipairs(data.hand_cards) do + local pcard = hand_cards[pcard_sort_id] + if pcard and not pcard.removed then + draw_card(G.deck, G.hand, nil, nil, nil, pcard) + pcard.ability.forced_selection = data.forced_selection[pcard_sort_id] + end + end + for _, pcard_sort_id in ipairs(data.discarded_cards) do + local pcard = discarded_cards[pcard_sort_id] + if pcard and not pcard.removed then + draw_card(G.deck, G.discard, nil, nil, nil, pcard) + pcard.ability.discarded = true + end + end + save_run() + G.STATE = G.STATES.SELECTING_HAND + G.CONTROLLER:recall_cardarea_focus('hand') + return + end + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + stop_use() + G.GAME.facing_blind = true + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + ease_round(1) + inc_career_stat('c_rounds', 1) + if _DEMO then + G.SETTINGS.DEMO_ROUNDS = (G.SETTINGS.DEMO_ROUNDS or 0) + 1 + inc_steam_stat('demo_rounds') + G:save_settings() + end + G.GAME.round_resets.blind = G.P_BLINDS[args.key] + G.GAME.round_resets.blind_states[G.GAME.blind_on_deck] = 'Current' + delay(0.2) + return true + end})) + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + new_round() + return true + end + })) + return true + end + })) + end, + on_exit = function (self, args) + args = args or {} + G.GAME.facing_blind = nil + if args.from_hold or args.no_defeat then + if G.HUD_blind and not args.leave_HUD_blind then + G.HUD_blind.alignment.offset.py = G.HUD_blind.alignment.offset.y + G.HUD_blind.alignment.offset.y = -10 + end + if args.from_hold then + local data = { + blind_key = G.GAME.blind.config.blind.key, + hand_cards = {}, + forced_selection = {}, + discarded_cards = {}, + chips = G.GAME.chips, + blind_chips = G.GAME.blind.chips, + hands_left = G.GAME.current_round.hands_left, + hands_played = G.GAME.current_round.hands_played, + discards_left = G.GAME.current_round.discards_left, + discards_used = G.GAME.current_round.discards_used, + } + for _, pcard in ipairs(G.hand.cards) do + data.hand_cards[#data.hand_cards+1] = pcard.sort_id + if pcard.ability.forced_selection then + data.forced_selection[pcard.sort_id] = true + end + end + for _, pcard in ipairs(G.discard.cards) do + data.discarded_cards[#data.discarded_cards+1] = pcard.sort_id + end + SMODS.state_stack[#SMODS.state_stack].data = data + end + if not args.leave_data then + G.FUNCS.draw_from_hand_to_discard() + G.FUNCS.draw_from_discard_to_deck() + G.E_MANAGER:add_event(Event({ + trigger = "after", + blockable = false, + delay = 0.7, + func = function () + G.GAME.blind:disable() + if G.buttons then G.buttons:remove(); G.buttons = nil end + return true + end + })) + for k, v in ipairs(G.playing_cards) do + v.ability.discarded = nil + v.ability.forced_selection = nil + end + delay(0.4) + end + return + end + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + blocking = false, + func = function () + if args.new_state == SMODS.STATES.ROUND_EVAL then -- Precise vanilla timing -> called from end_round() + if G.round_eval and G.round_eval.alignment.offset.y == -7.8 and math.abs(G.round_eval.T.y - G.round_eval.VT.y) < 3 then + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + G.E_MANAGER:add_event(Event({ + trigger = 'before', + blocking = false, + delay = 1.3*math.min(G.GAME.blind.dollars+2, 7)/2*0.15 + 0.5, + func = function() + G.GAME.blind:defeat() + return true + end + })) + delay(0.2) + return true + end + })) + return true + end + else + G.FUNCS.draw_from_hand_to_discard() + G.FUNCS.draw_from_discard_to_deck() + G.E_MANAGER:add_event(Event({ + trigger = "after", + blockable = false, + blocking = false, + delay = 0.7, + func = function () + G.GAME.blind:defeat() + return true + end + })) + for k, v in ipairs(G.playing_cards) do + v.ability.discarded = nil + v.ability.forced_selection = nil + end + delay(0.4) + return true + end + end + })) + end, + ease_background_colour = function (self, blind_override) + local blindname = ((blind_override or (G.GAME.blind and G.GAME.blind.name ~= '' and G.GAME.blind.name)) or 'Small Blind') + blindname = (blindname == '' and 'Small Blind' or blindname) + + local boss_col = G.C.BLACK + for k, v in pairs(G.P_BLINDS) do + if v.name == blindname then + if v.boss and v.boss.showdown or v.blind_types and v.blind_types.Showdown then + ease_background_colour{new_colour = G.C.BLUE, special_colour = G.C.RED, tertiary_colour = darken(G.C.BLACK, 0.4), contrast = 3} + return + end + boss_col = v.boss_colour or G.C.BLACK + end + end + ease_background_colour{new_colour = lighten(mix_colours(boss_col, G.C.BLACK, 0.3), 0.1), special_colour = boss_col, contrast = 2} + end +} + +SMODS.GameState { + key = SMODS.STATES.BLIND_SELECT, + on_enter = function (self, args) + args = args or {} + if args.force_refresh then + self:on_exit() + elseif args.from_hold then + if G.blind_select then + G.blind_select.alignment.offset.y = G.blind_select.alignment.offset.py + G.blind_select.alignment.offset.py = nil + G.E_MANAGER:add_event(Event({ + trigger = "after", + delay = 0.3, + func = function () + if not args.no_sound then play_sound('cancel') end + return true + end + })) + else + args.force_refresh = true + self:on_enter(args) + end + return + end + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function() + stop_use() + ease_background_colour_blind(SMODS.STATES.BLIND_SELECT) + G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) + G.CONTROLLER.interrupt.focus = true + G.E_MANAGER:add_event(Event({ func = function() + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + play_sound('cancel') + G.blind_select = UIBox{ + definition = create_UIBox_blind_select(), + config = {align="bmi", offset = {x=0,y=G.ROOM.T.y + 29},major = G.hand, bond = 'Weak'} + } + G.blind_select.alignment.offset.y = 0.8-(G.hand.T.y - G.jokers.T.y) + G.blind_select.T.h + G.ROOM.jiggle = G.ROOM.jiggle + 3 + G.blind_select.alignment.offset.x = 0 + G.CONTROLLER.lock_input = false + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'immediate'}) + end + for i = 1, #G.GAME.tags do + if G.GAME.tags[i]:apply_to_run({type = 'new_blind_choice'}) then break end + end + return true + end + })) + return true + end})) + return true + end + })) + end, + on_exit = function (self, args) + args = args or {} + if args.from_hold then + if G.blind_select and not G.blind_select.alignment.offset.py then + G.blind_select.alignment.offset.py = G.blind_select.alignment.offset.y + G.blind_select.alignment.offset.y = G.ROOM.T.y + 39 + end + return + end + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext1').config.object.pop_delay = 0 + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext1').config.object:pop_out(5) + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext2').config.object.pop_delay = 0 + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext2').config.object:pop_out(5) + + G.E_MANAGER:add_event(Event({ + trigger = 'before', delay = 0.2, + func = function() + G.blind_prompt_box.alignment.offset.y = -10 + G.blind_select.alignment.offset.y = 40 + G.blind_select.alignment.offset.x = 0 + return true + end})) + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + G.blind_select:remove() + G.blind_prompt_box:remove() + G.blind_select = nil + return true + end + })) + + end, +} \ No newline at end of file diff --git a/src/overrides.lua b/src/overrides.lua index 50583a8ff..d6a836290 100644 --- a/src/overrides.lua +++ b/src/overrides.lua @@ -2702,7 +2702,7 @@ function Card:set_ability(center, initial, delay_sprites) if delay_sprites == "quantum" then return self:quantum_set_ability(center) end local old_center = self.config.center set_ability(self, center, initial, delay_sprites) - if not initial and (G.STATE ~= G.STATES.SMODS_BOOSTER_OPENED and G.STATE ~= G.STATES.SHOP and not G.SETTINGS.paused or G.TAROT_INTERRUPT) then + if not initial and (G.STATE ~= SMODS.STATES.BOOSTER_OPENED and G.STATE ~= SMODS.STATES.SHOP and not G.SETTINGS.paused or G.TAROT_INTERRUPT) then SMODS.calculate_context({setting_ability = true, old = old_center.key, new = self.config.center_key, other_card = self, unchanged = old_center.key == self.config.center.key}) end self.front_hidden = self:should_hide_front() @@ -2873,6 +2873,46 @@ G.FUNCS.change_viewed_back = function(...) return g_funcs_change_viewed_back_ref(...) end + + +-- SMODS.GameState related overrides + +function G.FUNCS.toggle_shop(e) + if #SMODS.state_queue == 0 then + SMODS.queue_state(SMODS.STATES.BLIND_SELECT) + end + SMODS.advance_state_queue() +end + +function G.FUNCS.cash_out(e) + --stop_use() + if G.round_eval then + e.config.button = nil + if #SMODS.state_queue == 0 then + SMODS.queue_state(SMODS.STATES.SHOP) + end + SMODS.advance_state_queue() + end +end + +function G.FUNCS.select_blind(e) + stop_use() + if G.blind_select then + if #SMODS.state_queue == 0 then + SMODS.queue_state(SMODS.STATES.BLIND, {key = e.config.ref_table.key}) + end + SMODS.advance_state_queue() + end +end + +local ease_bg_col_bl_ref = ease_background_colour_blind +function ease_background_colour_blind(state, blind_override) + if SMODS.GameStates[state] and SMODS.GameStates[state].ease_background_colour then + return SMODS.GameStates[state]:ease_background_colour(blind_override) + end + return ease_bg_col_bl_ref(state, blind_override) +end + --Patch to allow `type = "name_text"` to take vars local oldlocalize = localize function localize(args, misc_cat) diff --git a/src/utils.lua b/src/utils.lua index 1ac017f7c..3eab66d15 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -1324,8 +1324,8 @@ SMODS.calculate_individual_effect = function(effect, scored_card, key, amount, f SMODS.calculate_context({ money_altered = true, amount = amount, - from_shop = (G.STATE == G.STATES.SHOP or G.STATE == G.STATES.SMODS_BOOSTER_OPENED or G.STATE == G.STATES.SMODS_REDEEM_VOUCHER) or nil, - from_consumeable = (G.STATE == G.STATES.PLAY_TAROT) or nil, + from_shop = (G.STATE == SMODS.STATES.SHOP or G.STATE == SMODS.STATES.BOOSTER_OPENED or G.STATE == SMODS.STATES.REDEEM_VOUCHER) or nil, + from_consumeable = (G.STATE == SMODS.STATES.USE_CONSUMABLE) or nil, from_scoring = (G.STATE == G.STATES.HAND_PLAYED) or nil, }) return true @@ -2879,7 +2879,7 @@ function SMODS.update_hand_limit_text(play, discard) end function SMODS.draw_cards(hand_space) - if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) and + if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED) and G.hand.config.card_limit <= 0 and #G.hand.cards == 0 then G.STATE = G.STATES.GAME_OVER; G.STATE_COMPLETE = false return true @@ -3034,7 +3034,7 @@ end local game_start_run = Game.start_run function Game:start_run(args) - game_start_run(self, args) + local ret = game_start_run(self, args) G.SCORE_DISPLAY_QUEUE = nil G.E_MANAGER:add_event(Event({ trigger = 'immediate', @@ -3043,6 +3043,20 @@ function Game:start_run(args) return true end })) + if args.savetext and args.savetext.SMODS then + local state_data = args.savetext.SMODS.state_data + if state_data then + SMODS.STATE = state_data.STATE + SMODS.state_stack = state_data.state_stack or {} + SMODS.state_queue = state_data.state_queue or {} + for _, state_table in ipairs(SMODS.state_stack) do + if SMODS.GameStates[state_table.state] then + SMODS.GameStates[state_table.state]:on_load() + end + end + end + end + return ret end G.FUNCS.SMODS_scoring_calculation_function = function(e) @@ -3375,8 +3389,8 @@ function ease_dollars(mod, instant) SMODS.calculate_context({ money_altered = true, amount = mod, - from_shop = (G.STATE == G.STATES.SHOP or G.STATE == G.STATES.SMODS_BOOSTER_OPENED or G.STATE == G.STATES.SMODS_REDEEM_VOUCHER) or nil, - from_consumeable = (G.STATE == G.STATES.PLAY_TAROT) or nil, + from_shop = (G.STATE == SMODS.STATES.SHOP or G.STATE == SMODS.STATES.BOOSTER_OPENED or G.STATE == SMODS.STATES.REDEEM_VOUCHER) or nil, + from_consumeable = (G.STATE == SMODS.STATES.USE_CONSUMABLE) or nil, from_scoring = (G.STATE == G.STATES.HAND_PLAYED) or nil, }) end @@ -3853,14 +3867,21 @@ function save_run() end end smods_hook_save_run() - if SMODS.last_hand and G.culled_table then + if G.culled_table then G.culled_table.SMODS = { - last_hand = { + state_data = { + STATE = SMODS.STATE, + state_stack = recursive_table_cull(SMODS.state_stack), + state_queue = recursive_table_cull(SMODS.state_queue), + } + } + if SMODS.last_hand then + G.culled_table.SMODS.last_hand = { scoring_name = SMODS.last_hand.scoring_name, scoring_hand = {}, full_hand = {} } - } + end end end @@ -4125,6 +4146,38 @@ function SMODS.create_unlock_text(center) return localize('k_'..string.lower(center and center.set or 'unknown')) end +-- Util function to get a single card by its sort_id. +function SMODS.get_card_by_sort_id(sort_id, card_lists) + card_lists = card_lists or {(G.jokers or {}).cards or {}, (G.consumeables or {}).cards or {}, G.playing_cards} + for _, cards in ipairs(card_lists) do + for _, card in ipairs(cards) do + if card.sort_id == sort_id then return card end + end + end +end + +-- Util function to get multiple cards by their sort_ids. +function SMODS.get_cards_by_sort_ids(sort_ids, card_lists) + card_lists = card_lists or {(G.jokers or {}).cards or {}, (G.consumeables or {}).cards or {}, G.playing_cards} + local sort_ids_map = {} + if sort_ids[1] and type(sort_ids[1]) == "number" then + for _, sort_id in ipairs(sort_ids) do + sort_ids_map[sort_id] = true + end + else + sort_ids_map = sort_ids + end + local ret = {} + for _, cards in ipairs(card_lists) do + for _, card in ipairs(cards) do + if sort_ids_map[card.sort_id] then + ret[card.sort_id] = card + end + end + end + return ret +end + function SMODS.copy_card(card, args) args = args or {} local playing_card @@ -4160,4 +4213,4 @@ function SMODS.add_to_deck(card, args) local area = args.area or G.jokers area:emplace(card) return card -end \ No newline at end of file +end