From f27e3c518068de133f91efb7d2bd69bfa0d2da36 Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Mon, 9 Feb 2026 18:32:58 +1000 Subject: [PATCH 1/2] Initial removal --- lovely/achievements.toml | 109 -- lovely/atlas.toml | 55 - lovely/back.toml | 207 --- lovely/better_calc.toml | 2039 -------------------------- lovely/better_calc_fixes.toml | 31 - lovely/blind.toml | 552 ------- lovely/blind_ui.toml | 176 --- lovely/booster.toml | 367 ----- lovely/calc_returns.toml | 45 - lovely/calculate_card_added.toml | 29 - lovely/can_calculate.toml | 63 - lovely/card_limit.toml | 389 ----- lovely/cardarea.toml | 30 - lovely/cardareas.toml | 51 - lovely/center.toml | 639 -------- lovely/challenge.toml | 93 -- lovely/compact_cashout.toml | 65 - lovely/compat_0_9_8.toml | 38 - lovely/core.toml | 28 - lovely/crash_handler.toml | 32 - lovely/create_sprite.toml | 97 -- lovely/deck_skins.toml | 110 -- lovely/dollar_row.toml | 77 - lovely/easings.toml | 52 - lovely/edition.toml | 219 --- lovely/enhancement.toml | 364 ----- lovely/event.toml | 64 - lovely/fixes.toml | 853 ----------- lovely/gradient.toml | 56 - lovely/hand_limit.toml | 93 -- lovely/jimboquip.toml | 202 --- lovely/joker_retriggers.toml | 324 ---- lovely/joker_size.toml | 73 - lovely/keybind.toml | 82 -- lovely/language.toml | 59 - lovely/libs.toml | 32 - lovely/listed_probabilities.toml | 162 -- lovely/loader.toml | 27 - lovely/menu.toml | 60 - lovely/mod.toml | 72 - lovely/multi_box_descriptions.toml | 158 -- lovely/number_formatting.toml | 177 --- lovely/perma_bonus.toml | 297 ---- lovely/playing_card.toml | 378 ----- lovely/poker_hand.toml | 127 -- lovely/poker_hand_screen.toml | 54 - lovely/poker_hand_text.toml | 42 - lovely/pool.toml | 208 --- lovely/preflight.toml | 47 - lovely/rarity.toml | 61 - lovely/scaling.toml | 708 --------- lovely/scoring_calculation.toml | 378 ----- lovely/screen_shader_stack.toml | 74 - lovely/seal.toml | 241 --- lovely/shaders.toml | 18 - lovely/shop.toml | 271 ---- lovely/sound.toml | 167 --- lovely/stake.toml | 281 ---- lovely/sticker.toml | 111 -- lovely/tag.toml | 166 --- lovely/text_effect.toml | 126 -- lovely/threads.toml | 30 - lovely/ui.toml | 405 ----- lovely/ui_additional_text_props.toml | 386 ----- lovely/ui_elements.toml | 152 -- 65 files changed, 13179 deletions(-) delete mode 100644 lovely/achievements.toml delete mode 100644 lovely/atlas.toml delete mode 100644 lovely/back.toml delete mode 100644 lovely/better_calc.toml delete mode 100644 lovely/better_calc_fixes.toml delete mode 100644 lovely/blind.toml delete mode 100644 lovely/blind_ui.toml delete mode 100644 lovely/booster.toml delete mode 100644 lovely/calc_returns.toml delete mode 100644 lovely/calculate_card_added.toml delete mode 100644 lovely/can_calculate.toml delete mode 100644 lovely/card_limit.toml delete mode 100644 lovely/cardarea.toml delete mode 100644 lovely/cardareas.toml delete mode 100644 lovely/center.toml delete mode 100644 lovely/challenge.toml delete mode 100644 lovely/compact_cashout.toml delete mode 100644 lovely/compat_0_9_8.toml delete mode 100644 lovely/core.toml delete mode 100644 lovely/crash_handler.toml delete mode 100644 lovely/create_sprite.toml delete mode 100644 lovely/deck_skins.toml delete mode 100644 lovely/dollar_row.toml delete mode 100644 lovely/easings.toml delete mode 100644 lovely/edition.toml delete mode 100644 lovely/enhancement.toml delete mode 100644 lovely/event.toml delete mode 100644 lovely/fixes.toml delete mode 100644 lovely/gradient.toml delete mode 100644 lovely/hand_limit.toml delete mode 100644 lovely/jimboquip.toml delete mode 100644 lovely/joker_retriggers.toml delete mode 100644 lovely/joker_size.toml delete mode 100644 lovely/keybind.toml delete mode 100644 lovely/language.toml delete mode 100644 lovely/libs.toml delete mode 100644 lovely/listed_probabilities.toml delete mode 100644 lovely/loader.toml delete mode 100644 lovely/menu.toml delete mode 100644 lovely/mod.toml delete mode 100644 lovely/multi_box_descriptions.toml delete mode 100644 lovely/number_formatting.toml delete mode 100644 lovely/perma_bonus.toml delete mode 100644 lovely/playing_card.toml delete mode 100644 lovely/poker_hand.toml delete mode 100644 lovely/poker_hand_screen.toml delete mode 100644 lovely/poker_hand_text.toml delete mode 100644 lovely/pool.toml delete mode 100644 lovely/preflight.toml delete mode 100644 lovely/rarity.toml delete mode 100644 lovely/scaling.toml delete mode 100644 lovely/scoring_calculation.toml delete mode 100644 lovely/screen_shader_stack.toml delete mode 100644 lovely/seal.toml delete mode 100644 lovely/shaders.toml delete mode 100644 lovely/shop.toml delete mode 100644 lovely/sound.toml delete mode 100644 lovely/stake.toml delete mode 100644 lovely/sticker.toml delete mode 100644 lovely/tag.toml delete mode 100644 lovely/text_effect.toml delete mode 100644 lovely/threads.toml delete mode 100644 lovely/ui.toml delete mode 100644 lovely/ui_additional_text_props.toml delete mode 100644 lovely/ui_elements.toml diff --git a/lovely/achievements.toml b/lovely/achievements.toml deleted file mode 100644 index 6c98ee42b..000000000 --- a/lovely/achievements.toml +++ /dev/null @@ -1,109 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -## Achievement API - -# fetch_achievements() -[[patches]] -[patches.regex] -target = "functions/common_events.lua" -pattern = '''(?[\t ]*)if G\.F_NO_ACHIEVEMENTS then return end[\n\s]*?--\|FROM LOCAL SETTINGS FILE''' -position = 'before' -# match_indent = true -line_prepend = '$indent' -payload = ''' -G.SETTINGS.ACHIEVEMENTS_EARNED = G.SETTINGS.ACHIEVEMENTS_EARNED or {} -for k, v in pairs(G.ACHIEVEMENTS) do - if not v.key then v.key = k end - for kk, vv in pairs(G.SETTINGS.ACHIEVEMENTS_EARNED) do - if G.ACHIEVEMENTS[kk] and G.ACHIEVEMENTS[kk].mod then - G.ACHIEVEMENTS[kk].earned = true - end - end -end -''' - -# check_for_unlock -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = '''if G.GAME.challenge then return end''' -position = "after" -payload = ''' -fetch_achievements() -- Refreshes achievements -for k, v in pairs(G.ACHIEVEMENTS) do - if (not v.earned) and (v.unlock_condition and type(v.unlock_condition) == 'function') and v:unlock_condition(args) then - unlock_achievement(k) - end -end''' -match_indent = true - -# unlock_achievement() -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = '''if G.PROFILES[G.SETTINGS.profile].all_unlocked then return end''' -position = "at" -payload = '''if G.PROFILES[G.SETTINGS.profile].all_unlocked and (G.ACHIEVEMENTS and G.ACHIEVEMENTS[achievement_name] and not G.ACHIEVEMENTS[achievement_name].bypass_all_unlocked and SMODS.config.achievements < 3) or (SMODS.config.achievements < 3 and (G.GAME.seeded or G.GAME.challenge)) then return true end''' -match_indent = true - -# unlock_achievement() - fix event queue leaking -# fixed smods achievements not unlocking due to above comment's memory leak fix -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = '''local achievement_set = false -if G.F_NO_ACHIEVEMENTS then return end''' -position = "at" -payload = '''local achievement_set = false -if not G.ACHIEVEMENTS then fetch_achievements() end -G.SETTINGS.ACHIEVEMENTS_EARNED[achievement_name] = true -G:save_progress() - -if G.ACHIEVEMENTS[achievement_name] and G.ACHIEVEMENTS[achievement_name].mod then - if not G.ACHIEVEMENTS[achievement_name].earned then - --|THIS IS THE FIRST TIME THIS ACHIEVEMENT HAS BEEN EARNED - achievement_set = true - G.FILE_HANDLER.force = true - end - G.ACHIEVEMENTS[achievement_name].earned = true -end - -if achievement_set then - notify_alert(achievement_name) - return true -end -if G.F_NO_ACHIEVEMENTS and not (G.ACHIEVEMENTS[achievement_name] or {}).mod then return true end''' -match_indent = true - -# option to allow unlocks and discoveries in seeded runs -[[patches]] -[patches.regex] -target = 'functions/common_events.lua' -pattern = 'if G\.GAME\.seeded or G\.GAME\.challenge then return end' -position = 'at' -payload = 'if not SMODS.config.seeded_unlocks and (G.GAME.seeded or G.GAME.challenge) then return end' - -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'at' -pattern = 'if not G.GAME.seeded and not G.GAME.challenge then' -payload = 'if (not G.GAME.seeded and not G.GAME.challenge) or SMODS.config.seeded_unlocks then' - -[[patches]] -[patches.regex] -target = 'functions/common_events.lua' -pattern = 'if G\.GAME\.seeded then' -position = 'at' -payload = 'if false then' - -[[patches]] -[patches.regex] -target = 'functions/common_events.lua' -pattern = 'if G\.GAME\.challenge then' -position = 'at' -payload = 'if false then' diff --git a/lovely/atlas.toml b/lovely/atlas.toml deleted file mode 100644 index df4f7fd68..000000000 --- a/lovely/atlas.toml +++ /dev/null @@ -1,55 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -### Sprite API - -# Game:set_render_settings() -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = "G:set_render_settings()" -position = 'at' -match_indent = true -payload = "SMODS.injectObjects(SMODS.Atlas)" - - -## Set ability resizing fix -[[patches]] -[patches.pattern] -target = 'engine/moveable.lua' -pattern = '''Node.init(self, args)''' -position = 'after' -match_indent = true -payload = '''self.original_T = copy_table(self.T)''' -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = '''self.T.scale = 0.95''' -position = 'after' -match_indent = true -payload = '''self.original_T.scale = 0.95''' -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = '''self:set_ability(center, true)''' -position = 'before' -match_indent = true -payload = '''self.original_T = copy_table(self.T)''' -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'before' -pattern = ''' -local X, Y, W, H = self.T.x, self.T.y, self.T.w, self.T.h -''' -payload = ''' -if delay_sprites ~= 'quantum' then - for key, _ in pairs(self.T) do - self.T[key] = self.original_T[key] - end -end -''' - diff --git a/lovely/back.toml b/lovely/back.toml deleted file mode 100644 index b6af41f9d..000000000 --- a/lovely/back.toml +++ /dev/null @@ -1,207 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -### Back API - -# Back:init() -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = "if not selected_back then selected_back = G.P_CENTERS.b_red end" -position = 'after' -match_indent = true -payload = "self.atlas = selected_back.unlocked and selected_back.atlas or nil" - -# Back:change_to() -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = "if not new_back then new_back = G.P_CENTERS.b_red end" -position = 'after' -match_indent = true -payload = "self.atlas = new_back.unlocked and new_back.atlas or nil" - -# G.FUNCS.change_viewed_back -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = "G.PROFILES[G.SETTINGS.profile].MEMORY.deck = args.to_val" -position = 'after' -match_indent = true -payload = ''' -for key, val in pairs(G.sticker_card.area.cards) do - val.children.back = false - val:set_ability(val.config.center, true) -end''' - -# Back:apply_to_run() -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = "function Back:apply_to_run()" -position = 'after' -match_indent = true -payload = ''' - local obj = self.effect.center - if obj.apply and type(obj.apply) == 'function' then - obj:apply(self) - end''' - -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = "if self.effect.config.randomize_rank_suit then" -position = 'before' -match_indent = true -payload = ''' -G.E_MANAGER:add_event(Event({ - func = function() - G.E_MANAGER:add_event(Event({ - func = function() - save_run() - return true - end - })) - return true - end -})) -''' - -# Back:trigger_effect(args) -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = "if not args then return end" -position = 'after' -match_indent = true -payload = ''' - local obj = self.effect.center - if type(obj.calculate) == 'function' then - local o = {obj:calculate(self, args)} - if next(o) ~= nil then return unpack(o) end - elseif type(obj.trigger_effect) == 'function' then - -- kept for compatibility - local o = {obj:trigger_effect(args)} - if next(o) ~= nil then - sendWarnMessage(('Found `trigger_effect` function on SMODS.Back object "%s". This field is deprecated; please use `calculate` instead.'):format(obj.key), 'Back') - return unpack(o) - end - end''' - -## Back:generate_UI - -# Localization with `unlock` field in loc_txt, same as for Jokers -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = 'if not back_config.unlock_condition then' -position = 'at' -payload = ''' -local localized_by_smods -local key_override -if back_config.locked_loc_vars and type(back_config.locked_loc_vars) == 'function' then - local res = back_config:locked_loc_vars() or {} - loc_args = res.vars or {} - key_override = res.key -end -if G.localization.descriptions.Back[key_override or back_config.key].unlock_parsed then - localize{type = 'unlocks', key = key_override or back_config.key, set = 'Back', nodes = loc_nodes, vars = loc_args} - localized_by_smods = true -end -if not back_config.unlock_condition then''' -match_indent = true -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = '''localize{type = 'descriptions', key = 'demo_locked', set = "Other", nodes = loc_nodes, vars = loc_args}''' -position = 'at' -payload = ''' -if not localized_by_smods then - localize{type = 'descriptions', key = 'demo_locked', set = "Other", nodes = loc_nodes, vars = loc_args} -end''' -match_indent = true - -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = 'loc_args = {other_name}' -position = 'at' -payload = 'loc_args = loc_args or {other_name}' -match_indent = true -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = 'loc_args = {tostring(back_config.unlock_condition.amount)}' -position = 'at' -payload = 'loc_args = loc_args or {tostring(back_config.unlock_condition.amount)}' -match_indent = true -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = 'loc_args = {other_name, colours = {get_stake_col(back_config.unlock_condition.stake)}}' -position = 'at' -payload = 'loc_args = loc_args or {other_name, colours = {get_stake_col(back_config.unlock_condition.stake)}}' -match_indent = true - -[[patches]] -[patches.pattern] -target = 'back.lua' -pattern = "if name_to_check == 'Blue Deck'*" -position = 'at' -match_indent = true -payload = ''' -local key_override -if back_config.loc_vars and type(back_config.loc_vars) == 'function' then - local res = back_config:loc_vars() or {} - loc_args = res.vars or {} - key_override = res.key -elseif name_to_check == 'Blue Deck' then loc_args = {effect_config.hands}''' - -[[patches]] -[patches.regex] -target = 'back.lua' -pattern = "key = back_config\\.key" -position = 'at' -payload = "key = key_override or back_config.key" - -# Back:apply_to_run() - add jokers support to config -[[patches]] -[patches.pattern] -target = 'back.lua' -match_indent = true -position = 'before' -pattern = ''' -if self.effect.config.voucher then -''' -payload = ''' -if self.effect.config.jokers then - delay(0.4) - G.E_MANAGER:add_event(Event({ - func = function() - for k, v in ipairs(self.effect.config.jokers) do - local card = create_card('Joker', G.jokers, nil, nil, nil, nil, v, 'deck') - card:add_to_deck() - G.jokers:emplace(card) - card:start_materialize() - end - return true - end - })) - end -''' - -# Load deck when continuing the run -# Game.start_run -[[patches]] -[patches.pattern] -target = 'game.lua' -pattern = "self.GAME.selected_back = Back(selected_back)" -position = 'after' -match_indent = true -payload = ''' -if saveTable then - self.GAME.selected_back:load(saveTable.BACK) -end -''' \ No newline at end of file diff --git a/lovely/better_calc.toml b/lovely/better_calc.toml deleted file mode 100644 index 57de48a98..000000000 --- a/lovely/better_calc.toml +++ /dev/null @@ -1,2039 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -## G.FUNCS.evaluate_play() -# evaluate main scoring - -[[patches]] -[patches.regex] -target = 'functions/state_events.lua' -pattern = '''(?[\t ]*)(?if modded then update_hand_text\(\{sound = 'chips2', modded = modded\}, \{chips = hand_chips, mult = mult\}\) end)(.*\n)*?\s+(?--\++--)''' -position = 'at' -line_prepend = '$indent' -payload = '''$handtext -delay(0.3) -SMODS.calculate_context({initial_scoring_step = true, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands}) -for _, v in ipairs(SMODS.get_card_areas('playing_cards')) do - SMODS.calculate_main_scoring({cardarea = v, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands}, v == G.play and scoring_hand or nil) - delay(0.3) -end -$delimiter''' -## eval_card() -# handle debuffed playing cards -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = ''' -function eval_card(card, context) - context = context or {} - local ret = {} -''' -position = 'at' -match_indent = true -payload = ''' -function eval_card(card, context) - context = context or {} - if not card:can_calculate(context.ignore_debuff, context.remove_playing_cards or context.joker_type_destroyed) then - if card.ability.rental then - local ret = {} - ret[SMODS.Stickers.rental] = card:calculate_sticker(context, 'rental') - return ret, {} - end - return {}, {} - end - local vanilla_debuff_handling - for _, key in ipairs(SMODS.custom_debuff_handling) do if card.config.center_key == key then vanilla_debuff_handling = true; break end end - if context.other_card and context.other_card.can_calculate and not context.other_card:can_calculate(context.ignore_other_debuff or context.ignore_debuff) and not vanilla_debuff_handling then return {}, {} end - local ret = {} -''' - -# built in config values -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = ''' -if context.cardarea == G.play then - local chips = card:get_chip_bonus() - if chips > 0 then - ret.chips = chips - end - - local mult = card:get_chip_mult() - if mult > 0 then - ret.mult = mult - end - - local x_mult = card:get_chip_x_mult(context) - if x_mult > 0 then - ret.x_mult = x_mult - end - - local p_dollars = card:get_p_dollars() - if p_dollars > 0 then - ret.p_dollars = p_dollars - end - - local jokers = card:calculate_joker(context) - if jokers then - ret.jokers = jokers - end - - local edition = card:get_edition(context) - if edition then - ret.edition = edition - end -end -''' -match_indent = true -position = "at" -payload = """ -if context.cardarea == G.play and context.main_scoring then - ret.playing_card = {} - local chips = card:get_chip_bonus() - if chips ~= 0 then - ret.playing_card.chips = chips - end - - local mult = card:get_chip_mult() - if mult ~= 0 then - ret.playing_card.mult = mult - end - - local x_mult = card:get_chip_x_mult(context) - if x_mult > 0 then - ret.playing_card.x_mult = x_mult - end - - local p_dollars = card:get_p_dollars() - if p_dollars ~= 0 then - ret.playing_card.p_dollars = p_dollars - end - - local x_chips = card:get_chip_x_bonus() - if x_chips > 0 then - ret.playing_card.x_chips = x_chips - end - - -- TARGET: main scoring on played cards -end -if context.end_of_round and context.cardarea == G.hand and context.playing_card_end_of_round then - local end_of_round = card:get_end_of_round_effect(context) - if end_of_round then - ret.end_of_round = end_of_round - end -end -""" -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = ''' -if context.cardarea == G.hand then - local h_mult = card:get_chip_h_mult() - if h_mult > 0 then - ret.h_mult = h_mult - end - - local h_x_mult = card:get_chip_h_x_mult() - if h_x_mult > 0 then - ret.x_mult = h_x_mult - end - - local jokers = card:calculate_joker(context) - if jokers then - ret.jokers = jokers - end -''' -match_indent = true -position = "at" -payload = """ -if context.cardarea == G.hand and context.main_scoring then - ret.playing_card = {} - local h_mult = card:get_chip_h_mult() - if h_mult ~= 0 then - ret.playing_card.h_mult = h_mult - end - - local h_x_mult = card:get_chip_h_x_mult() - if h_x_mult > 0 then - ret.playing_card.x_mult = h_x_mult - end - - local h_chips = card:get_chip_h_bonus() - if h_chips ~= 0 then - ret.playing_card.h_chips = h_chips - end - - local h_x_chips = card:get_chip_h_x_bonus() - if h_x_chips > 0 then - ret.playing_card.x_chips = h_x_chips - end - - -- TARGET: main scoring on held cards -""" - -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = ''' -local seals = card:calculate_seal(context) -if seals then - ret.seals = seals -end -''' -match_indent = true -position = "at" -payload = """ -if card.ability.set == 'Enhanced' then - local enhancement = card:calculate_enhancement(context) - if enhancement then - ret.enhancement = enhancement - end -end -if context.extra_enhancement then return ret end -if card.edition then - local edition = card:calculate_edition(context) - if edition then - ret.edition = edition - end -end -if card.seal then - local seals = card:calculate_seal(context) - if seals then - ret.seals = seals - end -end -for _,k in ipairs(SMODS.Sticker.obj_buffer) do - local v = SMODS.Stickers[k] - local sticker = card:calculate_sticker(context, k) - if sticker then - ret[v] = sticker - end -end - --- TARGET: evaluate your own repetition effects -if card.ability.repetitions and card.ability.repetitions > 0 then - ret.seals = ret.seals or { card = card, message = localize('k_again_ex') } - ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card.ability.repetitions) or card.ability.repetitions -end -if card.ability.perma_repetitions and card.ability.perma_repetitions > 0 then - ret.seals = ret.seals or { card = card, message = localize('k_again_ex') } - ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card.ability.perma_repetitions) or card.ability.perma_repetitions -end -""" -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = "if context.cardarea == G.jokers or context.card == G.consumeables then" -match_indent = true -position = "before" -payload = """ -if card.ability.set == 'Enhanced' then - local enhancement = card:calculate_enhancement(context) - if enhancement then - ret.enhancement = enhancement - end -end -if context.extra_enhancement then return ret end -if card.edition then - local edition = card:calculate_edition(context) - if edition then - ret.edition = edition - end -end -if card.seal then - local seals = card:calculate_seal(context) - if seals then - ret.seals = seals - end -end -for _,k in ipairs(SMODS.Sticker.obj_buffer) do - local v = SMODS.Stickers[k] - local sticker = card:calculate_sticker(context, k) - if sticker then - ret[v] = sticker - end -end - --- TARGET: evaluate your own general effects -""" -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = ''' -if context.cardarea == G.jokers or context.card == G.consumeables then - local jokers = nil - if context.edition then - jokers = card:get_edition(context) - elseif context.other_joker then - jokers = context.other_joker:calculate_joker(context) - else - jokers = card:calculate_joker(context) - end - if jokers then - ret.jokers = jokers - end -end - -return ret''' -match_indent = true -position = "at" -payload = """ -local post_trig = {} -local areas = SMODS.get_card_areas('jokers') -local area_set = {} -for _,v in ipairs(areas) do area_set[v] = true end -if card.area and area_set[card.area] then - local jokers, triggered = card:calculate_joker(context) - if jokers == true then jokers = { remove = true } end - if type(jokers) ~= 'table' then jokers = nil end - if jokers or triggered then - ret.jokers = jokers - if not (context.retrigger_joker_check or context.retrigger_joker) and not (jokers and jokers.no_retrigger) and not SMODS.is_getter_context(context) then - local retriggers = SMODS.calculate_retriggers(card, context, ret) - if next(retriggers) then - ret.retriggers = retriggers - end - end - if not context.post_trigger and not context.retrigger_joker_check and SMODS.optional_features.post_trigger then - SMODS.calculate_context({blueprint_card = context.blueprint_card, post_trigger = true, other_card = card, other_context = context, other_ret = ret}, post_trig) - end - end -end - -return ret, post_trig -""" -# patch card_eval_status_text to allow G.deck usage -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = ''' -elseif card.area == G.hand then''' -match_indent = true -position = "at" -payload = """ -elseif card.area == G.hand or card.area == G.deck then -""" - -# card_eval_status_text alignment patches -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'before' -pattern = '''elseif card.area == G.hand or card.area == G.deck then''' -payload = '''elseif card == G.deck then - y_off = -0.05*G.CARD_H - card_aligned = 'tm' -elseif card.area == G.discard or card.area == G.vouchers then - y_off = card.area == G.discard and -0.35*G.CARD_H or -0.65*G.CARD_H - card = G.deck.cards[1] or G.deck - card_aligned = 'tm' -''' - -# G.FUNCS.evaluate_play() - - -# Add deck/discard individual contexts -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -position = 'before' -match_indent = true -pattern = ''' ---+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- ---Joker Effects ---+++++++++++++++++++++++++++++++++++++++++++++++++++++++++--''' -payload = ''' - -''' - -# Joker Effects -# Edition effects -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' ---calculate the joker edition effects -local edition_effects = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, edition = true}) -if edition_effects.jokers then - edition_effects.jokers.edition = true - if edition_effects.jokers.chip_mod then - hand_chips = mod_chips(hand_chips + edition_effects.jokers.chip_mod) - update_hand_text({delay = 0}, {chips = hand_chips}) - card_eval_status_text(_card, 'jokers', nil, percent, nil, { - message = localize{type='variable',key='a_chips',vars={edition_effects.jokers.chip_mod}}, - chip_mod = edition_effects.jokers.chip_mod, - colour = G.C.EDITION, - edition = true}) - end - if edition_effects.jokers.mult_mod then - mult = mod_mult(mult + edition_effects.jokers.mult_mod) - update_hand_text({delay = 0}, {mult = mult}) - card_eval_status_text(_card, 'jokers', nil, percent, nil, { - message = localize{type='variable',key='a_mult',vars={edition_effects.jokers.mult_mod}}, - mult_mod = edition_effects.jokers.mult_mod, - colour = G.C.DARK_EDITION, - edition = true}) - end - percent = percent+percent_delta -end''' -match_indent = true -position = "at" -payload = ''' -local effects = {} --- remove base game joker edition calc -local eval = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, edition = true, pre_joker = true}) -if eval.edition then effects[#effects+1] = eval end - -''' -# Edition mult effects -## extra end to fix syntax from adding joker-like areas -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' - if edition_effects.jokers then - if edition_effects.jokers.x_mult_mod then - mult = mod_mult(mult*edition_effects.jokers.x_mult_mod) - update_hand_text({delay = 0}, {mult = mult}) - card_eval_status_text(_card, 'jokers', nil, percent, nil, { - message = localize{type='variable',key='a_xmult',vars={edition_effects.jokers.x_mult_mod}}, - x_mult_mod = edition_effects.jokers.x_mult_mod, - colour = G.C.EDITION, - edition = true}) - end - percent = percent+percent_delta - end -end''' -match_indent = false -position = "at" -payload = ''' - -- calculate edition multipliers - local eval = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, edition = true, post_joker = true}) - if eval.edition then effects[#effects+1] = eval end - - SMODS.trigger_effects(effects, _card) - end end -''' -# Joker effects - -# allows adding other areas (syntax is fixed further down) -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'at' -pattern = '''for i=1, #G.jokers.cards + #G.consumeables.cards do - local _card = G.jokers.cards[i] or G.consumeables.cards[i - #G.jokers.cards]''' -payload = '''for _, area in ipairs(SMODS.get_card_areas('jokers')) do for _, _card in ipairs(area.cards) do''' - -## I am NOT converting this to regex (yet) -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' - --calculate the joker effects - local effects = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, joker_main = true}) - - --Any Joker effects - if effects.jokers then - local extras = {mult = false, hand_chips = false} - if effects.jokers.mult_mod then mult = mod_mult(mult + effects.jokers.mult_mod);extras.mult = true end - if effects.jokers.chip_mod then hand_chips = mod_chips(hand_chips + effects.jokers.chip_mod);extras.hand_chips = true end - if effects.jokers.Xmult_mod then mult = mod_mult(mult*effects.jokers.Xmult_mod);extras.mult = true end - update_hand_text({delay = 0}, {chips = extras.hand_chips and hand_chips, mult = extras.mult and mult}) - card_eval_status_text(_card, 'jokers', nil, percent, nil, effects.jokers) - percent = percent+percent_delta - end''' -match_indent = true -position = "at" -payload = ''' --- Calculate context.joker_main -local joker_eval, post = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, joker_main = true}) -if next(joker_eval) then - if joker_eval.edition then joker_eval.edition = {} end - table.insert(effects, joker_eval) - for _, v in ipairs(post) do effects[#effects+1] = v end - if joker_eval.retriggers then - for rt = 1, #joker_eval.retriggers do - local rt_eval, rt_post = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, joker_main = true, retrigger_joker = true}) - if next(rt_eval) then - table.insert(effects, {retriggers = joker_eval.retriggers[rt]}) - table.insert(effects, rt_eval) - for _, v in ipairs(rt_post) do effects[#effects+1] = v end - end - end - end -end''' -# Joker on Joker effects -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' ---Joker on Joker effects -for _, v in ipairs(G.jokers.cards) do - local effect = v:calculate_joker{full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, other_joker = _card} - if effect then - local extras = {mult = false, hand_chips = false} - if effect.mult_mod then mult = mod_mult(mult + effect.mult_mod);extras.mult = true end - if effect.chip_mod then hand_chips = mod_chips(hand_chips + effect.chip_mod);extras.hand_chips = true end - if effect.Xmult_mod then mult = mod_mult(mult*effect.Xmult_mod);extras.mult = true end - if extras.mult or extras.hand_chips then update_hand_text({delay = 0}, {chips = extras.hand_chips and hand_chips, mult = extras.mult and mult}) end - if extras.mult or extras.hand_chips then card_eval_status_text(v, 'jokers', nil, percent, nil, effect) end - percent = percent+percent_delta - end -end''' -match_indent = true -position = "at" -payload = ''' --- Calculate context.other_joker effects -for _, _area in ipairs(SMODS.get_card_areas('jokers')) do - for _, _joker in ipairs(_area.cards) do - local other_key = 'other_unknown' - if _card.ability.set == 'Joker' then other_key = 'other_joker' end - if _card.ability.consumeable then other_key = 'other_consumeable' end - if _card.ability.set == 'Voucher' then other_key = 'other_voucher' end - -- TARGET: add context.other_something identifier to your cards - local joker_eval,post = eval_card(_joker, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, other_main = _card }) - if next(joker_eval) then - if joker_eval.edition then joker_eval.edition = {} end - joker_eval.jokers.juice_card = _joker - table.insert(effects, joker_eval) - for _, v in ipairs(post) do effects[#effects+1] = v end - if joker_eval.retriggers then - for rt = 1, #joker_eval.retriggers do - local rt_eval, rt_post = eval_card(_joker, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, retrigger_joker = true}) - if next(rt_eval) then - table.insert(effects, {retriggers = joker_eval.retriggers[rt]}) - table.insert(effects, rt_eval) - for _, v in ipairs(rt_post) do effects[#effects+1] = v end - end - end - end - end - end -end -for _, _area in ipairs(SMODS.get_card_areas('individual')) do - local other_key = 'other_unknown' - if _card.ability.set == 'Joker' then other_key = 'other_joker' end - if _card.ability.consumeable then other_key = 'other_consumeable' end - if _card.ability.set == 'Voucher' then other_key = 'other_voucher' end - -- TARGET: add context.other_something identifier to your cards - local _eval,post = SMODS.eval_individual(_area, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, other_main = _card }) - if next(_eval) then - _eval.individual.juice_card = _area.scored_card - table.insert(effects, _eval) - for _, v in ipairs(post) do effects[#effects+1] = v end - if _eval.retriggers then - for rt = 1, #_eval.retriggers do - local rt_eval, rt_post = SMODS.eval_individual(_area, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, retrigger_joker = true}) - if next(rt_eval) then - table.insert(effects, {_eval.retriggers[rt]}) - table.insert(effects, rt_eval) - for _, v in ipairs(rt_post) do effects[#effects+1] = v end - end - end - end - end -end -''' - -## Fix other evaluations -# Discarding cards -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' -for j = 1, #G.jokers.cards do - local eval = nil - eval = G.jokers.cards[j]:calculate_joker({discard = true, other_card = G.hand.highlighted[i], full_hand = G.hand.highlighted}) - if eval then - if eval.remove then removed = true end - card_eval_status_text(G.jokers.cards[j], 'jokers', nil, 1, nil, eval) - end -end''' -match_indent = true -position = "at" -payload = ''' -local effects = {} -SMODS.calculate_context({discard = true, other_card = G.hand.highlighted[i], full_hand = G.hand.highlighted, ignore_other_debuff = true}, effects) -SMODS.trigger_effects(effects) -for _, eval in pairs(effects) do - if type(eval) == 'table' then - for key, eval2 in pairs(eval) do - if key == 'remove' or (type(eval2) == 'table' and eval2.remove) then removed = true end - end - end -end''' - -# context.before -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' -for i=1, #G.jokers.cards do - --calculate the joker effects - local effects = eval_card(G.jokers.cards[i], {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, before = true}) - if effects.jokers then - card_eval_status_text(G.jokers.cards[i], 'jokers', nil, percent, nil, effects.jokers) - percent = percent + percent_delta - if effects.jokers.level_up then - level_up_hand(G.jokers.cards[i], text) - end - end -end''' -match_indent = true -position = "at" -payload = ''' --- context.before calculations -if SMODS.last_hand then - for _, v in ipairs({'scoring_hand', 'full_hand'}) do - for _, _c in ipairs(SMODS.last_hand[v]) do - _c.ability['SMODS_'..v] = nil - end - end -end -SMODS.last_hand = {scoring_hand = scoring_hand, scoring_name = text, full_hand = G.play.cards} -SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, before = true}) - --- TARGET: effects before scoring starts - -SMODS.displayed_hand = nil''' - -# context.final_scoring_step -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = '''local nu_chip, nu_mult = G.GAME.selected_back:trigger_effect{context = 'final_scoring_step', chips = hand_chips, mult = mult}''' -match_indent = true -position = "before" -payload = ''' --- context.final_scoring_step calculations -SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, final_scoring_step = true}) - --- TARGET: effects before deck final_scoring_step -''' - -# context.destroying_card -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' -for j = 1, #G.jokers.cards do - destroyed = G.jokers.cards[j]:calculate_joker({destroying_card = scoring_hand[i], full_hand = G.play.cards}) - if destroyed then break end -end - -if scoring_hand[i].ability.name == 'Glass Card' and not scoring_hand[i].debuff and pseudorandom('glass') < G.GAME.probabilities.normal/scoring_hand[i].ability.extra then - destroyed = true -end''' -match_indent = true -position = "at" -payload = ''' --- context.destroying_card calculations -for j = 1, #G.jokers.cards do - local eval, post = eval_card(G.jokers.cards[j], {destroying_card = scoring_hand[i], full_hand = G.play.cards}) - SMODS.trigger_effects({eval, post}, scoring_hand[i]) - if eval.jokers then destroyed = true end - -end - -if SMODS.has_enhancement(scoring_hand[i], 'm_glass') and scoring_hand[i]:can_calculate() and SMODS.pseudorandom_probability(scoring_hand[i], 'glass', 1, scoring_hand[i].ability.name == 'Glass Card' and scoring_hand[i].ability.extra or G.P_CENTERS.m_glass.config.extra) then - destroyed = true -end - -local eval, post = eval_card(scoring_hand[i], {destroying_card = scoring_hand[i], full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, cardarea = G.play}) -local self_destroy = false -for key, effect in pairs(eval) do - self_destroy = SMODS.calculate_effect(effect, scoring_hand[i]) -end -SMODS.trigger_effects({post}, scoring_hand[i]) -if self_destroy then destroyed = true end - --- TARGET: card destroyed when played -''' - -# context.remove_playing_cards -[[patches]] -[patches.regex] -target = "functions/state_events.lua" -pattern = '''(?[\t ]*)local cards_destroyed = \{\}\n(.*\n)*?\s+for j=1, #G\.jokers\.cards do\n\s+eval_card\(G\.jokers\.cards\[j\], \{cardarea = G\.jokers, remove_playing_cards = true, removed = cards_destroyed\}\)\n\s+end''' -line_prepend = '$indent' -position = "at" -payload = ''' -local cards_destroyed = {} -for _,v in ipairs(SMODS.get_card_areas('playing_cards', 'destroying_cards')) do - SMODS.calculate_destroying_cards({ full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, cardarea = v }, cards_destroyed, v == G.play and scoring_hand or nil) -end - --- context.remove_playing_cards calculations -if cards_destroyed[1] then - SMODS.calculate_context({scoring_hand = scoring_hand, remove_playing_cards = true, removed = cards_destroyed}) -end - --- TARGET: effects when cards are removed - -''' - -[[patches]] -[patches.regex] -target = 'card.lua' -line_prepend = '$indent' -position = 'at' -pattern = '(?[\t ]*)for i = 1, #G.jokers.cards do[\n\s]*G.jokers.cards\[i\]:calculate_joker\(\{remove_playing_cards = true, removed = destroyed_cards\}\)[\s\n]*end' -payload = '''SMODS.calculate_context({ remove_playing_cards = true, removed = destroyed_cards })''' - -# context.remove_playing_cards from discard -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' -if destroyed_cards[1] then - for j=1, #G.jokers.cards do - eval_card(G.jokers.cards[j], {cardarea = G.jokers, remove_playing_cards = true, removed = destroyed_cards}) - end -end -''' -position = "at" -match_indent = true -payload = ''' --- context.remove_playing_cards from discard -if destroyed_cards[1] then - SMODS.calculate_context({remove_playing_cards = true, removed = destroyed_cards}) -end - --- TARGET: effects after cards destroyed in discard''' - - -# context.debuffed_hand -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' -for i=1, #G.jokers.cards do - - --calculate the joker effects - local effects = eval_card(G.jokers.cards[i], {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, debuffed_hand = true}) - - --Any Joker effects - if effects.jokers then - card_eval_status_text(G.jokers.cards[i], 'jokers', nil, percent, nil, effects.jokers) - percent = percent+percent_delta - end -end''' -match_indent = true -position = "at" -payload = ''' --- context.debuffed_hand calculations -SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, debuffed_hand = true}) - --- TARGET: effects after hand debuffed by blind''' - -# context.after -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = ''' -for i=1, #G.jokers.cards do - --calculate the joker after hand played effects - local effects = eval_card(G.jokers.cards[i], {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, after = true}) - if effects.jokers then - card_eval_status_text(G.jokers.cards[i], 'jokers', nil, percent, nil, effects.jokers) - percent = percent + percent_delta - end -end''' -match_indent = true -position = "at" -payload = ''' --- context.after calculations -SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, after = true}) - --- TARGET: effects after hand evaluation''' - -# calc_dollar_bonus call through consumeables -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -pattern = ''' -for i = 1, #G.jokers.cards do - local ret = G.jokers.cards[i]:calculate_dollar_bonus() - if ret then - add_round_eval_row({dollars = ret, bonus = true, name='joker'..i, pitch = pitch, card = G.jokers.cards[i]}) - pitch = pitch + 0.06 - dollars = dollars + ret - end -end -''' -position = 'at' -match_indent = true -payload = ''' -local i = 0 -for _, area in ipairs(SMODS.get_card_areas('jokers')) do - for _, _card in ipairs(area.cards) do - local ret = _card:calculate_dollar_bonus() - - -- TARGET: calc_dollar_bonus per card - if ret then - i = i+1 - add_round_eval_row({dollars = ret, bonus = true, name='joker'..i, pitch = pitch, card = _card}) - pitch = pitch + 0.06 - dollars = dollars + ret - end - end -end -''' - -# context.round_eval -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -pattern = ''' -G.GAME.selected_back:trigger_effect({context = 'eval'}) -''' -position = 'before' -match_indent = true -payload = ''' -SMODS.calculate_context{round_eval = true} -''' - -# Add better `add_round_eval_row` support -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'before' -pattern = ''' -elseif string.find(config.name, 'joker') then -''' -payload = ''' -elseif config.name == 'custom' then - if config.number then table.insert(left_text, {n=G.UIT.T, config={text = config.number, scale = 0.8*scale, colour = config.number_colour or G.C.FILTER, shadow = true, juice = true}}) end - table.insert(left_text, {n=G.UIT.O, config={object = DynaText({string = {" "..config.text}, colours = {config.text_colour or G.C.UI.TEXT_LIGHT}, shadow = true, pop_in = 0, scale = 0.4*scale, silent = true})}}) -''' - - -# context.end_of_round -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -pattern = ''' -for i = 1, #G.jokers.cards do - local eval = nil - eval = G.jokers.cards[i]:calculate_joker({end_of_round = true, game_over = game_over}) - if eval then - if eval.saved then - game_over = false - end - card_eval_status_text(G.jokers.cards[i], 'jokers', nil, nil, nil, eval) - end - G.jokers.cards[i]:calculate_rental() - G.jokers.cards[i]:calculate_perishable() -end -''' -position = 'at' -match_indent = true -payload = ''' --- context.end_of_round calculations -SMODS.saved = false -G.GAME.saved_text = nil -SMODS.last_hand = SMODS.last_hand or {scoring_hand = {}, full_hand = {}} -SMODS.calculate_context({end_of_round = true, game_over = game_over, beat_boss = G.GAME.blind.boss, scoring_hand = SMODS.last_hand.scoring_hand, scoring_name = SMODS.last_hand.scoring_name, full_hand = SMODS.last_hand.full_hand }) -if SMODS.saved then game_over = false end --- TARGET: main end_of_round evaluation -''' - -# context.end_of_round individual effects -[[patches]] -[patches.regex] -target = 'functions/state_events.lua' -position = 'at' -pattern = '''(?[\t ]*)for i=1, #G\.hand\.cards do\n\s+--Check for hand doubling\n(.*\n)*?\s+delay\(0\.3\)''' -line_prepend = '$indent' -payload = '''for _,v in ipairs(SMODS.get_card_areas('playing_cards', 'end_of_round')) do - SMODS.calculate_end_of_round_effects({ cardarea = v, end_of_round = true, beat_boss = G.GAME.blind.boss }) -end -''' - -# store associated blind tag as a global object -[[patches]] -[patches.pattern] -target = "functions/button_callbacks.lua" -pattern = "G.GAME.round_resets.blind = e.config.ref_table" -position = "before" -payload = ''' -local _tag = e.UIBox:get_UIE_by_ID('tag_container') -G.GAME.round_resets.blind_tag = _tag and _tag.config and _tag.config.ref_table or nil -''' -match_indent = true - -# context.setting_blind -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -pattern = ''' -for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({setting_blind = true, blind = G.GAME.round_resets.blind}) -end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({setting_blind = true, blind = G.GAME.round_resets.blind}) - --- TARGET: setting_blind effects -''' - -# context.pre_discard -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -pattern = ''' -for j = 1, #G.jokers.cards do - G.jokers.cards[j]:calculate_joker({pre_discard = true, full_hand = G.hand.highlighted, hook = hook}) -end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({pre_discard = true, full_hand = G.hand.highlighted, hook = hook}) - --- TARGET: pre_discard -''' - -# context.selling_self in cards -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = ''' -self:calculate_joker{selling_self = true} -''' -position = 'at' -match_indent = true -payload = ''' -local eval, post = eval_card(self, {selling_self = true}) -local effects = {eval} -for _,v in ipairs(post) do effects[#effects+1] = v end -if eval.retriggers then - for rt = 1, #eval.retriggers do - local rt_eval, rt_post = eval_card(self, { selling_self = true, retrigger_joker = true}) - if next(rt_eval) then - table.insert(effects, {eval.retriggers[rt]}) - table.insert(effects, rt_eval) - for _, v in ipairs(rt_post) do effects[#effects+1] = v end - end - end -end -SMODS.trigger_effects(effects, self) -''' - -# context.open_booster -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({open_booster = true, card = self}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({open_booster = true, card = self, booster = booster_obj}) -''' - -# context.buying_card -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({buying_card = true, card = self}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({buying_card = true, card = self}) -''' - -# context.first_hand_drawn -[[patches]] -[patches.pattern] -target = 'game.lua' -pattern = ''' -if G.GAME.current_round.hands_played == 0 and - G.GAME.current_round.discards_used == 0 and G.GAME.facing_blind then - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({first_hand_drawn = true}) - end -end -''' -position = 'at' -match_indent = true -payload = ''' --- removed first hand drawn context -''' -# Hand Drawn Contexts -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'at' -pattern = ''' -self:emplace(card, nil, stay_flipped) -return true -''' -payload = ''' -self:emplace(card, nil, stay_flipped) -return card -''' -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -if to:draw_card_from(from, stay_flipped, discarded_only) then drawn = true end -''' -payload = ''' -card = to:draw_card_from(from, stay_flipped, discarded_only) -if card then drawn = true end -''' -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'after' -pattern = ''' -if sort then - to:sort() -end -''' -payload = ''' -SMODS.drawn_cards = SMODS.drawn_cards or {} -if card and card.playing_card then SMODS.drawn_cards[#SMODS.drawn_cards+1] = card end -''' -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'before' -pattern = ''' -for i=1, hand_space do --draw cards from deckL - if G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK then - draw_card(G.deck,G.hand, i*100/hand_space,'up', true) - else - draw_card(G.deck,G.hand, i*100/hand_space,'up', true) - end -end -''' -payload = ''' -SMODS.cards_to_draw = (SMODS.cards_to_draw or 0) + hand_space -SMODS.drawn_cards = {} -''' -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'after' -pattern = ''' -for i=1, hand_space do --draw cards from deckL - if G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK then - draw_card(G.deck,G.hand, i*100/hand_space,'up', true) - else - draw_card(G.deck,G.hand, i*100/hand_space,'up', true) - end -end -''' -payload = ''' -G.E_MANAGER:add_event(Event({ - trigger = 'immediate', - func = function() - SMODS.cards_to_draw = SMODS.cards_to_draw - hand_space - return true - end -})) -G.E_MANAGER:add_event(Event({ - trigger = 'before', - delay = 0.4, - func = function() - if #SMODS.drawn_cards > 0 then - SMODS.calculate_context({first_hand_drawn = not G.GAME.current_round.any_hand_drawn and G.GAME.facing_blind, - hand_drawn = G.GAME.facing_blind and SMODS.drawn_cards, - other_drawn = not G.GAME.facing_blind and SMODS.drawn_cards}) - SMODS.drawn_cards = {} - if G.GAME.facing_blind then G.GAME.current_round.any_hand_drawn = true end - end - return true - end -})) -''' -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'at' -pattern = ''' -draw_card(G.deck,G.hand, i*100/hand_space,'up', true) -''' -payload = ''' -draw_card(G.deck,G.hand, i*100/hand_space,'up', true, cards_to_draw[i]) -''' - - -# Used to identify first hand of round -# new_round -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'after' -pattern = 'G.GAME.current_round.discards_used = 0' -payload = ''' -G.GAME.current_round.any_hand_drawn = nil -''' - - -# context.using_consumeable -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({using_consumeable = true, consumeable = card}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({using_consumeable = true, consumeable = card, area = card.from_area}) -''' -# save area of used card -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'before' -pattern = ''' -c1.area:remove_card(c1) -''' -payload = ''' -c1.from_area = c1.area -''' - -# context.selling_card -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - if G.jokers.cards[i] ~= card then - G.jokers.cards[i]:calculate_joker({selling_card = true, card = card}) - end - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({selling_card = true, card = card}) -''' - - -# context.buying_card -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' -G.E_MANAGER:add_event(Event({func = function() c1:calculate_joker({buying_card = true, card = c1}) return true end})) -''' -position = 'at' -match_indent = true -payload = ''' -G.E_MANAGER:add_event(Event({func = function() - local eval, post = eval_card(c1, {buying_card = true, buying_self = true, card = c1}) -- buying_card left for back compat, buying_self recommended to use - SMODS.trigger_effects({eval, post}, c1) - return true - end})) -''' - -# context.buying_card -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({buying_card = true, card = c1}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({buying_card = true, card = c1}) -''' -# context.starting_shop -[[patches]] -[patches.pattern] -target = 'game.lua' -pattern = ''' -G.CONTROLLER:snap_to({node = G.shop:get_UIE_by_ID('next_round_button')}) -''' -position = 'before' -match_indent = true -payload = ''' -if not nosave_shop then SMODS.calculate_context({starting_shop = true}) end -''' -# context.ending_shop -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({ending_shop = true}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({ending_shop = true}) -''' - -# context.skipping_booster -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({skipping_booster = true}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({skipping_booster = true, booster = booster_obj}) -''' -# context.skip_blind -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({skip_blind = true}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({skip_blind = true}) -''' - -# context.reroll_shop -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = ''' - for i = 1, #G.jokers.cards do - G.jokers.cards[i]:calculate_joker({reroll_shop = true}) - end -''' -position = 'at' -match_indent = true -payload = ''' -SMODS.calculate_context({reroll_shop = true, cost = reroll_cost}) -''' -# reroll_cost for context -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'before' -pattern = ''' -if G.GAME.current_round.reroll_cost > 0 then -''' -payload = ''' -local reroll_cost = G.GAME.current_round.reroll_cost -''' - - -# Fix purple seal calc -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = ''' -if context.discard then - if self.seal == 'Purple' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then''' -position = 'at' -match_indent = true -payload = ''' -if context.discard and context.other_card == self then - if self.seal == 'Purple' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then''' - - -# Auto deal with negative chips card_eval_status_text() -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -text = localize{type='variable',key='a_chips',vars={amt}} -''' -payload = ''' -text = localize{type='variable',key='a_chips'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}} -''' - -# Auto deal with negative mult card_eval_status_text() -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -text = localize{type='variable',key='a_mult',vars={amt}} -''' -payload = ''' -text = localize{type='variable',key='a_mult'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}} -''' - -# Auto deal with negative xmult card_eval_status_text() -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -text = localize{type='variable',key='a_xmult',vars={amt}} -''' -payload = ''' -text = localize{type='variable',key='a_xmult'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}} -''' -# Make percent and percent_delta globals -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'at' -pattern = '''local percent = 0.3 -local percent_delta = 0.08 -''' -payload = '''percent = 0.3 -percent_delta = 0.08 -''' - -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'at' -pattern = '''local percent = (i-0.999)/(#G.hand.cards-0.998) + (j-1)*0.1''' -payload = '''percent = (i-0.999)/(#G.hand.cards-0.998) + (j-1)*0.1''' - -# Add support for pitch and volume returns in effects -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'after' -pattern = 'local volume = 1' -payload = ''' -local trigger = 'before' -local blocking = nil -local blockable = nil -''' -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'after' -pattern = ''' -volume = extra.edition and 0.3 or sound == 'multhit2' and 0.7 or 1 -''' -payload = ''' -sound = extra.sound or sound -percent = extra.pitch or percent -volume = extra.volume or volume -trigger = extra.trigger or 'before' -blocking = extra.blocking -blockable = extra.blockable -''' - -# Voucher cardarea -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'before' -pattern = 'self.deck = CardArea(' -payload = '''self.vouchers = CardArea( - G.discard.T.x, G.discard.T.y, - G.discard.T.w, G.discard.T.h, - { type = "discard", card_limit = 1e308 } -) -''' - -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'after' -pattern = 'function Card:apply_to_run(center)' -payload = ''' - local card_to_save = self and copy_card(self) or Card(0, 0, G.CARD_W, G.CARD_H, G.P_CARDS.empty, center) - card_to_save.VT.x, card_to_save.VT.y = G.vouchers.T.x, G.vouchers.T.y - G.vouchers:emplace(card_to_save) - SMODS.enh_cache:clear() -''' - -[[patches]] -[patches.pattern] -target = 'card.lua' -position = 'at' -match_indent = false -pattern = '''if self.ability.set == "Planet" and not self.debuff then - if context.joker_main then - if G.GAME.used_vouchers.v_observatory and self.ability.consumeable.hand_type == context.scoring_name then - return { - message = localize{type = 'variable', key = 'a_xmult', vars = {G.P_CENTERS.v_observatory.config.extra}}, - Xmult_mod = G.P_CENTERS.v_observatory.config.extra - } - end - end -end''' -payload = '' - -[[patches]] -[patches.regex] -target = 'back.lua' -position = 'at' -line_prepend = '$indent' -pattern = '(?[\t ]*)(?Card.apply_to_run\(nil, G\.P_CENTERS\[.*?\]\))' -payload = '''G.E_MANAGER:add_event(Event({ - func = function() - $line - return true - end -}))''' - -#wtf -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = '''self.discard_pos = { - r = 3.6*(math.random()-0.5), - x = math.random(), - y = math.random() -}''' -payload = '''self.discard_pos = { - r = 0, - x = 0, - y = 0, -} -''' - -# xchip support -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'before' -pattern = ''' -elseif (eval_type == 'x_mult') or (eval_type == 'h_x_mult') then -''' -payload = ''' -elseif eval_type == 'x_chips' then - sound = 'xchips' - volume = 0.7 - amt = amt - text = localize{type='variable',key='a_xchips'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}} - colour = G.C.BLUE - config.type = 'fade' - config.scale = 0.7 -''' -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -if extra.chip_mod then -''' -payload = ''' -if extra.chip_mod or extra.Xchip_mod then -''' - -# Modify scoring hand context -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'at' -pattern = ''' ---Add all the pure bonus cards to the scoring hand -local pures = {} -for i=1, #G.play.cards do - if next(find_joker('Splash')) then - scoring_hand[i] = G.play.cards[i] - else - if G.play.cards[i].ability.effect == 'Stone Card' then - local inside = false - for j=1, #scoring_hand do - if scoring_hand[j] == G.play.cards[i] then - inside = true - end - end - if not inside then table.insert(pures, G.play.cards[i]) end - end - end -end -for i=1, #pures do - table.insert(scoring_hand, pures[i]) -end -table.sort(scoring_hand, function (a, b) return a.T.x < b.T.x end ) -''' -payload = ''' -local final_scoring_hand = {} -for i=1, #G.play.cards do - local splashed = SMODS.always_scores(G.play.cards[i]) or next(find_joker('Splash')) - local unsplashed = SMODS.never_scores(G.play.cards[i]) - if not splashed then - for _, card in pairs(scoring_hand) do - if card == G.play.cards[i] then splashed = true end - end - end - local effects = {} - SMODS.calculate_context({modify_scoring_hand = true, other_card = G.play.cards[i], full_hand = G.play.cards, scoring_hand = scoring_hand, in_scoring = true, ignore_other_debuff = true}, effects) - local flags = SMODS.trigger_effects(effects, G.play.cards[i]) - if flags.add_to_hand then splashed = true end - if flags.remove_from_hand then unsplashed = true end - if splashed and not unsplashed then table.insert(final_scoring_hand, G.play.cards[i]) end -end --- TARGET: adding to hand effects -scoring_hand = final_scoring_hand -''' - -# Add ending_booster calculation context -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'before' -pattern = ''' -for i = 1, #G.GAME.tags do - if G.GAME.tags[i]:apply_to_run({type = 'new_blind_choice'}) then break end -end - -G.E_MANAGER:add_event(Event({trigger = 'after',delay = 0.2*delayfac, - func = function() - save_run() - return true - end})) -''' -payload = ''' -SMODS.calculate_context({ending_booster = true, booster = booster_obj}) -''' - -# Fix Certificate to use SMODS.poll_seal and use playing_card_added context correctly -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -G.E_MANAGER:add_event(Event({ - func = function() - local _card = create_playing_card({ - front = pseudorandom_element(G.P_CARDS, pseudoseed('cert_fr')), - center = G.P_CENTERS.c_base}, G.hand, nil, nil, {G.C.SECONDARY_SET.Enhanced}) - local seal_type = pseudorandom(pseudoseed('certsl')) - if seal_type > 0.75 then _card:set_seal('Red', true) - elseif seal_type > 0.5 then _card:set_seal('Blue', true) - elseif seal_type > 0.25 then _card:set_seal('Gold', true) - else _card:set_seal('Purple', true) - end - G.GAME.blind:debuff_card(_card) - G.hand:sort() - if context.blueprint_card then context.blueprint_card:juice_up() else self:juice_up() end - return true - end})) - -playing_card_joker_effects({true}) -''' -payload = ''' -G.E_MANAGER:add_event(Event({ - func = function() - local _card = create_playing_card({ - front = pseudorandom_element(G.P_CARDS, pseudoseed('cert_fr')), - center = G.P_CENTERS.c_base}, G.hand, nil, nil, {G.C.SECONDARY_SET.Enhanced}) - _card:set_seal(SMODS.poll_seal({type_key = 'certsl', guaranteed = true}), nil, true) - G.GAME.blind:debuff_card(_card) - G.hand:sort() - if context.blueprint_card then context.blueprint_card:juice_up() else self:juice_up() end - playing_card_joker_effects({_card}) - save_run() - return true - end})) - -''' -# create_playing_card -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -function create_playing_card(card_init, area, skip_materialize, silent, colours) -''' -payload = ''' -function create_playing_card(card_init, area, skip_materialize, silent, colours, skip_emplace) -''' -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -if area then area:emplace(card) end -''' -payload = ''' -if area and not skip_emplace then area:emplace(card) end -''' - -## Fix Marble Joker to send the created card to playing_card_added correctly -# Marble Joker -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -G.E_MANAGER:add_event(Event({ - func = function() - local front = pseudorandom_element(G.P_CARDS, pseudoseed('marb_fr')) - G.playing_card = (G.playing_card and G.playing_card + 1) or 1 - local card = Card(G.play.T.x + G.play.T.w/2, G.play.T.y, G.CARD_W, G.CARD_H, front, G.P_CENTERS.m_stone, {playing_card = G.playing_card}) - card:start_materialize({G.C.SECONDARY_SET.Enhanced}) - G.play:emplace(card) - table.insert(G.playing_cards, card) - return true - end})) -card_eval_status_text(context.blueprint_card or self, 'extra', nil, nil, nil, {message = localize('k_plus_stone'), colour = G.C.SECONDARY_SET.Enhanced}) - -G.E_MANAGER:add_event(Event({ - func = function() - G.deck.config.card_limit = G.deck.config.card_limit + 1 - return true - end})) - draw_card(G.play,G.deck, 90,'up', nil) - -playing_card_joker_effects({true}) -''' -payload = ''' -G.E_MANAGER:add_event(Event({ - func = function() - local card = create_playing_card({ - front = pseudorandom_element(G.P_CARDS, pseudoseed('marb_fr')), - center = G.P_CENTERS.m_stone}, G.play, nil, nil, {G.C.SECONDARY_SET.Enhanced}) - SMODS.calculate_effect({message = localize('k_plus_stone'), colour = G.C.SECONDARY_SET.Enhanced}, context.blueprint_card or self) - G.E_MANAGER:add_event(Event({ - func = function() - draw_card(G.play,G.deck, 90,'up', nil) - return true - end})) - playing_card_joker_effects({card}) - return true - end})) -''' -# DNA -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = 'playing_cards_created = {true}' -payload = 'playing_cards_created = {_card}' - -## Remove unneeded area check -# Card:calculate_joker -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = 'if context.cardarea == G.jokers then' -payload = 'do' - -[[patches]] -[patches.pattern] -target = 'game.lua' -position = 'at' -pattern = '{n=G.UIT.O, config={object = DynaText({scale = 0.6, string = G.GAME.blind:get_loc_debuff_text(), maxw = 9, colours = {G.C.WHITE},float = true, shadow = true, silent = true, pop_in = 0, pop_in_rate = 6})}},' -payload = '{n=G.UIT.O, config={func = "update_blind_debuff_text", object = DynaText({scale = 0.6, string = SMODS.debuff_text or G.GAME.blind:get_loc_debuff_text(), maxw = 9, colours = {G.C.WHITE},float = true, shadow = true, silent = true, pop_in = 0, pop_in_rate = 6})}},' -match_indent = true - -# Custom saved message -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'at' -pattern = ''' -{n=G.UIT.O, config={object = DynaText({string = {' '..localize('ph_mr_bones')..' '}, colours = {G.C.FILTER}, shadow = true, pop_in = 0, scale = 0.5*scale, silent = true})}} -''' -payload = ''' -{n=G.UIT.O, config={object = DynaText({string = {' '..(type(G.GAME.saved_text) == 'string' and (G.localization.misc.dictionary[G.GAME.saved_text] and localize(G.GAME.saved_text) or G.GAME.saved_text) or localize('ph_mr_bones'))..' '}, colours = {G.C.FILTER}, shadow = true, pop_in = 0, scale = 0.5*scale, silent = true})}} -''' - -# Customise dissolve colours from calculate -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'before' -pattern = ''' -local dissolve_time = 0.7*(dissolve_time_fac or 1) -''' -payload = ''' -dissolve_colours = dissolve_colours or (type(self.destroyed) == 'table' and self.destroyed.colours) or nil -dissolve_time_fac = dissolve_time_fac or (type(self.destroyed) == 'table' and self.destroyed.time) or nil -''' - -# Smart level up functionality -# G.FUNCS.evaluate_play() -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = false -position = 'after' -pattern = '''delay = G.GAME.current_round.current_hand.handname ~= disp_text and 0.4 or 0}, {handname=disp_text, level=G.GAME.hands[text].level, mult = G.GAME.hands[text].mult, chips = G.GAME.hands[text].chips})''' -payload = ''' -for name, parameter in pairs(SMODS.Scoring_Parameters) do - if name ~= 'chips' and name ~= 'mult' then update_hand_text({immediate = true}, {[name] = parameter.current}) end -end -SMODS.displayed_hand = text -SMODS.displaying_scoring = true''' -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'after' -pattern = '''hand_chips = mod_chips(0)''' -payload = '''SMODS.displayed_hand = nil''' -times = 1 -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = false -position = 'after' -pattern = ''' - func = (function() G.GAME.current_round.current_hand.handname = '';return true end) -})) -delay(0.3)''' -payload = ''' SMODS.displaying_scoring = nil''' - -# Adjust food jokers -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -G.E_MANAGER:add_event(Event({ - func = function() - play_sound('tarot1') - self.T.r = -0.2 - self:juice_up(0.3, 0.4) - self.states.drag.is = true - self.children.center.pinch.x = true - G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.3, blockable = false, - func = function() - G.jokers:remove_card(self) - self:remove() - self = nil - return true; end})) - return true - end -})) -''' -payload = ''' -SMODS.destroy_cards(self, nil, nil, true) -''' - -# Joker-type removed context added -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'after' -pattern = ''' -function Card:start_dissolve(dissolve_colours, silent, dissolve_time_fac, no_juice) -''' -payload = ''' - if self.getting_sliced and not (self.ability.set == 'Default' or self.ability.set == 'Enhanced') then - local flags = SMODS.calculate_context({joker_type_destroyed = true, card = self}) - if flags.no_destroy then self.getting_sliced = nil; return end - end - if self.skip_destroy_animation then - G.E_MANAGER:add_event(Event({ - func = function() - play_sound('tarot1') - self.T.r = -0.2 - self:juice_up(0.3, 0.4) - self.states.drag.is = true - self.children.center.pinch.x = true - G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.3, blockable = false, - func = function() - G.jokers:remove_card(self) - self:remove() - self = nil - return true; end})) - return true - end - })) - return - end -''' -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'after' -pattern = ''' -function Card:shatter() -''' -payload = ''' - if self.getting_sliced and not (self.ability.set == 'Default' or self.ability.set == 'Enhanced') then - local flags = SMODS.calculate_context({joker_type_destroyed = true, card = self, shatters = true}) - if flags.no_destroy then self.getting_sliced = nil; return end - end - if self.skip_destroy_animation then - G.E_MANAGER:add_event(Event({ - func = function() - play_sound('tarot1') - self.T.r = -0.2 - self:juice_up(0.3, 0.4) - self.states.drag.is = true - self.children.center.pinch.x = true - G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.3, blockable = false, - func = function() - G.jokers:remove_card(self) - self:remove() - self = nil - return true; end})) - return true - end - })) - return - end -''' - - -## Change eternal checks -# Ceremonial Dagger -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if my_pos and G.jokers.cards[my_pos+1] and not self.getting_sliced and not G.jokers.cards[my_pos+1].ability.eternal and not G.jokers.cards[my_pos+1].getting_sliced then -''' -payload = ''' -if my_pos and G.jokers.cards[my_pos+1] and not self.getting_sliced and not SMODS.is_eternal(G.jokers.cards[my_pos+1], self) and not G.jokers.cards[my_pos+1].getting_sliced then -''' -# Madness -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if G.jokers.cards[i] ~= self and not G.jokers.cards[i].ability.eternal and not G.jokers.cards[i].getting_sliced then destructable_jokers[#destructable_jokers+1] = G.jokers.cards[i] end -''' -payload = ''' -if G.jokers.cards[i] ~= self and not SMODS.is_eternal(G.jokers.cards[i], self) and not G.jokers.cards[i].getting_sliced then destructable_jokers[#destructable_jokers+1] = G.jokers.cards[i] end -''' -# Ankh -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if not v.ability.eternal then deletable_jokers[#deletable_jokers + 1] = v end -''' -payload = ''' -if not SMODS.is_eternal(v, self) then deletable_jokers[#deletable_jokers + 1] = v end -''' -# Hex SMODS.is_eternal and card.getting_sliced -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if v ~= eligible_card and (not v.ability.eternal) then v:start_dissolve(nil, _first_dissolve);_first_dissolve = true end -''' -payload = ''' -if v ~= eligible_card and (not SMODS.is_eternal(v, self)) then v.getting_sliced = true; v:start_dissolve(nil, _first_dissolve);_first_dissolve = true end -''' -# Card:can_sell() -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -not self.ability.eternal then -''' -payload = ''' -not SMODS.is_eternal(self, {from_sell = true}) then -''' - -# Adds tag_added context -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'after' -pattern = ''' -G.GAME.tags[#G.GAME.tags+1] = _tag -''' -payload = ''' -if not _tag.from_load then SMODS.calculate_context({tag_added = _tag}) end -_tag.from_load = nil -''' - -# Prevent tag added context on reloading -[[patches]] -[patches.pattern] -target = 'tag.lua' -match_indent = true -position = 'after' -pattern = ''' -G.GAME.tag_tally = math.max(self.tally, G.GAME.tag_tally) + 1 -''' -payload = ''' -self.from_load = true -''' - -# Fix Campfire 'upgrading' when it is sold -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if self.ability.name == 'Campfire' and not context.blueprint then -''' -payload = ''' -if self.ability.name == 'Campfire' and not context.blueprint and self ~= context.card then -''' - -# Support for ante contexts -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -pattern = ''' -delay(0.4); ease_ante(1); delay(0.4); check_for_unlock({type = 'ante_up', ante = G.GAME.round_resets.ante + 1}) -''' -position = 'at' -payload = ''' -delay(0.4); SMODS.ante_end = true; ease_ante(1); SMODS.ante_end = nil; delay(0.4); check_for_unlock({type = 'ante_up', ante = G.GAME.round_resets.ante + 1}) -''' - -# Sixth Sense only triggers once with multiple copies -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if self.ability.name == 'Sixth Sense' and #context.full_hand == 1 and context.full_hand[1]:get_id() == 6 and G.GAME.current_round.hands_played == 0 then -''' -payload = ''' -if self.ability.name == 'Sixth Sense' and #context.full_hand == 1 and context.full_hand[1]:get_id() == 6 and not context.full_hand[1].sixth_sense and G.GAME.current_round.hands_played == 0 then - context.full_hand[1].sixth_sense = true -''' - -# Blueprint uses SMODS.blueprint_effect -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if other_joker and other_joker ~= self then - context.blueprint = (context.blueprint and (context.blueprint + 1)) or 1 - context.blueprint_card = context.blueprint_card or self - if context.blueprint > #G.jokers.cards + 1 then return end - local other_joker_ret = other_joker:calculate_joker(context) - if other_joker_ret then - other_joker_ret.card = context.blueprint_card or self - other_joker_ret.colour = G.C.BLUE - return other_joker_ret - end -end -''' -payload = ''' -local ret = SMODS.blueprint_effect(self, other_joker, context) -if ret then - ret.colour = G.C.BLUE - return ret -end -''' - -# Brainstorm uses SMODS.blueprint_effect -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if other_joker and other_joker ~= self then - context.blueprint = (context.blueprint and (context.blueprint + 1)) or 1 - context.blueprint_card = context.blueprint_card or self - if context.blueprint > #G.jokers.cards + 1 then return end - local other_joker_ret = other_joker:calculate_joker(context) - if other_joker_ret then - other_joker_ret.card = context.blueprint_card or self - other_joker_ret.colour = G.C.RED - return other_joker_ret - end -end -''' -payload = ''' -local ret = SMODS.blueprint_effect(self, other_joker, context) -if ret then - ret.colour = G.C.RED - return ret -end -''' - -# Reload SMODS.last_hand -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'before' -pattern = ''' -table.sort(G.playing_cards, function (a, b) return a.playing_card > b.playing_card end ) -''' -payload = ''' -if saveTable then - if saveTable.SMODS 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 - if card.ability['SMODS_'..v] then - SMODS.last_hand[v][card.ability['SMODS_'..v]] = card - end - end - end - end -end -''' - diff --git a/lovely/better_calc_fixes.toml b/lovely/better_calc_fixes.toml deleted file mode 100644 index d572ff9a2..000000000 --- a/lovely/better_calc_fixes.toml +++ /dev/null @@ -1,31 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -## Delayed references to context.blueprint_card should be saved in a variable -## because context is modified in better calc -## (this patch just changes all references) -# Card:calculate_joker(context) -[[patches]] -[patches.pattern] -target = 'card.lua' -position = 'after' -pattern = '''function Card:calculate_joker(context) - if self.debuff then return nil end -''' -payload = ' local context_blueprint_card = context.blueprint_card' -match_indent = true -[[patches]] -[patches.regex] -target = 'card.lua' -position = 'at' -pattern = 'card_eval_status_text\(context\.blueprint_card or self' -payload = 'card_eval_status_text(context_blueprint_card or self' -[[patches]] -[patches.pattern] -target = 'card.lua' -position = 'at' -pattern = 'if context.blueprint_card then context.blueprint_card:juice_up() else self:juice_up() end' -payload = 'if context_blueprint_card then context_blueprint_card:juice_up() else self:juice_up() end' -match_indent = true \ No newline at end of file diff --git a/lovely/blind.toml b/lovely/blind.toml deleted file mode 100644 index ada510652..000000000 --- a/lovely/blind.toml +++ /dev/null @@ -1,552 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -### Blind API - -## Set debuffed_by_blind, use it for Matador behavior -## Blind:debuff_card() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = 'card:set_debuff(true)' -position = 'after' -payload = "if card.debuff then card.debuffed_by_blind = true end" -match_indent = true -[[patches]] -[patches.regex] -target = 'blind.lua' -pattern = 'card:set_debuff\(true\); return end' -position = 'at' -payload = """ -card:set_debuff(true); if card.debuff then card.debuffed_by_blind = true end; return end""" - -## Card:set_debuff() -[[patches]] -[patches.regex] -target = 'card.lua' -pattern = ''' -self\.debuff = should_debuff -(?[\t ]*)end -''' -position = 'after' -payload = """if not self.debuff then self.debuffed_by_blind = false end - -""" -line_prepend = '$indent' - -## Blind functions - -# Blind:set_blind() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "G.GAME.last_blind = G.GAME.last_blind or {}" -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.blind -self.children.animatedSprite = SMODS.create_sprite(self.T.x, self.T.y, self.T.w, self.T.h, obj.atlas or 'blind_chips', obj.pos or G.P_BLINDS.bl_small.pos) -self.children.animatedSprite.states = self.states -''' - -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "if self.name == 'The Eye' and not reset then" -position = 'at' -payload = ''' -if blind then - self.in_blind = true -end -local obj = self.config.blind -if not reset and obj.set_blind and type(obj.set_blind) == 'function' then - obj:set_blind() -elseif self.name == 'The Eye' and not reset then''' -match_indent = true - -# Blind:disable() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "if self.name == 'The Water' then" -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.blind -if obj.disable and type(obj.disable) == 'function' then - obj:disable() -end''' - -# Blind:defeat() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "if self.name == 'The Manacle' and not self.disabled then" -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.blind -if obj.defeat and type(obj.defeat) == 'function' then - obj:defeat() -end''' - -# Blind:debuff_card() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "if self.debuff and not self.disabled and card.area ~= G.jokers then" -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.blind -if not self.disabled and obj.recalc_debuff and type(obj.recalc_debuff) == 'function' then - if obj:recalc_debuff(card, from_blind) then - card:set_debuff(true) - if card.debuff then card.debuffed_by_blind = true end - else - card:set_debuff(false) - end - return -elseif not self.disabled and obj.debuff_card and type(obj.debuff_card) == 'function' then - sendWarnMessage(("Blind object %s has debuff_card function, recalc_debuff is preferred"):format(obj.key), obj.set) - if obj:debuff_card(card, from_blind) then - card:set_debuff(true) - if card.debuff then card.debuffed_by_blind = true end - else - card:set_debuff(false) - end - return -end''' - -# Blind:stay_flipped() - -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "function Blind:stay_flipped(area, card)" -position = 'at' -match_indent = true -payload = '''function Blind:stay_flipped(area, card, from_area)''' - -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "if area == G.hand then" -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.blind -if obj.stay_flipped and type(obj.stay_flipped) == 'function' then - return obj:stay_flipped(area, card, from_area) -end''' - - -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -pattern = "local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(self, card)" -position = 'at' -match_indent = true -payload = '''local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(self, card, area) -if SMODS.to_area then to = SMODS.to_area; SMODS.to_area = nil end''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(to, card)" -position = 'at' -match_indent = true -payload = '''local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(to, card, from) -if SMODS.to_area then to = SMODS.to_area; SMODS.to_area = nil end -''' - -# Blind:drawn_to_hand() -[[patches]] -[patches.regex] -target = 'blind.lua' -pattern = "(?[\t ]*)if self.name == 'Cerulean Bell' then\n" -position = 'before' -line_prepend = '$indent' -payload = ''' -local obj = self.config.blind -if obj.drawn_to_hand and type(obj.drawn_to_hand) == 'function' then - obj:drawn_to_hand() -end -''' - -# Blind:debuff_hand() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "if self.debuff then" -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.blind -if obj.debuff_hand and type(obj.debuff_hand) == 'function' then - return obj:debuff_hand(cards, hand, handname, check) -end''' - -# Blind:modify_hand() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "if self.disabled then return mult, hand_chips, false end" -position = 'after' -match_indent = true -payload = ''' -local obj = self.config.blind -if obj.modify_hand and type(obj.modify_hand) == 'function' then - return obj:modify_hand(cards, poker_hands, text, mult, hand_chips) -end''' - -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "function Blind:modify_hand(cards, poker_hands, text, mult, hand_chips)" -position = 'at' -match_indent = true -payload = '''function Blind:modify_hand(cards, poker_hands, text, mult, hand_chips, scoring_hand)''' - -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -pattern = "mult, hand_chips, modded = G.GAME.blind:modify_hand(G.play.cards, poker_hands, text, mult, hand_chips)" -position = 'at' -match_indent = true -payload = '''mult, hand_chips, modded = G.GAME.blind:modify_hand(G.play.cards, poker_hands, text, mult, hand_chips, scoring_hand)''' - -# Blind:press_play() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = 'if self.name == "The Hook" then' -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.blind -if obj.press_play and type(obj.press_play) == 'function' then - return obj:press_play() -end''' - -# Blind:get_loc_debuff_text() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = 'function Blind:get_loc_debuff_text()' -position = 'after' -match_indent = true -payload = ''' - local obj = self.config.blind - if obj.get_loc_debuff_text and type(obj.get_loc_debuff_text) == 'function' then - return obj:get_loc_debuff_text() - end''' - -# Blind:set_text() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = "local loc_target = localize{type = 'raw_descriptions', key = self.config.blind.key, set = 'Blind', vars = loc_vars or self.config.blind.vars}" -position = 'at' -match_indent = true -payload = ''' -local target = {type = 'raw_descriptions', key = self.config.blind.key, set = 'Blind', vars = loc_vars or self.config.blind.vars} -local obj = self.config.blind -if obj.loc_vars and type(obj.loc_vars) == 'function' then - local res = obj:loc_vars() or {} - target.vars = res.vars or target.vars - target.key = res.key or target.key - target.set = res.set or target.set - target.scale = res.scale - target.text_colour = res.text_colour -end -local loc_target = localize(target)''' - -# Blind:load() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = 'if G.P_BLINDS[blindTable.config_blind] then' -position = 'after' -match_indent = true -payload = ''' -if self.config.blind.atlas then - self.children.animatedSprite.atlas = SMODS.get_atlas(self.config.blind.atlas) -end''' - -# create_UIBox_blind_choice() -# create_UIBox_round_scores_row() -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = "(?[\t ]*)blind_choice.animation = AnimatedSprite\\(0,0, 1.4, 1.4, (?G.ANIMATION_ATLAS\\['blind_chips'\\]), blind_choice.config.pos\\)" -position = 'at' -root_capture = 'atlas' -payload = "SMODS.get_atlas(blind_choice.config.atlas) or G.ANIMATION_ATLAS['blind_chips']" - -# create_UIBox_your_collection_blinds() -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = "(?[\t ]*)local temp_blind = AnimatedSprite\\(0,0,1.3,1.3, G.ANIMATION_ATLAS\\['blind_chips'\\], discovered and v.pos or G.b_undiscovered.pos\\)" -position = 'at' -payload = ''' - -local s = 1.3 -if math.ceil(#blind_tab/6) > 6 then - s = s * 6/math.ceil(#blind_tab/6) -end -local temp_blind = AnimatedSprite(0,0,s,s, G.ANIMATION_ATLAS[discovered and v.atlas or 'blind_chips'], discovered and v.pos or G.b_undiscovered.pos)''' -line_prepend = '$indent' - -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = 'blind_matrix[math.ceil((k-1)/5+0.001)][1+((k-1)%5)] = {n=G.UIT.C, config={align = "cm", padding = 0.1}, nodes={' -match_indent = true -position = 'at' -payload = ''' -local blinds_per_row = math.ceil(#blind_tab / 6) -local row = math.ceil((k - 1) / blinds_per_row + 0.001) -table.insert(blind_matrix[row], { - n = G.UIT.C, - config = { align = "cm", padding = 0.1 }, - nodes = { - ((k - blinds_per_row) % (2 * blinds_per_row) == 1) and { n = G.UIT.B, config = { h = 0.2, w = 0.5 } } or nil, - { n = G.UIT.O, config = { object = temp_blind, focus_with_object = true } }, - ((k - blinds_per_row) % (2 * blinds_per_row) == 0) and { n = G.UIT.B, config = { h = 0.2, w = 0.5 } } or nil, - } -})''' - -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = '[\t ]*\(k==6 or k ==16 or k == 26\) and \{n=G.UIT.B, config=\{h=0.2,w=0.5\}\} or nil,\n[\t ]*\{n=G.UIT.O, config=\{object = temp_blind, focus_with_object = true\}\},\n[\t ]*\(k==5 or k ==15 or k == 25\) and \{n=G.UIT.B, config=\{h=0.2,w=0.5\}\} or nil,\n[\t ]*\}\}' -position = 'at' -payload = '' - -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = 'table.sort(blind_tab, function (a, b) return a.order < b.order end)' -match_indent = true -position = 'at' -payload = ''' -table.sort(blind_tab, function(a, b) return a.order + (a.boss and a.boss.showdown and 1000 or 0) < b.order + (b.boss and b.boss.showdown and 1000 or 0) end)''' - -# add_round_eval_row() -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "local blind_sprite = AnimatedSprite(0, 0, 1.2,1.2, G.ANIMATION_ATLAS['blind_chips'], copy_table(G.GAME.blind.pos))" -match_indent = true -position = 'at' -payload = ''' -local obj = G.GAME.blind.config.blind -local blind_sprite = AnimatedSprite(0, 0, 1.2, 1.2, G.ANIMATION_ATLAS[obj.atlas] or G.ANIMATION_ATLAS['blind_chips'], copy_table(G.GAME.blind.pos))''' - -# display blind descriptions in info_queue -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = "elseif _c.set == 'Tarot' then" -position = "before" -match_indent = true -payload = '''elseif _c.set == 'Blind' then - local coll_loc_vars = (_c.collection_loc_vars and type(_c.collection_loc_vars) == 'function' and _c:collection_loc_vars()) or {} - loc_vars = coll_loc_vars.vars or _c.vars - localize{type = 'descriptions', key = coll_loc_vars.key or _c.key, set = _c.set, nodes = desc_nodes, vars = loc_vars} -''' - -# create_UIBox_blind_choice() -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = "local loc_target = localize{type = 'raw_descriptions', key = blind_choice.config.key, set = 'Blind', vars = {localize(G.GAME.current_round.most_played_poker_hand, 'poker_hands')}}" -match_indent = true -position = 'at' -payload = ''' -local target = {type = 'raw_descriptions', key = blind_choice.config.key, set = 'Blind', vars = {}} -if blind_choice.config.name == 'The Ox' then - target.vars = {localize(G.GAME.current_round.most_played_poker_hand, 'poker_hands')} -end -local obj = blind_choice.config -if obj.loc_vars and _G['type'](obj.loc_vars) == 'function' then - local res = obj:loc_vars() or {} - target.vars = res.vars or target.vars - target.key = res.key or target.key - target.set = res.set or target.set - target.scale = res.scale - target.text_colour = res.text_colour -end -''' - -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = "local text_table = loc_target" -match_indent = true -position = 'at' -payload = ''' -local text_table = G.localization.descriptions[target.set][target.key].text_parsed -''' - -# create_UIBox_blind_popup() -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = '''local loc_target = localize{type = 'raw_descriptions', key = blind.key, set = 'Blind', vars = vars or blind.vars}''' -match_indent = true -position = 'at' -payload = ''' -local target = {type = 'raw_descriptions', key = blind.key, set = 'Blind', vars = vars or blind.vars} -if blind.collection_loc_vars and type(blind.collection_loc_vars) == 'function' then - local res = blind:collection_loc_vars() or {} - target.vars = res.vars or target.vars - target.key = res.key or target.key - target.set = res.set or target.set - target.scale = res.scale - target.text_colour = res.text_colour -end -local loc_target = G.localization.descriptions[target.set][target.key].text_parsed''' - -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = ''' -ability_text[#ability_text + 1] = {n=G.UIT.R, config={align = "cm"}, nodes={{n=G.UIT.T, config={text = (k ==1 and blind.name == 'The Wheel' and '1' or '')..v, scale = 0.35, shadow = true, colour = G.C.WHITE}}}} -''' -match_indent = true -position = 'at' -payload = ''' -ability_text[#ability_text + 1] = {n=G.UIT.R, config={align = "cm"}, nodes=SMODS.localize_box(v, {default_col = target.text_colour or G.C.WHITE, shadow = true, vars = target.vars, scale = target.scale})} -''' - -# get_new_boss() -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = ''' -if not v.boss then - -elseif not v.boss.showdown and (v.boss.min <= math.max(1, G.GAME.round_resets.ante) and ((math.max(1, G.GAME.round_resets.ante))%G.GAME.win_ante ~= 0 or G.GAME.round_resets.ante < 2)) then - eligible_bosses[k] = true -elseif v.boss.showdown and (G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= 2 then - eligible_bosses[k] = true -end -''' -match_indent = true -position = 'at' -payload = ''' -local res, options = SMODS.add_to_pool(v) -options = options or {} -if not v.boss then - -elseif options.ignore_showdown_check then - eligible_bosses[k] = res and true or nil -elseif v.in_pool and type(v.in_pool) == 'function' then - if - ( - ((G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= 2) == - (v.boss.showdown or false) - ) - then - eligible_bosses[k] = res and true or nil - end -elseif not v.boss.showdown and (v.boss.min <= math.max(1, G.GAME.round_resets.ante) and ((math.max(1, G.GAME.round_resets.ante))%G.GAME.win_ante ~= 0 or G.GAME.round_resets.ante < 2)) then - eligible_bosses[k] = res and true or nil -elseif v.boss.showdown and (G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= 2 then - eligible_bosses[k] = res and true or nil -end -''' - -# G.UIDEF.challenge_description_tab -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = "local temp_blind = AnimatedSprite(0,0,1,1, G.ANIMATION_ATLAS['blind_chips'], v.pos)" -position = 'at' -match_indent = true -payload = "local temp_blind = AnimatedSprite(0,0,1,1, G.ANIMATION_ATLAS[v.atlas or ''] or G.ANIMATION_ATLAS['blind_chips'], v.pos)" - -## elseif-ify some if chains -[[patches]] -[patches.regex] -target = 'blind.lua' -pattern = "end\n(?[\t ]*)if self.name == ['\"](?.*?)['\"]" -position = 'at' -payload = "elseif self.name == '$ability'" - -# revert the change for The Pillar -[[patches]] -[patches.regex] -target = 'blind.lua' -pattern = "(?[\t ]*)elseif self.name == 'The Pillar' and card.ability.played_this_ante then" -position = 'at' -line_prepend = '$indent' -payload = '''end -if self.name == 'The Pillar' and card.ability.played_this_ante then''' - -# revert the change for The Eye in debuff_hand -[[patches]] -[patches.regex] -target = 'blind.lua' -pattern = "(?[\t ]*)elseif self.name == 'The Eye' then" -position = 'at' -line_prepend = '$indent' -payload = '''end -if self.name == 'The Eye' then''' - -# revert the change for The Arm in debuff_hand -[[patches]] -[patches.regex] -target = 'blind.lua' -pattern = "(?[\t ]*)elseif self.name == 'The Arm' then" -position = 'at' -line_prepend = '$indent' -payload = '''end -if self.name == 'The Arm' then''' - -# revert the change for Crimson Heart in debuff_card -[[patches]] -[patches.regex] -target = 'blind.lua' -pattern = '''(?[\t ]*)elseif self\.name == 'Crimson Heart' and not self\.disabled''' -position = 'at' -line_prepend = '$indent' -payload = '''end -if self.name == 'Crimson Heart' and not self.disabled''' - -### Add blind.effect -# Blind.set_blind -[[patches]] -[patches.pattern] -target = "blind.lua" -match_indent = true -pattern = "self.config.blind = blind or {}" -position = "after" -payload = ''' -self.effect = type(self.config.blind.config) == "table" and copy_table(self.config.blind.config) or {} -''' -# Blind.load -[[patches]] -[patches.pattern] -target = "blind.lua" -match_indent = true -pattern = "function Blind:load(blindTable)" -position = "after" -payload = ''' -self.effect = blindTable.effect -''' -# Blind.save -[[patches]] -[patches.pattern] -target = "blind.lua" -match_indent = true -pattern = "local blindTable = {" -position = "after" -payload = ''' -effect = self.effect, -''' \ No newline at end of file diff --git a/lovely/blind_ui.toml b/lovely/blind_ui.toml deleted file mode 100644 index c2a739a5a..000000000 --- a/lovely/blind_ui.toml +++ /dev/null @@ -1,176 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -### Allow blinds to have more than 2 lines - -# create_UIBox_blind_choice() -[[patches]] -[patches.pattern] -target = "functions/UI_definitions.lua" -pattern = "if blind_state == 'Select' then blind_state = 'Current' end" -position = 'after' -payload = ''' -local blind_desc_nodes = {} -for k, v in ipairs(text_table) do - blind_desc_nodes[#blind_desc_nodes+1] = {n=G.UIT.R, config={align = "cm", maxw = 2.8}, nodes=SMODS.localize_box(v, {default_col = disabled and G.C.UI.TEXT_INACTIVE or target.text_colour or G.C.WHITE, shadow = not disabled, vars = target.vars, scale = target.scale})} -end''' -match_indent = true - -[[patches]] -[patches.regex] -target = "functions/UI_definitions.lua" -pattern = ''' -(?[\t ]*)text_table\[1\] and \{n=G\.UIT\.R, config=\{align = "cm", minh = 0\.7, padding = 0\.05, minw = 2\.9}, nodes=\{ -[\t ]* text_table\[1\] and \{n=G\.UIT\.R, config=\{align = "cm", maxw = 2\.8\}, nodes=\{ -[\t ]* \{n=G\.UIT\.T, config=\{id = blind_choice\.config\.key, ref_table = \{val = ''\}, ref_value = 'val', scale = 0\.32, colour = disabled and G\.C\.UI\.TEXT_INACTIVE or G\.C\.WHITE, shadow = not disabled, func = 'HUD_blind_debuff_prefix'\}\}, -[\t ]* \{n=G\.UIT\.T, config=\{text = text_table\[1\] or '\-', scale = 0\.32, colour = disabled and G\.C\.UI\.TEXT_INACTIVE or G\.C\.WHITE, shadow = not disabled\}\} -[\t ]* \}\} or nil, -[\t ]* text_table\[2\] and \{n=G\.UIT\.R, config=\{align = "cm", maxw = 2\.8\}, nodes=\{ -[\t ]* \{n=G\.UIT\.T, config=\{text = text_table\[2\] or '\-', scale = 0\.32, colour = disabled and G\.C\.UI\.TEXT_INACTIVE or G\.C\.WHITE, shadow = not disabled\}\} -[\t ]* \}\} or nil, -[\t ]*\}\} or nil,''' -position = "at" -payload = ''' -text_table[1] and {n=G.UIT.R, config={align = "cm", minh = 0.7, padding = 0.05, minw = 2.9}, nodes = blind_desc_nodes} or nil,''' -line_prepend = '$indent' - -# create_UIBox_HUD_blind() -# Padding and contained nodes are set in G.FUNCS.HUD_blind_debuff (overrides.lua) -[[patches]] -[patches.regex] -target = "functions/UI_definitions.lua" -pattern = ''' -(?[\t ]*)\{n=G\.UIT\.R, config=\{align = "cm", padding = 0\.05\}, nodes=\{ -[\t ]* \{n=G\.UIT\.R, config=\{align = "cm", minh = 0\.3, maxw = 4\.2\}, nodes=\{ -[\t ]* \{n=G\.UIT\.T, config=\{ref_table = \{val = ''\}, ref_value = 'val', scale = scale\*0\.9, colour = G\.C\.UI\.TEXT_LIGHT, func = 'HUD_blind_debuff_prefix'\}\}, -[\t ]* \{n=G\.UIT\.T, config=\{ref_table = G\.GAME\.blind\.loc_debuff_lines, ref_value = 1, scale = scale\*0\.9, colour = G\.C\.UI\.TEXT_LIGHT, id = 'HUD_blind_debuff_1', func = 'HUD_blind_debuff'\}\} -[\t ]* \}\}, -[\t ]* \{n=G\.UIT\.R, config=\{align = "cm", minh = 0\.3, maxw = 4\.2\}, nodes=\{ -[\t ]* \{n=G\.UIT\.T, config=\{ref_table = G\.GAME\.blind\.loc_debuff_lines, ref_value = 2, scale = scale\*0\.9, colour = G\.C\.UI\.TEXT_LIGHT, id = 'HUD_blind_debuff_2', func = 'HUD_blind_debuff'\}\} -[\t ]* \}\}, -[\t ]*\}\},''' -position = "at" -payload = ''' -{n=G.UIT.R, config={align = "cm", id = 'HUD_blind_debuff', func = 'HUD_blind_debuff'}, nodes={}},''' -line_prepend = '$indent' - -# Blind:set_text -[[patches]] -[patches.regex] -target = "blind.lua" -pattern = """ -(?[\t ]*)self\\.loc_debuff_lines\\[1\\] = '' -[\t ]*self\\.loc_debuff_lines\\[2\\] = ''""" -position = 'at' -payload = 'EMPTY(self.loc_debuff_lines)' -line_prepend = '$indent' - -[[patches]] -[patches.pattern] -target = "blind.lua" -pattern = "for k, v in ipairs(loc_target) do" -position = 'before' -payload = ''' -EMPTY(self.loc_debuff_lines) -if G.localization.descriptions[target.set][target.key] then - for k, v in ipairs(G.localization.descriptions[target.set][target.key].text_parsed) do - self.loc_debuff_lines[k] = v - end - self.loc_debuff_lines.vars = target.vars - self.loc_debuff_lines.scale = target.scale - self.loc_debuff_lines.text_colour = target.text_colour -else - for k, v in ipairs(loc_target) do - self.loc_debuff_lines[k] = v - end -end -''' -match_indent = true - -[[patches]] -[patches.regex] -target = "blind.lua" -pattern = """ -(?[\t ]*)self\\.loc_debuff_lines\\[1\\] = loc_target\\[1\\] or '' -[\t ]*self\\.loc_debuff_lines\\[2\\] = loc_target\\[2\\] or '' -""" -position = 'at' -payload = '' - -## Add a box with h=3.64 (magic number equal to the height of HUD_blind) -## centered inside 'row_blind' -# create_UIBox_HUD -[[patches]] -[patches.pattern] -target = "functions/UI_definitions.lua" -pattern = """{n=G.UIT.R, config={align = "cm", id = 'row_blind', minw = 1, minh = 3.75}, nodes={}},""" -position = 'at' -payload = """{n=G.UIT.R, config={align = "cm", id = 'row_blind', minw = 1, minh = 3.75}, nodes={ - {n=G.UIT.B, config={w=0, h=3.64, id = 'row_blind_bottom'}, nodes={}} -}},""" -match_indent = true - -## Blind UI's bottom edge is aligned to it -[[patches]] -[patches.pattern] -target = "game.lua" -pattern = "config = {major = G.HUD:get_UIE_by_ID('row_blind'), align = 'cm', offset = {x=0,y=-10}, bond = 'Weak'}" -position = 'at' -payload = "config = {major = G.HUD:get_UIE_by_ID('row_blind_bottom'), align = 'bmi', offset = {x=0,y=-10}, bond = 'Weak'}" -match_indent = true - -## Patch G.GAME.blind:juice_up() across all files - -[[patches]] -[patches.regex] -target = "functions/common_events.lua" -pattern = ''' -(?[\t ]*)G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_1'\):juice_up\(0\.3, 0\) -[\t ]*G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_2'\):juice_up\(0\.3, 0\) -[\t ]*G\.GAME\.blind:juice_up\(\)''' -position = 'at' -payload = 'SMODS.juice_up_blind()' -line_prepend = '$indent' - -[[patches]] -[patches.regex] -target = "functions/state_events.lua" -pattern = ''' -(?[\t ]*)G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_1'\):juice_up\(0\.3, 0\) -[\t ]*G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_2'\):juice_up\(0\.3, 0\) -[\t ]*G\.GAME\.blind:juice_up\(\)''' -position = 'at' -payload = 'SMODS.juice_up_blind()' -line_prepend = '$indent' - -[[patches]] -[patches.regex] -target = "functions/state_events.lua" -pattern = '''hand_chips = mod_chips\(0\)(\n.*)*?\n[\t ]*(?SMODS.juice_up_blind\(\))''' -position = 'at' -root_capture = 'juice' -payload = 'if SMODS.hand_debuff_source then SMODS.hand_debuff_source:juice_up(0.3,0) else SMODS.juice_up_blind() end' -times = 1 - -[[patches]] -[patches.pattern] -target = 'game.lua' -pattern = 'G.GAME.blind.children.animatedSprite:juice_up(0.05, 0.02)' -position = 'at' -match_indent = true -payload = '''if SMODS.hand_debuff_source then - SMODS.hand_debuff_source:juice_up(0.05, 0.1) -else - G.GAME.blind.children.animatedSprite:juice_up(0.05, 0.02) -end -''' - -# remove statically added 1 from The Wheel's collection description -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = '''\(k ==1 and blind\.name == 'The Wheel' and '1' or ''\)\.\.''' -position = 'at' -payload = '' diff --git a/lovely/booster.toml b/lovely/booster.toml deleted file mode 100644 index fa9c00ba6..000000000 --- a/lovely/booster.toml +++ /dev/null @@ -1,367 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -## Booster Pack API - -# Card:open -[[patches]] -[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 - - G.GAME.pack_choices = self.config.center.config.choose or 1''' -match_indent = true -position = "at" -payload = """ -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 -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) -""" - -# Card:open -# Adds modifier for size of booster -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -local _size = self.ability.extra -''' -payload = ''' -local _size = math.max(1, self.ability.extra + (G.GAME.modifiers.booster_size_mod or 0)) -''' - - -# Card:open -[[patches]] -[patches.regex] -target = "card.lua" -pattern = '''(?[\t ]*)if self\.ability\.name:find\('Arcana'\) then[\t\n ]*if G\.GAME\.used_vouchers\.v_omen_globe and pseudorandom\('omen_globe'\) > 0\.8 then''' # Possibly try to target something else -position = "at" -payload = '''if booster_obj.create_card and type(booster_obj.create_card) == "function" then - local _card_to_spawn = booster_obj:create_card(self, i) - if type((_card_to_spawn or {}).is) == 'function' and _card_to_spawn:is(Card) then - card = _card_to_spawn - else - card = SMODS.create_card(_card_to_spawn) - end -elseif self.ability.name:find('Arcana') then - if G.GAME.used_vouchers.v_omen_globe and pseudorandom('omen_globe') > 0.8 then''' -line_prepend = '$indent' - -# 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 -# 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''' - -# CardArea:draw() -[[patches]] -[patches.pattern] -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''' -match_indent = true - -# G.FUNCS.use_card -[[patches]] -[patches.pattern] -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''' -match_indent = true - -# CardArea:align_cards() -[[patches]] -[patches.pattern] -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" -match_indent = true - -# CardArea:align_cards() -[[patches]] -[patches.pattern] -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" -match_indent = true - -# Card:can_use_consumable() -[[patches]] -[patches.pattern] -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" -match_indent = true - -# G.FUNCS.use_card() -[[patches]] -[patches.pattern] -target = "functions/button_callbacks.lua" -pattern = "if G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SPECTRAL_PACK then" -position = "at" -payload = """ -if nc then - if area then area:remove_from_highlighted(card) end - 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""" -match_indent = true - -# G.FUNC.use_card() -[[patches]] -[patches.pattern] -target = "functions/button_callbacks.lua" -pattern = 'if area == G.consumeables then' -position = 'before' -match_indent = true -payload = ''' -if nc and area == G.pack_cards and not select_to then G.pack_cards:remove_card(card); G.consumeables:emplace(card) end''' - -# G.FUNC.use_card() -[[patches]] -[patches.pattern] -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''' - -# G.FUNC.use_card() -[[patches]] -[patches.pattern] -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''' - -# G.FUNC.use_card() -[[patches]] -[patches.pattern] -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" -match_indent = true - -# G.FUNC.use_card() -[[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" -match_indent = true - -# Card:use_consumeable() -[[patches]] -[patches.regex] -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},''' -line_prepend = '$indent' - -# G.FUNCS.use_card() -[[patches]] -[patches.pattern] -target = "functions/button_callbacks.lua" -pattern = "e.config.ref_table:redeem()" -position = "before" -payload = "if area == G.pack_cards then e.config.ref_table.cost = 0 end" -match_indent = true - -## Stopping ease_dollars anim from playing when voucher is free -# Card:redeem() -[[patches]] -[patches.regex] -target = "card.lua" -pattern = '''(?[\t ]*)ease_dollars\(-self\.cost\)\n[\s\S]{8}inc_career_stat\('c_shop_dollars_spent', self\.cost\)''' -position = "at" -payload = ''' -if self.cost ~= 0 then - ease_dollars(-self.cost) - inc_career_stat('c_shop_dollars_spent', self.cost) -end''' -line_prepend = '$indent' - -# Add support for saving consumables -# G.UIDEF.use_and_sell_buttons() -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'before' -pattern = ''' -if card.ability.consumeable then - if (card.area == G.pack_cards and G.pack_cards) then -''' -payload = ''' -if card.ability.consumeable and card.area == G.pack_cards and G.pack_cards and booster_obj and SMODS.card_select_area(card, booster_obj) and card:selectable_from_pack(booster_obj) then - if (card.area == G.pack_cards and G.pack_cards) then - local select_button_text = SMODS.get_select_text(card, booster_obj) or localize('b_select') - return {n=G.UIT.ROOT, config = {padding = 0, colour = G.C.CLEAR}, nodes={ - {n=G.UIT.R, config={ref_table = card, r = 0.08, padding = 0.1, align = "bm", minw = 0.5*card.T.w - 0.15, maxw = 0.9*card.T.w - 0.15, minh = 0.3*card.T.h, hover = true, shadow = true, colour = G.C.UI.BACKGROUND_INACTIVE, one_press = true, button = 'use_card', func = 'can_select_from_booster'}, nodes={ - {n=G.UIT.T, config={text = select_button_text, colour = G.C.UI.TEXT_LIGHT, scale = 0.45, shadow = true}} - }}, - }} - end -end -''' - -# G.FUNCS.use_card() -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'at' -pattern = ''' -if card.ability.consumeable then - if nc then -''' -payload = ''' -if select_to then - card:add_to_deck() - G[select_to]:emplace(card) - if card.config.center.on_select and type(card.config.center.on_select) == 'function' then - card.config.center:on_select(card) - end - play_sound('card1', 0.8, 0.6) - play_sound('generic1') - dont_dissolve = true - delay_fac = 0.2 -elseif card.ability.consumeable then - if nc then -''' -# G.FUNCS.end_consumeable() -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'before' -pattern = ''' -for i = 1, #G.GAME.tags do - if G.GAME.tags[i]:apply_to_run({type = 'new_blind_choice'}) then break end -end - -G.E_MANAGER:add_event(Event({trigger = 'after',delay = 0.2*delayfac, - func = function() - save_run() - return true - end})) -''' -payload = ''' -booster_obj = nil -''' -# G.FUNCS.skip_booster() -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'before' -pattern = ''' -G.FUNCS.end_consumeable(e) -''' -payload = ''' -booster_obj = nil -''' - -# G.FUNCS.can_select_card -# Support negative-ish on Jokers -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'at' -pattern = "if e.config.ref_table.ability.set ~= 'Joker' or (e.config.ref_table.edition and e.config.ref_table.edition.negative) or #G.jokers.cards < G.jokers.config.card_limit then" -payload = '''local card = e.config.ref_table -local card_limit = card.ability.card_limit - card.ability.extra_slots_used -if card.ability.set ~= 'Joker' or #G.jokers.cards < G.jokers.config.card_limit + card_limit then''' - -# Card:redeem() -# Specific G.STATE for when a Voucher is redeemed -[[patches]] -[patches.pattern] -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''' - -# G.FUNCS.use_card() -# Consumables in areas other than the consumable don't count -# as picking an item in boosters when used -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'at' -pattern = ''' -if area == G.consumeables then -''' -payload = ''' -if area ~= G.pack_cards then -''' diff --git a/lovely/calc_returns.toml b/lovely/calc_returns.toml deleted file mode 100644 index e403b5b6b..000000000 --- a/lovely/calc_returns.toml +++ /dev/null @@ -1,45 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -# Add support to attention text for UI nodes as a message -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'before' -pattern = ''' -{n=G.UIT.O, config={draw_layer = 1, object = DynaText({scale = args.scale, string = args.text, maxw = args.maxw, colours = {args.colour},float = true, shadow = true, silent = not args.noisy, args.scale, pop_in = 0, pop_in_rate = 6, rotate = args.rotate or nil})}}, -''' -payload = ''' -type(args.text) == "table" and args.text.n and args.text or -''' -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'at' -pattern = ''' -args.text = args.AT.UIRoot.children[1].config.object -args.text:pulse(0.5) -''' -payload = ''' -if type(args.text) == 'string' then - args.text = args.AT.UIRoot.children[1].config.object - args.text:pulse(0.5) -end -''' -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'at' -pattern = ''' -args.text:pop_out(3) -''' -payload = ''' -if Object.is(args.text, DynaText) then - args.text:pop_out(3) -end -''' diff --git a/lovely/calculate_card_added.toml b/lovely/calculate_card_added.toml deleted file mode 100644 index caeac142b..000000000 --- a/lovely/calculate_card_added.toml +++ /dev/null @@ -1,29 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -5 - -# Add card_added context -[[patches]] -[patches.regex] -target = "card.lua" -pattern = ''' -(?[\t ]*).*then G\.E_MANAGER:add_event\(Event\(\{ func = function\(\) G\.GAME\.blind:set_blind\(nil, true, nil\); return true end \}\)\) end -(?)[\s\S]* -function Card:remove_from_deck''' -position = "after" -line_prepend = "$indent" -payload = ''' -if not from_debuff and G.hand then - local is_playing_card = self.ability.set == 'Default' or self.ability.set == 'Enhanced' - - -- TARGET: calculate card_added - - if not is_playing_card then - SMODS.calculate_context({card_added = true, card = self}) - SMODS.enh_cache:clear() - end -end -''' -root_capture = '$root' -times = 1 diff --git a/lovely/can_calculate.toml b/lovely/can_calculate.toml deleted file mode 100644 index 3b2c01eb2..000000000 --- a/lovely/can_calculate.toml +++ /dev/null @@ -1,63 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -5 - -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = '''function Card:get_end_of_round_effect(context) - if self.debuff then return {} end''' -position = "at" -match_indent = true -payload = ''' -function Card:get_end_of_round_effect(context)''' - -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = '''function Card:calculate_dollar_bonus() - if self.debuff then return end''' -position = "at" -match_indent = true -payload = ''' -function Card:calculate_dollar_bonus() - if not self:can_calculate() then return end''' - -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = '''function Card:calculate_seal(context) - if self.debuff then return nil end''' -position = "at" -match_indent = true -payload = ''' -function Card:calculate_seal(context)''' - -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = '''function Card:calculate_joker(context) - if self.debuff then return nil end''' -position = "at" -match_indent = true -payload = ''' -function Card:calculate_joker(context)''' - - -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = '''if self.ability.set == "Joker" and not self.debuff then''' -position = "at" -match_indent = true -payload = '''if self.ability.set == "Joker" then''' - - -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = '''if self.debuff then return 0 end''' -position = "at" -match_indent = true -payload = '' \ No newline at end of file diff --git a/lovely/card_limit.toml b/lovely/card_limit.toml deleted file mode 100644 index fe0e54ae7..000000000 --- a/lovely/card_limit.toml +++ /dev/null @@ -1,389 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -# Reset SMODS.cards_to_draw on game start -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'after' -pattern = ''' -local saveTable = args.savetext or nil -''' -payload = ''' -SMODS.cards_to_draw = nil -''' - - -# Set metatable -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'at' -pattern = ''' -self.config = config or {} -''' -payload = ''' -self.config = setmetatable({card_limits = {card_limit}}, { - __index = function(t, key) - if key == "card_limit" then - return (t.card_limits.total_slots or 0) - (t.card_limits.extra_slots_used or 0) - end - end, - __newindex = function(t, key, value) - if key == 'card_limit' then - if not t.card_limits.base then rawset(t.card_limits, 'base', value) end - if not t.card_limits.total_slots then rawset(t.card_limits, 'total_slots', value) end - rawset(t.card_limits, 'mod', value - t.card_limits.base - (t.card_limits.extra_slots or 0)) - else - rawset(t, key, value) - end - end -}) - -SMODS.merge_defaults(self.config, config) -''' - -# Load metatable -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'at' -pattern = ''' -self.config = cardAreaTable.config -''' -payload = ''' -self.config = setmetatable(cardAreaTable.config, { - __index = function(t, key) - if key == "card_limit" then - return (t.card_limits.total_slots or 0) - (t.card_limits.extra_slots_used or 0) - end - end, - __newindex = function(t, key, value) - if key == 'card_limit' then - if not t.card_limits.base then rawset(t.card_limits, 'base', value) end - rawset(t.card_limits, 'mod', value - t.card_limits.base - (t.card_limits.extra_slots or 0)) - else - rawset(t, key, value) - end - end -}) - -''' - -# Bypass CardArea:change_size() -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'after' -pattern = ''' -function CardArea:change_size(delta) -''' -payload = ''' - if true then - self.config.card_limits.mod = (self.config.card_limits.mod or 0) + delta - return - end -''' - -# CardArea:update() -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'at' -pattern = ''' -if self == G.deck and self.config.card_limit > #G.playing_cards then self.config.card_limit = #G.playing_cards end -''' -payload = ''' -if self == G.deck and self.config.card_limit ~= #G.playing_cards then - self.config.card_limit = #G.playing_cards - self.config.card_limits.total_slots = #G.playing_cards -end -''' - -# Card:add_to_deck() -# remove vanilla functionality -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if self.edition and self.edition.negative then - if from_debuff then - self.ability.queue_negative_removal = nil - else - if self.ability.consumeable then - G.consumeables.config.card_limit = G.consumeables.config.card_limit + 1 - else - G.jokers.config.card_limit = G.jokers.config.card_limit + 1 - end - end - end -''' -payload = ''' --- removed by SMODS -''' - -# Card:remove_from_deck() -# remove vanilla functionality -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -if self.edition and self.edition.negative and G.jokers then - if from_debuff then - self.ability.queue_negative_removal = true - else - if self.ability.consumeable then - G.consumeables.config.card_limit = G.consumeables.config.card_limit - 1 - else - G.jokers.config.card_limit = G.jokers.config.card_limit - 1 - end - end - end -''' -payload = ''' --- removed by SMODS -''' - -# CardArea:update() -# Add support for extra_slots_used parameter -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'at' -pattern = ''' -self.config.temp_limit = math.max(#self.cards, self.config.card_limit) -self.config.card_count = #self.cards -''' -payload = ''' -self:handle_card_limit() -self.config.temp_limit = math.max(#self.cards, self.config.card_limit or 0) -''' - -# CardArea:draw() -# Change count display to use true_card_limit -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'at' -pattern = ''' -{n=G.UIT.T, config={ref_table = self.config, ref_value = 'card_limit', scale = 0.3, colour = G.C.WHITE}}, -''' -payload = ''' -{n=G.UIT.T, config={ref_table = self.config.card_limits, ref_value = 'total_slots', scale = 0.3, colour = G.C.WHITE}}, -''' - -# Add negative info_queue support -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'at' -pattern = ''' -{card_limit = self.GAME.starting_params.consumable_slots, type = 'joker', highlight_limit = 1}) -''' -payload = ''' -{card_limit = self.GAME.starting_params.consumable_slots, type = 'joker', highlight_limit = 1, negative_info = 'consumable'}) -''' -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'at' -pattern = ''' -{card_limit = self.GAME.starting_params.joker_slots, type = 'joker', highlight_limit = 1}) -''' -payload = ''' -{card_limit = self.GAME.starting_params.joker_slots, type = 'joker', highlight_limit = 1, negative_info = 'joker'}) -''' -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'at' -pattern = ''' -{card_limit = self.GAME.starting_params.hand_size, type = 'hand'}) -''' -payload = ''' -{card_limit = self.GAME.starting_params.hand_size, type = 'hand', negative_info = 'playing_card'}) -''' -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'at' -pattern = ''' -{card_limit = G.GAME.shop.joker_max, type = 'shop', highlight_limit = 1}) -''' -payload = ''' -{card_limit = G.GAME.shop.joker_max, type = 'shop', highlight_limit = 1, negative_info = true}) -''' -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'at' -pattern = ''' -{card_limit = 5, type = 'play'}) -''' -payload = ''' -{card_limit = 5, type = 'play', negative_info = 'playing_card'}) -''' - -# generate_card_ui() -# Adds info queue tooltips for generic card limit and slots used -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'before' -pattern = ''' -for _, v in ipairs(info_queue) do -''' -payload = ''' -if card and card.ability and (card.ability.extra_slots_used or 0) ~= 0 then - local str = 'generic_extra_slots' - if card.ability.set == 'Default' or card.ability.set == 'Enhanced' then str = str .. '_pc' end - info_queue[#info_queue + 1] = {set = 'Other', key = str, vars = {card.ability.extra_slots_used + 1}} -end -if card and card.ability and (card.ability.card_limit or 0) ~= 0 then - if not (card.edition and card.edition.card_limit == card.ability.card_limit) then - local amount = card.ability.card_limit - (card.edition and card.edition.card_limit or 0) - local str = 'generic_card_limit' - if card.ability.set == 'Default' or card.ability.set == 'Enhanced' then str = str .. '_pc' end - info_queue[#info_queue + 1] = {set = 'Other', key = amount == 1 and str or str..'_plural', vars = {localize({type='variable', key= amount > 0 and 'a_chips' or 'a_chips_minus', vars ={math.abs(amount)}})}} - end -end -''' - - - -# G.FUNCS.draw_from_deck_to_hand() -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = "local hand_space = e or*" -position = "at" -payload = """local hand_space = e -local cards_to_draw = {} -local space_taken = 0 -local limit = G.hand.config.card_limit - #G.hand.cards - (SMODS.cards_to_draw or 0) -local flags = SMODS.calculate_context({drawing_cards = true, amount = limit}) -limit = flags.cards_to_draw or flags.modify or limit -local unfixed = not G.hand.config.fixed_limit -local n = 0 -while n < #G.deck.cards and limit > 0 do - local card = G.deck.cards[#G.deck.cards-n] - local mod = unfixed and (card.ability.card_limit - card.ability.extra_slots_used) or 0 - if limit - 1 + mod < 0 then - else - limit = limit - 1 + mod - table.insert(cards_to_draw, card) - space_taken = space_taken + (1 - mod) - end - n = n + 1 -end -hand_space = #cards_to_draw -""" -match_indent = true - -# Handle The Serpent blind drawing -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'before' -pattern = ''' -hand_space = math.min(#G.deck.cards, 3) -''' -payload = ''' -G.hand.config.card_limits.blind_restriction = hand_space - math.min(#G.deck.cards, 3) -''' -# The Serpent only affects refilling the hand -[[patches]] -[patches.pattern] -target = 'functions/state_events.lua' -match_indent = true -position = 'after' -pattern = ''' -if G.GAME.blind.name == 'The Serpent' and -''' -payload = ''' - G.STATE == G.STATES.DRAW_TO_HAND and -''' - - -# check_for_unlock -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = "if args.type == 'min_hand_size' and G.hand and G.hand.config.card_limit <= card.unlock_condition.extra then" -position = "at" -payload = "if args.type == 'min_hand_size' and G.hand and G.hand.config.true_card_limit <= card.unlock_condition.extra then" -match_indent = true - -# Challenge UI tooltip support -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'before' -pattern = ''' -card_w = joker_size*G.CARD_W, type = 'title_2', highlight_limit = 0}) -''' -payload = ''' -negative_info = 'joker', -''' -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'before' -pattern = ''' -card_w = joker_size*G.CARD_W, type = 'title_2', spread = true, highlight_limit = 0}) -''' -payload = ''' -negative_info = 'consumable', -''' -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'at' -pattern = ''' -{card_limit = #SUITS[suit_map[j]], type = 'title_2', view_deck = true, highlight_limit = 0, card_w = G.CARD_W*0.5, draw_layers = {'card'}})''' -payload = ''' -{card_limit = #SUITS[suit_map[j]], type = 'title_2', view_deck = true, highlight_limit = 0, card_w = G.CARD_W*0.5, draw_layers = {'card'}, negative_info = 'playing_card'})''' - -# Stop updating during run initialisation -[[patches]] -[patches.pattern] -target = 'cardarea.lua' -match_indent = true -position = 'at' -pattern = ''' -if #self.cards > self.config.card_limit then - if self == G.deck then - self.config.card_limit = #self.cards - end -end -''' -payload = ''' -if #self.cards > self.config.card_limit then - if self == G.deck and G.GAME.blind then - self.config.card_limit = #self.cards - end -end -''' diff --git a/lovely/cardarea.toml b/lovely/cardarea.toml deleted file mode 100644 index 5f75f9361..000000000 --- a/lovely/cardarea.toml +++ /dev/null @@ -1,30 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -# Change CardArea's background colour -# CardArea.draw -[[patches]] -[patches.pattern] -target = "cardarea.lua" -pattern = "{n=G.UIT.R, config={minw = self.T.w,minh = self.T.h,align = \"cm\", padding = 0.1, mid = true, r = 0.1, colour = self ~= G.shop_vouchers and {0,0,0,0.1} or nil, ref_table = self}, nodes={" -position = 'at' -match_indent = true -payload = ''' -{n=G.UIT.R, config={minw = self.T.w,minh = self.T.h,align = "cm", padding = 0.1, mid = true, r = 0.1, colour = self.config.bg_colour or self ~= G.shop_vouchers and {0,0,0,0.1} or nil, ref_table = self}, nodes={ -''' - -# Remove CardArea count -# CardArea.draw -[[patches]] -[patches.pattern] -target = "cardarea.lua" -pattern = ''' -local card_count = self ~= G.shop_vouchers and {n=G.UIT.R, config={align = self == G.jokers and 'cl' or self == G.hand and 'cm' or 'cr', padding = 0.03, no_fill = true}, nodes={ -''' -position = 'at' -match_indent = true -payload = ''' -local card_count = not self.config.no_card_count and self ~= G.shop_vouchers and {n=G.UIT.R, config={align = self == G.jokers and 'cl' or self == G.hand and 'cm' or 'cr', padding = 0.03, no_fill = true}, nodes={ -''' diff --git a/lovely/cardareas.toml b/lovely/cardareas.toml deleted file mode 100644 index fab4f98f0..000000000 --- a/lovely/cardareas.toml +++ /dev/null @@ -1,51 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -# Create custom card areas at appropriate timing via helper function -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'after' -pattern = ''' -G.playing_cards = {} - -set_screen_positions() -''' -payload = ''' --- Add align config to existing areas that should use it -self.jokers.config.align_buttons = true -self.consumeables.config.align_buttons = true - -for _, mod in ipairs(SMODS.mod_list) do - if mod.can_load and mod.custom_card_areas and type(mod.custom_card_areas) == "function" then - mod.custom_card_areas(self) - end -end -''' - -# Check area.config.align_buttons if an area should align buttons like normal -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -((self.area == G.jokers) or (self.area == G.consumeables)) and "cr" or -''' -payload = ''' -self.area.config.align_buttons and "cr" or -''' -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = ''' -((self.area == G.jokers) or (self.area == G.consumeables)) and {x=x_off - 0.4,y=0} or -''' -payload = ''' -self.area.config.align_buttons and {x=x_off - 0.4,y=0} or -''' \ No newline at end of file diff --git a/lovely/center.toml b/lovely/center.toml deleted file mode 100644 index 9625c41e8..000000000 --- a/lovely/center.toml +++ /dev/null @@ -1,639 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -### Center API - -# Card:set_ability() -[[patches]] -[patches.regex] -target = "card.lua" -pattern = "(?[\t ]*)if not G\\.OVERLAY_MENU then \n" -position = 'before' -payload = ''' -local obj = self.config.center -if obj.set_ability and type(obj.set_ability) == 'function' then - obj:set_ability(self, initial, delay_sprites) -end - -''' -line_prepend = '$indent' -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = "self.ability.bonus = (self.ability.bonus or 0) + (center.config.bonus or 0)" -position = "after" -payload = """ -for k, v in pairs(center.config) do - if k ~= 'bonus' then - if type(v) == 'table' then - self.ability[k] = copy_table(v) - else - self.ability[k] = v - end - end -end""" -match_indent = true - -# Card:calculate_joker() -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = '''function Card:calculate_joker(context) - if self.debuff then return nil end -''' -position = 'after' -payload = ''' - local obj = self.config.center - if self.ability.set ~= "Enhanced" and obj.calculate and type(obj.calculate) == 'function' then - local o, t = obj:calculate(self, context) - if o or t then return o, t end - end''' -match_indent = true - -# Card:update() -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = 'if G.STAGE == G.STAGES.RUN then' -position = 'before' -match_indent = true -payload = ''' -local obj = self.config.center -if obj.update and type(obj.update) == 'function' then - obj:update(self, dt) -end''' - -# Card:generate_UIBox_ability_table() -[[patches]] -[patches.regex] -target = 'card.lua' -pattern = "(?else)\n[\t ]*if self.ability.name == 'Loyalty Card' then\n[\t ]*self.ability.loyalty_remaining" -root_capture = 'else' -position = 'at' -payload = 'elseif context.joker_main then' - - -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = 'return generate_card_ui(self.config.center, nil, loc_vars, card_type, badges, hide_desc, main_start, main_end)' -position = 'at' -match_indent = true -payload = 'return generate_card_ui(self.config.center, nil, loc_vars, card_type, badges, hide_desc, main_start, main_end, self)' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "full_UI_table.name = localize{type = 'name', set = _c.set, key = _c.key, nodes = full_UI_table.name}" -position = 'at' -match_indent = true -payload = ''' -if not _c.generate_ui or type(_c.generate_ui) ~= 'function' then - full_UI_table.name = localize{type = 'name', set = _c.set, key = _c.key, nodes = full_UI_table.name} -end''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "elseif specific_vars and specific_vars.debuffed then" -position = 'before' -match_indent = true -payload = ''' -elseif _c.generate_ui and type(_c.generate_ui) == 'function' then - local specific_vars = specific_vars or {} - _c:generate_ui(info_queue, card, desc_nodes, specific_vars, full_UI_table) - if desc_nodes ~= full_UI_table.main and not desc_nodes.name then - -- TODO should be moved into generate_ui; - -- also the multiple generate_ui cases should be refactored - -- to work off a base implementation - desc_nodes.name_styled = {} - local set = name_override and "Other" or _c.set - local key = name_override or _c.key - if set == "Seal" then - if G.localization.descriptions["Other"][_c.key.."_seal"] then set = "Other"; key = key.."_seal" end - else - if not G.localization.descriptions[set] or not G.localization.descriptions[set][_c.key] then set = "Other" end - end - - localize{type = 'name', key = key, set = set, nodes = desc_nodes.name_styled, fixed_scale = 0.63, no_pop_in = true, no_shadow = true, y_offset = 0, no_spacing = true, no_bump = true, vars = (_c.create_fake_card and _c.loc_vars and (_c:loc_vars({}, _c:create_fake_card()) or {}).vars) or {colours = {}}} - desc_nodes.name_styled = SMODS.info_queue_desc_from_rows(desc_nodes.name_styled, true) - desc_nodes.name_styled.config.align = "cm" - end - if specific_vars and specific_vars.pinned then info_queue[#info_queue+1] = {key = 'pinned_left', set = 'Other'} end - if specific_vars and specific_vars.sticker then info_queue[#info_queue+1] = {key = string.lower(specific_vars.sticker)..'_sticker', set = 'Other'} end''' - -[[patches]] -[patches.regex] -target = 'functions/common_events.lua' -pattern = "(?[\t ]+)if (?_c.name == 'Golden Ticket' then)" -line_prepend = '$indent' -position = 'at' -payload = ''' -local res = {} -if _c.locked_loc_vars and type(_c.locked_loc_vars) == 'function' then - local _card = _c.create_fake_card and _c:create_fake_card() - res = _c:locked_loc_vars(info_queue, _card) or {} - loc_vars = res.vars or {} - specific_vars = specific_vars or {} - specific_vars.not_hidden = res.not_hidden or specific_vars.not_hidden - if res.main_start then desc_nodes[#desc_nodes+1] = res.main_start end - main_end = res.main_end or main_end -elseif $rest''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -position = 'at' -match_indent = true -pattern = "localize{type = 'unlocks', key = 'joker_locked_legendary', set = 'Other', nodes = desc_nodes, vars = loc_vars}" -payload = "localize{type = 'unlocks', key = res.key or 'joker_locked_legendary', set = res.set or 'Other', nodes = desc_nodes, vars = loc_vars, text_colour = res.text_colour, scale = res.scale}" - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -position = 'at' -match_indent = true -pattern = "localize{type = 'unlocks', key = _c.key, set = _c.set, nodes = desc_nodes, vars = loc_vars}" -payload = "localize{type = 'unlocks', key = res.key or _c.key, set = res.set or _c.set, nodes = desc_nodes, vars = loc_vars, text_colour = res.text_colour, scale = res.scale}" - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -position = 'at' -match_indent = true -pattern = 'elseif desc_nodes ~= full_UI_table.main then' -payload = 'elseif desc_nodes ~= full_UI_table.main and not desc_nodes.name then' - - -# check_for_unlock() -[[patches]] -[patches.regex] -target = 'functions/common_events.lua' -pattern = "(?[\t ]*)if not card.unlocked and card.unlock_condition and args.type == 'career_stat' then" -line_prepend = '$indent' -position = 'before' -payload = ''' - -local custom_check -if not card.unlocked and card.check_for_unlock and type(card.check_for_unlock) == 'function' then - ret = card:check_for_unlock(args) - if ret then unlock_card(card) end - custom_check = true -end''' - -[[patches]] -[patches.regex] -target = 'functions/common_events.lua' -pattern = "(?[\t ]*)if(? )not card.unlocked and card.unlock_condition and args.type == 'career_stat' then" -position = 'at' -root_capture = 'a' -payload = ' not custom_check and ' - -[[patches]] -[patches.regex] -target = 'functions/common_events.lua' -pattern = "(?[\t ]*)if(? )not card.unlocked and card.unlock_condition and card.unlock_condition.type == args.type then" -position = 'at' -root_capture = 'a' -payload = ' not custom_check and ' - -#Card:use_consumable() -[[patches]] -[patches.regex] -target = 'card.lua' -pattern = "(?[\t ]*)if self.ability.consumeable.mod_conv or self.ability.consumeable.suit_conv then" -line_prepend = '$indent' -position = 'before' -payload = ''' -local obj = self.config.center -if obj.use and type(obj.use) == 'function' then - obj:use(self, area, copier) - return -end''' - -# Card:can_use_consumable() -[[patches]] -[patches.regex] -target = 'card.lua' -pattern = "(?[\t ]*)if self.ability.name == 'The Hermit' or self.ability.consumeable.hand_type" -line_prepend = '$indent' -position = 'before' -payload = ''' -local obj = self.config.center -if obj.can_use and type(obj.can_use) == 'function' then - return obj:can_use(self) -end''' - -# G.UIDEF.card_h_popup() -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = "(?[\t ]*)(?if AUT.badges.card_type or AUT.badges.force_rarity then)\n[\t ]*(?.*)\n[\t ]*end" -line_prepend = '$indent' -position = 'at' -payload = ''' -local obj = card.config.center -$if - if obj and (obj.set_card_type_badge or obj.type and obj.type.set_card_type_badge) then - if obj.type and type(obj.type.set_card_type_badge) == 'function' then - obj.type:set_card_type_badge(obj, card, badges) - end - if type(obj.set_card_type_badge) == 'function' then - obj:set_card_type_badge(card, badges) - end - else - $rest - end -end -if obj and obj.set_badges and type(obj.set_badges) == 'function' then - obj:set_badges(card, badges) -end''' - -# mod badges -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -match_indent = true -position = 'before' -pattern = ''' -if AUT.info then -''' -payload = ''' -if AUT.card_type ~= 'Locked' and AUT.card_type ~= 'Undiscovered' then - SMODS.create_mod_badges(card.config.center, badges) - if card.base then - SMODS.create_mod_badges(SMODS.Ranks[card.base.value], badges) - SMODS.create_mod_badges(SMODS.Suits[card.base.suit], badges) - end - if card.config and card.config.tag then - SMODS.create_mod_badges(SMODS.Tags[card.config.tag.key], badges) - end - badges.mod_set = nil -end -''' - -# set_discover_tallies() -[[patches]] -[patches.regex] -target = 'functions/misc_functions.lua' -pattern = "(?[\t ]*)if v.set == 'Planet' then(\n[\t ]*.*){15}" -line_prepend = '$indent' -position = 'at' -payload = ''' -local tally = G.DISCOVER_TALLIES[v.set:lower()..'s'] -if tally then - tally.of = tally.of + 1 - if v.discovered then - tally.tally = tally.tally + 1 - end -end''' - -[[patches]] -[patches.regex] -target = 'functions/misc_functions.lua' -pattern = "[\t ]*tarots = \\{tally = 0, of = 0\\},\n(.*\n){2}" -line_prepend = '$indent' -position = 'at' -payload = '' - -[[patches]] -[patches.regex] -target = 'functions/misc_functions.lua' -pattern = "(?[\t ]*)for _, v in pairs\\(G.DISCOVER_TALLIES\\) do" -line_prepend = '$indent' -position = 'before' -payload = ''' -for _, v in ipairs(SMODS.ConsumableType.obj_buffer) do - G.DISCOVER_TALLIES[v:lower()..'s'] = {tally = 0, of = 0} -end''' - -# create_UIBox_your_collection() -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = "(?[\t ]*)local t = create_UIBox_generic_options\\(\\{ back_func = G.STAGE" -line_prepend = '$indent' -position = 'before' -payload = ''' -local consumable_nodes = {} -if #SMODS.ConsumableType.visible_buffer <= 3 then - for _, key in ipairs(SMODS.ConsumableType.visible_buffer) do - local id = 'your_collection_'..key:lower()..'s' - consumable_nodes[#consumable_nodes+1] = UIBox_button({button = id, label = {localize('b_'..key:lower()..'_cards')}, count = G.DISCOVER_TALLIES[key:lower()..'s'], minw = 4, id = id, colour = G.C.SECONDARY_SET[key]}) - end -else - consumable_nodes[#consumable_nodes+1] = UIBox_button({ button = 'your_collection_consumables', label = {localize('b_stat_consumables'), localize{ type = 'variable', key = 'c_types', vars = {#SMODS.ConsumableType.visible_buffer} } }, count = G.DISCOVER_TALLIES['consumeables'], minw = 4, minh = 4, id = 'your_collection_consumables', colour = G.C.FILTER }) -end -''' - -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = "(?[\t ]*)nodes=\\{\n[\t ]*UIBox_button\\(\\{button = 'your_collection_tarots'(.*\n){3}[\t ]*}" -line_prepend = '$indent' -position = 'at' -payload = 'nodes = consumable_nodes' - -# Card:apply_to_run() -[[patches]] -[patches.regex] -target = 'card.lua' -pattern = "(?[\t ]*)if center_table.name == 'Overstock'" -line_prepend = '$indent' -position = 'before' -payload = ''' -local obj = center or self.config.center -if obj.redeem and type(obj.redeem) == 'function' then - obj:redeem(card_to_save) - return -end''' - -# create_card_for_shop() -[[patches]] -[patches.pattern] -target = 'functions/UI_definitions.lua' -pattern = "local total_rate = G.GAME.joker_rate + G.GAME.tarot_rate + G.GAME.planet_rate + G.GAME.playing_card_rate + G.GAME.spectral_rate" -match_indent = true -position = 'at' -payload = ''' -local total_rate = G.GAME.joker_rate + G.GAME.playing_card_rate -for _,v in ipairs(SMODS.ConsumableType.obj_buffer) do - total_rate = total_rate + G.GAME[v:lower()..'_rate'] -end''' - -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = '(?[\t ]*)for _, v in ipairs\((?
  • \{\n(.*\n){5}[\t ]*\})\) do' -line_prepend = '$indent' -position = 'at' -payload = ''' --- need to preserve order to leave RNG unchanged -local rates = $li -for _, v in ipairs(SMODS.ConsumableType.obj_buffer) do - if not (v == 'Tarot' or v == 'Planet' or v == 'Spectral') then - table.insert(rates, { type = v, val = G.GAME[v:lower()..'_rate'] }) - end -end -for _, v in ipairs(rates) do''' - -# create_card() -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "if not forced_key and soulable and (not G.GAME.banned_keys['c_soul']) then" -match_indent = true -position = 'after' -payload = ''' - local soul_total_rate = 0 - local non_soul_rate = 1 - local modded_souls = {} - for _, v in ipairs(SMODS.Consumable.legendaries) do - if (_type == v.type.key or _type == v.soul_set) and not (G.GAME.used_jokers[v.key] and not SMODS.showman(v.key) and not v.can_repeat_soul) and SMODS.add_to_pool(v) then - soul_total_rate = soul_total_rate + v.soul_rate - non_soul_rate = non_soul_rate * (1 - v.soul_rate) - non_soul_rate = math.max(non_soul_rate, 0) - table.insert(modded_souls, v) - end - end - local roll = pseudorandom('soul_smods_'.._type..G.GAME.round_resets.ante) - local threshold = 1 - for _, v in ipairs(modded_souls) do - threshold = threshold - v.soul_rate/soul_total_rate * (1-non_soul_rate) - if roll > threshold then - forced_key = v.key - break - end - end''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "local card = Card(area.T.x + area.T.w/2, area.T.y, G.CARD_W, G.CARD_H, front, center," -match_indent = true -position = 'at' -payload = ''' -local card = Card(area.T.x + area.T.w/2, area.T.y, G.CARD_W, G.CARD_H, SMODS.set_create_card_front or front, center,''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "{bypass_discovery_center = area==G.shop_jokers or area == G.pack_cards or area == G.shop_vouchers or (G.shop_demo and area==G.shop_demo) or area==G.jokers or area==G.consumeables," -match_indent = true -position = 'at' -payload = ''' -{bypass_discovery_center = SMODS.bypass_create_card_discovery_center or area==G.shop_jokers or area == G.pack_cards or area == G.shop_vouchers or (G.shop_demo and area==G.shop_demo) or area==G.jokers or area==G.consumeables,''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "bypass_discovery_ui = area==G.shop_jokers or area == G.pack_cards or area==G.shop_vouchers or (G.shop_demo and area==G.shop_demo)," -match_indent = true -position = 'at' -payload = ''' -bypass_discovery_ui = SMODS.bypass_create_card_discovery_center or area==G.shop_jokers or area == G.pack_cards or area==G.shop_vouchers or (G.shop_demo and area==G.shop_demo),''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = "discover = area==G.jokers or area==G.consumeables, " -match_indent = true -position = 'at' -payload = ''' -discover = SMODS.bypass_create_card_discover or area==G.jokers or area==G.consumeables, ''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = '''not (G.GAME.used_jokers['c_soul'] and not next(find_joker("Showman"))) then''' -match_indent = true -position = 'at' -payload = ''' -not (G.GAME.used_jokers['c_soul'] and not SMODS.showman('c_soul')) then''' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = '''not (G.GAME.used_jokers['c_black_hole'] and not next(find_joker("Showman"))) then''' -match_indent = true -position = 'at' -payload = ''' -not (G.GAME.used_jokers['c_black_hole'] and not SMODS.showman('c_black_hole')) then''' - -# Fix vanilla copy_card back bug -# copy_card() -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = ''' -local new_card = new_card or Card(other.T.x, other.T.y, G.CARD_W*(card_scale or 1), G.CARD_H*(card_scale or 1), G.P_CARDS.empty, G.P_CENTERS.c_base, {playing_card = playing_card}) -''' -position = "at" -payload = ''' -local new_card = new_card or Card(other.T.x, other.T.y, G.CARD_W*(card_scale or 1), G.CARD_H*(card_scale or 1), G.P_CARDS.empty, G.P_CENTERS.c_base, {playing_card = playing_card, bypass_back = G.GAME.selected_back.pos}) -''' -match_indent = true - -# Card:add_to_deck() -[[patches]] -[patches.regex] -target = 'card.lua' -pattern = '(?[\t ]*)if self.ability.h_size ~= 0 then\n[\t ]*G\.hand:change_size\(self.ability.h_size\)' -line_prepend = '$indent' -position = 'before' -payload = ''' -local obj = self.config.center -if obj and obj.add_to_deck and type(obj.add_to_deck) == 'function' then - obj:add_to_deck(self, from_debuff) -end''' - -# Card:remove_from_deck() -[[patches]] -[patches.regex] -target = 'card.lua' -pattern = '(?[\t ]*)if self.ability.h_size ~= 0 then\n[\t ]*G\.hand:change_size\(-self.ability.h_size\)' -line_prepend = '$indent' -position = 'before' -payload = ''' -local obj = self.config.center -if obj and obj.remove_from_deck and type(obj.remove_from_deck) == 'function' then - obj:remove_from_deck(self, from_debuff) -end''' - -# G.FUNCS.use_card() -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -match_indent = true -position = 'at' -pattern = ''' -if G.booster_pack and not G.booster_pack.alignment.offset.py and (card.ability.consumeable or not (G.GAME.pack_choices and G.GAME.pack_choices > 1)) then -''' -payload = ''' -local nc -local select_to = card.area == G.pack_cards and G.pack_cards and booster_obj and SMODS.card_select_area(card, booster_obj) and card:selectable_from_pack(booster_obj) -if card.ability.consumeable and not select_to then - local obj = card.config.center - if obj.keep_on_use and type(obj.keep_on_use) == 'function' then - nc = obj:keep_on_use(card) - end -end -if G.booster_pack and not G.booster_pack.alignment.offset.py and ((not select_to and card.ability.consumeable) or not (G.GAME.pack_choices and G.GAME.pack_choices > 1)) then - -''' - - -# G.FUNCS.use_card() -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = "if card.area then card.area:remove_card(card) end" -match_indent = true -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' -match_indent = true -position = 'before' -pattern = ''' -if area and area.cards[1] then -''' -payload = ''' -if nc and not area then G.consumeables:emplace(card) end -''' -[[patches]] -[patches.pattern] -target = 'functions/button_callbacks.lua' -pattern = "else draw_card(G.hand, G.play, 1, 'up', true, card, nil, mute) end" -match_indent = true -position = 'at' -payload = '''elseif not nc then draw_card(G.hand, G.play, 1, 'up', true, card, nil, mute) end''' - - -# Card:load() -[[patches]] -[patches.pattern] -target = 'card.lua' -pattern = 'if self.config.center.name == "Half Joker" then' -match_indent = true -position = 'at' -payload = ''' -local obj = self.config.center -if obj.load and type(obj.load) == 'function' then - obj:load(self, cardTable, other_card) -elseif self.config.center.name == "Half Joker" then''' - -# Card:calculate_dollar_bonus() -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = """function Card:calculate_dollar_bonus() - if self.debuff then return end""" -position = "after" -match_indent = true -payload = ''' - local obj = self.config.center - if obj.calc_dollar_bonus and type(obj.calc_dollar_bonus) == 'function' then - return obj:calc_dollar_bonus(self) - end -''' - -# extract joker loc_vars -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'at' -pattern = 'function Card:generate_UIBox_ability_table()' -payload = 'function Card:generate_UIBox_ability_table(vars_only)' - -[[patches]] -[patches.pattern] -target = 'card.lua' -match_indent = true -position = 'before' -pattern = 'local badges = {}' -payload = 'if vars_only then return loc_vars, main_start, main_end end' - -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'after' -pattern = "elseif _c.set == 'Joker' then" -payload = ''' - if not card then - local ability = copy_table(_c.config) - ability.set = 'Joker' - ability.name = _c.name - -- temporary stopgap. fake cards should be implemented better - ability.x_mult = _c['config'].Xmult or _c['config'].x_mult - if ability.name == 'To Do List' then - ability.to_do_poker_hand = "High Card" -- fallback - end - local ret = {Card.generate_UIBox_ability_table({ ability = ability, config = { center = _c }, bypass_lock = true}, true)} - specific_vars = ret[1] - if ret[2] then desc_nodes[#desc_nodes+1] = ret[2] end - main_end = ret[3] - end - ''' - -## Handle tracking cusotm consumable usage -[[patches]] -[patches.pattern] -target = 'functions/misc_functions.lua' -pattern = ''' -elseif card.config.center.set == 'Spectral' then G.GAME.consumeable_usage_total.spectral = G.GAME.consumeable_usage_total.spectral + 1 -''' -position = 'after' -payload = ''' -else - G.GAME.consumeable_usage_total[card.config.center.set:lower()] = G.GAME.consumeable_usage_total[card.config.center.set:lower()] or 0 - G.GAME.consumeable_usage_total[card.config.center.set:lower()] = G.GAME.consumeable_usage_total[card.config.center.set:lower()] + 1 -''' -match_indent = true \ No newline at end of file diff --git a/lovely/challenge.toml b/lovely/challenge.toml deleted file mode 100644 index 033c9a36f..000000000 --- a/lovely/challenge.toml +++ /dev/null @@ -1,93 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -5 - -# function G.UIDEF.challenge_list_page() -[[patches]] -[patches.pattern] -target = "functions/UI_definitions.lua" -pattern = "local challenge_unlocked = G.PROFILES[G.SETTINGS.profile].challenges_unlocked and (G.PROFILES[G.SETTINGS.profile].challenges_unlocked >= k)" -position = 'at' -payload = "local challenge_unlocked = SMODS.challenge_is_unlocked(v, k)" -match_indent = true - -# function G.UIDEF.challenges() - fix challenge unlock count -[[patches]] -[patches.pattern] -target = "functions/UI_definitions.lua" -pattern = """ -local _ch_tab = {comp = _ch_comp, unlocked = G.PROFILES[G.SETTINGS.profile].challenges_unlocked} -""" -position = 'at' -payload = """ -local unlock_count = 0 -for k, v in ipairs(G.CHALLENGES) do - if SMODS.challenge_is_unlocked(v, k) then - unlock_count = unlock_count + 1 - end -end -local _ch_tab = {comp = _ch_comp, unlocked = unlock_count} -""" -match_indent = true - -# Add button colour -[[patches]] -[patches.pattern] -target = "functions/UI_definitions.lua" -pattern = "UIBox_button({id = k, col = true, label = {challenge_unlocked and localize(v.id, 'challenge_names') or localize('k_locked'),}, button = challenge_unlocked and 'change_challenge_description' or 'nil', colour = challenge_unlocked and G.C.RED or G.C.GREY, minw = 4, scale = 0.4, minh = 0.6, focus_args = {snap_to = not snapped}})," -position = 'at' -match_indent = true -payload = ''' -UIBox_button({id = k, col = true, label = {challenge_unlocked and localize(v.id, 'challenge_names') or localize('k_locked'),}, button = challenge_unlocked and 'change_challenge_description' or 'nil', text_colour = v.text_colour, colour = challenge_unlocked and (v.button_colour or G.C.RED) or G.C.GREY, minw = 4, scale = 0.4, minh = 0.6, focus_args = {snap_to = not snapped}}), -''' - -[[patches]] -[patches.pattern] -target = "game.lua" -pattern = '''if _ch.restrictions.banned_cards then''' -position = "after" -payload = ''' if type(_ch.restrictions.banned_cards) == 'function' then - _ch.restrictions.banned_cards = _ch.restrictions.banned_cards() - end''' -match_indent = true -times = 1 - -[[patches]] -[patches.pattern] -target = "game.lua" -pattern = '''if _ch.restrictions.banned_tags then''' -position = "after" -payload = ''' - if type(_ch.restrictions.banned_tags) == 'function' then - _ch.restrictions.banned_tags = _ch.restrictions.banned_tags() - end''' -match_indent = true -times = 1 - -[[patches]] -[patches.pattern] -target = "game.lua" -pattern = '''if _ch.restrictions.banned_other then''' -position = "after" -payload = ''' - if type(_ch.restrictions.banned_other) == 'function' then - _ch.restrictions.banned_other = _ch.restrictions.banned_other() - end''' -match_indent = true -times = 1 - -# apply -[[patches]] -[patches.pattern] -target = "game.lua" -pattern = ''' -local _ch = args.challenge -''' -position = "after" -payload = ''' -if _ch.apply and type(_ch.apply) == "function" then - _ch:apply() -end -''' -match_indent = true \ No newline at end of file diff --git a/lovely/compact_cashout.toml b/lovely/compact_cashout.toml deleted file mode 100644 index 98676f316..000000000 --- a/lovely/compact_cashout.toml +++ /dev/null @@ -1,65 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -5 - - -# -# End of round money -# - -# Hide off screen rows - -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = "if config.name ~= 'bottom' then" -position = "after" -payload = ''' - total_cashout_rows = (total_cashout_rows or 0) + 1 - if total_cashout_rows > 7 then - return - end''' -match_indent = true - -# Reset rows amount - -[[patches]] -[patches.regex] -target = "functions/state_events.lua" -pattern = 'G\.FUNCS\.evaluate_round = function\(\)' -position = "after" -payload = ''' - - total_cashout_rows = 0''' - -# Add UI row with total rows hidden - -[[patches]] -[patches.pattern] -target = "functions/state_events.lua" -pattern = "add_round_eval_row({name = 'bottom', dollars = dollars})" -position = "before" -payload = ''' -if total_cashout_rows > 7 then - local total_hidden = total_cashout_rows - 7 - - G.E_MANAGER:add_event(Event({ - trigger = 'before',delay = 0.38, - func = function() - local hidden = {n=G.UIT.R, config={align = "cm"}, nodes={ - {n=G.UIT.O, config={object = DynaText({ - string = {localize{type = 'variable', key = 'cashout_hidden', vars = {total_hidden}}}, - colours = {G.C.WHITE}, shadow = true, float = false, - scale = 0.45, - font = G.LANGUAGES['en-us'].font, pop_in = 0 - })}} - }} - - G.round_eval:add_child(hidden, G.round_eval:get_UIE_by_ID('bonus_round_eval')) - return true - end - })) -end''' -match_indent = true - diff --git a/lovely/compat_0_9_8.toml b/lovely/compat_0_9_8.toml deleted file mode 100644 index a61c552ef..000000000 --- a/lovely/compat_0_9_8.toml +++ /dev/null @@ -1,38 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -5 - -# fallback for card.ability.name -# Card:set_ability() -[[patches]] -[patches.pattern] -target = "card.lua" -pattern = "self.ability.bonus = (self.ability.bonus or 0) + (center.config.bonus or 0)" -position = 'after' -payload = "if not self.ability.name then self.ability.name = center.key end" -match_indent = true - -# generate_card_ui() -# `card_type` is used to check whether card should be nil; non-recursive calls -# to generate_card_ui always have that arg set -[[patches]] -[patches.regex] -target = "functions/common_events.lua" -pattern = '(?[\t ]*)function generate_card_ui\([^)]*\)\n' -position = "after" -line_prepend = '$indent' -payload = """ - if card == nil and card_type then - card = SMODS.compat_0_9_8.generate_UIBox_ability_table_card - end - -""" -[[patches]] -[patches.pattern] -target = "functions/common_events.lua" -pattern = "for _, v in ipairs(info_queue) do" -position = 'before' -payload = "SMODS.compat_0_9_8.generate_UIBox_ability_table_card = nil" -match_indent = true - diff --git a/lovely/core.toml b/lovely/core.toml deleted file mode 100644 index 38932c104..000000000 --- a/lovely/core.toml +++ /dev/null @@ -1,28 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -5 - -[[patches]] -[patches.pattern] -target = "game.lua" -pattern = "self.SPEEDFACTOR = 1" -position = "after" -payload = 'require "SMODS.preflight.loader".initSteamodded()' -match_indent = true - -[[patches]] -[patches.copy] -target = "main.lua" -position = "append" -sources = ["src/core.lua"] - -[[patches]] -[patches.module] -source = "version.lua" -name = "SMODS.version" - -[[patches]] -[patches.module] -source = "release.lua" -name = "SMODS.release" diff --git a/lovely/crash_handler.toml b/lovely/crash_handler.toml deleted file mode 100644 index 3f9dca29d..000000000 --- a/lovely/crash_handler.toml +++ /dev/null @@ -1,32 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -10 - -[[patches]] -[patches.pattern] -target = "main.lua" -pattern = "function love.errhand(msg)" -position = "at" -payload = "if false then" -match_indent = true - -[[patches]] -[patches.copy] -target = "main.lua" -position = "prepend" -sources = [ - "src/crash_handler.lua", -] - -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'after' -pattern = ''' -local saveTable = args.savetext or nil -''' -payload = ''' -if G.SAVED_GAME then SMODS.save_game = G.SAVED_GAME.GAME.smods_version else SMODS.save_game = nil end -''' diff --git a/lovely/create_sprite.toml b/lovely/create_sprite.toml deleted file mode 100644 index fb427dbca..000000000 --- a/lovely/create_sprite.toml +++ /dev/null @@ -1,97 +0,0 @@ -[manifest] -version = "1.0.0" -dump_lua = true -priority = -5 - - -### Sprite/AnimatedSprite or G.ASSET_ATLAS/ANIMATION_ATLAS -> SMODS.create_sprite() - -# Game:set_render_settings() -[[patches]] -[patches.pattern] -target = 'game.lua' -pattern = ''' - self.ANIMATION_ATLAS[self.animation_atli[i].name].frames = self.animation_atli[i].frames -''' -position = 'after' -payload = ''' -self.ANIMATION_ATLAS[self.animation_atli[i].name].atlas_table = "ANIMATION_ATLAS" -''' -match_indent = true - -# Add custom FPS support -[[patches]] -[patches.pattern] -target = 'engine/animatedsprite.lua' -match_indent = true -position = 'at' -pattern = '''local new_frame = math.floor(G.ANIMATION_FPS*(G.TIMERS.REAL - self.offset_seconds))%self.current_animation.frames''' -payload = '''local new_frame = math.floor(self.current_animation.FPS*(G.TIMERS.REAL - self.offset_seconds))%self.current_animation.frames''' -[[patches]] -[patches.pattern] -target = 'engine/animatedsprite.lua' -match_indent = true -position = 'after' -pattern = '''frames = self.animation.frames,''' -payload = '''FPS = self.atlas.fps or G.ANIMATION_FPS,''' - - - - -# Case: ..ATLAS[] -# Blind:init() -[[patches]] -[patches.pattern] -target = 'blind.lua' -pattern = ''' - self.children.animatedSprite = AnimatedSprite(self.T.x, self.T.y, self.T.w, self.T.h, G.ANIMATION_ATLAS['blind_chips'], G.P_BLINDS.bl_small.pos) -''' -position = 'at' -payload = ''' -self.children.animatedSprite = SMODS.create_sprite(self.T.x, self.T.y, self.T.w, self.T.h, 'blind_chips', G.P_BLINDS.bl_small.pos) -''' -match_indent = true -# Tag:generate_UI() -[[patches]] -[patches.pattern] -target = 'tag.lua' -pattern = ''' - local tag_sprite = Sprite(0,0,_size*1,_size*1,G.ASSET_ATLAS[(not self.hide_ability) and G.P_TAGS[self.key].atlas or "tags"], (self.hide_ability) and G.tag_undiscovered.pos or self.pos) -''' -position = 'at' -payload = ''' -local tag_sprite = SMODS.create_sprite(0, 0, _size*1, _size*1, SMODS.get_atlas((not self.hide_ability) and G.P_TAGS[self.key].atlas or "tags"), (self.hide_ability) and G.tag_undiscovered.pos or self.pos) -''' -match_indent = true -# functions/common_events.lua -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = ''' - local blind_sprite = AnimatedSprite(0, 0, 1.2, 1.2, G.ANIMATION_ATLAS[obj.atlas] or G.ANIMATION_ATLAS['blind_chips'], copy_table(G.GAME.blind.pos)) -''' -position = 'at' -payload = ''' -local blind_sprite = SMODS.create_sprite(0, 0, 1.2, 1.2, obj.atlas or 'blind_chips', copy_table(G.GAME.blind.pos)) -''' -match_indent = true -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -pattern = ''' - local blind_sprite = Sprite(0, 0, 0.7,0.7, G.ASSET_ATLAS['tags'], copy_table(config.pos)) -''' -position = 'at' -payload = ''' -local blind_sprite = SMODS.create_sprite(0, 0, 0.7,0.7, 'tags', copy_table(config.pos)) -''' -match_indent = true -# functions/UI_definitions.lua -[[patches]] -[patches.regex] -target = 'functions/UI_definitions.lua' -pattern = '(?[\t ].*)(?:Animated)Sprite\((?
    .*)G\.(?:ASSET|ANIMATION)_ATLAS\[(?.*)\],(?.*)'
    -position = 'at'
    -payload = '''
    -$start SMODS.create_sprite($pre $key, $rest 
    -'''
    \ No newline at end of file
    diff --git a/lovely/deck_skins.toml b/lovely/deck_skins.toml
    deleted file mode 100644
    index 1ad2fcae8..000000000
    --- a/lovely/deck_skins.toml
    +++ /dev/null
    @@ -1,110 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -#========================================================#
    -# Choose any rank for custom deck and use provided atlas #
    -#========================================================#
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/UI_definitions.lua"
    -pattern = '''function create_UIBox_customize_deck()([\s\S]*?)end'''
    -position = "at"
    -payload = '''
    -function create_UIBox_customize_deck()
    -  local suitTabs = {}
    -
    -  local index = 1
    -  for i, suit in ipairs(SMODS.Suit:obj_list(true)) do
    -    if G.COLLABS.options[suit.key] then
    -        suitTabs[index] = {
    -                    label = localize(suit.key, 'suits_plural'),
    -                    tab_definition_function = G.UIDEF.custom_deck_tab,
    -                    tab_definition_function_args = suit.key
    -                }
    -        index = index + 1
    -    end
    -  end
    -
    -  if suitTabs[1] then
    -    suitTabs[1].chosen = true
    -  end
    -
    -  local t = create_UIBox_generic_options({ back_func = 'options', snap_back = nil, contents = {
    -    {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={
    -      create_tabs(
    -        {tabs = suitTabs, snap_to_nav = true, no_shoulders = true}
    -    )}}}
    -  })
    -
    -  return t
    -end
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = '''
    -if specific_vars.nominal_chips then
    -    localize{type = 'other', key = 'card_chips', nodes = desc_nodes, vars = {specific_vars.nominal_chips}}
    -end
    -if specific_vars.bonus_chips then
    -    localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {specific_vars.bonus_chips}}
    -end
    -'''
    -position = "at"
    -payload = '''
    -if card.area == G.cdds_cards and card.generate_ds_card_ui and type(card.generate_ds_card_ui) == 'function' and card.deckskin and card.palette then
    -    card.generate_ds_card_ui(card, card.deckskin, card.palette, info_queue, desc_nodes, specific_vars, full_UI_table)
    -else
    -    if specific_vars.nominal_chips then
    -        localize{type = 'other', key = 'card_chips', nodes = desc_nodes, vars = {specific_vars.nominal_chips}}
    -    end
    -    if specific_vars.bonus_chips then
    -        localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {specific_vars.bonus_chips}}
    -    end
    -end
    -'''
    -match_indent = true
    -
    -#=======================#
    -# DeckSkin Crediting UI #
    -#=======================#
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''
    -label = "Production",
    -chosen = true,
    -'''
    -position = "at"
    -payload = '''
    -label = "Production",
    -chosen = not SMODS.init_collab_credits,
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''label = "Collabs",'''
    -position = "after"
    -payload = '''chosen = SMODS.init_collab_credits,'''
    -match_indent = true
    -
    -# Add note for overriden function
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -function G.UIDEF.custom_deck_tab(_suit)
    -'''
    -payload = '''
    --- WARNING
    --- This function is overriden by SMODS and can be found in src/ui.lua
    --- WARNING
    -'''
    diff --git a/lovely/dollar_row.toml b/lovely/dollar_row.toml
    deleted file mode 100644
    index 0a02b1c73..000000000
    --- a/lovely/dollar_row.toml
    +++ /dev/null
    @@ -1,77 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Dollar row patches (API removed)
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if num_dollars > 60 then"
    -position = "at"
    -payload = '''
    -if num_dollars > 60 or num_dollars < -60 then'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "local dollar_string = localize('$')..num_dollars"
    -position = "at"
    -payload = '''
    -if num_dollars < 0 then --if negative
    -    G.E_MANAGER:add_event(Event({
    -        trigger = 'before',delay = 0.38,
    -        func = function()
    -            G.round_eval:add_child(
    -                {n=G.UIT.R, config={align = "cm", id = 'dollar_row_'..(dollar_row+1)..'_'..config.name}, nodes={
    -                    {n=G.UIT.O, config={object = DynaText({string = {localize('$')..format_ui_value(num_dollars)}, colours = {G.C.RED}, shadow = true, pop_in = 0, scale = 0.65, float = true})}}
    -                }},
    -                G.round_eval:get_UIE_by_ID('dollar_'..config.name))
    -            play_sound('coin3', 0.9+0.2*math.random(), 0.7)
    -            play_sound('coin6', 1.3, 0.8)
    -            return true
    -        end
    -    }))
    -else --if positive
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "for i = 1, num_dollars or 1 do"
    -position = "at"
    -payload = '''
    -local dollars_to_loop
    -if num_dollars < 0 then dollars_to_loop = (num_dollars*-1)+1 else dollars_to_loop = num_dollars end
    -for i = 1, dollars_to_loop do'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/common_events.lua"
    -pattern = '''(?[\t ]*)else\n[\t ]*local dollars_to_loop'''
    -position = "before"
    -line_prepend = "$indent"
    -payload = '''
    ---asdf
    -end'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "local r = {n=G.UIT.T, config={text = localize('$'), colour = G.C.MONEY, scale = ((num_dollars > 20 and 0.28) or (num_dollars > 9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}}"
    -position = "at"
    -payload = '''
    -local r
    -if i == 1 and num_dollars < 0 then
    -    r = {n=G.UIT.T, config={text = '-', colour = G.C.RED, scale = ((num_dollars < -20 and 0.28) or (num_dollars < -9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}}
    -    play_sound('coin3', 0.9+0.2*math.random(), 0.7 - (num_dollars < -20 and 0.2 or 0))
    -else
    -    if num_dollars < 0 then r = {n=G.UIT.T, config={text = localize('$'), colour = G.C.RED, scale = ((num_dollars > 20 and 0.28) or (num_dollars > 9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}}
    -    else r = {n=G.UIT.T, config={text = localize('$'), colour = G.C.MONEY, scale = ((num_dollars > 20 and 0.28) or (num_dollars > 9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}} end
    -end'''
    -match_indent = true
    diff --git a/lovely/easings.toml b/lovely/easings.toml
    deleted file mode 100644
    index 9da752454..000000000
    --- a/lovely/easings.toml
    +++ /dev/null
    @@ -1,52 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -## Adds extra easings to the "ease" event type
    -[[patches]]
    -[patches.pattern]
    -target = "engine/event.lua"
    -pattern = "type = config.ease or 'lerp',"
    -position = "at"
    -payload = "type = config.type and string.lower(config.type) or config.ease and string.lower(config.ease) or 'lerp',"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "engine/event.lua"
    -pattern = '''if self.ease.type == 'lerp' then
    -                    self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
    -                end
    -                if self.ease.type == 'elastic' then
    -                    percent_done = -math.pow(2, 10 * percent_done - 10) * math.sin((percent_done * 10 - 10.75) * 2*math.pi/3);
    -                    self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
    -                end
    -                if self.ease.type == 'quad' then
    -                    percent_done = percent_done * percent_done;
    -                    self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
    -                end'''
    -position = "at"
    -payload = '''
    -local c1 = 1.70158
    -local c2 = c1 * 1.525
    -local c3 = c1 + 1
    -
    -assert(SMODS.ease_types[self.ease.type], "Event created with invalid ease type: "..self.ease.type..(self.func and SMODS.log_crash_info(debug.getinfo(self.func), true)))
    -percent_done = SMODS.ease_types[self.ease.type](percent_done, c1, c2, c3)
    -
    -self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)'''
    -match_indent = true
    -
    -# Fix easing type of splash screen cards (vanilla implementation is broken and always uses 'lerp')
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -ease_value(card.T, 'scale', -card.T.scale, nil, nil, nil, 1.*speed, 'elastic')
    -'''
    -payload = '''
    -ease_value(card.T, 'scale', -card.T.scale, nil, nil, nil, 1.*speed)
    -'''
    \ No newline at end of file
    diff --git a/lovely/edition.toml b/lovely/edition.toml
    deleted file mode 100644
    index c64a50c3c..000000000
    --- a/lovely/edition.toml
    +++ /dev/null
    @@ -1,219 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Fix debug mode edition cycling
    -[[patches]]
    -[patches.regex]
    -target = "engine/controller.lua"
    -pattern = '''
    -(?[\t ]*)local _edition = \{
    -[\t ]*foil = not _card\.edition,
    -[\t ]*holo = _card\.edition and _card\.edition\.foil,
    -[\t ]*polychrome = _card\.edition and _card\.edition\.holo,
    -[\t ]*negative = _card\.edition and _card\.edition\.polychrome,
    -[\t ]*\}'''
    -position = "at"
    -payload = '''
    -local found_index = 1
    -if _card.edition then
    -    for i, v in ipairs(G.P_CENTER_POOLS.Edition) do
    -        if v.key == _card.edition.key then
    -            found_index = i
    -            break
    -        end
    -    end
    -end
    -found_index = found_index + 1
    -if found_index > #G.P_CENTER_POOLS.Edition then found_index = found_index - #G.P_CENTER_POOLS.Edition end
    -local _edition = G.P_CENTER_POOLS.Edition[found_index].key'''
    -line_prepend = "$indent"
    -
    -
    -# Sort P_CENTER_POOLS["Editions"]
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = 'table.sort(self.P_CENTER_POOLS["Enhanced"], function (a, b) return a.order < b.order end)'
    -position = 'after'
    -payload = 'table.sort(self.P_CENTER_POOLS["Edition"], function (a, b) return a.order < b.order end)'
    -match_indent = true
    -
    -
    -# generate_card_ui()
    -# Adds tooltips for all editions
    -[[patches]]
    -[patches.regex]
    -target = 'functions/common_events.lua'
    -pattern = '''
    -(?[\t ]*)if v == 'foil' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_foil'\] end
    -[\t ]*if v == 'holographic' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_holo'\] end
    -[\t ]*if v == 'polychrome' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_polychrome'\] end
    -[\t ]*if v == 'negative' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_negative'\] end
    -[\t ]*if v == 'negative_consumable' then info_queue\[#info_queue\+1\] = \{key = 'e_negative_consumable', set = 'Edition', config = \{extra = 1\}\} end'''
    -position = 'at'
    -payload = '''
    -v = (v == 'holographic' and 'holo' or v)
    -local ed_key = v
    -if v:sub(v:len()-14) == '_SMODS_INTERNAL' then
    -    if v:sub(1, 9) == 'negative_' then ed_key = 'negative' else ed_key = v:sub(1, v:find('_', v:find('_')+1)-1) end
    -    v = v:sub(1, v:len()-15)
    -end
    -
    -if G.P_CENTERS[ed_key] and G.P_CENTERS[ed_key].set == 'Edition' then
    -    info_queue[#info_queue + 1] = G.P_CENTERS[ed_key]
    -end
    -if G.P_CENTERS['e_'..ed_key] and G.P_CENTERS['e_'..ed_key].set == 'Edition' then
    -    local t = {key = 'e_'..v, set = 'Edition', config = {}}
    -    if localize(SMODS.merge_defaults(t, {type = 'name_text'})) == 'ERROR' then t.key = 'e_'..ed_key end
    -    info_queue[#info_queue + 1] = t
    -    if G.P_CENTERS['e_'..ed_key].loc_vars and type(G.P_CENTERS['e_'..ed_key].loc_vars) == 'function' then
    -        local res = G.P_CENTERS['e_'..ed_key]:loc_vars(info_queue, card) or {}
    -        t.vars = res.vars
    -        t.key = res.key or t.key
    -        t.set = res.set or t.set
    -    end
    -end'''
    -line_prepend = "$indent"
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''localize{type = 'descriptions', key = _c.key, set = _c.set, nodes = desc_nodes, vars = loc_vars}'''
    -position = 'at'
    -match_indent = true
    -payload = '''localize{type = 'descriptions', key = _c.key, set = _c.set, nodes = desc_nodes, vars = _c.vars or loc_vars}'''
    -
    -
    -# get_badge_colour()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -for _, v in ipairs(G.P_CENTER_POOLS.Edition) do
    -	G.BADGE_COL[v.key:sub(3)] = v.badge_colour
    -end'''
    -
    -# Remove prefix from shader key when calling send()
    -[[patches]]
    -[patches.pattern]
    -target = "engine/sprite.lua"
    -pattern = "if _send then G.SHADERS[_shader or 'dissolve']:send(_shader,_send) end"
    -position = "at"
    -payload = '''
    -if _send then
    -    G.SHADERS[_shader or 'dissolve']:send((SMODS.Shaders[_shader or 'dissolve'] and SMODS.Shaders[_shader or 'dissolve'].original_key) or _shader,_send)
    -end'''
    -match_indent = true
    -
    -# Inject change to edition cost in shop
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '(?[\t ]*)self.ex([a-z._\s=+(0-9)]*)\n([\t ]*)([a-z._\s=+(0-9)]*)or 0\)'
    -position = "at"
    -payload = '''
    -for k, v in pairs(G.P_CENTER_POOLS.Edition) do
    -    if self.edition[v.key:sub(3)] then
    -        if v.extra_cost then
    -            self.extra_cost = self.extra_cost + v.extra_cost
    -        end
    -    end
    -end'''
    -line_prepend = "$indent"
    -
    -# Card:save()
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "added_to_deck = self.added_to_deck,"
    -position = "after"
    -payload = "joker_added_to_deck_but_debuffed = self.joker_added_to_deck_but_debuffed,"
    -match_indent = true
    -
    -## Buying card_limit cards fix
    -# G.FUNCS.check_for_buy_space
    -[[patches]]
    -[patches.pattern]
    -target = "functions/button_callbacks.lua"
    -pattern = "    not (card.ability.consumeable and #G.consumeables.cards < G.consumeables.config.card_limit + ((card.edition and card.edition.negative) and 1 or 0)) then"
    -position = "at"
    -match_indent = true
    -payload = '''
    -    not (card.ability.consumeable and #G.consumeables.cards + (1 + card.ability.extra_slots_used) <= G.consumeables.config.card_limit + card.ability.card_limit) then
    -'''
    -times = 1
    -
    -# G.FUNCS.check_for_buy_space
    -[[patches]]
    -[patches.pattern]
    -target = "functions/button_callbacks.lua"
    -pattern = "not (card.ability.set == 'Joker' and #G.jokers.cards < G.jokers.config.card_limit + ((card.edition and card.edition.negative) and 1 or 0)) and"
    -position = "at"
    -match_indent = true
    -payload = '''
    -    not (card.ability.set == 'Joker' and #G.jokers.cards + (1 + card.ability.extra_slots_used) <= G.jokers.config.card_limit + card.ability.card_limit) and
    -'''
    -times = 1
    -
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -if self.edition.type == 'negative' and self.ability.consumeable then
    -    badges[#badges + 1] = 'negative_consumable'
    -'''
    -position = "at"
    -payload = """
    -if self.edition.card_limit then
    -    badges[#badges + 1] = SMODS.Edition.get_card_limit_key(self)
    -"""
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "engine/sprite.lua"
    -pattern = "love.graphics.setShader( G.SHADERS[_shader or 'dissolve'],  G.SHADERS[_shader or 'dissolve'])"
    -position = "before"
    -payload = '''
    -local p_shader = SMODS.Shader.obj_table[_shader or 'dissolve']
    -if p_shader and type(p_shader.send_vars) == "function" then
    -    local sh = G.SHADERS[_shader or 'dissolve']
    -    local parent_card = self.role.major and self.role.major:is(Card) and self.role.major
    -    local send_vars = p_shader.send_vars(self, parent_card)
    -
    -    if type(send_vars) == "table" then
    -        for key, value in pairs(send_vars) do
    -            sh:send(key, value)
    -        end
    -    end
    -end
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "if v == 'negative_consumable' then v = 'negative' end"
    -position = "at"
    -payload = '''
    -if v:sub(v:len()-14) == '_SMODS_INTERNAL' then
    -    if v:sub(1, 9) == 'negative_' then v = 'negative' else v = v:sub(1, v:find('_', v:find('_')+1)-1) end
    -end
    -'''
    -match_indent = true
    -
    -#
    -[[patches]]
    -[patches.regex]
    -target = 'functions/common_events.lua'
    -pattern = '''(?[\t ]*)(?local edition = poll_edition\('edi'\.\.\(key_append or ''\)\.\.G\.GAME\.round_resets\.ante\)(\n.*){2})'''
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -if not SMODS.bypass_create_card_edition and not card.edition then
    -    $edi
    -end'''
    diff --git a/lovely/enhancement.toml b/lovely/enhancement.toml
    deleted file mode 100644
    index de71a9c8f..000000000
    --- a/lovely/enhancement.toml
    +++ /dev/null
    @@ -1,364 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -## no_rank, no_suit, all_suits
    -
    -# Card:get_id()
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if self.ability.effect == 'Stone Card' and not self.vampired then"
    -match_indent = true
    -position = "at"
    -payload = '''if SMODS.has_no_rank(self) and not self.vampired then'''
    -
    -# Card:get_chip_bonus()
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -(?[\t ]*)if self\.ability\.effect == 'Stone Card' then
    -[\t ]*    return self\.ability\.bonus \+ \(self\.ability\.perma_bonus or 0\)
    -[\t ]*end'''
    -position = "at"
    -payload = '''if self.ability.effect == 'Stone Card' or self.config.center.replace_base_card then
    -    return self.ability.bonus + (self.ability.perma_bonus or 0)
    -end'''
    -line_prepend = '$indent'
    -
    -# Card:calculate_joker()
    -# Raised Fist
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if temp_ID >= G.hand.cards[i].base.id and G.hand.cards[i].ability.effect ~= 'Stone Card' then temp_Mult = G.hand.cards[i].base.nominal; temp_ID = G.hand.cards[i].base.id; raised_card = G.hand.cards[i] end"
    -match_indent = true
    -position = "at"
    -payload = """if temp_ID >= G.hand.cards[i].base.id and not SMODS.has_no_rank(G.hand.cards[i]) then 
    -    temp_Mult = G.hand.cards[i].base.nominal
    -    temp_ID = G.hand.cards[i].base.id
    -    raised_card = G.hand.cards[i]
    -end"""
    -# Flower Pot, Seeing Double
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if context.scoring_hand[i].ability.name ~= 'Wild Card' then"
    -match_indent = true
    -position = "at"
    -payload = '''if not SMODS.has_any_suit(context.scoring_hand[i]) then'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if context.scoring_hand[i].ability.name == 'Wild Card' then"
    -match_indent = true
    -position = "at"
    -payload = '''if SMODS.has_any_suit(context.scoring_hand[i]) then'''
    -
    -# Card:get_suit()
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''(?[\t ]*)if self\.ability\.effect == 'Stone Card' then'''
    -line_prepend = '$indent'
    -position = "at"
    -payload = '''if SMODS.has_no_suit(self) then'''
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'if self.ability.name == "Wild Card" then'
    -match_indent = true
    -position = "at"
    -payload = '''if SMODS.has_any_suit(self) then'''
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'if self.ability.name == "Wild Card" and not self.debuff then'
    -match_indent = true
    -position = "at"
    -payload = '''if SMODS.has_any_suit(self) and self:can_calculate() then'''
    -
    -# check_for_unlock
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if v.ability.name ~= 'Stone Card' and v.base.suit == 'Hearts' then"
    -match_indent = true
    -position = "at"
    -payload = "if not SMODS.has_no_suit(v) and v.base.suit == 'Hearts' then"
    -
    -# reset_idol_card()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "valid_idol_cards[#valid_idol_cards+1] = v"
    -match_indent = true
    -position = "at"
    -payload = """if not SMODS.has_no_suit(v) and not SMODS.has_no_rank(v) then
    -    valid_idol_cards[#valid_idol_cards+1] = v
    -end"""
    -
    -# reset_mail_rank()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "valid_mail_cards[#valid_mail_cards+1] = v"
    -match_indent = true
    -position = "at"
    -payload = """if not SMODS.has_no_rank(v) then
    -    valid_mail_cards[#valid_mail_cards+1] = v
    -end"""
    -
    -# reset_castle_card()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "valid_castle_cards[#valid_castle_cards+1] = v"
    -match_indent = true
    -position = "at"
    -payload = """if not SMODS.has_no_suit(v) then
    -    valid_castle_cards[#valid_castle_cards+1] = v
    -end"""
    -
    -# G.FUNCS.evaluate_play()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "if scoring_hand[i].ability.effect ~= 'Stone Card' then"
    -match_indent = true
    -position = "at"
    -payload = '''if not SMODS.has_no_rank(scoring_hand[i]) then'''
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "G.GAME.cards_played[scoring_hand[i].base.value].suits[scoring_hand[i].base.suit] = true"
    -match_indent = true
    -position = "at"
    -payload = """if not SMODS.has_no_suit(scoring_hand[i]) then
    -    G.GAME.cards_played[scoring_hand[i].base.value].suits[scoring_hand[i].base.suit] = true
    -end"""
    -
    -# Add the delayed property to sprites that are delayed
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if delay_sprites then 
    -    G.E_MANAGER:add_event(Event({
    -        func = function()
    -            if not self.REMOVED then
    -                self:set_sprites(center)
    -            end
    -            return true
    -        end
    -    })) 
    -'''
    -payload = '''
    -if delay_sprites == 'quantum' or delay_sprites == 'manual' then
    -elseif delay_sprites then 
    -    self.ability.delayed = true
    -    G.E_MANAGER:add_event(Event({
    -        func = function()
    -            if not self.REMOVED then
    -                self:set_sprites(center)
    -                self.ability.delayed = false
    -            end
    -            return true
    -        end
    -    })) 
    -'''
    -
    -
    -
    -# Card:generate_UIBox_ability_table()
    -# replaces two consecutive lines
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if (_c.name == 'Stone Card') then full_UI_table.name = true end"
    -match_indent = true
    -position = "at"
    -payload = "if _c.name == 'Stone Card' or _c.replace_base_card then full_UI_table.name = true"
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if (specific_vars.playing_card and (_c.name ~= 'Stone Card')) then"
    -match_indent = true
    -position = "at"
    -payload = "elseif specific_vars.playing_card then"
    -
    -
    -## Allow cards to function as multiple enhancements (e.g. from jokers)
    -# Calculate extra enhancements when held in hand at end of round
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "local effects = {G.hand.cards[i]:get_end_of_round_effect()}"
    -position = "at"
    -payload = '''
    -local effects = {[1] = {playing_card = G.hand.cards[i]:get_end_of_round_effect()}}
    -local extra_enhancements = SMODS.get_enhancements(G.hand.cards[i], true)
    -local old_ability = copy_table(G.hand.cards[i].ability)
    -local old_center = G.hand.cards[i].config.center
    -local old_center_key = G.hand.cards[i].config.center_key
    -for k, _ in pairs(extra_enhancements) do
    -    if G.P_CENTERS[k] then
    -        G.hand.cards[i]:set_ability(G.P_CENTERS[k])
    -        G.hand.cards[i].ability.extra_enhancement = k
    -        effects[#effects+1] = {[1] = {playing_card = G.hand.cards[i]:get_end_of_round_effect()}}
    -    end
    -end
    -G.hand.cards[i].ability = old_ability
    -G.hand.cards[i].config.center = old_center
    -G.hand.cards[i].config.center_key = old_center_key
    -G.hand.cards[i]:set_sprites(old_center)
    -'''
    -match_indent = true
    -
    -# Prevent blue seal effect on extra enhancements at end of round
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if self.seal == 'Blue' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then"
    -position = "before"
    -payload = '''
    -if self.extra_enhancement then return ret end
    -'''
    -match_indent = true
    -
    -# Use the has enhancement function for enhancement gates
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if vv.config.center.key == v.enhancement_gate then"
    -position = "at"
    -payload = "if SMODS.has_enhancement(vv, v.enhancement_gate) then"
    -match_indent = true
    -
    -# Glass Card shattering
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if card.ability.name == 'Glass Card' then"
    -position = "at"
    -payload = "if SMODS.shatters(card) then"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "if G.hand.highlighted[i].ability.name == 'Glass Card' then"
    -position = "at"
    -payload = "if SMODS.shatters(G.hand.highlighted[i]) then"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "if scoring_hand[i].ability.name == 'Glass Card' then"
    -position = "at"
    -payload = "if SMODS.shatters(scoring_hand[i]) then"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "if cards_destroyed[i].ability.name == 'Glass Card' then"
    -position = "at"
    -payload = "if cards_destroyed[i].shattered then"
    -match_indent = true
    -
    -# Prevent blue seals from applying on quantum enhancement calc
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if self.seal == 'Blue' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then"
    -position = "at"
    -payload = "if self.seal == 'Blue' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit and not self.ability.extra_enhancement then"
    -match_indent = true
    -
    -# Reset enh cache on game update
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "modulate_sound(dt)"
    -position = "after"
    -payload = "SMODS.enh_cache:clear()"
    -match_indent = true
    -
    -# Invalidate enhancement cache when card changes / replace_base_card fix pt1
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "function Card:set_ability(center, initial, delay_sprites)"
    -position = "after"
    -payload = '''
    -    SMODS.enh_cache:write(self, nil)
    -
    -    if self.ability and not initial then
    -        self.ability.card_limit = self.ability.card_limit - (self.config.center.config.card_limit or 0)
    -        self.ability.extra_slots_used = self.ability.extra_slots_used - (self.config.center.config.extra_slots_used or 0)
    -    end
    -  
    -    if self.ability and not initial then
    -        self.front_hidden = self:should_hide_front()
    -    end
    -'''
    -match_indent = true
    -
    -# replace_base_card Fix Part 2
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "self:set_sprites(center)"
    -position = "after"
    -payload = '''if self.ability and not initial then
    -  self.front_hidden = self:should_hide_front()
    -end'''
    -match_indent = true
    -
    -# replace_base_card Fix Part 3
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if not G.OVERLAY_MENU then "
    -position = "before"
    -payload = '''if self.ability and not initial then
    -  self.front_hidden = self:should_hide_front()
    -end'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "function Card:set_base(card, initial)"
    -position = "after"
    -payload = "SMODS.enh_cache:write(self, nil)"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "function Card:set_seal(_seal, silent, immediate)"
    -position = "after"
    -payload = '''
    -    SMODS.enh_cache:write(self, nil)
    -    if self.seal then
    -        self.ability.card_limit = self.ability.card_limit - (self.ability.seal.card_limit or 0)
    -        self.ability.extra_slots_used = self.ability.extra_slots_used - (self.ability.seal.extra_slots_used or 0)
    -    end
    -'''
    -match_indent = true
    -
    -# safeguards an infloop with debuff context
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = 'if not initial then G.GAME.blind:debuff_card(self) end'
    -position = 'at'
    -payload = 'if not initial and delay_sprites ~= "quantum" and G.GAME.blind then G.GAME.blind:debuff_card(self) end'
    -match_indent = true
    \ No newline at end of file
    diff --git a/lovely/event.toml b/lovely/event.toml
    deleted file mode 100644
    index 528011d71..000000000
    --- a/lovely/event.toml
    +++ /dev/null
    @@ -1,64 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# These 4 patches fix a bug when appending events to the queue while it's processing an event
    -# Initalize a count in the class
    -# EventManager:init()
    -[[patches]]
    -[patches.pattern]
    -target = 'event.lua'
    -pattern = '''
    -self.queue_last_processed = G.TIMERS.REAL
    -'''
    -position = 'after'
    -payload = '''
    -self.append_count = 0
    -'''
    -match_indent = true
    -
    -# Reset the count for use
    -# EventManager:update(dt, forced)
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/event.lua'
    -pattern = '''
    -if (not blocked or not v[i].blockable) then v[i]:handle(results) end
    -'''
    -position = 'before'
    -payload = '''
    -self.append_count = 0
    -self.append_queue = k
    -'''
    -match_indent = true
    -
    -# Increment i to fix our place in the queue
    -# EventManager:update(dt, forced)
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/event.lua'
    -pattern = '''
    -if (not blocked or not v[i].blockable) then v[i]:handle(results) end
    -'''
    -position = 'after'
    -payload = '''
    -i = i + self.append_count
    -'''
    -match_indent = true
    -
    -# Increment the count when an event is added to the front
    -# EventManager:add_event(event, queue, front)
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/event.lua'
    -pattern = '''
    -if front then
    -'''
    -position = 'after'
    -payload = '''
    -if self.append_queue == queue then
    -    self.append_count = self.append_count + 1
    -end
    -'''
    -match_indent = true
    diff --git a/lovely/fixes.toml b/lovely/fixes.toml
    deleted file mode 100644
    index 0ff85bafa..000000000
    --- a/lovely/fixes.toml
    +++ /dev/null
    @@ -1,853 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Fixes for either base game code or general mod compatibility
    -
    -## Mods assume Game:start_run() is called with non-nil argument
    -# G.FUNCS.start_run()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = "G.FUNCS.start_run = function(e, args)"
    -position = 'after'
    -match_indent = true
    -payload = "args = args or {}"
    -
    -## Allows running the game without Steam being active
    -# love.load()
    -[[patches]]
    -[patches.regex]
    -target = 'main.lua'
    -pattern = "(?[\t ]*)if not \\(st.init and st:init\\(\\)\\) then\n[\t ]*(?love.event.quit\\(\\))"
    -position = 'at'
    -root_capture = 'quit'
    -payload = 'st = nil'
    -
    -
    -## Prevents the game from crashing when hitting play with a corrupt/invalid save file
    -# G.FUNCS.can_continue(e)
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = "if G.SAVED_GAME ~= nil then G.SAVED_GAME = STR_UNPACK(G.SAVED_GAME) end"
    -position = 'after'
    -match_indent = true
    -payload = """
    -if G.SAVED_GAME == nil then
    -    e.config.colour = G.C.UI.BACKGROUND_INACTIVE
    -    e.config.button = nil
    -    return _can_continue
    -end
    -"""
    -
    -## Fix loading a blind with $0 reward
    -# Blind:load()
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = '''
    -(?[\t ]*)    G\.HUD_blind\.alignment\.offset\.y = 0
    -[\t ]*end'''
    -position = 'at'
    -payload = '''
    -end
    -if G.GAME.blind.name and G.GAME.blind.name ~= '' then
    -    G.HUD_blind.alignment.offset.y = 0
    -end'''
    -line_prepend = '$indent'
    -
    -## Remove incorrect check for Moveable alignment change
    -# Moveable:align_to_major()
    -[[patches]]
    -[patches.regex]
    -target = 'engine/moveable.lua'
    -pattern = '''
    -(?[\t ]*)if +self\.alignment\.prev_offset\.x == self\.alignment\.offset\.x[\s\S]*?return end
    -'''
    -position = 'at'
    -payload = 'if not self.alignment.type_list then return end'
    -line_prepend = '$indent'
    -
    -## Don't save run while booster pack opened (always softlocks)
    -# save_run()
    -[[patches]]
    -[patches.pattern]
    -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"""
    -match_indent = true
    -
    -## Set `G.your_collection.config.collection` to true in all cases
    -# create_UIBox_your_collection_seals()
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''\{card_limit = 4, type = 'title', highlight_limit = 0\}'''
    -position = 'at'
    -payload = '''{card_limit = 4, type = 'title', highlight_limit = 0, collection = true}'''
    -
    -## Save and load Card.unique_val, edition options
    -# Card:save()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "bypass_lock = self.bypass_lock,"
    -position = "after"
    -payload = """
    -unique_val = self.unique_val,
    -unique_val__saved_ID = self.unique_val__saved_ID or self.ID,
    -ignore_base_shader = self.ignore_base_shader,
    -ignore_shadow = self.ignore_shadow,"""
    -match_indent = true
    -
    -# Card:load()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "self.bypass_lock = cardTable.bypass_lock"
    -position = "after"
    -payload = """
    -self.unique_val = cardTable.unique_val or self.unique_val
    -self.unique_val__saved_ID = cardTable.unique_val__saved_ID or self.unique_val__saved_ID
    -if cardTable.unique_val__saved_ID and G.ID <= cardTable.unique_val__saved_ID then
    -    G.ID = cardTable.unique_val__saved_ID + 1
    -end
    -
    -self.ignore_base_shader = cardTable.ignore_base_shader or {}
    -self.ignore_shadow = cardTable.ignore_shadow or {}"""
    -match_indent = true
    -
    -## Vars in card descriptions should use `card.ability` instead of `_c.config` where possible
    -## Allow passing in custom vars
    -# generate_card_ui()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = 'function generate_card_ui(_c, full_UI_table, specific_vars, card_type, badges, hide_desc, main_start, main_end)'
    -position = 'at'
    -match_indent = true
    -payload = '''function generate_card_ui(_c, full_UI_table, specific_vars, card_type, badges, hide_desc, main_start, main_end, card)
    -    if _c.specific_vars then specific_vars = _c.specific_vars end
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if _c.set == 'Other' then"
    -position = 'before'
    -match_indent = true
    -payload = "local cfg = (card and card.ability) or _c['config']" # string index to make sure the next patch doesn't eat it
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if _c.name ~= 'Stone Card' and ((specific_vars and specific_vars.bonus_chips) or _c.config.bonus) then"
    -position = 'at'
    -match_indent = true
    -payload = "if _c.name ~= 'Stone Card' and ((specific_vars and specific_vars.bonus_chips) or (cfg.bonus ~= 0 and cfg.bonus)) then"
    -
    -[[patches]]
    -[patches.regex]
    -target = 'functions/common_events.lua'
    -pattern = '_c.config'
    -position = 'at'
    -payload = 'cfg'
    -
    -## When overriding with set_ability and card is added to deck, call add / remove effects
    -# Card:set_ability()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "self.config.center = center"
    -position = 'at'
    -match_indent = true
    -payload = '''
    -if delay_sprites == 'quantum' then self.from_quantum = true end
    -local was_added_to_deck = false
    -if self.added_to_deck and old_center and not self.debuff then
    -    self:remove_from_deck()
    -    was_added_to_deck = true
    -end
    -if type(center) == 'string' then
    -    assert(G.P_CENTERS[center], ("Could not find center \"%s\""):format(center))
    -    center = G.P_CENTERS[center]
    -end
    -self.config.center = center
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "if G.consumeables and self.area == G.consumeables then"
    -position = 'before'
    -match_indent = true
    -payload = '''
    -if was_added_to_deck and not self.debuff then
    -    self:add_to_deck()
    -end
    -self.from_quantum = nil'''
    -
    -
    -## set_ability() transfers over old fields
    -# special cases:
    -# extra_value should be transferred
    -# name, effect, set, extra should always be overwritten
    -[[patches]]
    -[patches.regex]
    -target = 'card.lua'
    -pattern = '''
    -(?[\t ]*)self\.ability = (\{[\s\S]*?
    -[\t ]*\})
    -'''
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local new_ability = $2
    -self.ability = self.ability or {}
    -new_ability.extra_value = nil
    -new_ability.debuff_sources = {}
    -self.ability.extra_value = self.ability.extra_value or 0
    -for k, v in pairs(new_ability) do
    -    self.ability[k] = v
    -end
    -
    --- handles card_limit/extra_slots_used changes
    -self.ability.card_limit = self.ability.card_limit + (center.config.card_limit or 0)
    -self.ability.extra_slots_used = self.ability.extra_slots_used + (center.config.extra_slots_used or 0)
    -
    -
    --- reset keys do not persist on ability change
    -local reset_keys = {'name', 'effect', 'set', 'extra', 'played_this_ante', 'perma_debuff'}
    -for _, mod in ipairs(SMODS.mod_list) do
    -    if mod.set_ability_reset_keys then
    -        local keys = mod.set_ability_reset_keys()
    -        for _, v in pairs(keys) do table.insert(reset_keys, v) end
    -    end
    -end
    -for _, k in ipairs(reset_keys) do
    -    self.ability[k] = new_ability[k]
    -end
    -'''
    -
    -## Fix crash if self.config.card == nil for non-vanilla set_ability() calls
    -# Card:set_ability()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "self.label = center.label or self.config.card.label or self.ability.set"
    -position = 'at'
    -match_indent = true
    -payload = "self.label = center.label or self.config.card and self.config.card.label or self.ability.set"
    -
    -### Fix Matador
    -
    -# These patches have been removed for altering vanilla behavior. Git blame this line to see what they were
    -
    -### Debuffing boss blinds and Verdant Leaf don't debuff things
    -### that aren't a Joker or playing card
    -
    -## Blind:debuff_card
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = '''card\.area ~= G.jokers'''
    -position = 'at'
    -payload = """card.playing_card"""
    -
    -### Fix Crimson Heart
    -
    -## Blind:drawn_to_hand()
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = "if self.name == 'Crimson Heart' and self.prepped and G.jokers.cards\\[1\\] then"
    -position = 'after'
    -payload = """
    -
    -            local prev_chosen_set = {}
    -            local fallback_jokers = {}"""
    -# Fix bad logic if not enough choices for debuff
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = '''
    -(?[\t ]*)for i = 1, #G\.jokers\.cards do
    -[\t ]*if not G\.jokers\.cards\[i\]\.debuff or #G\.jokers\.cards < 2 then jokers\[#jokers\+1\] = ?G\.jokers\.cards\[i\] end
    -[\t ]*G\.jokers\.cards\[i\]:set_debuff\(false\)
    -[\t ]*end'''
    -position = 'at'
    -line_prepend = '$indent'
    -payload = """
    -for i = 1, #G.jokers.cards do
    -    if G.jokers.cards[i].ability.crimson_heart_chosen then
    -        prev_chosen_set[G.jokers.cards[i]] = true
    -        G.jokers.cards[i].ability.crimson_heart_chosen = nil
    -        if G.jokers.cards[i].debuff then SMODS.recalc_debuff(G.jokers.cards[i]) end
    -    end
    -end
    -for i = 1, #G.jokers.cards do
    -    if not G.jokers.cards[i].debuff then
    -        if not prev_chosen_set[G.jokers.cards[i]] then
    -            jokers[#jokers+1] = G.jokers.cards[i]
    -        end
    -        table.insert(fallback_jokers, G.jokers.cards[i])
    -    end
    -end
    -if #jokers == 0 then jokers = fallback_jokers end"""
    -# Add variable for Crimson Heart's choice
    -[[patches]]
    -[patches.pattern]
    -target = 'blind.lua'
    -pattern = "_card:set_debuff(true)"
    -position = "at"
    -match_indent = true
    -payload = """
    -_card.ability.crimson_heart_chosen = true
    -SMODS.recalc_debuff(_card)"""
    -
    -## Blind:debuff_card()
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = '''
    -if self\.name == 'Crimson Heart' and not self\.disabled and card\.area == G\.jokers then\s+
    -((?[\t ]*)return)'''
    -root_capture = '$1'
    -position = "at"
    -line_prepend = '$indent'
    -payload = """
    -if card.ability.crimson_heart_chosen then
    -    card:set_debuff(true);
    -    if card.debuff then card.debuffed_by_blind = true end
    -    return
    -end"""
    -
    -## Blind:press_play()
    -# Shouldn't work with Matador
    -# yes it should
    -
    -## Blind:disable()
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = "elseif self.name == 'The Water' then"
    -position = 'at'
    -payload = """
    -elseif self.name == 'Crimson Heart' then
    -        for _, v in ipairs(G.jokers.cards) do
    -            v.ability.crimson_heart_chosen = nil
    -        end
    -    elseif self.name == 'The Water' then"""
    -
    -## Blind:defeat()
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = "elseif self.name == 'The Manacle' and not self.disabled then"
    -position = 'at'
    -payload = """
    -elseif self.name == 'Crimson Heart' then
    -        for _, v in ipairs(G.jokers.cards) do
    -            v.ability.crimson_heart_chosen = nil
    -        end
    -    elseif self.name == 'The Manacle' and not self.disabled then"""
    -
    -
    -## Fix Manacle's unnecessary card draw after positive G.hand:change_size()
    -# Blind:disable()
    -[[patches]]
    -[patches.regex]
    -target = 'blind.lua'
    -pattern = 'G\.hand:change_size\(1\)(\s+G\.FUNCS\.draw_from_deck_to_hand\(1\))'
    -root_capture = '$1'
    -position = 'at'
    -payload = ""
    -
    -#
    -# Money scaling fix
    -#
    -
    -## create_UIBox_HUD
    -[[patches]]
    -[patches.regex]
    -target = "functions/UI_definitions.lua"
    -pattern = '''
    -string = \{\{ref_table = G\.GAME\, ref_value = 'dollars'\, prefix = localize\('\$'\)\}\}\,'''
    -position = "after"
    -payload = '''
    -
    -                            scale_function = function ()
    -                                return scale_number(G.GAME.dollars, 2.2 * scale, 99999, 1000000)
    -                            end,'''
    -
    -## DynaText:update_text
    -[[patches]]
    -[patches.pattern]
    -target = "engine/text.lua"
    -pattern = 'self.config.H = 0'
    -position = "after"
    -payload = "self.scale = self.config.scale_function and self.config.scale_function() or self.scale"
    -match_indent = true
    -
    -
    -#
    -# Fix gold stake legendary infloop
    -# Do not try to roll for jokers that are not in_pool
    -#
    -
    -# generate_starting_seed()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''if win_ante and (win_ante >= 8) then'''
    -match_indent = true
    -position = "at"
    -payload = '''if win_ante and (win_ante >= 8) or not SMODS.add_to_pool(v) then'''
    -
    -#
    -# Fix G.GAME.blind:set_blind(nil, true, nil)
    -# being called when not in blind.
    -#
    -
    -# Card:add_to_deck
    -# Card:remove_from_deck
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = 'if G\.GAME\.blind then'
    -position = "at"
    -payload = "if G.GAME.blind and G.GAME.blind.in_blind and not self.from_quantum then"
    -
    -# end_round()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "local game_over = true"
    -position = "before"
    -payload = "G.GAME.blind.in_blind = false"
    -match_indent = true
    -
    -# Allow winning game if winning ante is skipped
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = "if G.GAME.round_resets.ante == G.GAME.win_ante and G.GAME.blind:get_type() == 'Boss' then"
    -position = "at"
    -payload = "if not G.GAME.won and G.GAME.round_resets.ante >= G.GAME.win_ante and G.GAME.blind:get_type() == 'Boss' then"
    -match_indent = true
    -
    -
    -
    -# Make sure new param is loaded
    -[[patches]]
    -[patches.pattern]
    -target = "blind.lua"
    -match_indent = true
    -pattern = "function Blind:load(blindTable)"
    -position = "after"
    -payload = '''
    -    self.in_blind = blindTable.in_blind'''
    -
    -# Make sure new param is saved
    -[[patches]]
    -[patches.pattern]
    -target = "blind.lua"
    -match_indent = true
    -pattern = "local blindTable = {"
    -position = "after"
    -payload = '''
    -    in_blind = self.in_blind,'''
    -
    -# Cartomancer and astronomer unlock when *actually all* Tarot/Planet cards are discovered
    -# check_for_unlock()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -match_indent = true
    -pattern = "if card.unlock_condition.tarot_count <= args.tarot_count then"
    -position = "at"
    -payload = 'if #G.P_CENTER_POOLS.Tarot <= args.tarot_count then'
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -match_indent = true
    -pattern = "if card.unlock_condition.planet_count <= args.planet_count then"
    -position = "at"
    -payload = 'if #G.P_CENTER_POOLS.Planet <= args.planet_count then'
    -
    -# wtf
    -[[patches]]
    -[patches.pattern]
    -target = "engine/animatedsprite.lua"
    -match_indent = true
    -pattern = "for _, v in pairs(G.ANIMATIONS) do"
    -position = "at"
    -payload = 'for k, v in pairs(G.ANIMATIONS) do'
    -
    -[[patches]]
    -[patches.pattern]
    -target = "engine/animatedsprite.lua"
    -match_indent = true
    -pattern = "for _, v in pairs(G.I.SPRITE) do"
    -position = "at"
    -payload = 'for k, v in pairs(G.I.SPRITE) do'
    -
    -## Make vanilla enhancement jokers work with extra enhancements
    -
    -# Steel Joker
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -match_indent = true
    -pattern = "if v.config.center == G.P_CENTERS.m_steel then self.ability.steel_tally = self.ability.steel_tally+1 end"
    -position = "at"
    -payload = "if SMODS.has_enhancement(v, 'm_steel') then self.ability.steel_tally = self.ability.steel_tally+1 end"
    -
    -# Stone Joker
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -match_indent = true
    -pattern = "if v.config.center == G.P_CENTERS.m_stone then self.ability.stone_tally = self.ability.stone_tally+1 end"
    -position = "at"
    -payload = "if SMODS.has_enhancement(v, 'm_stone') then self.ability.stone_tally = self.ability.stone_tally+1 end"
    -
    -# Golden Ticket
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -match_indent = true
    -pattern = "context.other_card.ability.name == 'Gold Card' then"
    -position = "at"
    -payload = "SMODS.has_enhancement(context.other_card, 'm_gold') then"
    -
    -# Golden Ticket Unlock
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -match_indent = true
    -pattern = "if args.cards[j].ability.name == 'Gold Card' then"
    -position = "at"
    -payload = "if SMODS.has_enhancement(args.cards[j], 'm_gold') then"
    -
    -# Glass Joker
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -match_indent = true
    -pattern = "if val.ability.name == 'Glass Card' then shattered_glass = shattered_glass + 1 end"
    -position = "at"
    -payload = "if SMODS.has_enhancement(val, 'm_glass') then shattered_glass = shattered_glass + 1 end"
    -
    -# Driver's License
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -match_indent = true
    -pattern = "if v.config.center ~= G.P_CENTERS.c_base then self.ability.driver_tally = self.ability.driver_tally+1 end"
    -position = "at"
    -payload = "if next(SMODS.get_enhancements(v)) then self.ability.driver_tally = self.ability.driver_tally+1 end"
    -# Basegame fix for the reroll vouchers redeem function when only the center but no card object exists
    -
    -# Card:apply_to_run
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = """G.GAME.round_resets.reroll_cost = G.GAME.round_resets.reroll_cost - self.ability.extra"""
    -position = 'at'
    -match_indent = true
    -payload = """G.GAME.round_resets.reroll_cost = G.GAME.round_resets.reroll_cost - center_table.extra"""
    -
    -# Card:apply_to_run
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = """G.GAME.current_round.reroll_cost = math.max(0, G.GAME.current_round.reroll_cost - self.ability.extra)"""
    -position = 'at'
    -match_indent = true
    -payload = """G.GAME.current_round.reroll_cost = math.max(0, G.GAME.current_round.reroll_cost - center_table.extra)"""
    -
    -
    -# Fix booster skip issues maybe?
    -[[patches]]
    -[patches.pattern]
    -target = "functions/button_callbacks.lua"
    -pattern = "if G.pack_cards and (G.pack_cards.cards[1]) and"
    -position = "at"
    -payload = '''
    -if G.pack_cards and (not (G.GAME.STOP_USE and G.GAME.STOP_USE > 0)) and
    -'''
    -match_indent = true
    -
    -# Due to STOP_USE being used we can remove this dumb check
    -# could probably remove the rest of it since it serves no purpose otherwise, but whatever
    -[[patches]]
    -[patches.regex]
    -target = "functions/button_callbacks.lua"
    -pattern = '''and \(G\.hand\.cards\[1\] or \(G\.hand\.config\.card_limit <= 0\)\)'''
    -position = "at"
    -payload = ''' '''
    -
    -# Fix prng calls on collection advancing seeds
    -# Keep vanilla behaviour for to-do list
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "if key == 'seed' then return math.random() end"
    -position = "after"
    -payload = """
    -if G.SETTINGS.paused and key ~= 'to_do' then return math.random() end
    -"""
    -overwrite = true
    -match_indent = true
    -
    -# Fixes Steam API not loading on unix
    -[[patches]]
    -[patches.pattern]
    -target = 'main.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '--To control when steam communication happens, make sure to send updates to steam as little as possible'
    -payload = '''local cwd = SMODS.NFS.getWorkingDirectory()
    -SMODS.NFS.setWorkingDirectory(love.filesystem.getSourceBaseDirectory())
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'main.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '--Set up the render window and the stage for the splash screen, then enter the gameloop with :update'
    -payload = '''SMODS.NFS.setWorkingDirectory(cwd)
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "main.lua"
    -pattern = '''if os == 'OS X' or os == 'Windows' then'''
    -position = "at"
    -payload = '''if os == 'OS X' or os == 'Windows' or os == 'Linux' then'''
    -overwrite = true
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "main.lua"
    -pattern = '''if os == 'OS X' then'''
    -position = "at"
    -payload = '''if os == 'OS X' or os == 'Linux' then'''
    -overwrite = true
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "main.lua"
    -pattern = "st = require 'luasteam'"
    -position = "at"
    -payload = """local success, _st = pcall(require, 'luasteam')
    -if success then st = _st else sendWarnMessage(_st, "LuaSteam"); st = {} end"""
    -overwrite = true
    -match_indent = true
    -
    -# copy_card: Subtract from .ability.card_limit
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -position = 'before'
    -match_indent = true
    -pattern = 'if not strip_edition then'
    -payload = '''
    -if other.edition then
    -    new_card.ability.card_limit = new_card.ability.card_limit - (other.edition.card_limit or 0)
    -    new_card.ability.extra_slots_used = new_card.ability.extra_slots_used - (other.edition.extra_slots_used or 0)
    -end
    -if other.seal then
    -    new_card.ability.card_limit = new_card.ability.card_limit - (other.ability.seal.card_limit or 0)
    -    new_card.ability.extra_slots_used = new_card.ability.extra_slots_used - (other.ability.seal.extra_slots_used or 0)
    -end'''
    -
    -# copy_card edition config
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -position = 'after'
    -match_indent = true
    -pattern = 'new_card:set_edition(other.edition or {}, nil, true)'
    -payload = '''
    -for k,v in pairs(other.edition or {}) do
    -    if type(v) == 'table' then
    -        new_card.edition[k] = copy_table(v)
    -    else
    -        new_card.edition[k] = v
    -    end
    -end'''
    -
    -# fix Smeared Joker compat issues with modded suits
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if next(find_joker('Smeared Joker')) and (self.base.suit == 'Hearts' or self.base.suit == 'Diamonds') == (suit == 'Hearts' or suit == 'Diamonds') then"
    -position = "at"
    -payload = "if SMODS.smeared_check(self, suit) then"
    -overwrite = true
    -match_indent = true
    -
    -# fix Seeing Double compat issues with modded suits
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = """local suits = {
    -    ['Hearts'] = 0,
    -    ['Diamonds'] = 0,
    -    ['Spades'] = 0,
    -    ['Clubs'] = 0
    -}
    -for i = 1, #context.scoring_hand do
    -    if not SMODS.has_any_suit(context.scoring_hand[i]) then
    -        if context.scoring_hand[i]:is_suit('Hearts') then suits["Hearts"] = suits["Hearts"] + 1 end
    -        if context.scoring_hand[i]:is_suit('Diamonds') then suits["Diamonds"] = suits["Diamonds"] + 1 end
    -        if context.scoring_hand[i]:is_suit('Spades') then suits["Spades"] = suits["Spades"] + 1 end
    -        if context.scoring_hand[i]:is_suit('Clubs') then suits["Clubs"] = suits["Clubs"] + 1 end
    -    end
    -end
    -for i = 1, #context.scoring_hand do
    -    if SMODS.has_any_suit(context.scoring_hand[i]) then
    -        if context.scoring_hand[i]:is_suit('Clubs') and suits["Clubs"] == 0 then suits["Clubs"] = suits["Clubs"] + 1
    -        elseif context.scoring_hand[i]:is_suit('Diamonds') and suits["Diamonds"] == 0  then suits["Diamonds"] = suits["Diamonds"] + 1
    -        elseif context.scoring_hand[i]:is_suit('Spades') and suits["Spades"] == 0  then suits["Spades"] = suits["Spades"] + 1
    -        elseif context.scoring_hand[i]:is_suit('Hearts') and suits["Hearts"] == 0  then suits["Hearts"] = suits["Hearts"] + 1 end
    -    end
    -end
    -if (suits["Hearts"] > 0 or
    -suits["Diamonds"] > 0 or
    -suits["Spades"] > 0) and
    -suits["Clubs"] > 0 then
    -    return {
    -        message = localize{type='variable',key='a_xmult',vars={self.ability.extra}},
    -        Xmult_mod = self.ability.extra
    -    }
    -end"""
    -position = "at"
    -payload = """if SMODS.seeing_double_check(context.scoring_hand, 'Clubs') then
    -    return {
    -        message = localize{type='variable',key='a_xmult',vars={self.ability.extra}},
    -        Xmult_mod = self.ability.extra
    -    }
    -end"""
    -overwrite = true
    -match_indent = true
    -
    -# Card:get_end_of_round_effect
    -# prevents Blue Seal crash from a hand with no matching planet card (spawns random planet card)
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = """local card = create_card(card_type,G.consumeables, nil, nil, nil, nil, _planet, 'blusl')"""
    -position = 'before'
    -payload = """if _planet == 0 then _planet = nil end"""
    -match_indent = true
    -
    -## Fix remove_from_deck being called and unlocking stuff when exiting a run and
    -## clearing screen
    -# Game:delete_run()
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = 'function Game:delete_run()'
    -position = 'after'
    -payload = '''    G.in_delete_run = true
    -    booster_obj = nil'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = 'G.STATE = -1'
    -position = 'after'
    -payload = 'G.in_delete_run = false'
    -match_indent = true
    -# Card:remove()
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'if self.area then self.area:remove_card(self) end'
    -position = 'after'
    -payload = 'if G.in_delete_run then goto skip_game_actions_during_remove end'
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'if G.playing_cards then'
    -position = 'before'
    -payload = '::skip_game_actions_during_remove::'
    -match_indent = true
    -# CardArea:remove_card()
    -[[patches]]
    -[patches.pattern]
    -target = "cardarea.lua"
    -pattern = '''
    -self:set_ranks()
    -if self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end'''
    -position = 'at'
    -payload = '''
    -self:set_ranks()
    -if not G.in_delete_run and self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end'''
    -match_indent = true
    -
    -# Assign `getting_sliced` to destruction events
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -if v ~= chosen_joker then 
    -'''
    -payload = '''
    -v.getting_sliced = true
    -'''
    -
    -# Allow pseudoshuffle to take a string as a seed
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -function pseudoshuffle(list, seed)
    -'''
    -payload = '''
    -    if seed and type(seed) == "string" then seed = pseudoseed(seed) end
    -'''
    -
    -## Add initial playing cards to deck when starting run
    -# card_from_control
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -local _card = Card(G.deck.T.x, G.deck.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[control.s..'_'..control.r], G.P_CENTERS[control.e or 'c_base'], {playing_card = G.playing_card})
    -if control.d then _card:set_edition({[control.d] = true}, true, true) end
    -if control.g then _card:set_seal(control.g, true, true) end
    -'''
    -payload = '''
    -_card:add_to_deck()
    -'''
    -## Add cards created by create_playing_card to deck
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -local card = Card(_area.T.x, _area.T.y, G.CARD_W, G.CARD_H, card_init.front, card_init.center, {playing_card = G.playing_card})
    -'''
    -payload = '''
    -card:add_to_deck()
    -'''
    diff --git a/lovely/gradient.toml b/lovely/gradient.toml
    deleted file mode 100644
    index 458165d77..000000000
    --- a/lovely/gradient.toml
    +++ /dev/null
    @@ -1,56 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Game:update
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "self.C.EDITION[2] = 0.7+0.2*(1+math.sin(self.TIMERS.REAL*1.5 + 6))"
    -position = "after"
    -payload = '''
    -for _,v in pairs(SMODS.Gradients) do
    -   v:update(dt)
    -end'''
    -match_indent = true
    -
    -# Fix for effect messages
    -# attention_text
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "args.colour = copy_table(args.colour or G.C.WHITE)"
    -position = "at"
    -payload = '''
    -args.colour = SMODS.shallow_copy(args.colour or G.C.WHITE)
    -'''
    -match_indent = true
    -
    -# attention_text
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''
    -args.cover_colour = copy_table(args.cover_colour or G.C.RED)
    -args.cover_colour_l = copy_table(lighten(args.cover_colour, 0.2))
    -args.cover_colour_d = copy_table(darken(args.cover_colour, 0.2))
    -'''
    -position = "at"
    -payload = '''
    -args.cover_colour = SMODS.shallow_copy(args.cover_colour or G.C.RED)
    -args.cover_colour_l = SMODS.shallow_copy(lighten(args.cover_colour, 0.2))
    -args.cover_colour_d = SMODS.shallow_copy(darken(args.cover_colour, 0.2))
    -'''
    -match_indent = true
    -
    -# attention_text
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "args.backdrop_colour = copy_table(args.backdrop_colour)"
    -position = "at"
    -payload = '''
    -args.backdrop_colour = SMODS.shallow_copy(args.backdrop_colour)
    -'''
    -match_indent = true
    diff --git a/lovely/hand_limit.toml b/lovely/hand_limit.toml
    deleted file mode 100644
    index d6d8fb57e..000000000
    --- a/lovely/hand_limit.toml
    +++ /dev/null
    @@ -1,93 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Add starting params
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -consumable_slots = 2,
    -'''
    -payload = '''
    -play_limit = 5,
    -discard_limit = 5,
    -no_limit = '',
    -'''
    -# Reset visual indicators
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -self.GAME = saveTable and saveTable.GAME or self:init_game_object()
    -'''
    -payload = '''
    -SMODS.update_hand_limit_text(true, true)
    -'''
    -
    -
    -# Change hand limit
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if #G.hand.highlighted <= 0 or G.GAME.blind.block_play or #G.hand.highlighted > 5 then 
    -'''
    -payload = '''
    -if #G.hand.highlighted <= 0 or G.GAME.blind.block_play or #G.hand.highlighted > math.max(G.GAME.starting_params.play_limit, 1) then 
    -'''
    -
    -# Change discard limit
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if G.GAME.current_round.discards_left <= 0 or #G.hand.highlighted <= 0 then 
    -'''
    -payload = '''
    -if G.GAME.current_round.discards_left <= 0 or #G.hand.highlighted <= 0 or #G.hand.highlighted > math.max(G.GAME.starting_params.discard_limit, 0) then 
    -'''
    -
    -# Add play limit indicator to UI
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -{n=G.UIT.R, config={align = "bcm", padding = 0}, nodes={
    -    {n=G.UIT.T, config={text = localize('b_play_hand'), scale = text_scale, colour = G.C.UI.TEXT_LIGHT, focus_args = {button = 'x', orientation = 'bm'}, func = 'set_button_pip'}}
    -}},
    -'''
    -payload = '''
    -{n=G.UIT.R, config={align = "bcm", padding = 0}, nodes = {
    -    {n=G.UIT.T, config={ref_table = SMODS.hand_limit_strings, ref_value = 'play', scale = text_scale * 0.65, colour = G.C.UI.TEXT_LIGHT}}
    -}},
    -'''
    -# Add discard limit indicator to UI
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -{n=G.UIT.R, config={align = "cm", padding = 0}, nodes={
    -    {n=G.UIT.T, config={text = localize('b_discard'), scale = text_scale, colour = G.C.UI.TEXT_LIGHT, focus_args = {button = 'y', orientation = 'bm'}, func = 'set_button_pip'}}
    -}}'''
    -payload = '''
    -{n=G.UIT.R, config={align = "cm", padding = 0}, nodes={
    -    {n=G.UIT.T, config={text = localize('b_discard'), scale = text_scale, colour = G.C.UI.TEXT_LIGHT, focus_args = {button = 'y', orientation = 'bm'}, func = 'set_button_pip'}}
    -}},
    -{n=G.UIT.R, config={align = "cm", padding = 0}, nodes={
    -    {n=G.UIT.T, config={ref_table = SMODS.hand_limit_strings, ref_value = 'discard', scale = text_scale * 0.65, colour = G.C.UI.TEXT_LIGHT}}
    -}},
    -'''
    diff --git a/lovely/jimboquip.toml b/lovely/jimboquip.toml
    deleted file mode 100644
    index 3d150b6ed..000000000
    --- a/lovely/jimboquip.toml
    +++ /dev/null
    @@ -1,202 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Select custom win quips
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = '''Jimbo = Card_Character({x = 0, y = 5})
    -local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    -spot.config.object:remove()
    -spot.config.object = Jimbo
    -Jimbo.ui_object_updated = true
    -Jimbo:add_speech_bubble('wq_'..math.random(1,7), nil, {quip = true})
    -Jimbo:say_stuff(5)
    -'''
    -position = "at"
    -payload = '''
    -local quip, extra = SMODS.quip("win")
    -extra.x = 0
    -extra.y = 5
    -Jimbo = Card_Character(extra)
    -local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    -spot.config.object:remove()
    -spot.config.object = Jimbo
    -Jimbo.ui_object_updated = true
    -Jimbo:add_speech_bubble(quip, nil, {quip = true}, extra)
    -Jimbo:say_stuff((extra and extra.times) or 5, false, quip)
    -'''
    -match_indent = true
    -
    -# Select custom loss quips
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = '''Jimbo = Card_Character({x = 0, y = 5})
    -local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    -spot.config.object:remove()
    -spot.config.object = Jimbo
    -Jimbo.ui_object_updated = true
    -Jimbo:add_speech_bubble('lq_'..math.random(1,10), nil, {quip = true})
    -Jimbo:say_stuff(5)
    -'''
    -position = "at"
    -payload = '''
    -local quip, extra = SMODS.quip("loss")
    -extra.x = 0
    -extra.y = 5
    -Jimbo = Card_Character(extra)
    -local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    -spot.config.object:remove()
    -spot.config.object = Jimbo
    -Jimbo.ui_object_updated = true
    -Jimbo:add_speech_bubble(quip, nil, {quip = true}, extra)
    -Jimbo:say_stuff((extra and extra.times) or 5, false, quip)
    -'''
    -match_indent = true
    -
    -# Allow custom materialize colours
    -[[patches]]
    -[patches.pattern]
    -target = "card_character.lua"
    -pattern = '''self.children.card:start_materialize({G.C.BLUE, G.C.WHITE, G.C.RED})'''
    -position = "at"
    -payload = '''self.children.card:start_materialize({args.materialize_colours and args.materialize_colours[1] or G.C.BLUE, args.materialize_colours and args.materialize_colours[2] or G.C.WHITE, args.materialize_colours and args.materialize_colours[3] or G.C.RED})'''
    -match_indent = true
    -
    -# Allow custom particle_colours
    -[[patches]]
    -[patches.pattern]
    -target = "card_character.lua"
    -pattern = '''colours = {G.C.RED, G.C.BLUE, G.C.ORANGE},'''
    -position = "at"
    -payload = '''colours = {args.particle_colours and args.particle_colours[1] or G.C.RED, args.particle_colours and args.particle_colours[2] or G.C.BLUE, args.particle_colours and args.particle_colours[3] or G.C.ORANGE},'''
    -match_indent = true
    -
    -# Allow custom centers to be used for quips
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.children.card = Card(self.T.x, self.T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, args.center or G.P_CENTERS.j_joker, {bypass_discovery_center = true})
    -'''
    -payload = '''
    -self.children.card = Card(self.T.x, self.T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, args.center and G.P_CENTERS[args.center] or G.P_CENTERS.j_joker, {bypass_discovery_center = true})
    -'''
    -
    -# Pass args into add_speech_bubble
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -function Card_Character:add_speech_bubble(text_key, align, loc_vars)
    -'''
    -payload = '''
    -function Card_Character:add_speech_bubble(text_key, align, loc_vars, quip_args)
    -    if quip_args and quip_args.text_key then text_key = quip_args.text_key end
    -'''
    -
    -
    -
    -
    -# Pass extra table into say_stuff
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -pattern = '''
    -function Card_Character:say_stuff(n, not_first)
    -'''
    -position = 'at'
    -payload = '''
    -function Card_Character:say_stuff(n, not_first, quip_key)
    -    local quip = SMODS.JimboQuips[quip_key] or {}
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -pattern = '''
    -self:say_stuff(n, true)
    -'''
    -position = 'at'
    -payload = '''
    -self:say_stuff(n, true, quip_key)
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -pattern = '''
    -self:say_stuff(n-1, true)
    -'''
    -position = 'at'
    -payload = '''
    -self:say_stuff(n-1, true, quip_key)
    -'''
    -match_indent = true
    -
    -
    -# Allow custom sounds to be used for the voice
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -pattern = '''
    -play_sound('voice'..math.random(1, 11), G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    -'''
    -position = 'at'
    -payload = '''
    -if quip.play_sounds and type(quip.play_sounds) == 'function' then
    -    quip:play_sounds(n)
    -elseif quip.extra and quip.extra.sound then
    -    local custom_pitch = quip.extra.pitch
    -    if type(quip.extra.sound) == 'table' then
    -        for k, v in pairs(quip.extra.sound) do
    -            play_sound(v, custom_pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    -        end
    -    elseif type(quip.extra.sound) == 'string' then
    -        play_sound(quip.extra.sound, custom_pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    -    else
    -        play_sound('voice'..math.random(1, 11), custom_pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    -    end
    -else
    -    play_sound('voice'..math.random(1, 11), quip.extra and quip.extra.pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    -end
    -'''
    -match_indent = true
    -
    -
    -# Allow custom delay between talking sounds
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -pattern = '''
    -delay = 0.13,
    -'''
    -position = 'at'
    -payload = '''
    -delay = quip.extra and quip.extra.delay or 0.13,
    -'''
    -match_indent = true
    -
    -
    -# Allow custom juice parameters
    -[[patches]]
    -[patches.pattern]
    -target = 'card_character.lua'
    -pattern = '''
    -self.children.card:juice_up()
    -'''
    -position = 'at'
    -payload = '''
    -local juice_params = quip.extra and quip.extra.juice or {nil, nil}
    -self.children.card:juice_up(juice_params[1], juice_params[2])
    -'''
    -match_indent = true
    \ No newline at end of file
    diff --git a/lovely/joker_retriggers.toml b/lovely/joker_retriggers.toml
    deleted file mode 100644
    index 03343f479..000000000
    --- a/lovely/joker_retriggers.toml
    +++ /dev/null
    @@ -1,324 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -
    -# Luchador
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''[ \t]*G\.GAME\.blind:disable\(\)
    -(?[ \t]*)end'''
    -position = "at"
    -line_prepend = '$indent'
    -payload = '''   G.GAME.blind:disable()
    -    return nil, true
    -end'''
    -
    -# Diet Cola
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*return true
    -[ \t]*end\)
    -[ \t]*\}\)\)
    -(?[ \t]*)end
    -'''
    -position = "at"
    -line_prepend = '$indent'
    -payload = '''           return true
    -       end)
    -    }))
    -    return nil, true
    -end
    -'''
    -
    -# Invisible Joker
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''[ \t]*if card\.ability\.invis_rounds then card\.ability\.invis_rounds = 0 end
    -[ \t]*card:add_to_deck\(\)
    -(?[ \t]*)G\.jokers:emplace\(card\)'''
    -position = "after"
    -line_prepend = '$indent'
    -payload = "\nreturn nil, true"
    -
    -# Perkeo
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''[ \t]*card_eval_status_text\(context[._]blueprint_card or self, 'extra', nil, nil, nil, \{message = localize\('k_duplicated_ex'\)\}\)
    -(?[ \t]*)end'''
    -position = "at"
    -line_prepend = '$indent'
    -payload = '''    card_eval_status_text(context_blueprint_card or self, 'extra', nil, nil, nil, {message = localize('k_duplicated_ex')})
    -    return nil, true
    -end'''
    -
    -# Throwback
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -[ \t]*return
    -(?[ \t]*)elseif context\.skipping_booster'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        return nil, true\n"
    -
    -# Red Card
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -[ \t]*return
    -(?[ \t]*)elseif context\.playing_card_added'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        return nil, true\n"
    -
    -# Hologram
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)elseif context\.first_hand_drawn'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        return nil, true\n"
    -
    -# Certificate
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'DNA' and not context\.blueprint'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "    return nil, true\n"
    -
    -# Chicot
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Madness' '''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "    return nil, true\n"
    -
    -# Madness
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Burglar' '''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "    return nil, true\n"
    -
    -# Burglar
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Riff-raff' '''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "    return nil, true\n"
    -
    -# Riff-raff
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Cartomancer' '''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "    return nil, true\n"
    -
    -# Cartomancer
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Ceremonial Dagger' and not context.blueprint'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "    return nil, true\n"
    -
    -# Ceremonial Dagger
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Marble Joker' '''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        return nil, true\n"
    -
    -# Marble Joker
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -[ \t]*return
    -(?[ \t]*)elseif context.destroying_card'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        return nil, true\n"
    -
    -# Glass Joker
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -if glass_cards > 0 then(.*\n){0,20}?.*
    -[ \t]*return true
    -[ \t]*end
    -(?[ \t]*)\}\)\)'''
    -position = "after"
    -line_prepend = '$indent'
    -payload = "\nreturn nil, true"
    -
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -[ \t]*return
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Fortune Teller' and not context\.blueprint'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        return nil, true\n"
    -
    -# Fortune Teller
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)if self\.ability\.name == 'Constellation' and not context\.blueprint'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "    return nil, true\n"
    -
    -# Constellation
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -[ \t]*return
    -(?[ \t]*)elseif context.debuffed_hand'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        nil, true\n"
    -
    -# Burnt Joker
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -(?[ \t]*)elseif context.discard'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "        return nil, true\n"
    -
    -# Faceless Joker
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -[ \t]*end
    -[ \t]*end
    -[ \t]*return
    -(?[ \t]*)elseif context.end_of_round'''
    -position = "before"
    -line_prepend = '$indent'
    -payload = "            nil, true\n"
    -
    -# Yorick
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "self.ability.yorick_discards = self.ability.yorick_discards - 1"
    -position = "after"
    -match_indent = true
    -payload = "return nil, true"
    -
    -# Hallucination
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize('k_plus_tarot'), colour = G.C.PURPLE})"
    -position = "after"
    -match_indent = true
    -payload = "return nil, true"
    -
    -## Change card returns
    -# Ramen
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -message = localize('k_eaten_ex'),
    -colour = G.C.FILTER'''
    -position = "before"
    -match_indent = true
    -payload = "card = self,"
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''message = localize{type='variable',key='a_xmult_minus',vars={self.ability.extra}},'''
    -position = "before"
    -match_indent = true
    -payload = "card = self,"
    -
    -# Yorick
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -delay = 0.2,
    -message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},'''
    -position = "before"
    -match_indent = true
    -payload = "card = self,"
    -
    -# To Do List
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''message = localize('$')..self.ability.extra.dollars,
    -    dollars = self.ability.extra.dollars,'''
    -position = "at"
    -match_indent = true
    -payload = '''message = localize('$')..self.ability.extra.dollars,'''
    -
    -# Matador
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''message = localize('$')..self.ability.extra,
    -    dollars = self.ability.extra,'''
    -position = "at"
    -match_indent = true
    -payload = '''message = localize('$')..self.ability.extra,'''
    diff --git a/lovely/joker_size.toml b/lovely/joker_size.toml
    deleted file mode 100644
    index aa78d15ca..000000000
    --- a/lovely/joker_size.toml
    +++ /dev/null
    @@ -1,73 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -if _center.name == 'Square Joker' and (_center.discovered or self.bypass_discovery_center) then 
    -    self.children.center.scale.y = self.children.center.scale.x
    -end
    -'''
    -position = "after"
    -payload = '''
    -if _center.pixel_size and _center.pixel_size.h and (_center.discovered or self.bypass_discovery_center) then
    -    self.children.center.scale.y = self.children.center.scale.y*(_center.pixel_size.h/95)
    -end
    -if _center.pixel_size and _center.pixel_size.w and (_center.discovered or self.bypass_discovery_center) then
    -    self.children.center.scale.x = self.children.center.scale.x*(_center.pixel_size.w/71)
    -end
    -'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -if center.name == "Wee Joker" and (center.discovered or self.bypass_discovery_center) then 
    -    H = H*0.7
    -    W = W*0.7
    -    self.T.h = H
    -    self.T.w = W
    -end
    -'''
    -position = "after"
    -payload = '''
    -if center.display_size and center.display_size.h and (center.discovered or self.bypass_discovery_center) then
    -    H = H*(center.display_size.h/95)
    -    self.T.h = H
    -elseif center.pixel_size and center.pixel_size.h and (center.discovered or self.bypass_discovery_center) then
    -    H = H*(center.pixel_size.h/95)
    -    self.T.h = H
    -end
    -if center.display_size and center.display_size.w and (center.discovered or self.bypass_discovery_center) then
    -    W = W*(center.display_size.w/71)
    -    self.T.w = W
    -elseif center.pixel_size and center.pixel_size.w and (center.discovered or self.bypass_discovery_center) then
    -    W = W*(center.pixel_size.w/71)
    -    self.T.w = W
    -end
    -'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.VT.h = self.T.h
    -self.VT.w = self.T.w
    -'''
    -position = "before"
    -payload = '''
    -if self.config.center.display_size and self.config.center.display_size.h then
    -    self.T.h = H*(self.config.center.display_size.h/95)
    -elseif self.config.center.pixel_size and self.config.center.pixel_size.h then
    -    self.T.h = H*(self.config.center.pixel_size.h/95)
    -end
    -if self.config.center.display_size and self.config.center.display_size.w then
    -    self.T.w = W*(self.config.center.display_size.w/71)
    -elseif self.config.center.pixel_size and self.config.center.pixel_size.w then
    -    self.T.w = W*(self.config.center.pixel_size.w/71)
    -end
    -'''
    -match_indent = true
    diff --git a/lovely/keybind.toml b/lovely/keybind.toml
    deleted file mode 100644
    index 80272b8cc..000000000
    --- a/lovely/keybind.toml
    +++ /dev/null
    @@ -1,82 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -# Check all registered keybinds
    -# inserted inside Controller:key_press_update
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/controller.lua'
    -pattern = "if not _RELEASE_MODE then"
    -position = "before"
    -payload = '''
    -for _, keybind in pairs(SMODS.Keybinds) do
    -    if keybind.action and keybind.key_pressed == key and keybind.event == 'pressed' then
    -        local execute = true
    -        for _, other_key in pairs(keybind.held_keys) do
    -            if not self.held_keys[other_key] then
    -                execute = false
    -                break
    -            end
    -        end
    -        if execute then
    -            keybind:action()
    -        end
    -    end
    -end
    -'''
    -match_indent = true
    -
    -# Controller:key_release_update
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/controller.lua'
    -pattern = "function Controller:key_release_update(key, dt)"
    -position = "after"
    -payload = '''
    -for _, keybind in pairs(SMODS.Keybinds) do
    -    if keybind.action and keybind.key_pressed == key and keybind.event == 'released' then
    -        local execute = true
    -        for _, other_key in pairs(keybind.held_keys) do
    -            if not self.held_keys[other_key] then
    -                execute = false
    -                break
    -            end
    -        end
    -        if execute then
    -            keybind:action()
    -        end
    -    end
    -end
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.regex]
    -target = 'engine/controller.lua'
    -pattern = 'if key == "r"'
    -position = 'before'
    -line_prepend = '$indent'
    -payload = '''
    -for _, keybind in pairs(SMODS.Keybinds) do
    -    if keybind.key_pressed == key and keybind.event == 'held' and keybind.held_duration then
    -        if self.held_key_times[key] > keybind.held_duration then
    -            local execute = true
    -            for _, other_key in pairs(keybind.held_keys) do
    -                if not self.held_keys[other_key] then
    -                    execute = false
    -                    break
    -                end
    -            end
    -            if execute then
    -                keybind:action()
    -                self.held_key_times[key] = nil
    -            end
    -        else
    -            self.held_key_times[key] = self.held_key_times[key] + dt
    -        end
    -    end
    -end
    -'''
    diff --git a/lovely/language.toml b/lovely/language.toml
    deleted file mode 100644
    index b89ebff4b..000000000
    --- a/lovely/language.toml
    +++ /dev/null
    @@ -1,59 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Language API
    -
    -# Game:set_language()
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = "if not (love.filesystem.read('localization/'..G.SETTINGS.language..'.lua')) or G.F_ENGLISH_ONLY then"
    -position = 'at'
    -payload = 'if false then'
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = "local localization = love.filesystem.getInfo('localization/'..G.SETTINGS.language..'.lua')"
    -position = 'at'
    -payload = "local localization = love.filesystem.getInfo('localization/'..G.SETTINGS.language..'.lua') or love.filesystem.getInfo('localization/en-us.lua')"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = "self.localization = assert(loadstring(love.filesystem.read('localization/'..G.SETTINGS.language..'.lua')))()"
    -position = 'at'
    -payload = """self.localization = assert(loadstring(love.filesystem.read('localization/'..G.SETTINGS.language..'.lua') or love.filesystem.read('localization/en-us.lua'), '=[localization "'..G.SETTINGS.language..'.lua"]'))()"""
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = "self.LANG = self.LANGUAGES[self.SETTINGS.language] or self.LANGUAGES['en-us']"
    -position = 'at'
    -payload = "self.LANG = self.LANGUAGES[self.SETTINGS.real_language or self.SETTINGS.language] or self.LANGUAGES['en-us']"
    -match_indent = true
    -
    -# G.FUNCS.change_lang
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = "G.SETTINGS.language = lang.key"
    -position = 'at'
    -payload = """G.SETTINGS.language = lang.loc_key or lang.key
    -G.SETTINGS.real_language = lang.key"""
    -match_indent = true
    -
    -# G.FUNCS.warn_lang (wtf)
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = 'if (_infotip_object.config.set ~= e.config.ref_table.label) and (not G.F_NO_ACHIEVEMENTS) then'
    -position = 'at'
    -payload = 'if (_infotip_object.config.set ~= e.config.ref_table.label) then'
    -match_indent = true
    diff --git a/lovely/libs.toml b/lovely/libs.toml
    deleted file mode 100644
    index 04cbc7bee..000000000
    --- a/lovely/libs.toml
    +++ /dev/null
    @@ -1,32 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -[[patches]]
    -[patches.module]
    -source = "libs/json/json.lua"
    -name = "json" 
    -
    -[[patches]]
    -[patches.module]
    -source = "libs/nativefs/nativefs.lua"
    -name = "nativefs"
    -
    -# We use our own module because we modifed it
    -# and other mods may vendor their own which doesn't
    -# have our patches
    -[[patches]]
    -[patches.module]
    -source = "libs/nativefs/nativefs.lua"
    -name = "SMODS.nativefs"
    -
    -[[patches]]
    -[patches.module]
    -source = "libs/https/luajit-curl.lua"
    -name = "luajit-curl"
    -
    -[[patches]]
    -[patches.module]
    -source = "libs/https/smods-https.lua"
    -name = "SMODS.https"
    diff --git a/lovely/listed_probabilities.toml b/lovely/listed_probabilities.toml
    deleted file mode 100644
    index 65698901b..000000000
    --- a/lovely/listed_probabilities.toml
    +++ /dev/null
    @@ -1,162 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -# Listed pseudorandom probabilities in cards
    -[[patches]]
    -[patches.regex]
    -target = 'card.lua'
    -pattern = '''pseudorandom\((.*?)\) ?< ?G\.GAME\.probabilities\.normal ?\/ ?(.*?)( |\)|$)'''
    -position = 'at'
    -payload = "SMODS.pseudorandom_probability(self, $1, 1, $2)$3"
    -
    -# The Wheel
    -# Don't have to modify loc_debuff_text since that's already done via override
    -# Also don't have for HUD_blind_debuff_prefix since SMODS makes it unused
    -[[patches]]
    -[patches.pattern]
    -target = "blind.lua"
    -pattern = '''if self.name == 'The Wheel' and pseudorandom(pseudoseed('wheel')) < G.GAME.probabilities.normal/7 then'''
    -position = "at"
    -match_indent = true
    -payload = '''if self.name == 'The Wheel' and SMODS.pseudorandom_probability(self, pseudoseed('wheel'), 1, 7, 'wheel') then'''
    -
    -# Space Joker prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == 'Space Joker' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == 'Space Joker' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, 'space')}'''
    -match_indent = true
    -
    -# 8 ball prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == '8 Ball' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1),self.ability.extra}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == '8 Ball' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, '8ball')}'''
    -match_indent = true
    -
    -# Gros michel prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == 'Gros Michel' then loc_vars = {self.ability.extra.mult, ''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == 'Gros Michel' then loc_vars = {self.ability.extra.mult, SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'gros_michel')}'''
    -match_indent = true
    -
    -# Business card prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == 'Business Card' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == 'Business Card' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, 'business')}'''
    -match_indent = true
    -
    -# Bloodstone prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == 'Bloodstone' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds, self.ability.extra.Xmult}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == 'Bloodstone' then 
    -    local a, b = SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'bloodstone')
    -    loc_vars = {a, b, self.ability.extra.Xmult}'''
    -match_indent = true
    -
    -# Cavendish prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == 'Cavendish' then loc_vars = {self.ability.extra.Xmult, ''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == 'Cavendish' then loc_vars = {self.ability.extra.Xmult, SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'cavendish')}'''
    -match_indent = true
    -
    -# Reserved parking prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == 'Reserved Parking' then loc_vars = {self.ability.extra.dollars, ''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == 'Reserved Parking' then loc_vars = {self.ability.extra.dollars, SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'parking')}'''
    -match_indent = true
    -
    -# Hallucination prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''elseif self.ability.name == 'Hallucination' then loc_vars = {G.GAME.probabilities.normal, self.ability.extra}'''
    -position = 'at'
    -payload = '''elseif self.ability.name == 'Hallucination' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, 'halu'..G.GAME.round_resets.ante)}'''
    -match_indent = true
    -
    -# Glass card prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''elseif _c.effect == 'Glass Card' then loc_vars = {cfg.Xmult, G.GAME.probabilities.normal, cfg.extra}'''
    -position = 'at'
    -payload = '''elseif _c.effect == 'Glass Card' then loc_vars = {cfg.Xmult, SMODS.get_probability_vars(card, 1, cfg.extra, 'glass')}'''
    -match_indent = true
    -
    -# Wheel prob vars
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''elseif _c.name == "The Wheel of Fortune" then loc_vars = {G.GAME.probabilities.normal, cfg.extra};  info_queue[#info_queue+1] = G.P_CENTERS.e_foil; info_queue[#info_queue+1] = G.P_CENTERS.e_holo; info_queue[#info_queue+1] = G.P_CENTERS.e_polychrome;'''
    -position = 'at'
    -payload = '''elseif _c.name == "The Wheel of Fortune" then 
    -    loc_vars = {SMODS.get_probability_vars(card, 1, cfg.extra, 'wheel_of_fortune')}
    -    info_queue[#info_queue+1] = G.P_CENTERS.e_foil
    -    info_queue[#info_queue+1] = G.P_CENTERS.e_holo
    -    info_queue[#info_queue+1] = G.P_CENTERS.e_polychrome'''
    -match_indent = true
    -
    -# Modify Oops All 6s! behaviour - removed for back compat for now
    -# [[patches]]
    -# [patches.pattern]
    -# target = 'card.lua'
    -# match_indent = true
    -# position = 'at'
    -# pattern = '''
    -# for k, v in pairs(G.GAME.probabilities) do 
    -#     G.GAME.probabilities[k] = v*2
    -# end
    -# '''
    -# payload = '''
    -# -- removed by smods
    -# '''
    -# [[patches]]
    -# [patches.pattern]
    -# target = 'card.lua'
    -# match_indent = true
    -# position = 'at'
    -# pattern = '''
    -# for k, v in pairs(G.GAME.probabilities) do 
    -#     G.GAME.probabilities[k] = v/2
    -# end
    -# '''
    -# payload = '''
    -# -- removed by smods
    -# '''
    -# Add Opps All 6s! calculation
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -elseif context.buying_card then
    -'''
    -payload = '''
    -elseif context.mod_probability and not context.blueprint and self.config.center_key == 'j_oops' then
    -    return {
    -        numerator = context.numerator * 2
    -    }
    -'''
    diff --git a/lovely/loader.toml b/lovely/loader.toml
    deleted file mode 100644
    index dfe4bd96a..000000000
    --- a/lovely/loader.toml
    +++ /dev/null
    @@ -1,27 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -### Supporting code for loader.lua
    -
    -## Save discovered, unlocked states
    -# Game:init_item_prototypes()
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = "meta.alerted = meta.alerted or {}"
    -position = 'after'
    -payload = '''
    -for _, t in ipairs{
    -    G.P_CENTERS,
    -    G.P_BLINDS,
    -    G.P_TAGS,
    -    G.P_SEALS,
    -} do
    -    for k, v in pairs(t) do
    -        SMODS._save_d_u(v)
    -        v._discovered_unlocked_overwritten = true
    -    end
    -end'''
    -match_indent = true
    diff --git a/lovely/menu.toml b/lovely/menu.toml
    deleted file mode 100644
    index 0e3e9fb57..000000000
    --- a/lovely/menu.toml
    +++ /dev/null
    @@ -1,60 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''local main_menu = nil'''
    -position = "after"
    -payload = '''local mods = nil'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''main_menu = UIBox_button{ label = {localize('b_main_menu')}, button = "go_to_menu", minw = 5}'''
    -position = "after"
    -payload = '''mods = UIBox_button{ id = "mods_button", label = {localize('b_mods')}, button = "mods_button", minw = 5}'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = '''G.ARGS.set_alerts_alertables[11].should_alert = alert_booster'''
    -position = "after"
    -payload = '''table.insert(G.ARGS.set_alerts_alertables, {id = 'mods_button', alert_uibox_name = 'mods_button_alert', should_alert = SMODS.mod_button_alert})'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''main_menu,'''
    -position = "after"
    -payload = '''mods,'''
    -match_indent = true
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = '''self.ASSET_ATLAS[self.asset_atli[i].name].image = love.graphics.newImage(self.asset_atli[i].path, {mipmaps = true, dpiscale = self.SETTINGS.GRAPHICS.texture_scaling})'''
    -position = 'after'
    -payload = '''
    -local mipmap_level = SMODS.config.graphics_mipmap_level_options[SMODS.config.graphics_mipmap_level]
    -if mipmap_level and mipmap_level > 0 then
    -    self.ASSET_ATLAS[self.asset_atli[i].name].image:setMipmapFilter('linear', mipmap_level)
    -end'''
    -match_indent = true
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''create_option_cycle({w = 4,scale = 0.8, label = localize("b_set_CRT_bloom"),options = localize('ml_bloom_opt'), opt_callback = 'change_crt_bloom', current_option = G.SETTINGS.GRAPHICS.bloom}),'''
    -position = 'after'
    -payload = '''
    -create_option_cycle({label = localize('b_graphics_mipmap_level'),scale = 0.8, options = SMODS.config.graphics_mipmap_level_options, opt_callback = 'SMODS_change_mipmap', current_option = SMODS.config.graphics_mipmap_level}),'''
    -match_indent = true
    -
    diff --git a/lovely/mod.toml b/lovely/mod.toml
    deleted file mode 100644
    index 8feb0d80f..000000000
    --- a/lovely/mod.toml
    +++ /dev/null
    @@ -1,72 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -### Per-mod functions
    -
    -# end_round()
    -[[patches]]
    -[patches.regex]
    -target = 'functions/state_events.lua'
    -pattern = '''(?[\t ]*)reset_castle_card\(\)'''
    -line_prepend = '$indent'
    -position = 'after'
    -payload = '''
    -
    -for _, mod in ipairs(SMODS.mod_list) do
    -	if mod.reset_game_globals and type(mod.reset_game_globals) == 'function' then
    -		mod.reset_game_globals(false)
    -	end
    -end'''
    -
    -# Game:start_run()
    -[[patches]]
    -[patches.regex]
    -target = 'game.lua'
    -pattern = '''(?[\t ]*)reset_castle_card\(\)'''
    -line_prepend = '$indent'
    -position = 'after'
    -payload = '''
    -
    -for _, mod in ipairs(SMODS.mod_list) do
    -	if mod.reset_game_globals and type(mod.reset_game_globals) == 'function' then
    -		mod.reset_game_globals(true)
    -	end
    -end'''
    -
    -# Card:set_debuff()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "function Card:set_debuff(should_debuff)"
    -position = 'after'
    -match_indent = true
    -payload = '''
    -	for _, mod in ipairs(SMODS.mod_list) do
    -		if mod.set_debuff and type(mod.set_debuff) == 'function' then
    -            local res = mod.set_debuff(self)
    -            if res == 'prevent_debuff' then
    -                if self.debuff then
    -                    self.debuff = false
    -                    if self.area == G.jokers then self:add_to_deck(true) end
    -					self.debuffed_by_blind = false
    -                end
    -                return
    -            end
    -			should_debuff = should_debuff or res
    -		end
    -	end
    -	for k, v in pairs(self.ability.debuff_sources or {}) do
    -		if v == 'prevent_debuff' then
    -			if self.debuff then
    -				self.debuff = false
    -				if self.area == G.jokers then self:add_to_deck(true) end
    -			end
    -			self.debuffed_by_blind = false
    -			return
    -		end
    -		should_debuff = should_debuff or v
    -	end
    -	
    -'''
    diff --git a/lovely/multi_box_descriptions.toml b/lovely/multi_box_descriptions.toml
    deleted file mode 100644
    index b60194f0e..000000000
    --- a/lovely/multi_box_descriptions.toml
    +++ /dev/null
    @@ -1,158 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Handle multi boxes in localize
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -for _, lines in ipairs(args.type == 'unlocks' and loc_target.unlock_parsed or args.type == 'name' and loc_target.name_parsed or (args.type == 'text' or args.type == 'tutorial' or args.type == 'quips') and loc_target or loc_target.text_parsed) do
    -'''
    -payload = '''
    -args.AUT = args.AUT or {}
    -args.AUT.box_colours = {}
    -if (args.type == 'descriptions' or args.type == 'other') and type(loc_target.text) == 'table' and type(loc_target.text[1]) == 'table' then
    -    args.AUT.multi_box = args.AUT.multi_box or {} 
    -    for i, box in ipairs(loc_target.text_parsed) do
    -        for j, line in ipairs(box) do
    -            local final_line = SMODS.localize_box(line, args)
    -            if i == 1 or next(args.AUT.info) then
    -                args.nodes[#args.nodes+1] = final_line -- Sends main box to AUT.main
    -                if not next(args.AUT.info) then args.nodes.main_box_flag = true end
    -            elseif not next(args.AUT.info) then 
    -                args.AUT.multi_box[i-1] = args.AUT.multi_box[i-1] or {}
    -                args.AUT.multi_box[i-1][#args.AUT.multi_box[i-1]+1] = final_line
    -            end
    -            if not next(args.AUT.info) then args.AUT.box_colours[i] = args.vars.box_colours and args.vars.box_colours[i] or G.C.UI.BACKGROUND_WHITE end
    -        end
    -    end
    -    return
    -end
    -'''
    -
    -# Patch importing localizations
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -for _, line in ipairs(center.text) do
    -    center.text_parsed[#center.text_parsed+1] = loc_parse_string(line)
    -end
    -'''
    -payload = '''
    -for _, line in ipairs(center.text) do
    -    if type(line) == 'table' then
    -        center.text_parsed[#center.text_parsed+1] = {}
    -        for _, new_line in ipairs(line) do
    -             center.text_parsed[#center.text_parsed][#center.text_parsed[#center.text_parsed]+1] = loc_parse_string(new_line)
    -        end
    -    else
    -        center.text_parsed[#center.text_parsed+1] = loc_parse_string(line)
    -    end
    -end
    -'''
    -
    -# Create extra boxes
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -if AUT.info then
    -'''
    -payload = '''
    -AUT.main.background_colour = AUT.main.background_colour or AUT.box_colours and AUT.box_colours[1] or nil
    -local multi_boxes = {}
    -if AUT.multi_box then
    -    for i, box in ipairs(AUT.multi_box) do
    -        box.background_colour = box.background_colour or AUT.box_colours and AUT.box_colours[i+1] or nil
    -        multi_boxes[#multi_boxes+1] = desc_from_rows(box)
    -    end
    -end
    -'''
    -
    -# Change return so it can be modified
    -# Includes some info_boxes patch that got munched
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -return {n=G.UIT.ROOT, config = {align = 'cm', colour = G.C.CLEAR}, nodes={
    -    {n=G.UIT.C, config={align = "cm", func = 'show_infotip',object = Moveable(),ref_table = next(info_boxes) and info_boxes or nil}, nodes={
    -'''
    -payload = '''
    -local cols
    -if #info_boxes <= 3 then
    -    cols = 1
    -elseif #info_boxes <= 10 then
    -    cols = 2
    -elseif #info_boxes <= 24 then
    -    cols = 3
    -else
    -    cols = 4
    -end
    -local nodes_per_col = math.ceil(#info_boxes/cols)
    -local info_cols = {}
    -for i = 0, cols-1 do
    -    local col = {}
    -    for j = 1, nodes_per_col do
    -        local info_box = info_boxes[i*nodes_per_col+j]
    -        if info_box then
    -            table.insert(col, info_box)
    -        else break end
    -    end
    -    table.insert(info_cols, {n=G.UIT.C, config = {align="cm"}, nodes = col})
    -end
    -info_boxes = {{n=G.UIT.R, config = {align="cm", padding = 0.05, card_pos = card.T.x }, nodes = info_cols}}
    -local ret_val = {n=G.UIT.ROOT, config = {align = 'cm', colour = G.C.CLEAR}, nodes={
    -    {n=G.UIT.C, config={align = "cm", func = 'show_infotip',object = Moveable(),ref_table = next(info_boxes) and info_boxes or nil}, nodes={
    -'''
    -
    -# Add multi boxes to return table
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -            badges[1] and {n=G.UIT.R, config={align = "cm", padding = 0.03}, nodes=badges} or nil,
    -            }}
    -        }}
    -    }},
    -}}
    -'''
    -payload = '''
    -if multi_boxes[1] then
    -    for i=1, #ret_val.nodes[1].nodes[1].nodes[1].nodes do -- find the main box
    -        if ret_val.nodes[1].nodes[1].nodes[1].nodes[i] and ret_val.nodes[1].nodes[1].nodes[1].nodes[i].config and ret_val.nodes[1].nodes[1].nodes[1].nodes[i].config.main_box_flag then
    -            for j=#multi_boxes, 1, -1 do -- add the extra boxes
    -                table.insert(ret_val.nodes[1].nodes[1].nodes[1].nodes, i+1, multi_boxes[j])
    -            end
    -            break
    -        end
    -    end
    -    
    -end
    -
    -return ret_val
    -'''
    -
    -# Add main_box_flag to the main box
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -return {n=G.UIT.R, config={align = "cm", colour = empty and G.C.CLEAR or G.C.UI.BACKGROUND_WHITE, r = 0.1, padding = 0.04, minw = 2, minh = 0.8, emboss = not empty and 0.05 or nil, filler = true}, nodes={'''
    -payload = '''
    -return {n=G.UIT.R, config={align = "cm", colour = empty and G.C.CLEAR or G.C.UI.BACKGROUND_WHITE, r = 0.1, padding = 0.04, minw = 2, minh = 0.8, emboss = not empty and 0.05 or nil, filler = true, main_box_flag = desc_nodes.main_box_flag and true or nil}, nodes={'''
    diff --git a/lovely/number_formatting.toml b/lovely/number_formatting.toml
    deleted file mode 100644
    index 0d57d7a49..000000000
    --- a/lovely/number_formatting.toml
    +++ /dev/null
    @@ -1,177 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -#
    -# Use number_format for...
    -#
    -
    -# DynaText
    -
    -[[patches]] 
    -[patches.regex]
    -target = "engine/text.lua"
    -pattern = 'tostring\((?v\.ref_table and v\.ref_table\[v\.ref_value\] or v\.string)\)'
    -position = "at"
    -payload = "format_ui_value($param)"
    -
    -# Cash Out
    -
    -[[patches]] 
    -[patches.regex]
    -target = "functions/common_events.lua"
    -pattern = '''
    -localize\('\$'\)\.\.config\.dollars'''
    -position = "at"
    -payload = "localize('$')..format_ui_value(config.dollars)"
    -
    -# End of round money
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/common_events.lua"
    -pattern = '''
    -localize\('\$'\)\.\.num_dollars\}'''
    -position = "at"
    -payload = "localize('$')..format_ui_value(num_dollars)}"
    -
    -# Tooltip numbers
    -
    -[[patches]] 
    -[patches.regex]
    -target = "functions/misc_functions.lua"
    -pattern = '(?args\.vars\[tonumber\(subpart\[1\]\)\])'
    -position = "at"
    -payload = 'format_ui_value($param)'
    -
    -# Poker Hand chips
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "{n=G.UIT.T, config={text = G.GAME.hands[handname].chips, scale = 0.45, colour = G.C.UI.TEXT_LIGHT}},"
    -position = "at"
    -payload = "{n=G.UIT.T, config={text = number_format(G.GAME.hands[handname].chips, 1000000), scale = 0.45, colour = G.C.UI.TEXT_LIGHT}},"
    -match_indent = true
    -
    -# Poker Hand mult
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "{n=G.UIT.T, config={text = G.GAME.hands[handname].mult, scale = 0.45, colour = G.C.UI.TEXT_LIGHT}}"
    -position = "at"
    -payload = "{n=G.UIT.T, config={text = number_format(G.GAME.hands[handname].mult, 1000000), scale = 0.45, colour = G.C.UI.TEXT_LIGHT}}"
    -match_indent = true
    -
    -# Continue Run - Money
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/UI_definitions.lua"
    -pattern = 'tostring\(saved_game\.GAME\.dollars\)'
    -position = "at"
    -payload = "format_ui_value(saved_game.GAME.dollars)"
    -
    -# Continue Run - Best Hand - bigger size
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/UI_definitions.lua"
    -pattern = 'scale_number\(saved_game\.GAME\.round_scores\.hand\.amt\, 0\.8\*scale\)'
    -position = "at"
    -payload = "scale_number(saved_game.GAME.round_scores.hand.amt, 0.8*scale, 100000000000)"
    -
    -
    -#
    -# Custom sci notation switch point
    -#
    -
    -## number_format
    -[[patches]] 
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'function number_format(num)'
    -position = "at"
    -payload = '''
    -function number_format(num, e_switch_point)
    -    if type(num) ~= 'number' then return num end
    -
    -    local sign = (num >= 0 and "") or "-"
    -    num = math.abs(num)'''
    -match_indent = true
    -
    -[[patches]] 
    -[patches.regex]
    -target = "functions/misc_functions.lua"
    -pattern = 'num >= G\.E_SWITCH_POINT'
    -position = "at"
    -payload = "num >= (e_switch_point or G.E_SWITCH_POINT)"
    -
    -# 1. Fix floating point error (1.000e92 instead of 10.000e91)
    -# 2. Lower precision with higher numbers
    -[[patches]] 
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -return string.format("%.3f",x/(10^fac))..'e'..fac'''
    -position = "at"
    -payload = '''
    -if num == math.huge then
    -    return sign.."naneinf"
    -end
    -
    -local mantissa = round_number(x/(10^fac), 3)
    -if mantissa >= 10 then
    -    mantissa = mantissa / 10
    -    fac = fac + 1
    -end
    -return sign..(string.format(fac >= 100 and "%.1fe%i" or fac >= 10 and "%.2fe%i" or "%.3fe%i", mantissa, fac))'''
    -match_indent = true
    -
    -# Remove trailing zeroes
    -# E.g. X1.5 being displayed as X1.50
    -[[patches]] 
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -return string.format(num ~= math.floor(num) and (num >= 100 and "%.0f" or num >= 10 and "%.1f" or "%.2f") or "%.0f", num):reverse():gsub("(%d%d%d)", "%1,"):gsub(",$", ""):reverse()'''
    -position = "at"
    -payload = '''
    -local formatted
    -if num ~= math.floor(num) and num < 100 then
    -    formatted = string.format(num >= 10 and "%.1f" or "%.2f", num)
    -    if formatted:sub(-1) == "0" then
    -        formatted = formatted:gsub("%.?0+$", "")
    -    end
    -    -- Return already to avoid comas being added
    -    if num < 0.01 then return tostring(num) end
    -else 
    -    formatted = string.format("%.0f", num)
    -end
    -return sign..(formatted:reverse():gsub("(%d%d%d)", "%1,"):gsub(",$", ""):reverse())'''
    -match_indent = true
    -
    -## scale_number
    -[[patches]] 
    -[patches.pattern]
    -target = "functions/button_callbacks.lua"
    -pattern = 'function scale_number(number, scale, max)'
    -position = "at"
    -payload = 'function scale_number(number, scale, max, e_switch_point)'
    -match_indent = true
    -
    -[[patches]] 
    -[patches.regex]
    -target = "functions/button_callbacks.lua"
    -pattern = 'number >='
    -position = "at"
    -payload = "math.abs(number) >="
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/button_callbacks.lua"
    -pattern = 'math\.log\(number\*10, 10\)'
    -position = "at"
    -payload = "math.log(math.abs(number)*10, 10)"
    diff --git a/lovely/perma_bonus.toml b/lovely/perma_bonus.toml
    deleted file mode 100644
    index 8a87adff0..000000000
    --- a/lovely/perma_bonus.toml
    +++ /dev/null
    @@ -1,297 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -### Permanent card values implementations
    -## Adapted from AMM (https://github.com/AutumnMood924/AutumnMoodMechanics)
    -
    -# generate_card_ui(): mult card special case
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if _c.effect == 'Mult Card' then loc_vars = {cfg.mult}"
    -position = "at"
    -payload = "if _c.effect == 'Mult Card' then loc_vars = {SMODS.signed(cfg.mult)}"
    -match_indent = true
    -
    -# generate_card_ui(): gold card special case
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "elseif _c.effect == 'Gold Card' then loc_vars = {cfg.h_dollars}"
    -position = "at"
    -payload = "elseif _c.effect == 'Gold Card' then loc_vars = {SMODS.signed_dollars(cfg.h_dollars)}"
    -match_indent = true
    -
    -# generate_card_ui(): stone card special case
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "elseif _c.effect == 'Stone Card' then loc_vars = {((specific_vars and specific_vars.bonus_chips) or cfg.bonus)}"
    -position = "at"
    -payload = "elseif _c.effect == 'Stone Card' then loc_vars = {((specific_vars and SMODS.signed(specific_vars.bonus_chips)) or cfg.bonus and SMODS.signed(cfg.bonus) or 0)}"
    -match_indent = true
    -
    -# generate_card_ui(): show permanent bonuses on default playing cards
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "elseif _c.set == 'Enhanced' then"
    -position = "before"
    -payload = '''SMODS.localize_perma_bonuses(specific_vars, desc_nodes)'''
    -match_indent = true
    -overwrite = false
    -
    -# generate_card_ui(): signed(extra_chips)
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {specific_vars.bonus_chips}}"
    -position = "at"
    -match_indent = true
    -payload = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_chips)}}"
    -
    -# generate_card_ui(): signed(extra_chips)
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {((specific_vars and specific_vars.bonus_chips) or cfg.bonus)}}"
    -position = "at"
    -match_indent = true
    -payload = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {SMODS.signed((specific_vars and specific_vars.bonus_chips) or cfg.bonus)}}"
    -
    -# generate_card_ui(): show permanent bonuses on enhanced playing cards
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "elseif _c.set == 'Booster' then"
    -position = "before"
    -payload = '''SMODS.localize_perma_bonuses(specific_vars, desc_nodes)'''
    -match_indent = true
    -overwrite = false
    -
    -# generate_UIBox_ability_table(): prime locals for easier boolean magic
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "loc_vars = { playing_card = not not self.base.colour, value = self.base.value, suit = self.base.suit, colour = self.base.colour,"
    -position = "before"
    -payload = '''local bonus_chips = self.ability.bonus + (self.ability.perma_bonus or 0)
    -local total_h_dollars = self:get_h_dollars()'''
    -match_indent = true
    -overwrite = false
    -
    -# generate_UIBox_ability_table(): prime specific_vars for playing cards
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "bonus_chips = (self.ability.bonus + (self.ability.perma_bonus or 0)) > 0 and (self.ability.bonus + (self.ability.perma_bonus or 0)) or nil,"
    -position = "at"
    -payload = '''
    -bonus_x_chips = self.ability.perma_x_chips ~= 0 and (self.ability.perma_x_chips + 1) or nil,
    -bonus_mult = self.ability.perma_mult ~= 0 and self.ability.perma_mult or nil,
    -bonus_x_mult = self.ability.perma_x_mult ~= 0 and (self.ability.perma_x_mult + 1) or nil,
    -bonus_h_chips = self.ability.perma_h_chips ~= 0 and self.ability.perma_h_chips or nil,
    -bonus_h_x_chips = self.ability.perma_h_x_chips ~= 0 and (self.ability.perma_h_x_chips + 1) or nil,
    -bonus_h_mult = self.ability.perma_h_mult ~= 0 and self.ability.perma_h_mult or nil,
    -bonus_h_x_mult = self.ability.perma_h_x_mult ~= 0 and (self.ability.perma_h_x_mult + 1) or nil,
    -bonus_p_dollars = self.ability.perma_p_dollars ~= 0 and self.ability.perma_p_dollars or nil,
    -bonus_h_dollars = self.ability.perma_h_dollars ~= 0 and self.ability.perma_h_dollars or nil,
    -total_h_dollars = total_h_dollars ~= 0 and total_h_dollars or nil,
    -bonus_chips = bonus_chips ~= 0 and bonus_chips or nil,
    -bonus_repetitions = self.ability.perma_repetitions ~= 0 and self.ability.perma_repetitions or nil,'''
    -match_indent = true
    -overwrite = false
    -
    -# set_ability: set defaults for temporary bonuses
    -# Also add conformance with SMODS documentation.
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "x_mult = center.config.Xmult or 1,"
    -position = "at"
    -payload = '''
    -x_mult = center.config.Xmult or center.config.x_mult or 1,
    -h_chips = center.config.h_chips or 0,
    -x_chips = center.config.x_chips or 1,
    -h_x_chips = center.config.h_x_chips or 1,
    -repetitions = center.config.repetitions or 0,
    -'''
    -match_indent = true
    -overwrite = false
    -
    -# set_ability: set defaults for permanent bonuses
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "perma_bonus = self.ability and self.ability.perma_bonus or 0,"
    -position = "after"
    -payload = '''
    -perma_x_chips = self.ability and self.ability.perma_x_chips or 0,
    -perma_mult = self.ability and self.ability.perma_mult or 0,
    -perma_x_mult = self.ability and self.ability.perma_x_mult or 0,
    -perma_h_chips = self.ability and self.ability.perma_h_chips or 0,
    -perma_h_x_chips = self.ability and self.ability.perma_h_x_chips or 0,
    -perma_h_mult = self.ability and self.ability.perma_h_mult or 0,
    -perma_h_x_mult = self.ability and self.ability.perma_h_x_mult or 0,
    -perma_p_dollars = self.ability and self.ability.perma_p_dollars or 0,
    -perma_h_dollars = self.ability and self.ability.perma_h_dollars or 0,
    -perma_repetitions = self.ability and self.ability.perma_repetitions or 0,
    -card_limit = self.ability and self.ability.card_limit or 0,
    -extra_slots_used = self.ability and self.ability.extra_slots_used or 0,
    -'''
    -match_indent = true
    -overwrite = false
    -
    -# Card:get_chip_bonus
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''function Card:get_chip_bonus(*'''
    -position = "after"
    -match_indent = true
    -payload = '''
    -    if self.ability.extra_enhancement then return self.ability.bonus end'''
    -
    -# Card:get_chip_mult
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''if self.ability.effect == "Lucky Card" then
    -    if pseudorandom('lucky_mult') < G.GAME.probabilities.normal/5 then
    -        self.lucky_trigger = true
    -        return self.ability.mult
    -    else
    -        return 0
    -    end
    -else
    -    return self.ability.mult
    -end'''
    -position = "at"
    -match_indent = true
    -payload = '''local ret = (not self.ability.extra_enhancement and self.ability.perma_mult) or 0
    -if self.ability.effect == "Lucky Card" then
    -    if SMODS.pseudorandom_probability(self, 'lucky_mult', 1, 5) then
    -        self.lucky_trigger = true
    -        ret = ret + self.ability.mult
    -    end
    -else
    -    ret = ret + self.ability.mult
    -end
    --- TARGET: get_chip_mult
    -return ret'''
    -
    -# Card:get_chip_x_mult
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''if self.ability.x_mult <= 1 then return 0 end
    -return self.ability.x_mult'''
    -position = "at"
    -match_indent = true
    -payload = '''local ret = SMODS.multiplicative_stacking(self.ability.x_mult or 1, (not self.ability.extra_enhancement and self.ability.perma_x_mult) or 0)
    --- TARGET: get_chip_x_mult
    -return ret
    -'''
    -
    -# Card:get_chip_h_mult
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'return self.ability.h_mult'
    -position = "at"
    -match_indent = true
    -payload = '''local ret = (self.ability.h_mult or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_mult) or 0)
    --- TARGET: get_chip_h_mult
    -return ret
    -'''
    -
    -# Card:get_chip_h_x_mult
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'return self.ability.h_x_mult'
    -position = "at"
    -match_indent = true
    -payload = '''local ret = SMODS.multiplicative_stacking(self.ability.h_x_mult or 1, (not self.ability.extra_enhancement and self.ability.perma_h_x_mult) or 0)
    --- TARGET: get_chip_h_x_mult
    -return ret
    -'''
    -
    -# Card:get_chip_x_bonus
    -# Card:get_chip_h_bonus
    -# Card:get_chip_h_x_bonus
    -# Card:get_h_dollars
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'function Card:get_edition()'
    -position = "before"
    -match_indent = true
    -payload = '''
    -function Card:get_chip_x_bonus()
    -    if self.debuff then return 0 end
    -    local ret = SMODS.multiplicative_stacking(self.ability.x_chips or 1, (not self.ability.extra_enhancement and self.ability.perma_x_chips) or 0)
    -    -- TARGET: get_chip_x_bonus
    -    return ret
    -end
    -
    -function Card:get_chip_h_bonus()
    -    if self.debuff then return 0 end
    -    local ret = (self.ability.h_chips or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_chips) or 0)
    -    -- TARGET: get_chip_h_bonus
    -    return ret
    -end
    -
    -function Card:get_chip_h_x_bonus()
    -    if self.debuff then return 0 end
    -    local ret = SMODS.multiplicative_stacking(self.ability.h_x_chips or 1, (not self.ability.extra_enhancement and self.ability.perma_h_x_chips) or 0)
    -    -- TARGET: get_chip_h_x_bonus
    -    return ret
    -end
    -
    -function Card:get_h_dollars()
    -    if self.debuff then return 0 end
    -    local ret = (self.ability.h_dollars or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_dollars) or 0)
    -    -- TARGET: get_h_dollars
    -    return ret
    -end
    -'''
    -
    -# Card:get_p_dollars
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''end
    -if ret > 0 then
    -    G.GAME.dollar_buffer = (G.GAME.dollar_buffer or 0) + ret'''
    -position = "at"
    -match_indent = true
    -payload = '''elseif self.ability.p_dollars < 0 then
    -    ret = ret + self.ability.p_dollars
    -end
    -ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_p_dollars) or 0)
    --- TARGET: get_p_dollars
    -if ret ~= 0 then
    -    G.GAME.dollar_buffer = (G.GAME.dollar_buffer or 0) + ret'''
    -
    -# Card:get_end_of_round_effect
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -if self.ability.h_dollars > 0 then
    -    ret.h_dollars = self.ability.h_dollars
    -    ret.card = self
    -end
    -'''
    -position = "at"
    -match_indent = true
    -payload = '''
    -local h_dollars = self:get_h_dollars()
    -if h_dollars ~= 0 then
    -    ret.h_dollars = h_dollars
    -    ret.card = self
    -end
    -'''
    \ No newline at end of file
    diff --git a/lovely/playing_card.toml b/lovely/playing_card.toml
    deleted file mode 100644
    index 1c7c22435..000000000
    --- a/lovely/playing_card.toml
    +++ /dev/null
    @@ -1,378 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Playing Card API
    -
    -# Game:init_game_object()
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -pattern = 'function Game:init_game_object()'
    -position = 'after'
    -match_indent = true
    -payload = '''
    -    local cards_played = {}
    -    for _,v in ipairs(SMODS.Rank.obj_buffer) do
    -        cards_played[v] = { suits = {}, total = 0 }
    -    end'''
    -
    -[[patches]]
    -[patches.regex]
    -target = "game.lua"
    -pattern = '(?[\t ]*)cards_played = \{\n(.*\n){13}[\t ]*\},'
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -    cards_played = cards_played,
    -    disabled_suits = {},
    -    disabled_ranks = {},'''
    -
    -# Game:start_run()
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = 'local _ = nil'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -if not SMODS.add_to_pool(SMODS.Ranks[v.value], {initial_deck = true, suit = v.suit})
    -or not SMODS.add_to_pool(SMODS.Suits[v.suit], {initial_deck = true, rank = v.value}) then
    -    goto continue
    -end'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = 'local _ = nil'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -if self.GAME.selected_back_key.initial_deck then
    -    local _id = self.GAME.selected_back_key.initial_deck
    -    local _exclude = _id.exclude
    -    for i,_tab in ipairs({_id.Ranks or _id.exclude and {} or SMODS.Rank.obj_buffer, _id.Suits}) do
    -        if _tab ~= nil then
    -            local _create = not not _exclude
    -            for _,_v in ipairs(_tab) do
    -                if (i==1 and v.value or v.suit) == _v then _create = not _exclude; break end
    -            end
    -            if not _create then
    -                goto continue
    -            end
    -        end
    -    end
    -end'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "if self.GAME.starting_params.erratic_suits_and_ranks then _, k = pseudorandom_element(G.P_CARDS, pseudoseed('erratic')) end"
    -position = 'at'
    -match_indent = true
    -payload = '''if self.GAME.starting_params.erratic_suits_and_ranks then
    -    v, k = pseudorandom_element(G.P_CARDS, pseudoseed('erratic'), {starting_deck = true})
    -end'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = 'local _r, _s = string.sub(k, 3, 3), string.sub(k, 1, 1)'
    -position = 'at'
    -match_indent = true
    -payload = 'local _r, _s = SMODS.Ranks[v.value].card_key, SMODS.Suits[v.suit].card_key'
    -
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "if self.GAME.starting_params.no_faces and (_r == 'K' or _r == 'Q' or _r == 'J') then keep = false end"
    -position = 'at'
    -match_indent = true
    -payload = '''
    -if self.GAME.starting_params.no_faces and SMODS.Ranks[v.value].face then keep = false end'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "if keep then card_protos[#card_protos+1] = {s=_s,r=_r,e=_e,d=_d,g=_g} end"
    -position = "after"
    -payload = "::continue::"
    -match_indent = true
    -
    -# loc_colour()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = 'return G.ARGS.LOC_COLOURS[_c] or _default or G.C.UI.TEXT_DARK'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -    for _, v in ipairs(SMODS.Rarity.obj_buffer) do
    -        G.ARGS.LOC_COLOURS[v:lower()] = G.C.RARITY[v]
    -    end
    -    for _, v in ipairs(SMODS.Gradient.obj_buffer) do
    -        G.ARGS.LOC_COLOURS[v:lower()] = SMODS.Gradients[v]
    -    end
    -    for _, v in ipairs(SMODS.ConsumableType.obj_buffer) do
    -        G.ARGS.LOC_COLOURS[v:lower()] = G.C.SECONDARY_SET[v]
    -    end
    -    for _, v in ipairs(SMODS.Suit.obj_buffer) do
    -        G.ARGS.LOC_COLOURS[v:lower()] = G.C.SUITS[v]
    -    end'''
    -
    -# get_flush()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -local suits = {
    -    "Spades",
    -    "Hearts",
    -    "Clubs",
    -    "Diamonds"
    -  }
    -'''
    -position = 'at'
    -payload = 'local suits = SMODS.Suit.obj_buffer'
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "local four_fingers = next(find_joker('Four Fingers'))"
    -position = 'at'
    -payload = "local four_fingers = SMODS.four_fingers('flush')"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "if #hand > 5 or #hand < (5 - (four_fingers and 1 or 0)) then return ret else"
    -position = 'at'
    -payload = "if #hand < four_fingers then return ret else"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "if flush_count >= (5 - (four_fingers and 1 or 0)) then"
    -position = 'at'
    -payload = "if flush_count >= four_fingers then"
    -match_indent = true
    -
    -# get_X_same()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = 'local vals = {{},{},{},{},{},{},{},{},{},{},{},{},{},{}}'
    -position = 'at'
    -match_indent = true
    -payload = '''
    -local vals = {}
    -for i = 1, SMODS.Rank.max_id.value do
    -    vals[i] = {}
    -end'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = 'function get_X_same(num, hand)'
    -position = 'at'
    -match_indent = true
    -payload = '''function get_X_same(num, hand, or_more)'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = 'if #curr == num then'
    -position = 'at'
    -match_indent = true
    -payload = '''if or_more and (#curr >= num) or (#curr == num) then'''
    -
    -# Card:get_nominal()
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = 'function Card:get_nominal\(mod\)\n([\t ]+.*\n)*end'
    -position = 'at'
    -payload = '''
    -function Card:get_nominal(mod)
    -    local mult = 1
    -    local rank_mult = 1
    -    if mod == 'suit' then mult = 10000 end
    -    if self.ability.effect == 'Stone Card' or (self.config.center.no_suit and self.config.center.no_rank) then
    -        mult = -10000
    -    elseif self.config.center.no_suit then
    -        mult = 0
    -    elseif self.config.center.no_rank then
    -        rank_mult = 0
    -    end
    -    return 10*self.base.nominal*rank_mult + self.base.suit_nominal*mult + (self.base.suit_nominal_original or 0)*0.0001*mult + 10*self.base.face_nominal*rank_mult + 0.000001*self.unique_val
    -end'''
    -
    -# Card:set_base()
    -[[patches]]
    -[patches.regex]
    -target = 'card.lua'
    -pattern = "(?[\t ]*)if self.base.value == '2' then self.base.nominal = 2; self.base.id = 2(\n[\t ]+elseif .*)*"
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local rank = SMODS.Ranks[self.base.value] or {}
    -self.base.nominal = rank.nominal or 0
    -self.base.face_nominal = rank.face_nominal or 0
    -self.base.id = rank.id'''
    -
    -[[patches]]
    -[patches.regex]
    -target = 'card.lua'
    -pattern = "(?[\t ]*)if self.base.suit == 'Diamonds' then self.base.suit_nominal = 0.01; self.base.suit_nominal_original = suit_base_nominal_original or 0.001 (\n[\t ]+elseif .*)*"
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local suit = SMODS.Suits[self.base.suit] or {}
    -self.base.suit_nominal = suit.suit_nominal or 0
    -self.base.suit_nominal_original = suit_base_nominal_original or suit.suit_nominal or 0'''
    -
    -# Card:change_suit()
    -[[patches]]
    -[patches.regex]
    -target = 'card.lua'
    -pattern = "(?[\t ]*)local new_code = [\\s\\S]*?local new_val = [\\s\\S]*?local new_card = G.P_CARDS\\[new_code..new_val\\]"
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local new_code = SMODS.Suits[new_suit].card_key
    -local new_val = SMODS.Ranks[self.base.value].card_key
    -local new_card = G.P_CARDS[new_code..'_'..new_val]'''
    -
    -# Card:is_face()
    -[[patches]]
    -[patches.regex]
    -target = 'card.lua'
    -pattern = "(?[\t ]*)if id == 11 or id == 12 or id == 13 or next\\(find_joker\\(\"Pareidolia\"\\)\\) then"
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local rank = SMODS.Ranks[self.base.value]
    -if not id then return end
    -if (id > 0 and rank and rank.face) or next(find_joker("Pareidolia")) then'''
    -
    -# Card:load()
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''self.VT.h = self.T.H'''
    -position = "at"
    -payload = '''self.VT.h = self.T.h'''
    -match_indent = true
    -
    -# tally_sprite()
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -pattern = '(?[\t ]*local t_s = Sprite\(0,0,0.5,0.5,)G.ASSET_ATLAS\[.*?\](?.*?\))'
    -position = 'at'
    -payload = '$start G.ASSET_ATLAS[suit and SMODS.Suits[suit][G.SETTINGS.colourblind_option and "hc_ui_atlas" or "lc_ui_atlas"]] or G.ASSET_ATLAS[("ui_"..(G.SETTINGS.colourblind_option and "2" or "1"))]$rest'
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = 'function tally_sprite(pos, value, tooltip)'
    -position = 'at'
    -match_indent = true
    -payload = 'function tally_sprite(pos, value, tooltip, suit)'
    -
    -# G.UIDEF.challenge_description_tab()
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -pattern = "(?[\t ]*)local SUITS = \\{(\n.*){5}\n[\t ]*local suit_map = \\{'S', 'H', 'C', 'D'\\}"
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local SUITS = {}
    -local suit_map = {}
    -for i = #SMODS.Suit.obj_buffer, 1, -1 do
    -    local suit = SMODS.Suits[SMODS.Suit.obj_buffer[i]]
    -    SUITS[suit.card_key] = {}
    -    suit_map[#suit_map+1] = suit.card_key
    -end'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = 'local _r, _s = string.sub(k, 3, 3), string.sub(k, 1, 1)'
    -position = 'at'
    -match_indent = true
    -payload = 'local _r, _s = SMODS.Ranks[v.value].card_key, SMODS.Suits[v.suit].card_key'
    -
    -# TODO there may need to be a way to let in_pool know what challenge is being displayed
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = 'local keep, _e, _d, _g = true, nil, nil, nil'
    -position = 'after'
    -match_indent = true
    -payload = '''
    -if not SMODS.add_to_pool(SMODS.Ranks[v.value], {initial_deck = true, suit = v.suit}) then
    -    keep = false
    -end
    -if not SMODS.add_to_pool(SMODS.Suits[v.suit], {initial_deck = true, rank = v.value}) then
    -    keep = false
    -end'''
    -
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -pattern = '(?[\t ]*)for j = 1, 4 do\n[\t ]*(?if SUITS\[suit_map\[j\]\]\[1\] then\n[\t ]*table.sort.*(\n.*)*?)\n[\t ]*0\.42\*G.CARD_H,'
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local num_suits = 0
    -for j = 1, #suit_map do
    -    if SUITS[suit_map[j]][1] then num_suits = num_suits + 1 end
    -end
    -for j = 1, #suit_map do
    -    $mid
    -            (0.42 - (num_suits <= 4 and 0 or num_suits >= 8 and 0.28 or 0.07 * (num_suits - 4))) * G.CARD_H,'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '--Fill all remaining info if this is the main desc'
    -position = 'before'
    -match_indent = true
    -payload = '''if card_type == 'Default' or card_type == 'Enhanced' and not _c.replace_base_card and card and card.base then
    -    if not _c.no_suit then
    -        local suit = SMODS.Suits[card.base.suit] or {}
    -        if suit.loc_vars and type(suit.loc_vars) == 'function' then
    -            suit:loc_vars(info_queue, card)
    -        end
    -    end
    -    if not _c.no_rank then
    -        local rank = SMODS.Ranks[card.base.value] or {}
    -        if rank.loc_vars and type(rank.loc_vars) == 'function' then
    -            rank:loc_vars(info_queue, card)
    -        end
    -    end
    -end
    -
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''function Card:set_base(card, initial)'''
    -position = 'at'
    -match_indent = true
    -payload = '''function Card:set_base(card, initial, manual_sprites)'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''if next(card) then'''
    -position = 'at'
    -match_indent = true
    -payload = '''if next(card) and not manual_sprites then'''
    diff --git a/lovely/poker_hand.toml b/lovely/poker_hand.toml
    deleted file mode 100644
    index 8bb29183a..000000000
    --- a/lovely/poker_hand.toml
    +++ /dev/null
    @@ -1,127 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -### Poker Hand API
    -
    -# evaluate_poker_hand()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "local parts = {"
    -position = 'before'
    -payload = '''
    -for _,v in ipairs(SMODS.PokerHand.obj_buffer) do
    -    results[v] = {}
    -end'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "if next(parts._5) and next(parts._flush) then"
    -position = 'before'
    -payload = '''
    -for _,_hand in pairs(SMODS.PokerHands) do
    -    if _hand.atomic_part and type(_hand.atomic_part) == 'function' then
    -        parts[_hand.key] = _hand.atomic_part(hand)
    -    end
    -end'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "return results"
    -position = 'before'
    -payload = '''
    -for _,_hand in pairs(SMODS.PokerHands) do
    -    if _hand.composite and type(_hand.composite) == 'function' then
    -        local other_hands
    -        results[_hand.key], other_hands = _hand.composite(parts)
    -        results[_hand.key] = results[_hand.key] or {}
    -        if other_hands and type(other_hands) == 'table' then
    -            for k, v in pairs(other_hands) do
    -                results[k] = v
    -            end
    -        end
    -    else
    -        results[_hand.key] = parts[_hand.key]
    -    end
    -end
    -results.top = nil
    -for _, v in ipairs(G.handlist) do
    -    if not results.top and results[v] then
    -        results.top = results[v]
    -        break
    -    end
    -end'''
    -match_indent = true
    -
    -## is_visible
    -
    -# create_UIBox_blind_choice
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''if v.visible then _poker_hands[#_poker_hands+1] = k end'''
    -position = "at"
    -payload = '''
    -if SMODS.is_poker_hand_visible(k) then _poker_hands[#_poker_hands+1] = k end
    -'''
    -match_indent = true
    -
    -# card:calculate_joker
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''if k ~= context.scoring_name and v.played >= play_more_than and v.visible then'''
    -position = "at"
    -payload = '''
    -if k ~= context.scoring_name and v.played >= play_more_than and SMODS.is_poker_hand_visible(k) then
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''if v.visible and k ~= self.ability.to_do_poker_hand then _poker_hands[#_poker_hands+1] = k end'''
    -position = "at"
    -payload = '''
    -if SMODS.is_poker_hand_visible(k) and k ~= self.ability.to_do_poker_hand then _poker_hands[#_poker_hands+1] = k end
    -'''
    -match_indent = true
    -
    -# card:set_ability
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''if v.visible then _poker_hands[#_poker_hands+1] = k end'''
    -position = "at"
    -payload = '''
    -if SMODS.is_poker_hand_visible(k) then _poker_hands[#_poker_hands+1] = k end
    -'''
    -match_indent = true
    -
    -# G.FUNCS.evaluate_play()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/state_events.lua"
    -pattern = '''G.GAME.hands[text].played_this_round = G.GAME.hands[text].played_this_round + 1'''
    -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/lovely/poker_hand_screen.toml b/lovely/poker_hand_screen.toml
    deleted file mode 100644
    index e37d667b9..000000000
    --- a/lovely/poker_hand_screen.toml
    +++ /dev/null
    @@ -1,54 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Add enhancements to poker hand screen
    -# create_UIBox_hand_tip
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''local card = Card(0,0, 0.5*G.CARD_W, 0.5*G.CARD_H, G.P_CARDS[v[1]], G.P_CENTERS.c_base)'''
    -position = "at"
    -payload = '''
    -local card = Card(0,0, 0.5*G.CARD_W, 0.5*G.CARD_H, G.P_CARDS[v[1]], G.P_CENTERS[v.enhancement or 'c_base'])
    -if v.edition then card:set_edition(v.edition, true, true) end
    -if v.seal then card:set_seal(v.seal, true, true) end
    -'''
    -match_indent = true
    -
    -# Add text styling to poker hand screen
    -# create_popup_UIBox_tooltip
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''local r = {n=G.UIT.R, config={align = "cm", padding = 0.03}, nodes={
    -                {n=G.UIT.T, config={text = text[i],colour = G.C.UI.TEXT_DARK, scale = 0.4}}}}'''
    -position = "at"
    -payload = '''
    -local r = {n=G.UIT.R, config={align = "cm", padding = 0.03}, nodes=SMODS.localize_box(loc_parse_string(text[i]), {scale = 1.25})}
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''
    -function create_UIBox_current_hand_row(handname, simple)
    -    return (G.GAME.hands[handname].visible) and
    -'''
    -position = "at"
    -payload = '''
    -function create_UIBox_current_hand_row(handname, simple, in_collection)
    -    return (in_collection or SMODS.is_poker_hand_visible(handname)) and
    -'''
    -match_indent = true
    -
    -# Display total played count in collection
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''{n=G.UIT.T, config={text = G.GAME.hands[handname].played, scale = 0.45, colour = G.C.FILTER, shadow = true}},'''
    -position = "at"
    -payload = '''{n=G.UIT.O, config={object = DynaText({string = {tostring(in_collection and G.PROFILES[G.SETTINGS.profile].hand_usage[string.gsub(handname, " ", "")] and G.PROFILES[G.SETTINGS.profile].hand_usage[string.gsub(handname, " ", "")].count or G.GAME.hands[handname].played)}, maxw = 0.9, scale = 0.45, colours = {G.C.FILTER}, shadow = true})}},'''
    -match_indent = true
    \ No newline at end of file
    diff --git a/lovely/poker_hand_text.toml b/lovely/poker_hand_text.toml
    deleted file mode 100644
    index 80fedc101..000000000
    --- a/lovely/poker_hand_text.toml
    +++ /dev/null
    @@ -1,42 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -# statustext parameter in level_up_hand
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = 'function level_up_hand(card, hand, instant, amount)'
    -position = 'at'
    -match_indent = true
    -payload = 'function level_up_hand(card, hand, instant, amount, statustext)'
    -
    -# Allow custom StatusText values in update_hand_text
    -[[patches]]
    -[patches.regex]
    -target = 'functions/common_events.lua'
    -position = 'at'
    -line_prepend = '$indent'
    -pattern = '''(?[\t ]*)if vals\.StatusText[A-z0-9\n\t .~=()'\-+,<;>:{}]+config\.align\n[\t ]+\}\)\n[\t ]+end'''
    -payload = '''
    -if vals.StatusText then
    -    local StatusText = {
    -        text = delta,
    -        scale = 0.8, 
    -        hold = 1,
    -        cover = G.hand_text_area[name].parent,
    -        cover_colour = mix_colours(parameter.colour, col, 0.1),
    -        emboss = 0.05,
    -        align = 'cm',
    -        cover_align = G.hand_text_area[name].parent.config.align
    -    }
    -    if type(vals.StatusText) == 'string' then StatusText.text = vals.StatusText
    -    elseif type(vals.StatusText) == 'table' then
    -        for k,v in pairs(vals.StatusText) do
    -            if v ~= nil then StatusText[k] = v end
    -        end
    -    end
    -    attention_text(StatusText)
    -end
    -'''
    diff --git a/lovely/pool.toml b/lovely/pool.toml
    deleted file mode 100644
    index c21ea79ea..000000000
    --- a/lovely/pool.toml
    +++ /dev/null
    @@ -1,208 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Functions that affect random selection from pools
    -
    -# pseudorandom_element()
    -# TODO special cases for now
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "function pseudorandom_element(_t, seed)"
    -position = "at"
    -payload = """function pseudorandom_element(_t, seed, args)
    -    if seed and type(seed) == "string" then seed = pseudoseed(seed) end
    -    -- TODO special cases for now
    -    -- Preserves reverse nominal order for Suits, nominal+face_nominal order for Ranks
    -    -- for vanilla RNG
    -    if _t == SMODS.Suits then
    -        _t = SMODS.Suit:obj_list(true)
    -    end
    -    if _t == SMODS.Ranks then
    -        _t = SMODS.Rank:obj_list()
    -    end
    -"""
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "keys[#keys+1] = {k = k,v = v}"
    -position = "at"
    -payload = """
    -local keep = true
    -local in_pool_func =
    -    args and args.in_pool
    -    or type(v) == 'table' and type(v.in_pool) == 'function' and v.in_pool
    -    or _t == G.P_CARDS and function(c)
    -            --Handles special case for Erratic Deck
    -            local initial_deck = args and args.starting_deck or false
    -
    -            return not (
    -                not SMODS.add_to_pool(SMODS.Ranks[c.value], {initial_deck = initial_deck, suit = c.suit})
    -                or not SMODS.add_to_pool(SMODS.Suits[c.suit], {initial_deck = initial_deck, rank = c.value})
    -            )
    -        end
    -if in_pool_func then
    -    keep = in_pool_func(v, args)
    -end
    -if keep then
    -    keys[#keys+1] = {k = k,v = v}
    -end"""
    -match_indent = true
    -
    -# fixes pseudorandom_element on an empty list
    -# nil, nil is returned in that case
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "local key = keys[math.random(#keys)].k"
    -position = "before"
    -payload = "if #keys == 0 then return nil, nil end"
    -match_indent = true
    -
    -## get_current_pool()
    -
    -# Centers
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "else _starting_pool, _pool_key = G.P_CENTER_POOLS[_type], _type..(_append or '')"
    -match_indent = true
    -position = 'before'
    -payload = '''
    -elseif SMODS.ObjectTypes[_type] and SMODS.ObjectTypes[_type].rarities then
    -    local rarities = SMODS.ObjectTypes[_type].rarities
    -    local rarity
    -    if _legendary and rarities.legendary then
    -        rarity = rarities.legendary.key
    -    else
    -        rarity = _rarity or SMODS.poll_rarity(_type, 'rarity_'.._type..G.GAME.round_resets.ante..(_append or ''))
    -    end
    -    _starting_pool, _pool_key = SMODS.ObjectTypes[_type].rarity_pools[rarity], _type..rarity..(_append or '')'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if _type == 'Tarot' or _type == 'Tarot_Planet' then _pool[#_pool + 1] = \"c_strength\""
    -match_indent = true
    -position = 'at'
    -payload = '''
    -if _rarity and SMODS.Rarities[_rarity] and SMODS.Rarities[_rarity].disable_if_empty then _pool[#_pool + 1] = "empty_rarity"
    -elseif SMODS.ObjectTypes[_type] and SMODS.ObjectTypes[_type].default and G.P_CENTERS[SMODS.ObjectTypes[_type].default] then
    -    _pool[#_pool+1] = SMODS.ObjectTypes[_type].default
    -elseif _type == 'Tarot' or _type == 'Tarot_Planet' then _pool[#_pool + 1] = "c_strength"'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if v.name == 'Black Hole' or v.name == 'The Soul' then"
    -match_indent = true
    -position = 'at'
    -payload = "if v.name == 'Black Hole' or v.name == 'The Soul' or v.hidden then"
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if _type == 'Enhanced' then"
    -match_indent = true
    -position = 'before'
    -payload = '''
    -local in_pool, pool_opts = SMODS.add_to_pool(v, { source = _append })
    -pool_opts = pool_opts or {}
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''
    -if _type == 'Enhanced' then
    -    add = true
    -'''
    -match_indent = true
    -position = 'after'
    -payload = '''
    -elseif _type == 'Edition' then
    -    if v.in_shop then add = true end
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = 'elseif not (G.GAME.used_jokers[v.key] and not next(find_joker("Showman"))) and'
    -match_indent = true
    -position = 'at'
    -payload = '''elseif not (G.GAME.used_jokers[v.key] and not pool_opts.allow_duplicates and not SMODS.showman(v.key)) and'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if add and not G.GAME.banned_keys[v.key] then"
    -match_indent = true
    -position = 'before'
    -payload = '''
    -add = in_pool and (add or pool_opts.override_base_checks)
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''
    -elseif _type == 'Tag' then _pool[#_pool + 1] = "tag_handy"
    -'''
    -match_indent = true
    -position = 'after'
    -payload = '''
    -elseif _type == 'Edition' then _pool[#_pool + 1] = "e_foil"
    -'''
    -
    -## G.GAME.used_jokers now checks keys, not names
    -# Card:set_ability()
    -# Remove the old center from `used_jokers` if set_ability overrides
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "self.config.center = center"
    -position = 'after'
    -payload = '''
    -if not G.OVERLAY_MENU and old_center and not next(SMODS.find_card(old_center.key, true)) then
    -    G.GAME.used_jokers[old_center.key] = nil
    -end'''
    -match_indent = true
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -(?[\t ]*)for k, v in pairs\(G\.P_CENTERS\) do
    -[\t ]*if v\.name == self\.ability\.name then
    -[\t ]*G\.GAME\.used_jokers\[k\] = true
    -[\t ]*end
    -[\t ]*end'''
    -position = "at"
    -payload = '''
    -if self.config.center.key then
    -    G.GAME.used_jokers[self.config.center.key] = true
    -end
    -'''
    -line_prepend = "$indent"
    -# Card:remove()
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''
    -(?[\t ]*)for k, v in pairs\(G\.P_CENTERS\) do
    -[\t ]*if v\.name == self\.ability\.name then
    -[\t ]*if not next\(find_joker\(self\.ability\.name, true\)\) then 
    -[\t ]*G\.GAME\.used_jokers\[k\] = nil
    -[\t ]*end
    -[\t ]*end
    -[\t ]*end'''
    -position = "at"
    -payload = '''
    -if not next(SMODS.find_card(self.config.center.key, true)) then
    -    G.GAME.used_jokers[self.config.center.key] = nil
    -end'''
    -line_prepend = "$indent"
    diff --git a/lovely/preflight.toml b/lovely/preflight.toml
    deleted file mode 100644
    index 41371ede6..000000000
    --- a/lovely/preflight.toml
    +++ /dev/null
    @@ -1,47 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -11
    -
    -[[patches]]
    -[patches.module]
    -source = "src/preflight/sharedUtil.lua"
    -name = "SMODS.preflight.sharedUtil" 
    -
    -[[patches]]
    -[patches.module]
    -source = "src/preflight/logging.lua"
    -name = "SMODS.preflight.logging" 
    -
    -[[patches]]
    -[patches.module]
    -source = "src/preflight/loader.lua"
    -name = "SMODS.preflight.loader" 
    -
    -[[patches]]
    -[patches.module]
    -source = "src/preflight/sharedUI.lua"
    -name = "SMODS.preflight.sharedUI" 
    -
    -[[patches]]
    -[patches.module]
    -source = "src/preflight/core.lua"
    -before = "main.lua"
    -load_now = true
    -name = "SMODS.preflight.core" 
    -
    -[[patches]]
    -[patches.pattern]
    -target = '=[lovely SMODS.preflight.core "src/preflight/core.lua"]'
    -pattern = "local lovely_path = false -- This line is patched, don't edit it"
    -position = "at"
    -payload = """local lovely_path = [[{{lovely_hack:patch_dir}}/]]"""
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "main.lua"
    -pattern = "if (love.system.getOS() == 'OS X' ) and (jit.arch == 'arm64' or jit.arch == 'arm') then jit.off() end"
    -position = "before"
    -payload = """if SMODS and SMODS.preflight_force_quit then if SMODS.preflight_quit_before then SMODS.preflight_quit_before() end return end"""
    -match_indent = true
    diff --git a/lovely/rarity.toml b/lovely/rarity.toml
    deleted file mode 100644
    index 6b426ee32..000000000
    --- a/lovely/rarity.toml
    +++ /dev/null
    @@ -1,61 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Rarity API
    -
    -# get_badge_colour
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -for k, v in pairs(SMODS.Rarity.obj_buffer) do
    -    G.BADGE_COL[k] = G.C.RARITY[v]
    -end'''
    -
    -# G.UIDEF.card_h_popup
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "if AUT.card_type == 'Joker' or (AUT.badges and AUT.badges.force_rarity) then card_type = ({localize('k_common'), localize('k_uncommon'), localize('k_rare'), localize('k_legendary')})[card.config.center.rarity] end"
    -position = "at"
    -payload = "if AUT.card_type == 'Joker' or (AUT.badges and AUT.badges.force_rarity) then card_type = SMODS.Rarity:get_rarity_badge(card.config.center.rarity) end"
    -match_indent = true
    -
    -# Game:update
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "self.C.EDITION[2] = 0.7+0.2*(1+math.sin(self.TIMERS.REAL*1.5 + 6))"
    -position = "after"
    -payload = '''
    -for k, v in pairs(SMODS.Rarities) do
    -    if v.gradient and type(v.gradient) == "function" then v:gradient(dt) end
    -end'''
    -match_indent = true
    -
    -# get_current_pool
    -[[patches]]
    -[patches.regex]
    -target = "functions/common_events.lua"
    -pattern = '''(?[\t ]*)local rarity = _rarity or pseudorandom\('rarity'\.\.G\.GAME\.round_resets\.ante\.\.\(_append or ''\)\) \n[\s\S]{12}rarity = \(_legendary and 4\) or \(rarity > 0\.95 and 3\) or \(rarity > 0\.7 and 2\) or 1'''
    -position = "at"
    -payload = '''
    -_rarity = (_legendary and 4) or (type(_rarity) == "number" and ((_rarity > 0.95 and 3) or (_rarity > 0.7 and 2) or 1)) or _rarity
    -_rarity = ({Common = 1, Uncommon = 2, Rare = 3, Legendary = 4})[_rarity] or _rarity
    -local rarity = _rarity or SMODS.poll_rarity("Joker", 'rarity'..G.GAME.round_resets.ante..(_append or ''))
    -'''
    -
    -## Ensure that other cards set to string rarity work the same as set for int rarity
    -# Card:calculate_joker
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if self.ability.name == 'Baseball Card' and context.other_joker.config.center.rarity == 2 and self ~= context.other_joker then"
    -position = "at"
    -payload = '''if self.ability.name == 'Baseball Card' and self ~= context.other_joker and context.other_joker:is_rarity("Uncommon") then'''
    -match_indent = true
    diff --git a/lovely/scaling.toml b/lovely/scaling.toml
    deleted file mode 100644
    index f0f1d80c5..000000000
    --- a/lovely/scaling.toml
    +++ /dev/null
    @@ -1,708 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Ceremonial Dagger
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.ability.mult = self.ability.mult + sliced_card.sell_cost*2
    -'''
    -payload = '''
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.mult+2*sliced_card.sell_cost}}, colour = G.C.RED, no_juice = true})'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "mult",
    -    scalar_table = sliced_card,
    -    scalar_value = "sell_cost",
    -    operation = function(ref_table, ref_value, initial, scaling)
    -        ref_table[ref_value] = initial + 2*scaling
    -    end,
    -    scaling_message = {
    -        message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.mult+2*sliced_card.sell_cost}},
    -        colour = G.C.RED,
    -        no_juice = true
    -    }
    -})
    -'''
    -
    -# Flash Card
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.ability.mult = self.ability.mult + self.ability.extra
    -G.E_MANAGER:add_event(Event({
    -    func = (function()
    -        card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.mult}}, colour = G.C.MULT})
    -    return true
    -end)}))
    -'''
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "mult",
    -    scalar_value = "extra",
    -    message_key = 'a_mult',
    -    message_colour = G.C.RED
    -})
    -return nil, true
    -'''
    -# Red Card
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.ability.mult = self.ability.mult + self.ability.extra
    -                G.E_MANAGER:add_event(Event({
    -    func = function() 
    -        card_eval_status_text(self, 'extra', nil, nil, nil, {
    -            message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.extra}},
    -            colour = G.C.RED,
    -            delay = 0.45, 
    -            card = self
    -        }) 
    -        return true
    -    end}))
    -'''
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "mult",
    -    scalar_value = "extra",
    -    message_key = 'a_mult',
    -    message_colour = G.C.RED,
    -    message_delay = 0.45,
    -})
    -'''
    -# Spare Trousers
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.ability.mult = self.ability.mult + self.ability.extra
    -return {
    -    message = localize('k_upgrade_ex'),
    -    colour = G.C.RED,
    -    card = self
    -}
    -'''
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "mult",
    -    scalar_value = "extra",
    -    message_colour = G.C.RED
    -})
    -return nil, true
    -'''
    -# Ride The Bus
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -else
    -    self.ability.mult = self.ability.mult + self.ability.extra
    -end
    -'''
    -payload = '''
    -else
    -    SMODS.scale_card(self, {
    -        ref_table = self.ability,
    -        ref_value = "mult",
    -        scalar_value = "extra",
    -        no_message = true
    -    })
    -    return nil, true
    -end
    -'''
    -
    -# Egg
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.extra_value = self.ability.extra_value + self.ability.extra
    -self:set_cost()
    -return {
    -    message = localize('k_val_up'),
    -    colour = G.C.MONEY
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "extra_value",
    -    scalar_value = "extra",
    -    scaling_message = {
    -        message = localize('k_val_up'),
    -        colour = G.C.MONEY
    -    }
    -})
    -self:set_cost()
    -return nil, true
    -'''
    -
    -# Runner/Square Joker
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.extra.chips = self.ability.extra.chips + self.ability.extra.chip_mod
    -return {
    -    message = localize('k_upgrade_ex'),
    -    colour = G.C.CHIPS,
    -    card = self
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability.extra,
    -    ref_value = "chips",
    -    scalar_value = "chip_mod",
    -})
    -return nil, true
    -'''
    -
    -# Castle
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.extra.chips = self.ability.extra.chips + self.ability.extra.chip_mod
    -                  
    -return {
    -    message = localize('k_upgrade_ex'),
    -    card = self,
    -    colour = G.C.CHIPS
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability.extra,
    -    ref_value = "chips",
    -    scalar_value = "chip_mod",
    -})
    -return nil, true
    -'''
    -
    -# Wee Joker
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.extra.chips = self.ability.extra.chips + self.ability.extra.chip_mod
    -
    -return {
    -    extra = {focus = self, message = localize('k_upgrade_ex')},
    -    card = self,
    -    colour = G.C.CHIPS
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability.extra,
    -    ref_value = "chips",
    -    scalar_value = "chip_mod",
    -    no_message = true
    -})
    -return {
    -    extra = {focus = self, message = localize('k_upgrade_ex')},
    -    card = self
    -}
    -'''
    -
    -# Ice Cream
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.extra.chips = self.ability.extra.chips - self.ability.extra.chip_mod
    -return {
    -    message = localize{type='variable',key='a_chips_minus',vars={self.ability.extra.chip_mod}},
    -    colour = G.C.CHIPS
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability.extra,
    -    ref_value = "chips",
    -    scalar_value = "chip_mod",
    -    operation = "-",
    -    message_key = 'a_chips_minus'
    -})
    -return nil, true
    -'''
    -
    -# Madness
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -if self.ability.name == 'Madness' and not context.blueprint and not context.blind.boss then
    -    self.ability.x_mult = self.ability.x_mult + self.ability.extra
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -if self.ability.name == 'Madness' and not context.blueprint and not context.blind.boss then
    -'''
    -# Madness pt2
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if not (context.blueprint_card or self).getting_sliced then
    -    card_eval_status_text((context.blueprint_card or self), 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.x_mult}}})
    -end
    -'''
    -payload = '''
    -if not (context.blueprint_card or self).getting_sliced then
    -    SMODS.scale_card(context.blueprint_card or self, {
    -        ref_table = self.ability,
    -        ref_value = "x_mult",
    -        scalar_value = "extra",
    -        message_key = 'a_xmult'
    -    })
    -end
    -'''
    -
    -# Constellation
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -if self.ability.name == 'Constellation' and not context.blueprint and context.consumeable.ability.set == 'Planet' then
    -    self.ability.x_mult = self.ability.x_mult + self.ability.extra
    -    G.E_MANAGER:add_event(Event({
    -        func = function() card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}}}); return true
    -        end}))
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -if self.ability.name == 'Constellation' and not context.blueprint and context.consumeable.ability.set == 'Planet' then
    -    SMODS.scale_card(self, {
    -        ref_table = self.ability,
    -        ref_value = "x_mult",
    -        scalar_value = "extra",
    -        message_key = 'a_xmult'
    -    })
    -'''
    -# Campfire
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.ability.x_mult = self.ability.x_mult + self.ability.extra
    -G.E_MANAGER:add_event(Event({
    -    func = function() card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize('k_upgrade_ex')}); return true
    -    end}))
    -'''
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "x_mult",
    -    scalar_value = "extra",
    -    message_colour = G.C.FILTER
    -})
    -return nil, true
    -'''
    -# Lucky Cat
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.ability.x_mult = self.ability.x_mult + self.ability.extra
    -return {
    -    extra = {focus = self, message = localize('k_upgrade_ex'), colour = G.C.MULT},
    -    card = self
    -}
    -'''
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "x_mult",
    -    scalar_value = "extra",
    -    no_message = true
    -})
    -return {
    -    extra = {focus = self, message = localize('k_upgrade_ex'), colour = G.C.MULT},
    -    card = self
    -}
    -'''
    -# Obelisk
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -else
    -    self.ability.x_mult = self.ability.x_mult + self.ability.extra
    -end
    -'''
    -payload = '''
    -else
    -    SMODS.scale_card(self, {
    -        ref_table = self.ability,
    -        ref_value = "x_mult",
    -        scalar_value = "extra",
    -        no_message = true
    -    })
    -    return nil, true
    -end
    -'''
    -
    -# Hit the Road/Lucky Cat
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.x_mult = self.ability.x_mult + self.ability.extra
    -return {
    -    message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},
    -        colour = G.C.RED,
    -        delay = 0.45, 
    -    card = self
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "x_mult",
    -    scalar_value = "extra",
    -    message_key = 'a_xmult',
    -    message_colour = G.C.RED,
    -    message_delay = 0.45,
    -})
    -return nil, true
    -'''
    -
    -# Green Joker: Subtraction
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.mult = math.max(0, self.ability.mult - self.ability.extra.discard_sub)
    -if self.ability.mult ~= prev_mult then 
    -    return {
    -        message = localize{type='variable',key='a_mult_minus',vars={self.ability.extra.discard_sub}},
    -        colour = G.C.RED,
    -        card = self
    -    }
    -end
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -if self.ability.mult ~= 0 then
    -    SMODS.scale_card(self, {
    -        ref_table = self.ability,
    -        ref_value = "mult",
    -        scalar_table = self.ability.extra,
    -        scalar_value = "discard_sub",
    -        operation = function(ref_table, ref_value, initial, change)
    -            ref_table[ref_value] = math.max(0, initial - change)
    -        end,
    -        message_key = 'a_mult_minus',
    -        message_colour = G.C.RED
    -    })
    -    return nil, true
    -end
    -'''
    -
    -# Green Joker: Addition
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.mult = self.ability.mult + self.ability.extra.hand_add
    -return {
    -    card = self,
    -    message = localize{type='variable',key='a_mult',vars={self.ability.extra.hand_add}}
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "mult",
    -    scalar_table = self.ability.extra,
    -    scalar_value = "hand_add"
    -})
    -return nil, true
    -'''
    -
    -# Vampire
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.x_mult = self.ability.x_mult + self.ability.extra*#enhanced
    -return {
    -    message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},
    -    colour = G.C.MULT,
    -    card = self
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "x_mult",
    -    scalar_value = "extra",
    -    message_key = 'a_xmult',
    -    message_colour = G.C.MULT,
    -    operation = function(ref_table, ref_value, initial, scaling)
    -        ref_table[ref_value] = initial + scaling*#enhanced
    -    end
    -})
    -return nil, true
    -'''
    -
    -# Hologram
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.x_mult = self.ability.x_mult + #context.cards*self.ability.extra
    -card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.x_mult}}})
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "x_mult",
    -    scalar_value = "extra",
    -    message_key = 'a_xmult',
    -    operation = function(ref_table, ref_value, initial, scaling)
    -        ref_table[ref_value] = initial + scaling*#context.cards
    -    end
    -})
    -'''
    -
    -# Rocket
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.extra.dollars = self.ability.extra.dollars + self.ability.extra.increase
    -return {
    -    message = localize('k_upgrade_ex'),
    -    colour = G.C.MONEY
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability.extra,
    -    ref_value = "dollars",
    -    scalar_value = "increase",
    -    message_colour = G.C.MONEY
    -})
    -return nil, true
    -'''
    -
    -# Turtle Bean
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.extra.h_size = self.ability.extra.h_size - self.ability.extra.h_mod
    -G.hand:change_size(- self.ability.extra.h_mod)
    -return {
    -    message = localize{type='variable',key='a_handsize_minus',vars={self.ability.extra.h_mod}},
    -    colour = G.C.FILTER
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability.extra,
    -    ref_value = "h_size",
    -    scalar_value = "h_mod",
    -    message_key = 'a_handsize_minus',
    -    operation = function(ref_table, ref_value, initial, change)
    -        ref_table[ref_value] = initial - change
    -        G.hand:change_size(- change)
    -    end
    -})
    -return nil, true
    -'''
    -
    -# Popcorn
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.mult = self.ability.mult - self.ability.extra
    -return {
    -    message = localize{type='variable',key='a_mult_minus',vars={self.ability.extra}},
    -    colour = G.C.MULT
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "mult",
    -    scalar_value = "extra",
    -    message_key = 'a_mult_minus',
    -    colour = G.C.MULT,
    -    operation = '-'
    -})
    -return nil, true
    -'''
    -
    -# Ramen
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.x_mult = self.ability.x_mult - self.ability.extra
    -return {
    -    delay = 0.2,
    -    card = self,
    -    message = localize{type='variable',key='a_xmult_minus',vars={self.ability.extra}},
    -    colour = G.C.RED
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "x_mult",
    -    scalar_value = "extra",
    -    operation = "-",
    -    message_key = 'a_xmult_minus',
    -    colour = G.C.RED,
    -    message_delay = 0.2,
    -})
    -return nil, true
    -'''
    -
    -# Yorick
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''
    -self.ability.x_mult = self.ability.x_mult + self.ability.extra.xmult
    -return {
    -    card = self,
    -    delay = 0.2,
    -    message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},
    -    colour = G.C.RED
    -}
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = "x_mult",
    -    scalar_table = self.ability.extra,
    -    scalar_value = "xmult",
    -    message_key = 'a_xmult',
    -    message_colour = G.C.RED,
    -    message_delay = 0.2,
    -})
    -return nil, true
    -'''
    -
    -# Canio
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.ability.caino_xmult = self.ability.caino_xmult + face_cards*self.ability.extra
    -G.E_MANAGER:add_event(Event({
    -func = function() card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.caino_xmult}}}); return true
    -end}))
    -'''
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = 'caino_xmult',
    -    scalar_value = 'extra',
    -    operation = function(ref_table, ref_value, initial, change)
    -        ref_table[ref_value] = initial + face_cards*change
    -    end,
    -    message_key = 'a_xmult'
    -})
    -return nil, true
    -'''
    -
    -# Glass Joker
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -G.E_MANAGER:add_event(Event({
    -    func = function()
    -        self.ability.x_mult = self.ability.x_mult + self.ability.extra*glass_cards
    -    return true
    -    end
    -}))
    -card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.x_mult + self.ability.extra*glass_cards}}})
    -'''
    -payload = '''
    -SMODS.scale_card(self, {
    -    ref_table = self.ability,
    -    ref_value = 'x_mult',
    -    scalar_value = 'extra',
    -    operation = function(ref_table, ref_value, initial, change)
    -        ref_table[ref_value] = initial + glass_cards*change
    -    end,
    -    message_key = 'a_xmult'
    -})
    -'''
    diff --git a/lovely/scoring_calculation.toml b/lovely/scoring_calculation.toml
    deleted file mode 100644
    index b0b6d7a41..000000000
    --- a/lovely/scoring_calculation.toml
    +++ /dev/null
    @@ -1,378 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/state_events.lua"
    -pattern = "^(.*)hand_chips\\s*\\*\\s*mult(.*)$"
    -position = "at"
    -payload = '''$1 SMODS.calculate_round_score() $2'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "G.ARGS.score_intensity.earned_score = G.GAME.current_round.current_hand.chips*G.GAME.current_round.current_hand.mult"
    -position = "at"
    -payload = '''
    -local all_numbers = true
    -for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -    if type(G.GAME.current_round.current_hand[name]) ~= 'number' then all_numbers = false end
    -end
    -G.ARGS.score_intensity.earned_score = all_numbers and SMODS.calculate_round_score(true) or 0
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''contents.hand ='''
    -position = "before"
    -payload = '''--[['''
    -match_indent = false
    -
    -# Replace contexts.hand
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -contents.dollars_chips = {n=G.UIT.R, config={align = "cm",r=0.1, padding = 0,colour = G.C.DYN_UI.BOSS_MAIN, emboss = 0.05, id = 'row_dollars_chips'}, nodes={
    -'''
    -payload = '''
    ---]]
    -contents.hand = SMODS.GUI.hand_score_display_ui(scale)
    -'''
    -
    -# Add operator set up/loading
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -self.GAME.selected_back_key = selected_back
    -'''
    -payload = '''
    -
    -if saveTable then
    -    self.GAME.current_scoring_calculation = SMODS.Scoring_Calculations[saveTable.SCORING_CALC.key]:load({
    -        config = saveTable.SCORING_CALC.config
    -    })
    -else
    -    self.GAME.current_scoring_calculation = SMODS.Scoring_Calculations['multiply']:new()
    -end
    -'''
    -
    -# Operator saving
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -BLIND = G.GAME.blind:save(),
    -'''
    -payload = '''
    -SCORING_CALC = G.GAME.current_scoring_calculation:save(),
    -'''
    -
    -# UIElement remove functions
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/ui.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -if self == G.CONTROLLER.text_input_hook then 
    -'''
    -payload = '''
    -if self.config and self.config.func then
    -    self.config.func = nil
    -end
    -
    -'''
    -
    -# Add Scoring_Parameters to update_hand_text
    -[[patches]]
    -[patches.regex]
    -target = 'functions/common_events.lua'
    -position = 'at'
    -line_prepend = '$indent'
    -pattern = '''(?[\t ]*)if vals\.[A-z0-9\n\t .~=()'\-+,<;>:{}]+mult:juice_up\(\) end\n[\t ]+end'''
    -payload = '''
    -for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -    if vals[name] and G.GAME.current_round.current_hand[name] ~= vals[name] then
    -        local delta = (type(vals[name]) == 'number' and type(G.GAME.current_round.current_hand[name]) == 'number') and (vals[name] - G.GAME.current_round.current_hand[name]) or 0
    -        if delta < 0 then delta = ''..delta; col = G.C.RED
    -        elseif delta > 0 then delta = '+'..delta
    -        else delta = ''..delta
    -        end
    -        if type(vals[name]) == 'string' then delta = vals[name] end
    -        G.GAME.current_round.current_hand[name] = vals[name]
    -        G.hand_text_area[name] = G.hand_text_area[name] or G.HUD:get_UIE_by_ID('hand_'..name) or nil
    -        if G.hand_text_area[name] then
    -            G.hand_text_area[name]:update(0)
    -            if vals.StatusText then 
    -                attention_text({
    -                    text =delta,
    -                    scale = 0.8, 
    -                    hold = 1,
    -                    cover = G.hand_text_area[name].parent,
    -                    cover_colour = mix_colours(parameter.colour, col, 0.1),
    -                    emboss = 0.05,
    -                    align = 'cm',
    -                    cover_align = G.hand_text_area[name].parent.config.align
    -                })
    -            end
    -            if (vals[name.."_juice"] or parameter.juice_on_update) and not G.TAROT_INTERRUPT then G.hand_text_area[name]:juice_up() end
    -        end
    -    end
    -end
    -'''
    -
    -# Reset other values
    -[[patches]]
    -[patches.pattern]
    -target = 'cardarea.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -update_hand_text({immediate = true, nopulse = true, delay = 0}, {mult = 0, chips = 0, level = '', handname = ''})
    -'''
    -payload = '''
    -for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -    update_hand_text({immediate = true, nopulse = true, delay = 0}, {[name] = parameter.default_value})
    -end
    -'''
    -# Reset when discard
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/state_events.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -update_hand_text({immediate = true, nopulse = true, delay = 0}, {mult = 0, chips = 0, level = '', handname = ''})
    -'''
    -payload = '''
    -for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -    update_hand_text({immediate = true, nopulse = true, delay = 0}, {[name] = parameter.default_value})
    -end
    -'''
    -
    -
    -# Reset display after hand played
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/state_events.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -check_and_set_high_score('hand', hand_chips*mult)
    -'''
    -payload = '''
    -for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -    update_hand_text({delay = 0}, {[name] = parameter.default_value})
    -end
    -'''
    -
    -# Set based on hand selected
    -[[patches]]
    -[patches.pattern]
    -target = 'cardarea.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname=disp_text, level=G.GAME.hands[text].level, mult = G.GAME.hands[text].mult, chips = G.GAME.hands[text].chips})
    -'''
    -payload = '''
    -for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -    parameter.current = G.GAME.hands[text][name] or parameter.default_value
    -    update_hand_text({immediate = true, nopulse = nil, delay = 0}, {[name] = parameter.current})
    -end
    -'''
    -
    -# Reset values after hand played
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/state_events.lua'
    -match_indent = false
    -position = 'after'
    -pattern = '''
    -  func = (function() G.GAME.current_round.current_hand.handname = '';return true end)
    -}))
    -delay(0.3)
    -'''
    -payload = '''
    -    SMODS.last_hand_oneshot = SMODS.calculate_round_score() > G.GAME.blind.chips
    -    G.E_MANAGER:add_event(Event({
    -      trigger = 'immediate',
    -      func = (function() 
    -        for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -            parameter.current = parameter.default_value
    -        end
    -        return true 
    -      end)
    -    }))
    -    
    -'''
    -
    -# Level Up Hand uses scoring parameters
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -G.GAME.hands[hand].level = math.max(0, G.GAME.hands[hand].level + amount)
    -G.GAME.hands[hand].mult = math.max(G.GAME.hands[hand].s_mult + G.GAME.hands[hand].l_mult*(G.GAME.hands[hand].level - 1), 1)
    -G.GAME.hands[hand].chips = math.max(G.GAME.hands[hand].s_chips + G.GAME.hands[hand].l_chips*(G.GAME.hands[hand].level - 1), 0)
    -'''
    -payload = '''
    -SMODS.upgrade_poker_hands({
    -    hands = hand,
    -    func = function(base, hand, parameter)
    -            return base + G.GAME.hands[hand]['l_' .. parameter] * amount
    -    end,
    -    level_up = amount,
    -    from = card,
    -    instant = instant,
    -    StatusText = statustext
    -})
    ---[[
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -G.E_MANAGER:add_event(Event({
    -    trigger = 'immediate',
    -    func = (function() check_for_unlock{type = 'upgrade_hand', hand = hand, level = G.GAME.hands[hand].level} return true end)
    -}))
    -'''
    -payload = '''
    -]]
    -'''
    -
    -# Remove update_hand_text duplicates
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -update_hand_text({sound = 'button', volume = 0.7, pitch = 0.8, delay = 0.3}, {handname=localize(self.ability.consumeable.hand_type, 'poker_hands'),chips = G.GAME.hands[self.ability.consumeable.hand_type].chips, mult = G.GAME.hands[self.ability.consumeable.hand_type].mult, level=G.GAME.hands[self.ability.consumeable.hand_type].level})
    -level_up_hand(used_tarot, self.ability.consumeable.hand_type)
    -update_hand_text({sound = 'button', volume = 0.7, pitch = 1.1, delay = 0}, {mult = 0, chips = 0, handname = '', level = ''})
    -'''
    -payload = '''
    -level_up_hand(used_tarot, self.ability.consumeable.hand_type)
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -update_hand_text({sound = 'button', volume = 0.7, pitch = 0.8, delay = 0.3}, {handname=localize(text, 'poker_hands'),chips = G.GAME.hands[text].chips, mult = G.GAME.hands[text].mult, level=G.GAME.hands[text].level})
    -level_up_hand(context.blueprint_card or self, text, nil, 1)
    -update_hand_text({sound = 'button', volume = 0.7, pitch = 1.1, delay = 0}, {mult = 0, chips = 0, handname = '', level = ''})
    -'''
    -payload = '''
    -level_up_hand(context.blueprint_card or self, text, nil, 1)
    -'''
    -
    -# Face down hands
    -[[patches]]
    -[patches.pattern]
    -target = 'cardarea.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname='????', level='?', mult = '?', chips = '?'})
    -'''
    -payload = '''
    -for name, parameter in pairs(SMODS.Scoring_Parameters) do
    -    update_hand_text({immediate = true, nopulse = nil, delay = 0}, {[name] = '?'})
    -end
    -'''
    -
    -# Handle flame_handler
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -G.C.UI_CHIPLICK = G.C.UI_CHIPLICK or {1, 1, 1, 1}
    -  G.C.UI_MULTLICK = G.C.UI_MULTLICK or {1, 1, 1, 1}
    -  for i=1, 3 do
    -    G.C.UI_CHIPLICK[i] = math.min(math.max(((G.C.UI_CHIPS[i]*0.5+G.C.YELLOW[i]*0.5) + 0.1)^2, 0.1), 1)
    -    G.C.UI_MULTLICK[i] = math.min(math.max(((G.C.UI_MULT[i]*0.5+G.C.YELLOW[i]*0.5) + 0.1)^2, 0.1), 1)
    -  end
    -
    -  G.ARGS.flame_handler = G.ARGS.flame_handler or {
    -    chips = {
    -      id = 'flame_chips', 
    -      arg_tab = 'chip_flames',
    -      colour = G.C.UI_CHIPS,
    -      accent = G.C.UI_CHIPLICK
    -    },
    -    mult = {
    -      id = 'flame_mult', 
    -      arg_tab = 'mult_flames',
    -      colour = G.C.UI_MULT,
    -      accent = G.C.UI_MULTLICK
    -    }
    -  }
    -'''
    -payload = '''
    -G.ARGS.flame_handler = G.ARGS.flame_handler or {}
    -
    -for key, parameter in pairs(SMODS.Scoring_Parameters) do
    -    for i=1, 3 do
    -        parameter.lick[i] = math.min(math.max(((parameter.colour[i]*0.5+G.C.YELLOW[i]*0.5) + 0.1)^2, 0.1), 1)
    -    end
    -    G.ARGS.flame_handler[key] = G.ARGS.flame_handler[key] or parameter:flame_handler()
    -end
    -'''
    -# pt2
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -e.config.object = Sprite(0, 0, 2.5, 2.5, G.ASSET_ATLAS["ui_1"], {x = 2, y = 0})
    -'''
    -payload = '''
    -e.config.object = Sprite(0, 0, e.config._w, e.config._h, G.ASSET_ATLAS["ui_1"], {x = 2, y = 0})
    -'''
    -
    -# Patch mod_chips/mod_mult
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -return _mult
    -'''
    -payload = '''
    -SMODS.Scoring_Parameters.mult:modify(nil, _mult - (mult or 0))
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -return _chips
    -'''
    -payload = '''
    -SMODS.Scoring_Parameters.chips:modify(nil, _chips - (hand_chips or 0))
    -'''
    diff --git a/lovely/screen_shader_stack.toml b/lovely/screen_shader_stack.toml
    deleted file mode 100644
    index ee3fd793e..000000000
    --- a/lovely/screen_shader_stack.toml
    +++ /dev/null
    @@ -1,74 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -# i think prio 0 is correct. idk if this patch builds off of any other patches
    -
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = '''
    -if G.AA_CANVAS then 
    -    love.graphics.push()
    -        love.graphics.scale(1/G.CANV_SCALE)
    -        love.graphics.draw(G.AA_CANVAS, 0, 0)
    -    love.graphics.pop()
    -end
    -'''
    -position = "before"
    -payload = '''
    -local last_canvas = self.CANVAS
    -G.SHADER_CANVAS_A = G.SHADER_CANVAS_A or SMODS.create_canvas()
    -G.SHADER_CANVAS_B = G.SHADER_CANVAS_B or SMODS.create_canvas()
    -for index, key in ipairs(SMODS.ScreenShader.obj_buffer) do
    -    local shader = SMODS.ScreenShaders[key]
    -	if (shader.should_apply and shader:should_apply()) or (not shader.should_apply) then
    -        --hypothetically less table accesses
    -        local shader_object = G.SHADERS[shader.shader]
    -		assert(shader_object, "Shader " .. shader.key .. " not found in G.SHADERS")
    -
    -        -- effectively swaps between A and B
    -        local current_canvas = (last_canvas == G.SHADER_CANVAS_A) and G.SHADER_CANVAS_B or G.SHADER_CANVAS_A
    -
    -        if shader.send_vars then
    -            local vars = shader:send_vars()
    -            for k,v in pairs(vars) do
    -                shader_object:send(k, v)
    -            end
    -
    -        end
    -		
    -		love.graphics.setCanvas(current_canvas)
    -		love.graphics.setShader(shader_object)
    -		love.graphics.draw(last_canvas, 0, 0)
    -
    -        last_canvas = current_canvas
    -	end
    -end
    --- I don't like having this out here but id rather not run it when i don't have to
    --- also less setcanvas and setshader means it should run a smidge faster
    -love.graphics.setCanvas()
    -love.graphics.setShader()
    -if last_canvas then
    -    love.graphics.draw(last_canvas, 0, 0)
    -    -- Draw everything we just did to the main canvas (what the player actually sees)
    -end
    -'''
    -match_indent = true
    -
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = "main.lua"
    -pattern = '''
    -G.CANVAS = love.graphics.newCanvas(w*G.CANV_SCALE, h*G.CANV_SCALE, {type = '2d', readable = true})
    -G.CANVAS:setFilter('linear', 'linear')
    -'''
    -position = "after"
    -payload = '''
    -G.SHADER_CANVAS_A = SMODS.create_canvas()
    -G.SHADER_CANVAS_B = SMODS.create_canvas()
    -'''
    -match_indent = true
    \ No newline at end of file
    diff --git a/lovely/seal.toml b/lovely/seal.toml
    deleted file mode 100644
    index 1e7f73d60..000000000
    --- a/lovely/seal.toml
    +++ /dev/null
    @@ -1,241 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Seal API
    -# Card:open()
    -[[patches]]
    -[patches.regex]
    -target = 'card.lua'
    -pattern = '''
    -(?[\t ]*)local seal_rate = 10
    -[\n\t ]*local seal_poll = pseudorandom\(pseudoseed\('stdseal'..G.GAME.round_resets.ante\)\)
    -[\n\t ]*if seal_poll > 1 - 0.02\*seal_rate then
    -[\n\t ]*local seal_type = pseudorandom\(pseudoseed\('stdsealtype'..G.GAME.round_resets.ante\)\)
    -[\n\t ]*if seal_type > 0.75 then card:set_seal\('Red'\)
    -[\n\t ]*elseif seal_type > 0.5 then card:set_seal\('Blue'\)
    -[\n\t ]*elseif seal_type > 0.25 then card:set_seal\('Gold'\)
    -[\n\t ]*else card:set_seal\('Purple'\)
    -[\n\t ]*end
    -[\n\t ]*end'''
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -card:set_seal(SMODS.poll_seal({mod = 10}), true, true)'''
    -
    -# get_badge_colour()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -for k, v in pairs(SMODS.Seals) do
    -    G.BADGE_COL[k:lower()..'_seal'] = v.badge_colour
    -end'''
    -
    -# Card:calculate_seal()
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = 'function Card:calculate_seal\(context\)\n(?[\t ]*)if self.debuff then return nil end'
    -position = 'after'
    -line_prepend = '$indent'
    -payload = '''
    -
    -
    -local obj = G.P_SEALS[self.seal] or {}
    -if obj.calculate and type(obj.calculate) == 'function' then
    -	local o = obj:calculate(self, context)
    -	if o then
    -        if not o.card then o.card = self end
    -        return o
    -    end
    -end'''
    -
    -# Card:update()
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = 'if G.STAGE == G.STAGES.RUN then'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -local obj = G.P_SEALS[self.seal] or {}
    -if obj.update and type(obj.update) == 'function' then
    -    obj:update(self, dt)
    -end'''
    -
    -# Card:get_p_dollars()
    -# Also, Gold Seal respects quantum enhancements
    -# Patch is here to avoid conflict
    -[[patches]]
    -[patches.regex]
    -target = "card.lua"
    -pattern = '''(?[\t ]*)if self\.seal == 'Gold' then'''
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local obj = G.P_SEALS[self.seal] or {}
    -if obj.get_p_dollars and type(obj.get_p_dollars) == 'function' then
    -    ret = ret + obj:get_p_dollars(self)
    -elseif self.seal == 'Gold' and not self.ability.extra_enhancement then'''
    -# note for later: the Card:get_xxx functions sometimes take context and sometimes don't,
    -# which is annoying for enhancements
    -# this should probably be changed to be consistent in better calc
    -
    -# generate_card_ui()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if v == 'gold_seal'*"
    -match_indent = true
    -position = 'before'
    -payload = '''
    -local seal = G.P_SEALS[v] or G.P_SEALS[SMODS.Seal.badge_to_key[v] or '']
    -if seal then
    -    local t = {key = v, set = 'Other', config = {}}
    -    info_queue[#info_queue + 1] = t
    -    if seal.loc_vars and type(seal.loc_vars) == 'function' then
    -        local res = seal:loc_vars(info_queue, card) or {}
    -        t.vars = res.vars
    -        t.key = res.key or t.key
    -        t.set = res.set or t.set
    -    end
    -else'''
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = "if v == 'purple_seal'*"
    -match_indent = true
    -position = 'after'
    -payload = 'end'
    -
    -[[patches]]
    -[patches.regex]
    -target = 'functions/common_events.lua'
    -position = 'at'
    -pattern = '''\{key = (?'.*?_seal'), set = 'Other'\}'''
    -payload = '''G.P_SEALS[$badge] or G.P_SEALS[SMODS.Seal.badge_to_key[$badge] or '']'''
    -
    -# Card:update_alert()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "function Card:update_alert()"
    -match_indent = true
    -position = 'after'
    -payload = '''
    -    if self.ability.set == 'Default' and self.config.center and self.config.center.key == 'c_base' and self.seal then
    -        if G.P_SEALS[self.seal].alerted and self.children.alert then
    -            self.children.alert:remove()
    -            self.children.alert = nil
    -        elseif not G.P_SEALS[self.seal].alerted and not self.children.alert and G.P_SEALS[self.seal].discovered then
    -            self.children.alert = UIBox{
    -                definition = create_UIBox_card_alert(), 
    -                config = {align="tli",
    -                        offset = {x = 0.1, y = 0.1},
    -                        parent = self}
    -            }
    -        end
    -    end'''
    -
    -# Card:hover()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = "G:save_progress()"
    -match_indent = false
    -position = "after"
    -payload = '''
    -        elseif self.children.alert and self.seal and not G.P_SEALS[self.seal].alerted then
    -            G.P_SEALS[self.seal].alerted = true
    -            G:save_progress()'''
    -
    -# Game:init_item_prototypes()
    -[[patches]]
    -[patches.regex]
    -target = 'game.lua'
    -pattern = '''(?[\t ]*)Gold =[ {A-z=1-4,"}\n]*},[\n\t ]*}'''
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -Red = {order = 1,  discovered = false, set = "Seal"},
    -Blue = {order = 2,  discovered = false, set = "Seal"},
    -Gold = {order = 3,  discovered = false, set = "Seal"},
    -Purple = {order = 4,  discovered = false, set = "Seal"},
    -}
    -'''
    -
    -# Card:set_seal()
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''G.CONTROLLER.locks.seal = true'''
    -position = 'after'
    -match_indent = true
    -payload = '''local sound = G.P_SEALS[_seal].sound or {sound = 'gold_seal', per = 1.2, vol = 0.4}'''
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''play_sound('gold_seal', 1.2, 0.4)'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -self.ability.delay_seal = false
    -play_sound(sound.sound, sound.per, sound.vol)'''
    -## Populate Seal Ability Table
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -pattern = '''self.seal = _seal'''
    -position = 'after'
    -match_indent = true
    -payload = '''
    -self.ability.seal = {}
    -for k, v in pairs(G.P_SEALS[_seal].config or {}) do
    -    if type(v) == 'table' then
    -        self.ability.seal[k] = copy_table(v)
    -    else
    -        self.ability.seal[k] = v
    -    end
    -end
    -
    -self.ability.delay_seal = not silent
    -'''
    -
    -# card_limit support
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -end
    -    if self.ability.name == 'Gold Card' and self.seal == 'Gold' and self.playing_card then 
    -'''
    -payload = '''
    -    self.ability.card_limit = self.ability.card_limit + (self.ability.seal.card_limit or 0)
    -    self.ability.extra_slots_used = self.ability.extra_slots_used + (self.ability.seal.extra_slots_used or 0)
    -'''
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''new_card:set_seal(other.seal, true)'''
    -position = 'after'
    -match_indent = true
    -payload = '''
    -if other.seal then
    -    for k, v in pairs(other.ability.seal or {}) do
    -        if type(v) == 'table' then
    -            new_card.ability.seal[k] = copy_table(v)
    -        else
    -            new_card.ability.seal[k] = v
    -        end
    -    end
    -end
    -''' 
    diff --git a/lovely/shaders.toml b/lovely/shaders.toml
    deleted file mode 100644
    index a7ebeb361..000000000
    --- a/lovely/shaders.toml
    +++ /dev/null
    @@ -1,18 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Shader patching
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = '''self.SHADERS[shader_name] = love.graphics.newShader("resources/shaders/"..filename)'''
    -position = "at"
    -payload = '''
    -local shader = "resources/shaders/"..filename
    -local lovely = require "lovely"
    -shader = assert(lovely.apply_patches(filename, love.filesystem.read(shader)))
    -self.SHADERS[shader_name] = love.graphics.newShader(shader)
    -'''
    -match_indent = true
    diff --git a/lovely/shop.toml b/lovely/shop.toml
    deleted file mode 100644
    index 89ec1f4e4..000000000
    --- a/lovely/shop.toml
    +++ /dev/null
    @@ -1,271 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Set defaults
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -erratic_suits_and_ranks = false,
    -'''
    -payload = '''
    -boosters_in_shop = 2,
    -vouchers_in_shop = 1,
    -'''
    -
    -# Allow booster count to be controlled by G.GAME.modifiers.extra_boosters
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -for i = 1, 2 do
    -    G.GAME.current_round.used_packs = G.GAME.current_round.used_packs or {}
    -'''
    -payload = '''
    -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 {}
    -'''
    -# Custom deck functionality
    -[[patches]]
    -[patches.pattern]
    -target = 'back.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -if self.effect.config.no_interest then
    -'''
    -payload = '''
    -if self.effect.config.boosters_in_shop then
    -    G.GAME.starting_params.boosters_in_shop = self.effect.config.boosters_in_shop
    -end
    -'''
    -
    -# Allow voucher count to be controlled by G.GAME.modifiers.extra_vouchers
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if G.GAME.current_round.voucher and G.P_CENTERS[G.GAME.current_round.voucher] then
    -    local card = Card(G.shop_vouchers.T.x + G.shop_vouchers.T.w/2,
    -    G.shop_vouchers.T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, G.P_CENTERS[G.GAME.current_round.voucher],{bypass_discovery_center = true, bypass_discovery_ui = true})
    -    card.shop_voucher = true
    -    create_shop_card_ui(card, 'Voucher', G.shop_vouchers)
    -    card:start_materialize()
    -    G.shop_vouchers:emplace(card)
    -end
    -'''
    -payload = '''
    -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
    -'''
    -# Modify generating vouchers
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/state_events.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -G.GAME.current_round.voucher = get_next_voucher_key()
    -'''
    -payload = '''
    -G.GAME.current_round.voucher = SMODS.get_next_vouchers()
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -self.GAME.current_round.voucher = G.SETTINGS.tutorial_progress and G.SETTINGS.tutorial_progress.forced_voucher or get_next_voucher_key()
    -'''
    -payload = '''
    -local forced_voucher = (G.SETTINGS.tutorial_progress or {}).forced_voucher
    -self.GAME.current_round.voucher = forced_voucher and {forced_voucher, spawn = {[forced_voucher] = true }} or SMODS.get_next_vouchers()
    -'''
    -# Stop redeeming vouchers deleting the table of vouchers
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if self.shop_voucher then G.GAME.current_round.voucher = nil end
    -'''
    -payload = '''
    -if self.shop_voucher then G.GAME.current_round.voucher.spawn[self.config.center_key] = false end
    -if self.from_tag then G.GAME.current_round.voucher.spawn[G.GAME.current_round.voucher[1]] = false end
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -G.GAME.current_round.voucher = nil
    -'''
    -payload = '''
    ---G.GAME.current_round.voucher = nil
    -'''
    -# Add voucher restock message
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -  if (G.shop_vouchers and G.shop_vouchers.cards and (G.shop_vouchers.cards[1] or G.GAME.current_round.voucher)) then
    -'''
    -payload = '''
    -  if (G.shop_vouchers and G.shop_vouchers.cards and G.shop_vouchers.cards[1]) then
    -'''
    -# Maintain voucher tag jank interaction
    -[[patches]]
    -[patches.pattern]
    -target = 'tag.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    -create_shop_card_ui(card, 'Voucher', G.shop_vouchers)
    -'''
    -payload = '''
    -card.from_tag = true
    -'''
    -
    -
    -# Free Rerolls
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/state_events.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -local chaos = find_joker('Chaos the Clown')
    -G.GAME.current_round.free_rerolls = #chaos
    -'''
    -payload = '''
    -G.GAME.current_round.free_rerolls = G.GAME.round_resets.free_rerolls
    -'''
    -# G.GAME.round_resets.free_rerolls
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -reroll_cost = 1,
    -'''
    -payload = '''
    -free_rerolls = 0,
    -'''
    -# Adjust Chaos the Clown
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -G.GAME.current_round.free_rerolls = G.GAME.current_round.free_rerolls + 1
    -'''
    -payload = '''
    -SMODS.change_free_rerolls(1)
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'card.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -G.GAME.current_round.free_rerolls = G.GAME.current_round.free_rerolls - 1
    -'''
    -payload = '''
    -SMODS.change_free_rerolls(-1)
    -'''
    -
    -## Shop Card Area Width
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''G.GAME.shop.joker_max*1.02*G.CARD_W,'''
    -position = 'at'
    -match_indent = true
    -payload = '''math.min(G.GAME.shop.joker_max*1.02*G.CARD_W,4.08*G.CARD_W),'''
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''G.shop_jokers.T.w = G.GAME.shop.joker_max*1.01*G.CARD_W'''
    -position = 'at'
    -match_indent = true
    -payload = '''G.shop_jokers.T.w = math.min(G.GAME.shop.joker_max*1.02*G.CARD_W,4.08*G.CARD_W)'''
    -
    -# for some reason shop_voucher is not saved/loaded so... that's what's gonna happen
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''ability = self.ability,'''
    -position = "before"
    -payload = '''
    -shop_voucher = self.shop_voucher,
    -'''
    -match_indent = true
    -
    -# water is wet
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = '''self.ability = cardTable.ability'''
    -position = "before"
    -payload = '''
    -self.shop_voucher = cardTable.shop_voucher
    -'''
    -match_indent = true
    -
    -# poll_edition for playing cards in shop
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''
    -local edition_poll = pseudorandom(pseudoseed('illusion'))
    -local edition = {}
    -if edition_poll > 1 - 0.15 then edition.polychrome = true
    -elseif edition_poll > 0.5 then edition.holo = true
    -else edition.foil = true
    -end
    -card:set_edition(edition)
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''card:set_edition(poll_edition('illusion', nil, true, true))'''
    -
    -
    -# modify_shop_card context for allowing shop cards to be modified (duh)
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''
    -local card = create_card(v.type, area, nil, nil, nil, nil, nil, 'sho')
    -'''
    -position = 'at'
    -payload = '''
    -local args = {set = v.type, area = area, key_append = 'sho'}
    -local flags = SMODS.calculate_context({create_shop_card = true, set = v.type})
    -local create_flags = SMODS.merge_defaults(flags.shop_create_flags or {}, args)
    -local card = SMODS.create_card(create_flags)
    -SMODS.calculate_context({modify_shop_card = true, card = card})
    -'''
    -match_indent = true
    \ No newline at end of file
    diff --git a/lovely/sound.toml b/lovely/sound.toml
    deleted file mode 100644
    index 481300eee..000000000
    --- a/lovely/sound.toml
    +++ /dev/null
    @@ -1,167 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -#modulate_sound()
    -[[patches]]
    -[patches.pattern] 
    -target = 'functions/misc_functions.lua'
    -pattern = 'G.SOUND_MANAGER.channel:push(G.ARGS.push)'
    -match_indent = true
    -position = 'after'
    -payload = '''
    -SMODS.previous_track = SMODS.previous_track or ''
    -local in_sync = (SMODS.Sounds[desired_track] or {}).sync
    -local out_sync = (SMODS.Sounds[SMODS.previous_track] or {}).sync
    -local should_sync = true
    -if (type(in_sync) == 'table' and not in_sync[SMODS.previous_track]) or in_sync == false then should_sync = false end
    -if (type(out_sync) == 'table' and not out_sync[desired_track]) or out_sync == false then should_sync = false end
    -if 
    -    SMODS.previous_track and SMODS.previous_track ~= desired_track and
    -    not should_sync
    -then
    -    G.ARGS.push.type = 'restart_music'
    -    G.SOUND_MANAGER.channel:push(G.ARGS.push)
    -end
    -SMODS.previous_track = desired_track'''
    -
    -[[patches]]
    -[patches.pattern] 
    -target = 'functions/misc_functions.lua'
    -pattern = 'G.ARGS.push.ambient_control = G.SETTINGS.ambient_control'
    -match_indent = true
    -position = 'after'
    -payload = '''
    -if SMODS.remove_replace_sound and SMODS.remove_replace_sound ~= desired_track then
    -    SMODS.Sound.replace_sounds[SMODS.remove_replace_sound] = nil
    -    SMODS.remove_replace_sound = nil
    -end
    -local replace_sound = SMODS.Sound.replace_sounds[desired_track]
    -if replace_sound then
    -    local replaced_track = desired_track
    -    desired_track = replace_sound.key
    -    G.ARGS.push.desired_track = desired_track
    -    if SMODS.previous_track ~= desired_track then
    -        if replace_sound.times > 0 then replace_sound.times = replace_sound.times - 1 end
    -        if replace_sound.times == 0 then SMODS.remove_replace_sound = replaced_track end
    -    end
    -end
    -local stop_sound = SMODS.Sound.stop_sounds[desired_track]
    -if SMODS.Sound.stop_sounds[desired_track] then
    -    if SMODS.previous_track ~= '' and stop_sound > 0 then stop_sound = stop_sound - 1 end
    -    SMODS.Sound.stop_sounds[desired_track] = stop_sound ~= 0 and stop_sound or nil
    -    SMODS.previous_track = ''
    -    return
    -end
    -'''
    -
    -[[patches]]
    -[patches.pattern] 
    -target = 'functions/misc_functions.lua'
    -pattern = "(G.STATE == G.STATES.SPLASH and '') or"
    -match_indent = true
    -position = 'after'
    -payload = 'SMODS.Sound:get_current_music() or'
    -
    -# PLAY_SOUND 
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/sound_manager.lua'
    -pattern = '''local s = {sound = love.audio.newSource("resources/sounds/"..args.sound_code..'.ogg', should_stream and "stream" or 'static')}'''
    -match_indent = true
    -position = 'at'
    -payload = '''
    -local c = SMODS_Sounds[args.sound_code]
    -local s = c and
    -{sound = love.audio.newSource(love.sound.newDecoder(c.data), c.should_stream and 'stream' or 'static'), per = c.per, vol = c.vol } or
    -{sound = love.audio.newSource("resources/sounds/"..args.sound_code..'.ogg', should_stream and "stream" or 'static')}''' 
    -
    -# pass in custom sounds
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/sound_manager.lua'
    -pattern = "DISABLE_SFX = false"
    -match_indent = true
    -position = 'after'
    -payload = '''
    -SMODS_Sounds = {}
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/sound_manager.lua'
    -pattern = "elseif request.type == 'stop' then"
    -match_indent = true
    -position = 'before'
    -payload = '''
    -elseif request.type == 'sound_source' then
    -    SMODS_Sounds[request.sound_code] = {
    -        sound_code = request.sound_code,
    -        data = request.data,
    -        sound = sound,
    -        per = request.per,
    -        vol = request.vol,
    -    }
    -    SOURCES[request.sound_code] = {}
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/sound_manager.lua'
    -pattern = "s.original_pitch = args.per or 1"
    -match_indent = true
    -position = 'at'
    -payload = 's.original_pitch = ((args.type ~= "sound") and s.per) or args.per or 1'
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/sound_manager.lua'
    -pattern = "s.original_volume = args.vol or 1"
    -match_indent = true
    -position = 'at'
    -payload = 's.original_volume = ((args.type ~= "sound") and s.vol) or args.vol or 1'
    -
    -# don't crash RESTART_MUSIC
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/sound_manager.lua'
    -pattern = "RESTART_MUSIC()"
    -match_indent = true
    -position = 'at'
    -payload = 'RESTART_MUSIC(request)'
    -
    -# fix looping for music of different length
    -
    -[[patches]]
    -[patches.regex]
    -target = 'engine/sound_manager.lua'
    -pattern = """(?[\t ]*)function MODULATE\\(args\\)(\n.*){9}"""
    -line_prepend = '$indent'
    -position = 'at'
    -payload = """function MODULATE(args)
    -    if args.desired_track ~= '' then
    -        local sound = ((SOURCES[current_track or {}] or {})[1] or {}).sound
    -        if not sound or not sound:isPlaying() then
    -            RESTART_MUSIC(args)
    -        end
    -    end
    -"""
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/sound_manager.lua'
    -pattern = "for _, s in pairs(v) do"
    -match_indent = true
    -position = 'at'
    -payload = """current_track = args.desired_track
    -for _, s in pairs(v) do""" 
    -
    -# [[patches]]
    -# [patches.pattern]
    -# target = 'engine/sound_manager.lua'
    -# pattern = 'if s.sound and not s.sound:isPlaying() then'
    -# match_indent = true
    -# position = 'at'
    -# payload = '''if s.sound and s.sound:isPlaying() then
    -# 	s.sound:stop()
    -# elseif s.sound and not s.sound:isPlaying() then'''
    diff --git a/lovely/stake.toml b/lovely/stake.toml
    deleted file mode 100644
    index e3016925b..000000000
    --- a/lovely/stake.toml
    +++ /dev/null
    @@ -1,281 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -# Fix areas where highest stake is hardcoded as Gold Stake
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = 8 end"
    -position = "at"
    -payload = "if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = #G.P_CENTER_POOLS['Stake'] end"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "for i = 1, math.min(max_stake+1, 8) do"
    -position = "at"
    -payload = "for i = 1, math.min(max_stake+1, #G.P_CENTER_POOLS['Stake']) do"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "if G.GAME.stake >= 8 then"
    -position = "at"
    -payload = "if G.GAME.stake >= #G.P_CENTER_POOLS['Stake'] then"
    -match_indent = true
    -
    -# Stake modifier API
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "if self.GAME.stake >= 2 then"
    -position = "before"
    -payload = "if false then"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "game.lua"
    -pattern = "if self.GAME.stake >= 8 then self.GAME.modifiers.enable_rentals_in_shop = true end"
    -position = "after"
    -payload = "end SMODS.setup_stake(self.GAME.stake)"
    -match_indent = true
    -
    -# Stake shininess API
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "if _stake == 8 then"
    -position = "at"
    -payload = "if G.P_CENTER_POOLS['Stake'][_stake].shiny then"
    -match_indent = true
    -
    -# Override stake listing to make room for our recursive version
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = "for i = G.GAME.stake-1, 2, -1 do"
    -position = "before"
    -payload = "if false then"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = 'other_col = {n=G.UIT.R, config={align = "cm", padding = 0.05, r = 0.1, colour = G.C.L_BLACK}, nodes=stake_desc_rows}'
    -position = "before"
    -payload = "end SMODS.applied_stakes_UI(G.GAME.stake, stake_desc_rows)"
    -match_indent = true
    -
    -# Set win stake to that specified in unlocked stake
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'for i = 1, G.GAME.stake do'
    -position = "at"
    -payload = '''for i = 1,
    -(G.P_CENTER_POOLS["Stake"][G.GAME.stake].unlocked_stake) and 
    -(G.P_STAKES[G.P_CENTER_POOLS["Stake"][G.GAME.stake].unlocked_stake].stake_level-1) or (G.GAME.stake-1)
    -do'''
    -match_indent = true
    -
    -# Stake Sprites
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'local stake_sprite = Sprite(0,0,_scale*1,_scale*1,G.ASSET_ATLAS["chips"], G.P_CENTER_POOLS.Stake[_stake].pos)'
    -position = "at"
    -payload = 'local stake_sprite = Sprite(0,0,_scale*1,_scale*1,G.ASSET_ATLAS[G.P_CENTER_POOLS.Stake[_stake].atlas], G.P_CENTER_POOLS.Stake[_stake].pos)'
    -match_indent = true
    -
    -# Achievements and unlocks
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = 'if highest_win >= 2 then'
    -position = "at"
    -payload = 'if highest_win >= G.P_STAKES["stake_red"].stake_level then'
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = 'if highest_win >= 4 then'
    -position = "at"
    -payload = 'if highest_win >= G.P_STAKES["stake_black"].stake_level then'
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = 'if highest_win >= 8 then'
    -position = "at"
    -payload = 'if highest_win >= G.P_STAKES["stake_gold"].stake_level then'
    -match_indent = true
    -
    -# get_blind_amount
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'function get_blind_amount(ante)'
    -position = "after"
    -payload = '''if G.GAME.modifiers.scaling and (G.GAME.modifiers.scaling ~= 1 and G.GAME.modifiers.scaling ~= 2 and G.GAME.modifiers.scaling ~= 3) then return SMODS.get_blind_amount(ante) end'''
    -match_indent = true
    -
    -# set_joker_usage
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = {count = 1, order = v.config.center.order, wins = {}, losses = {}}'
    -position = "at"
    -payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = {count = 1, order = v.config.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}'
    -match_indent = true
    -
    -# set_joker_win
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] or {count = 1, order = v.config.center.order, wins = {}, losses = {}}'
    -position = "at"
    -payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] or {count = 1, order = v.config.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}'
    -match_indent = true
    -
    -#set_joker_win
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins[G.GAME.stake] or 0) + 1'
    -position = "after"
    -payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins_by_key[SMODS.stake_from_index(G.GAME.stake)] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins_by_key[SMODS.stake_from_index(G.GAME.stake)] or 0) + 1'
    -match_indent = true
    -
    -#set_joker_loss
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses[G.GAME.stake] or 0) + 1'
    -position = "after"
    -payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses_by_key[SMODS.stake_from_index(G.GAME.stake)] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses_by_key[SMODS.stake_from_index(G.GAME.stake)] or 0) + 1'
    -match_indent = true
    -
    -# set_deck_usage
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}}'
    -position = "at"
    -payload = 'G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}'
    -match_indent = true
    -
    -# set_deck_loss
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = 'if not G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}} end'
    -position = "at"
    -payload = 'if not G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}} end'
    -match_indent = true
    -
    -# G.UIDEF.viewed_stake_option
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = 'G.viewed_stake = math.min(max_stake+1, G.viewed_stake)'
    -position = "after"
    -payload = '''if G.viewed_stake > #G.P_CENTER_POOLS.Stake then G.viewed_stake = #G.P_CENTER_POOLS.Stake end'''
    -match_indent = true
    -
    -# Fix stake select in run start
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key) or 0
    -G.viewed_stake = math.min(G.viewed_stake, max_stake + 1)
    -'''
    -payload = '''
    --- local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key) or 0
    --- G.viewed_stake = math.min(G.viewed_stake, max_stake + 1)
    -'''
    -
    -# pt 2
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key)
    -if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = #G.P_CENTER_POOLS['Stake'] end
    -
    -G.viewed_stake = math.min(max_stake+1, G.viewed_stake)
    -'''
    -payload = '''
    ---[[
    -local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key)
    -if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = #G.P_CENTER_POOLS['Stake'] end
    -  
    -G.viewed_stake = math.min(max_stake+1, G.viewed_stake)
    -]]--
    -'''
    -
    -# Stake loc_vars
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if _stake_center then localize{type = 'descriptions', key = _stake_center.key, set = _stake_center.set, nodes = ret_nodes} end 
    -'''
    -payload = '''
    -local t, res = {}, {}
    -if _stake_center then
    -  if _stake_center.loc_vars and type(_stake_center.loc_vars) == 'function' then
    -    res = _stake_center:loc_vars() or {}
    -  end
    -  t.vars = res.vars or {}
    -  t.key = res.key or _stake_center.key
    -  t.set = res.set or _stake_center.set
    -  localize{type = 'descriptions', key = t.key, set = t.set, nodes = ret_nodes, vars = t.vars}
    -end
    -'''
    -
    -# Stake loc_vars pt 2
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -{n=G.UIT.T, config={text = localize{type = 'name_text', key = _stake_center.key, set = _stake_center.set}, scale = 0.35, colour = G.C.WHITE}}
    -'''
    -payload = '''
    -{n=G.UIT.T, config={text = localize{type = 'name_text', key = t.key, set = t.set}, scale = 0.35, colour = G.C.WHITE}}
    -'''
    -
    -# Stake loc_vars pt 3
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -localize{type = 'descriptions', key = _stake_center.key, set = _stake_center.set, nodes = _stake_desc}
    -'''
    -payload = '''
    -local t, res = {}, {}
    -if _stake_center.loc_vars and type(_stake_center.loc_vars) == 'function' then
    -  res = _stake_center:loc_vars() or {}
    -end
    -t.vars = res.vars or {}
    -t.key = res.key or _stake_center.key
    -t.set = res.set or _stake_center.set
    -localize{type = 'descriptions', key = t.key, set = t.set, nodes = _stake_desc, vars = t.vars}
    -'''
    diff --git a/lovely/sticker.toml b/lovely/sticker.toml
    deleted file mode 100644
    index f251f2cf6..000000000
    --- a/lovely/sticker.toml
    +++ /dev/null
    @@ -1,111 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Sticker API
    -
    -# generate_UIBox_ability_table()
    -[[patches]]
    -[patches.pattern]
    -target = "card.lua"
    -pattern = "if self.sticker or ((self.sticker_run and self.sticker_run~='NONE') and G.SETTINGS.run_stake_stickers)  then loc_vars = loc_vars or {}; loc_vars.sticker=(self.sticker or self.sticker_run) end"
    -position = "before"
    -match_indent = true
    -payload = '''
    -for k, v in ipairs(SMODS.Sticker.obj_buffer) do
    -	if self.ability[v] and not SMODS.Stickers[v].hide_badge then
    -        badges[#badges+1] = v
    -    end
    -end'''
    -
    -# generate_card_ui()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if v == 'eternal' then*"
    -match_indent = true
    -position = "before"
    -payload = '''
    -local sticker = SMODS.Stickers[v]
    -if sticker then
    -    local t = { key = v, set = 'Other' }
    -    local res = {}
    -    if sticker.loc_vars and type(sticker.loc_vars) == 'function' then
    -        res = sticker:loc_vars(info_queue, card) or {}
    -        t.vars = res.vars or {}
    -        t.key = res.key or t.key
    -        t.set = res.set or t.set
    -    end
    -    info_queue[#info_queue+1] = t
    -else'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if v == 'rental' then*"
    -match_indent = true
    -position = "after"
    -payload = '''end'''
    -
    -# create_card()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if card.ability.consumeable and not skip_materialize then card:start_materialize() end"
    -position = "after"
    -match_indent = true
    -payload = '''
    -for k, v in ipairs(SMODS.Sticker.obj_buffer) do
    -    local sticker = SMODS.Stickers[v]
    -    if sticker.should_apply and type(sticker.should_apply) == 'function' and sticker:should_apply(card, center, area) then
    -        sticker:apply(card, true)
    -    end
    -end'''
    -
    -## Remove base game sticker rolls if one is added
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if G.GAME.modifiers.enable_eternals_in_shop and eternal_perishable_poll > 0.7 then"
    -position = "at"
    -match_indent = true
    -payload = '''if G.GAME.modifiers.enable_eternals_in_shop and eternal_perishable_poll > 0.7 and not SMODS.Stickers["eternal"].should_apply then'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "elseif G.GAME.modifiers.enable_perishables_in_shop and ((eternal_perishable_poll > 0.4) and (eternal_perishable_poll <= 0.7)) then"
    -position = "at"
    -match_indent = true
    -payload = '''elseif G.GAME.modifiers.enable_perishables_in_shop and ((eternal_perishable_poll > 0.4) and (eternal_perishable_poll <= 0.7)) and not SMODS.Stickers["perishable"].should_apply then'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "if G.GAME.modifiers.enable_rentals_in_shop and pseudorandom((area == G.pack_cards and 'packssjr' or 'ssjr')..G.GAME.round_resets.ante) > 0.7 then"
    -position = "at"
    -match_indent = true
    -payload = '''if G.GAME.modifiers.enable_rentals_in_shop and pseudorandom((area == G.pack_cards and 'packssjr' or 'ssjr')..G.GAME.round_resets.ante) > 0.7 and not SMODS.Stickers["rental"].should_apply then'''
    -
    -# get_badge_colour()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}'
    -position = 'before'
    -match_indent = true
    -payload = '''
    -for k, v in pairs(SMODS.Stickers) do
    -    G.BADGE_COL[k] = v.badge_colour
    -end'''
    -
    -## Remove Pinned effect when in Sticker collections
    -# CardArea:aling_cards
    -[[patches]]
    -[patches.pattern]
    -target = 'cardarea.lua'
    -pattern = '''table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*(a.pinned and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*(b.pinned and b.sort_id or 0) end)'''
    -position = 'at'
    -match_indent = true
    -payload = '''table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end)'''
    diff --git a/lovely/tag.toml b/lovely/tag.toml
    deleted file mode 100644
    index f2523a02f..000000000
    --- a/lovely/tag.toml
    +++ /dev/null
    @@ -1,166 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Tag API
    -# Tag:apply_to_run()
    -# Adds prevent_tag_trigger context
    -[[patches]]
    -[patches.pattern]
    -target = "tag.lua"
    -pattern = "function Tag:apply_to_run(_context)"
    -position = 'after'
    -match_indent = true
    -payload = '''
    -    if self.triggered then return end
    -    local flags = SMODS.calculate_context({prevent_tag_trigger = self, other_context = _context})
    -    if flags.prevent_trigger then return end
    -    local obj = SMODS.Tags[self.key]
    -    local res
    -    if obj and obj.apply and type(obj.apply) == 'function' then
    -        res = obj:apply(self, _context)
    -    end
    -    if res then return res end
    -'''
    -
    -# Tag:set_ability()
    -[[patches]]
    -[patches.pattern]
    -target = "tag.lua"
    -pattern = "function Tag:set_ability()"
    -position = 'after'
    -match_indent = true
    -payload = '''
    -    local obj = SMODS.Tags[self.key]
    -    local res
    -    if obj and obj.set_ability and type(obj.set_ability) == 'function' then
    -        obj:set_ability(self)
    -    end
    -'''
    -
    -# Orbital Tag's set_ability
    -# Initialize fallback Poker Hand
    -[[patches]]
    -[patches.pattern]
    -target = "tag.lua"
    -pattern = '''
    -elseif self.ability.blind_type then
    -    if G.GAME.orbital_choices and G.GAME.orbital_choices[G.GAME.round_resets.ante][self.ability.blind_type] then
    -        self.ability.orbital_hand = G.GAME.orbital_choices[G.GAME.round_resets.ante][self.ability.blind_type]       
    -    end
    -end
    -'''
    -position = 'after'
    -match_indent = true
    -payload = '''
    -if self.ability.orbital_hand == '['..localize('k_poker_hand')..']' then
    -    local _poker_hands = {}
    -    for k, v in pairs(G.GAME.hands) do
    -        if SMODS.is_poker_hand_visible(k) then _poker_hands[#_poker_hands+1] = k end
    -    end
    -    self.ability.orbital_hand = pseudorandom_element(_poker_hands, pseudoseed('orbital'))
    -end
    -'''
    -
    -# create_UIBox_your_collection_tags()
    -[[patches]]
    -[patches.regex]
    -target = "functions/UI_definitions.lua"
    -pattern = "(?[\t ]*)local tag_matrix = \\{(\n.*){6}"
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''
    -local tag_matrix = {}
    -local counter = 0
    -local tag_tab = {}
    -local tag_pool = {}
    -if G.ACTIVE_MOD_UI then
    -    for k, v in pairs(G.P_TAGS) do
    -        if v.mod and G.ACTIVE_MOD_UI.id == v.mod.id then tag_pool[k] = v end
    -    end
    -else
    -    tag_pool = G.P_TAGS
    -end
    -for k, v in pairs(tag_pool) do
    -    counter = counter + 1
    -    tag_tab[#tag_tab+1] = v
    -end
    -for i = 1, math.ceil(counter / 6) do
    -    table.insert(tag_matrix, {})
    -end'''
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/UI_definitions.lua"
    -pattern = '''(?[\t ]*)v\.children\.alert\.states\.collide\.can = false\n[\s\S]{8}end\n[\s\S]{8}return true\n[\s\S]{4}end\)\n[\s\S]{2}\}\)\)\n{3}'''
    -position = 'after'
    -line_prepend = '$indent'
    -payload = '''
    -local table_nodes = {}
    -for i = 1, math.ceil(counter / 6) do
    -    table.insert(table_nodes, {n=G.UIT.R, config={align = "cm"}, nodes=tag_matrix[i]})
    -end'''
    -
    -[[patches]]
    -[patches.regex]
    -target = "functions/UI_definitions.lua"
    -pattern = '''(?[\t ]*)\{\n[\s\S]{10}\{n=G\.UIT\.R, config=\{align = "cm"\}, nodes=tag_matrix\[1\]},[\s\S]*tag_matrix\[4\]\},\n[\s\S]{8}\}'''
    -position = 'at'
    -line_prepend = '$indent'
    -payload = '''table_nodes'''
    -
    -# Tag:generate_UI()
    -[[patches]]
    -[patches.regex]
    -target = "tag.lua"
    -pattern = 'G.ASSET_ATLAS\["tags"\]'
    -position = 'at'
    -payload = 'G.ASSET_ATLAS[(not self.hide_ability) and G.P_TAGS[self.key].atlas or "tags"]'
    -
    -# Tag:get_uibox_table()
    -[[patches]]
    -[patches.pattern]
    -target = "tag.lua"
    -pattern = '''function Tag:get_uibox_table(tag_sprite)'''
    -position = 'at'
    -match_indent = true
    -payload = '''function Tag:get_uibox_table(tag_sprite, vars_only)'''
    -[[patches]]
    -[patches.pattern]
    -target = "tag.lua"
    -pattern = '''tag_sprite.ability_UIBox_table = generate_card_ui(G.P_TAGS[self.key], nil, loc_vars, (self.hide_ability) and 'Undiscovered' or 'Tag', nil, (self.hide_ability))'''
    -position = 'at'
    -match_indent = true
    -payload = '''if vars_only then return loc_vars end
    -tag_sprite.ability_UIBox_table = generate_card_ui(G.P_TAGS[self.key], nil, loc_vars, (self.hide_ability) and 'Undiscovered' or 'Tag', nil, (self.hide_ability), nil, nil, self)'''
    -
    -# generate_card_ui()
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = "elseif _c.set == 'Tag' then"
    -position = "after"
    -match_indent = true
    -payload = '''specific_vars = specific_vars or Tag.get_uibox_table({ name = _c.name, config = _c.config, ability = { orbital_hand = '['..localize('k_poker_hand')..']' }}, nil, true)
    -'''
    -
    -# Prevent Boss Tag from crashing when triggered at a bad time
    -# by quietly rerolling it if blind select UI is not there
    -[[patches]]
    -[patches.pattern]
    -target = "functions/button_callbacks.lua"
    -pattern = "G.FUNCS.reroll_boss = function(e)"
    -position = "after"
    -match_indent = true
    -payload = '''if not G.blind_select_opts then
    -    G.GAME.round_resets.boss_rerolled = true
    -    if not G.from_boss_tag then ease_dollars(-10) end
    -    G.from_boss_tag = nil
    -    G.GAME.round_resets.blind_choices.Boss = get_new_boss()
    -    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'''
    \ No newline at end of file
    diff --git a/lovely/text_effect.toml b/lovely/text_effect.toml
    deleted file mode 100644
    index d5a651409..000000000
    --- a/lovely/text_effect.toml
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -pattern = "if self.config.bump then letter.offset.y = (G.SETTINGS.reduced_motion and 0 or 1)*self.bump_amount*math.sqrt(self.scale)*7*math.max(0, (5+self.bump_rate)*math.sin(self.bump_rate*G.TIMERS.REAL+200*k) - 3 - self.bump_rate) end"
    -position = "after"
    -payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].func) == "function" then
    -    SMODS.DynaTextEffects[self.config.text_effect].func(self, k, letter) -- k is index
    -end'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = "          local _float, _silent, _pop_in, _bump, _spacing = nil, true, nil, nil, nil"
    -position = "after"
    -payload = '''local text_effects'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = "            _bump = true; _spacing = 1"
    -position = "after"
    -payload = '''elseif SMODS.DynaTextEffects[part.control.E] then
    -    text_effects = part.control.E'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = "            spacing = _spacing,"
    -position = "after"
    -payload = '''text_effect = text_effects,'''
    -match_indent = true
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -pattern = """        love.graphics.draw(
    -            letter.letter,
    -            0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.x,
    -            0.5*(letter.dims.y - letter.offset.y)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.y, 
    -            letter.r or 0,
    -            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -            0.5*letter.dims.x/(self.scale),
    -            0.5*letter.dims.y/(self.scale)
    -    )"""
    -position = "before"
    -payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].draw_letter) == "function" then
    -    SMODS.DynaTextEffects[self.config.text_effect].draw_letter(self, k, letter, false) -- actual text
    -else'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -pattern = """        love.graphics.draw(
    -            letter.letter,
    -            0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.x,
    -            0.5*(letter.dims.y - letter.offset.y)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.y, 
    -            letter.r or 0,
    -            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -            0.5*letter.dims.x/(self.scale),
    -            0.5*letter.dims.y/(self.scale)
    -    )"""
    -position = "after"
    -payload = '''end'''
    -match_indent = true
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -pattern = """            love.graphics.draw(
    -                letter.letter,
    -                0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.x*self.scale/(G.TILESIZE),
    -                0.5*(letter.dims.y)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.y*self.scale/(G.TILESIZE), 
    -                letter.r or 0,
    -                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -                0.5*letter.dims.x/self.scale,
    -                0.5*letter.dims.y/self.scale
    -            )
    -            love.graphics.translate(letter.dims.x*self.font.FONTSCALE/G.TILESIZE, 0)"""
    -position = "before"
    -payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].draw_shadow) == "function" then
    -    SMODS.DynaTextEffects[self.config.text_effect].draw_shadow(self, k, letter) -- shadow
    -else'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -pattern = """            love.graphics.draw(
    -                letter.letter,
    -                0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.x*self.scale/(G.TILESIZE),
    -                0.5*(letter.dims.y)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.y*self.scale/(G.TILESIZE), 
    -                letter.r or 0,
    -                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    -                0.5*letter.dims.x/self.scale,
    -                0.5*letter.dims.y/self.scale
    -            )
    -            love.graphics.translate(letter.dims.x*self.font.FONTSCALE/G.TILESIZE, 0)"""
    -position = "after"
    -payload = '''end'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -pattern = """    if self.children.particle_effect then self.children.particle_effect:draw() end"""
    -position = "before"
    -payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].draw_override) == "function" then
    -    SMODS.DynaTextEffects[self.config.text_effect].draw_override(self)
    -    return
    -end'''
    -match_indent = true
    diff --git a/lovely/threads.toml b/lovely/threads.toml
    deleted file mode 100644
    index 85c258013..000000000
    --- a/lovely/threads.toml
    +++ /dev/null
    @@ -1,30 +0,0 @@
    -# Necessary to kill threads which lets us restart the game.
    -
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -5
    -
    -[[patches]]
    -[patches.pattern]
    -target = "engine/save_manager.lua"
    -pattern = "if request then"
    -position = "after"
    -payload = "if request.type == 'kill' then return end"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "engine/http_manager.lua"
    -pattern = "if request then"
    -position = "after"
    -payload = "if request.type == 'kill' then return end"
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "engine/sound_manager.lua"
    -pattern = "if request then"
    -position = "after"
    -payload = "if request.type == 'kill' then return end"
    -match_indent = true
    diff --git a/lovely/ui.toml b/lovely/ui.toml
    deleted file mode 100644
    index 17427bb55..000000000
    --- a/lovely/ui.toml
    +++ /dev/null
    @@ -1,405 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -### Addition Tab
    -
    -## Decks tab
    -# create_UIBox_your_collection_decks()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''G.GAME.viewed_back = Back(G.P_CENTERS.b_red)'''
    -position = "at"
    -payload = '''
    -local deck_pool = SMODS.collection_pool(G.P_CENTER_POOLS.Back)
    -G.GAME.viewed_back = Back(G.ACTIVE_MOD_UI and deck_pool[1] or G.P_CENTERS.b_red)'''
    -match_indent = true
    -
    -# create_UIBox_your_collection_decks()
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''(?[\t ]*)for k, v in ipairs\(G\.P_CENTER_POOLS\.Back\) do\n[\s\S]{4}ordered_names\[#ordered_names\+1\] = v\.name\n[\s\S]{2}end'''
    -position = 'at'
    -payload = '''
    -for k, v in ipairs(deck_pool) do
    -    ordered_names[#ordered_names+1] = v.key
    -end'''
    -line_prepend = '$indent'
    -
    -# create_UIBox_your_collection_decks()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''local t = create_UIBox_generic_options({ back_func = 'your_collection', contents = {'''
    -position = "at"
    -payload = '''local t = create_UIBox_generic_options({ 
    -colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_colour or
    -    (G.ACTIVE_MOD_UI.ui_config or {}).colour),
    -bg_colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_bg_colour or
    -    (G.ACTIVE_MOD_UI.ui_config or {}).bg_colour),
    -back_colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_back_colour or
    -    (G.ACTIVE_MOD_UI.ui_config or {}).back_colour),
    -outline_colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_outline_colour or
    -    (G.ACTIVE_MOD_UI.ui_config or {}).outline_colour),
    -back_func = G.ACTIVE_MOD_UI and "openModUI_"..G.ACTIVE_MOD_UI.id or 'your_collection', contents = {'''
    -match_indent = true
    -
    -# create_UIBox_your_collection_decks()
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''create_option_cycle({options = ordered_names, opt_callback = 'change_viewed_back', current_option = 1, colour = G.C.RED, w = 4.5, focus_args = {snap_to = true}, mid = '''
    -position = "at"
    -payload = '''create_option_cycle({options = ordered_names, opt_callback = 'change_viewed_back', current_option = 1, colour = G.ACTIVE_MOD_UI and (G.ACTIVE_MOD_UI.ui_config or {}).collection_option_cycle_colour or G.C.RED, w = 4.5, focus_args = {snap_to = true}, mid = '''
    -match_indent = true
    -
    -# G.FUNCS.your_collection_deck_page
    -[[patches]]
    -[patches.pattern]
    -target = "functions/button_callbacks.lua"
    -pattern = '''G.GAME.viewed_back:change_to(G.P_CENTER_POOLS.Back[args.to_key])'''
    -position = "at"
    -payload = '''
    -local deck_pool = SMODS.collection_pool(G.P_CENTER_POOLS.Back)
    -G.GAME.viewed_back:change_to(deck_pool[args.to_key])'''
    -match_indent = true
    -
    -# create_UIBox_your_collection()
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''(?[\t ]*)UIBox_button\(\{button = 'your_collection_blinds', label = \{localize\('b_blinds'\)\}, count = G\.DISCOVER_TALLIES\.blinds, minw = 5, minh = 2.0, id = 'your_collection_blinds', focus_args = \{snap_to = true\}\}\),'''
    -position = 'after'
    -payload = '''UIBox_button({button = 'your_collection_other_gameobjects', label = {localize('k_other')}, minw = 5, id = 'your_collection_other_gameobjects', focus_args = {snap_to = true}}),'''
    -
    -# Fix UIElement.config.chosen being overriden if choice=true is set
    -# UIElement:click()
    -[[patches]]
    -[patches.pattern]
    -target = "engine/ui.lua"
    -match_indent = true
    -position = "after"
    -pattern = "if self.config.choice then"
    -payload = "    local chosen_temp = self.config.chosen"
    -
    -[[patches]]
    -[patches.pattern]
    -target = "engine/ui.lua"
    -match_indent = true
    -position = "at"
    -pattern = "self.config.chosen = true"
    -payload = "self.config.chosen = chosen_temp or true"
    -
    -# Escape from mod menu saves config
    -# Needs to be before all checks
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/controller.lua'
    -pattern = "function Controller:key_press_update(key, dt)"
    -position = "after"
    -payload = '''
    -    if key == "escape" and (G.ACTIVE_MOD_UI or SMODS.IN_MODS_TAB) then
    -        G.FUNCS.exit_mods()
    -    end
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -match_indent = true
    -position = 'at'
    -pattern = "config = {offset = {x=-0.03,y=0}, align = 'cl', parent = e}"
    -payload = """config = (not e.config.ref_table or not e.config.ref_table[1].config.card_pos or e.config.ref_table[1].config.card_pos > G.ROOM.T.w*0.4) and
    -    {offset = {x=-0.03,y=0}, align = 'cl', parent = e} or
    -    {offset = {x=0.03,y=0}, align = 'cr', parent = e}"""
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'tag.lua'
    -match_indent = true
    -position = 'at'
    -pattern = "_self.config.h_popup_config ={align = 'cl', offset = {x=-0.1,y=0},parent = _self}"
    -payload = """_self.config.h_popup_config = (_self.T.x > G.ROOM.T.w*0.4) and
    -    {align =  'cl', offset = {x=-0.1,y=0},parent = _self} or
    -    {align =  'cr', offset = {x=0.1,y=0},parent = _self}"""
    -    
    -# desc_from_rows
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -position = 'at'
    -pattern = 'colour = empty and G\.C\.CLEAR or G\.C\.UI\.BACKGROUND_WHITE'
    -payload = 'colour = desc_nodes.background_colour or empty and G.C.CLEAR or G.C.UI.BACKGROUND_WHITE'
    -
    -# info_tip_from_rows
    -[[patches]]
    -[patches.regex]
    -target = 'functions/UI_definitions.lua'
    -position = 'at'
    -pattern = 'padding = 0\.05, colour = G\.C\.WHITE\}'
    -payload = 'padding = 0.05, colour = desc_nodes.background_colour or G.C.WHITE}'
    -
    -# localize
    -[[patches]]
    -[patches.regex]
    -target = 'functions/misc_functions.lua'
    -position = 'after'
    -pattern = '\(part\.control\.C and loc_colour\(part\.control\.C\)\)'
    -payload = ' or args.text_colour'
    -
    -[[patches]]
    -[patches.regex]
    -target = 'functions/misc_functions.lua'
    -position = 'at'
    -pattern = 'loc_colour\(part\.control\.C or nil, args\.default_col\)'
    -payload = 'not part.control.C and args.text_colour or loc_colour(part.control.C or nil, args.default_col)'
    -
    -[[patches]]
    -[patches.regex]
    -target = 'functions/misc_functions.lua'
    -position = 'after'
    -pattern = 'part\.control\.s and tonumber\(part\.control\.s\)'
    -payload = ' or args.scale '
    -
    -# set_discover_tallies()
    -# exclude no_collection objects
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = "if not v.omit then"
    -payload = "if not v.omit and not v.no_collection then"
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = "if v.set == 'Joker' then"
    -position = "at"
    -payload = "if v.set == 'Joker' and not v.no_collection and not v.omit then "
    -match_indent = true
    -times = 1
    -
    -[[patches]]
    -[patches.regex]
    -target = 'functions/misc_functions.lua'
    -line_prepend = '$indent'
    -position = 'at'
    -pattern = '(?[\t ]*)(?for _, v in pairs\(G\.P_[BT].*)(?(\n.*){7})'
    -payload = '''$start
    -    if not v.no_collection then
    -    $rest
    -end
    -'''
    -
    -#set_alerts()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -match_indent = true
    -position = 'at'
    -pattern = "if v.discovered and not v.alerted then"
    -payload = "if v.discovered and not v.alerted and not v.no_collection then"
    -
    -## Description controls
    -# localize
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = 'final_line[#final_line+1] = {n=G.UIT.O, config={'
    -payload = '''
    -final_line[#final_line+1] = {n=G.UIT.C, config={align = "m", colour = part.control.B and args.vars.colours[tonumber(part.control.B)] or part.control.X and loc_colour(part.control.X) or nil, r = 0.05, padding = 0.03, res = 0.15}, nodes={}}
    -final_line[#final_line].nodes[1] = {n=G.UIT.O, config={
    -'''
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = 'elseif part.control.X then'
    -payload = 'elseif part.control.X or part.control.B then' 
    -
    -[[patches]]
    -[patches.regex]
    -target = 'functions/misc_functions.lua'
    -position = 'at'
    -pattern = 'colour = loc_colour\(part.control.X\)'
    -payload = 'colour = part.control.B and args.vars.colours[tonumber(part.control.B)] or loc_colour(part.control.X)' 
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = 'colour = loc_colour(part.control.C or nil),'
    -payload = 'colour = part.control.V and args.vars.colours[tonumber(part.control.V)] or loc_colour(part.control.C or nil),'
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/common_events.lua"
    -pattern = '''
    -G.E_MANAGER:add_event(Event({ --Add bonus chips from this card
    -                    trigger = 'before',
    -                    delay = delay,
    -'''
    -position = "at"
    -payload = '''
    -G.E_MANAGER:add_event(Event({ --Add bonus chips from this card
    -                    trigger = trigger,
    -                    delay = delay,
    -                    blocking = blocking,
    -                    blockable = blockable,
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/UI_definitions.lua"
    -pattern = '''   if args.info then 
    -     t = {n=args.col and G.UIT.C or G.UIT.R, config={align = "cm"}, nodes={'''
    -position = "before"
    -payload = """if args.hide_label then 
    -    local t2 = {}
    -    for i = 1, #t.nodes do
    -        if i ~= 1 then table.insert(t2, t.nodes[i]) end
    -    end
    -    t.nodes = t2
    -end"""
    -match_indent = true
    -
    -# UIBox_button():
    -# the counters on collection buttons use text_colour instead of being hardcoded to white
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = "{n=G.UIT.T, config={scale = 0.35,text = args.count.tally..' / '..args.count.of, colour = {1,1,1,0.9}}}"
    -position = "at"
    -match_indent = true
    -payload = "{n=G.UIT.T, config={scale = 0.35,text = args.count.tally..' / '..args.count.of, colour = args.text_colour}}"
    -
    -# G.UIDEF.card_h_popup():
    -# add a "card_type_text_colour" variable
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = 'local card_type_colour = get_type_colour(card.config.center or card.config, card)'
    -position = 'after'
    -match_indent = true
    -payload = 'local card_type_text_colour = (AUT.card_type and SMODS.ConsumableTypes[AUT.card_type] and SMODS.ConsumableTypes[AUT.card_type].text_colour) or G.C.UI.TEXT_LIGHT'
    -
    -# G.UIDEF.card_h_popup():
    -# pass "card_type_text_colour" variable to create_badge() when creating the badge for a card type
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = "badges[#badges + 1] = create_badge(((card.ability.name == 'Pluto' or card.ability.name == 'Ceres' or card.ability.name == 'Eris') and localize('k_dwarf_planet')) or (card.ability.name == 'Planet X' and localize('k_planet_q') or card_type),card_type_colour, nil, 1.2)"
    -position = 'at'
    -match_indent = true
    -payload = "badges[#badges + 1] = create_badge(((card.ability.name == 'Pluto' or card.ability.name == 'Ceres' or card.ability.name == 'Eris') and localize('k_dwarf_planet')) or (card.ability.name == 'Planet X' and localize('k_planet_q') or card_type), card_type_colour, card_type_text_colour, 1.2)"
    -
    -# Fixing description error when info_queue has multi-box descriptions.
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -pattern = '''
    -bump = true,
    -silent = true,
    -pop_in = 0,
    -pop_in_rate = 4,
    -maxw = 5,
    -shadow = true,
    -y_offset = -0.6,
    -'''
    -position = "at"
    -payload = '''
    -bump = not args.no_bump,
    -text_effect = SMODS.DynaTextEffects[part.control.E] and part.control.E,
    -silent = not args.no_silent,
    -pop_in = (not args.no_pop_in and (args.pop_in or 0)) or nil,
    -pop_in_rate = (not args.no_pop_in and (args.pop_in_rate or 4)) or nil,
    -maxw = args.maxw or 5,
    -shadow = not args.no_shadow,
    -y_offset = args.y_offset or -0.6,'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/common_events.lua'
    -pattern = '''
    -desc_nodes.name = localize{type = 'name_text', key = name_override or _c.key, set = name_override and 'Other' or _c.set} 
    -'''
    -position = "after"
    -payload = '''
    --- If statement guards against setting `name_styled` twice. This apparently happens
    --- when generating ui for Lucky Cards: smods takes ownership of vanilla, so
    --- this code is reached in both places it appears
    -if not desc_nodes.name_styled then
    -  local set = name_override and "Other" or _c.set
    -  local key = name_override or _c.key
    -  if set == "Seal" then
    -    if G.localization.descriptions["Other"][_c.key.."_seal"] then set = "Other"; key = key.."_seal" end
    -  else
    -    if not G.localization.descriptions[set][_c.key] then set = "Other" end
    -  end
    -  desc_nodes.name_styled = {}
    -  localize{type = 'name', key = key, set = set, nodes = desc_nodes.name_styled, fixed_scale = 0.63, no_pop_in = true, no_shadow = true, y_offset = 0, no_spacing = true, no_bump = true, vars = (_c.create_fake_card and _c.loc_vars and (_c:loc_vars({}, _c:create_fake_card()) or {}).vars) or {colours = {}}} 
    -  desc_nodes.name_styled = SMODS.info_queue_desc_from_rows(desc_nodes.name_styled, true)
    -  desc_nodes.name_styled.config.align = "cm"
    -end
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''
    -function info_tip_from_rows(desc_nodes, name)
    -'''
    -position = "after"
    -payload = '''
    -  local name_nodes
    -  if not desc_nodes.name_styled then
    -    name_nodes = {{n=G.UIT.T, config={text = name, scale = 0.32, colour = G.C.UI.TEXT_LIGHT}}}
    -  else
    -    name_nodes = {desc_nodes.name_styled}
    -  end
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/node.lua'
    -pattern = '''
    -self.children.h_popup.states.drag.can = true
    -'''
    -position = "after"
    -payload = '''
    --- Fixes styled info_queue names
    --- This ensures show_infotip runs just after the main hover box is created instead of being able to fall one frame after
    -if ((self.children.h_popup.UIRoot.children[1] or {}).config or {}).func == "show_infotip" then
    -  G.FUNCS.show_infotip(self.children.h_popup.UIRoot.children[1])
    -end
    -'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''
    -{n=G.UIT.R, config={align = "tm", minh = 0.36, padding = 0.03}, nodes={{n=G.UIT.T, config={text = name, scale = 0.32, colour = G.C.UI.TEXT_LIGHT}}}},
    -'''
    -position = "at"
    -payload = '''
    -{n=G.UIT.R, config={align = "tm", minh = 0.36, padding = 0.03}, nodes=name_nodes},
    -'''
    -match_indent = true
    diff --git a/lovely/ui_additional_text_props.toml b/lovely/ui_additional_text_props.toml
    deleted file mode 100644
    index a67727523..000000000
    --- a/lovely/ui_additional_text_props.toml
    +++ /dev/null
    @@ -1,386 +0,0 @@
    -[manifest]
    -version = "1.2"
    -dump_lua = true
    -priority = -10
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = """spacing = math.max(0, 0.32*(17 - #assembled_string)),"""
    -position = "after"
    -payload = """font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],"""
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = """spacing = _spacing,"""
    -position = "after"
    -payload = """font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],"""
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = """text = assembled_string,"""
    -position = "after"
    -payload = """font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],"""
    -match_indent = true
    -
    -# descsacle
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -local desc_scale = G.LANG.font.DESCSCALE
    -'''
    -payload = '''
    -local desc_scale = (SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)] or G.LANG.font).DESCSCALE
    -'''
    -
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = """loc_target = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key]"""
    -position = "at"
    -payload = """
    -loc_target = loc_target or {}
    -if pcall(function() loc_target.name_parsed = {loc_parse_string(G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name)} end) then
    -else loc_target.name_parsed = {} end"""
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = """if ret_string then return ret_string end"""
    -position = "before"
    -payload = """if ret_string and type(ret_string) == 'string' then ret_string = string.gsub(ret_string, "{.-}", "") end"""
    -match_indent = true
    -
    -# Replace all instances of the language font
    -[[patches]]
    -[patches.regex]
    -target = "engine/ui.lua"
    -pattern = '''
    -self.config.lang.font'''
    -position = "at"
    -payload = '''(self.config.font or self.config.lang.font)'''
    -line_prepend = "$indent"
    -
    -[[patches]]
    -[patches.regex]
    -target = "engine/ui.lua"
    -pattern = '''
    -node.config.lang.font'''
    -position = "at"
    -payload = '''(node.config.font or node.config.lang.font)'''
    -line_prepend = "$indent"
    -
    -# Fix name scale when using formatting
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -local final_line = {}
    -'''
    -position = 'after'
    -match_indent = true
    -payload = '''
    -local final_name_assembled_string = ''
    -if args.type == 'name' and loc_target.name_parsed then
    -    for _, part in ipairs(lines) do
    -        local assembled_string_part = ''
    -        for _, subpart in ipairs(part.strings) do
    -            assembled_string_part = assembled_string_part..(type(subpart) == 'string' and subpart or format_ui_value(args.vars[tonumber(subpart[1])]) or 'ERROR')
    -        end
    -        final_name_assembled_string = final_name_assembled_string..assembled_string_part
    -    end
    -end
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -spacing = math.max(0, 0.32*(17 - #assembled_string)),
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -spacing = (not args.no_spacing and math.max(0, 0.32*(17 - #(final_name_assembled_string or assembled_string)))) or nil,
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -position = 'at'
    -match_indent = true
    -pattern = 'scale =  (0.55 - 0.004*#assembled_string)*(part.control.s and tonumber(part.control.s) or 1)'
    -payload = 'scale = (0.55 - 0.004*#(final_name_assembled_string or assembled_string))*(part.control.s and tonumber(part.control.s) or 1)*(args.fixed_scale or 1)'
    -
    -# Add support for multi line name
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -if pcall(function() loc_target.name_parsed = {loc_parse_string(G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name)} end) then
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -if pcall(function()
    -local name = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key]
    -loc_target.name_parsed = name.name_parsed or {loc_parse_string(name.name)}
    -end) then
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -if pcall(function() ret_string = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name end) then
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -if pcall(function()
    -    local name_text = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name
    -    if type(name_text) == "table" then
    -        ret_string = ""
    -        for i, line in ipairs(name_text) do
    -            ret_string = ret_string.. (i ~= 1 and " " or "")..line
    -        end
    -    else
    -        ret_string = name_text
    -    end
    -end) then
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = "functions/misc_functions.lua"
    -pattern = '''
    -    if args.type == 'name' or args.type == 'text' then return final_line end
    -    args.nodes[#args.nodes+1] = final_line
    -end
    -'''
    -position = 'at'
    -match_indent = true
    -payload = '''
    -    if args.type == 'text' then return final_line end
    -    if not args.nodes and args.type == 'name' then args.nodes = {} end
    -    args.nodes[#args.nodes+1] = final_line
    -end
    -if args.type == 'name' then
    -    local final_name = {}
    -
    -    for _, line in ipairs(args.nodes or {}) do
    -        final_name[#final_name+1] = {n=G.UIT.R, config={align = "m"}, nodes=line}
    -    end
    -
    -    return final_name
    -end
    -'''
    -
    -# Marquee text in DynaText
    -# Adjust width when marquee is enabled
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if self.strings[k].W > self.config.W then self.config.W = self.strings[k].W; self.strings[k].W_offset = 0 end
    -'''
    -payload = '''
    -if self.strings[k].W > self.config.W then
    -    self.config.W = self.strings[k].W
    -    self.strings[k].W_offset = 0
    -    if self.config.marquee and self.config.maxw then
    -        if self.config.W > self.config.maxw then
    -            self.config.marquee_width = self.config.W/self.config.maxw
    -            self.config.W = self.config.maxw
    -        else
    -            self.config.marquee = 'no'
    -        end
    -    end
    -end
    -'''
    -# Adjust width when marquee is enabled
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -if self.config.maxw and self.config.W > self.config.maxw then
    -'''
    -payload = '''
    -if self.config.maxw and self.config.W > self.config.maxw and not self.config.marquee then
    -'''
    -# Adjust width when marquee is enabled
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -v.W_offset = 0.5*(self.config.W - v.W)
    -'''
    -payload = '''
    -v.W_offset = 0.5*(self.config.W - (self.config.marquee and self.config.maxw and self.config.maxw < v.W and self.config.maxw or v.W))
    -'''
    -
    -# Add start/end index calculation
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -if self.children.particle_effect then self.children.particle_effect:draw() end
    -'''
    -payload = '''
    -local start_index = 1
    -local end_index = #self.strings[self.focused_string].letters
    -if self.config.marquee and self.config.marquee ~= 'no' then
    -    local padding = math.floor(#self.strings[self.focused_string].letters / (self.config.marquee_width or 1)) - 1
    -    if self.dt and (self.dt - self.config.hold) / self.config.scroll_speed > (#self.strings[self.focused_string].letters + math.ceil(padding/4)) then self.dt = 0 end
    -    if self.dt and self.dt > self.config.hold then
    -        start_index = 1 + (math.floor((self.dt - self.config.hold) / self.config.scroll_speed) % (#self.strings[self.focused_string].letters + math.ceil(padding/4)))
    -    end
    -    end_index = math.min(start_index + padding, #self.strings[self.focused_string].letters)
    -end
    -'''
    -
    -# Use start/end index
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -for k, letter in ipairs(self.strings[self.focused_string].letters) do
    -    local real_pop_in = self.config.min_cycle_time == 0 and 1 or letter.pop_in
    -'''
    -payload = '''
    -for k=start_index, end_index do
    -    local letter = self.strings[self.focused_string].letters[k]
    -    local real_pop_in = self.config.min_cycle_time == 0 and 1 or letter.pop_in
    -'''
    -
    -# Update dt
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -function DynaText:update(dt)
    -'''
    -payload = '''
    -function DynaText:update(dt, real_dt)
    -if self.config.marquee then
    -    self.dt = (self.dt or 0) + real_dt
    -end
    -'''
    -
    -# Ensure hold is present
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/text.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -self.silent = (config.silent)
    -'''
    -payload = '''
    -self.config.marquee = self.config.marquee and not G.SETTINGS.reduced_motion
    -self.config.hold = self.config.hold or self.config.marquee and 1.5 or nil
    -self.config.scroll_speed = self.config.scroll_speed or self.config.marquee and 0.1 or nil
    -'''
    -
    -# Pass real_dt to update function
    -[[patches]]
    -[patches.pattern]
    -target = 'game.lua'
    -match_indent = true
    -position = 'at'
    -pattern = '''
    -v:update(dt*self.SPEEDFACTOR)
    -'''
    -payload = '''
    -v:update(dt*self.SPEEDFACTOR, self.real_dt)
    -'''
    -
    -
    -# Add underline property for text
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/ui.lua'
    -match_indent = true
    -position = 'before'
    -pattern = '''
    ---Draw the 'chosen triangle'
    -'''
    -payload = '''
    -if self.config.underline and self.config.underline[4] > 0.01 then 
    -    prep_draw(self, 1)
    -    love.graphics.scale(1/(G.TILESIZE))
    -    love.graphics.setLineWidth(1)
    -    love.graphics.setColor(self.config.underline)
    -    self:draw_pixellated_under('line', parallax_dist)
    -    love.graphics.pop()
    -end
    -'''
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],
    -'''
    -payload = '''
    -underline = part.control.u and loc_colour(part.control.u),
    -'''
    -# DynaText compat
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -final_line[#final_line].nodes[1] = {n=G.UIT.O, config={
    -'''
    -payload = '''
    -underline = part.control.u and loc_colour(part.control.u),
    -'''
    -
    -# Add button support for text formatting
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],
    -'''
    -payload = '''
    -button = part.control.button,
    -'''
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/misc_functions.lua'
    -match_indent = true
    -position = 'after'
    -pattern = '''
    -final_line[#final_line].nodes[1] = {n=G.UIT.O, config={
    -'''
    -payload = '''
    -button = part.control.button,
    -'''
    \ No newline at end of file
    diff --git a/lovely/ui_elements.toml b/lovely/ui_elements.toml
    deleted file mode 100644
    index 820a48a4b..000000000
    --- a/lovely/ui_elements.toml
    +++ /dev/null
    @@ -1,152 +0,0 @@
    -[manifest]
    -version = "1.0.0"
    -dump_lua = true
    -priority = -10
    -
    -## colour argument fix
    -# create_tabs()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''tab_buttons[#tab_buttons+1] = UIBox_button({id = 'tab_but_'..(v.label or ''), ref_table = v, button = 'change_tab', label = {v.label}, minh = 0.8*args.scale, minw = 2.5*args.scale, col = true, choice = true, scale = args.text_scale, chosen = v.chosen, func = v.func, focus_args = {type = 'none'}})'''
    -position = 'at'
    -payload = '''tab_buttons[#tab_buttons+1] = UIBox_button({id = 'tab_but_'..(v.label or ''), ref_table = v, button = 'change_tab', label = {v.label}, minh = 0.8*args.scale, minw = 2.5*args.scale, col = true, choice = true, scale = args.text_scale, chosen = v.chosen, func = v.func, colour = args.colour, focus_args = {type = 'none'}})'''
    -match_indent = true
    -
    -# UIElement:draw_self()
    -[[patches]]
    -[patches.pattern]
    -target = 'engine/ui.lua'
    -pattern = '''love.graphics.polygon("fill", get_chosen_triangle_from_rect(self.layered_parallax.x, self.layered_parallax.y, self.VT.w*G.TILESIZE, self.VT.h*G.TILESIZE, self.config.chosen == 'vert'))'''
    -position = 'before'
    -payload = '''love.graphics.setColor(self.config.colour)'''
    -match_indent = true
    -
    -
    -## multiple text input fix
    -# create_text_input()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''args.current_prompt_text = '''''
    -position = 'after'
    -payload = '''args.id = args.id or "text_input"'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''ui_letters[#ui_letters+1] = {n=G.UIT.T, config={ref_table = args, ref_value = 'current_prompt_text', scale = args.text_scale, colour = lighten(copy_table(args.colour), 0.4), id = 'prompt'}}'''
    -position = 'at'
    -payload = '''ui_letters[#ui_letters+1] = {n=G.UIT.T, config={ref_table = args, ref_value = 'current_prompt_text', scale = args.text_scale, colour = lighten(copy_table(args.colour), 0.4), id = args.id..'_prompt'}}'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''ui_letters[i] = {n=G.UIT.T, config={ref_table = text.letters, ref_value = i, scale = args.text_scale, colour = G.C.UI.TEXT_LIGHT, id = 'letter_'..i}}'''
    -position = 'at'
    -payload = '''ui_letters[i] = {n=G.UIT.T, config={ref_table = text.letters, ref_value = i, scale = args.text_scale, colour = G.C.UI.TEXT_LIGHT, id = args.id..'_letter_'..i}}'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''ui_letters[#ui_letters+1] = {n=G.UIT.B, config={r = 0.03,w=0.1, h=0.4, colour = position_text_colour, id = 'position', func = 'flash'}}'''
    -position = 'at'
    -payload = '''ui_letters[#ui_letters+1] = {n=G.UIT.B, config={r = 0.03,w=0, h=0.4, colour = position_text_colour, id = args.id..'_position', func = 'flash'}}'''
    -match_indent = true
    -
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''{n=G.UIT.C, config={align = "cm", draw_layer = 1, colour = G.C.CLEAR}, nodes = {'''
    -position = 'at'
    -payload = '''{n=G.UIT.C, config={align = "cm", colour = G.C.CLEAR}, nodes = {'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = '''{n=G.UIT.C, config={id = 'text_input', align = "cm", padding = 0.05, r = 0.1, draw_layer = 2, hover = true, colour = args.colour,minw = args.w, min_h = args.h, button = 'select_text_input', shadow = true}, nodes={'''
    -position = 'at'
    -payload = '''{n=G.UIT.C, config={id = args.id, align = "cm", padding = 0.05, r = 0.1, hover = true, colour = args.colour,minw = args.w, min_h = args.h, button = 'select_text_input', shadow = true}, nodes={'''
    -match_indent = true
    -
    -# G.FUNCS.select_text_input
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = '''G.CONTROLLER.text_input_hook = e.children[1].children[1]'''
    -position = 'after'
    -payload = '''G.CONTROLLER.text_input_id = e.config.id'''
    -match_indent = true
    -
    -# G.FUNCS.paste_seed
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = '''G.CONTROLLER.text_input_hook = e.UIBox:get_UIE_by_ID('text_input').children[1].children[1]'''
    -position = 'after'
    -payload = """G.CONTROLLER.text_input_id = 'text_input'"""
    -match_indent = true
    -
    -# G.FUNCS.flash
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = '''if G.CONTROLLER.text_input_hook then'''
    -position = 'at'
    -payload = '''if G.CONTROLLER.text_input_hook and G.CONTROLLER.text_input_id == e.config.id:sub(1,string.len(G.CONTROLLER.text_input_id)) then'''
    -match_indent = true
    -
    -# TRANSPOSE_TEXT_INPUT()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = '''if hook.children[i].config.id == 'position' then'''
    -position = 'at'
    -payload = '''if hook.children[i].config.id == G.CONTROLLER.text_input_id..'_position' then'''
    -match_indent = true
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = '''local real_letter = hook.children[position_child+dir].config.id:sub(1, 7) == 'letter_' and hook.children[position_child+dir].config.text ~= '''''
    -position = 'at'
    -payload = '''local real_letter = hook.children[position_child+dir].config.id:sub(1, 8+string.len(G.CONTROLLER.text_input_id)) == G.CONTROLLER.text_input_id..'_letter_' and hook.children[position_child+dir].config.text ~= '''''
    -match_indent = true
    -
    -# GET_TEXT_FROM_INPUT()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/button_callbacks.lua'
    -pattern = '''if hook.children[i].config and hook.children[i].config.id:sub(1, 7) == 'letter_' and hook.children[i].config.text ~= '' then'''
    -position = 'at'
    -payload = '''if hook.children[i].config and hook.children[i].config.id:sub(1, 8+string.len(G.CONTROLLER.text_input_id)) == G.CONTROLLER.text_input_id..'_letter_' and hook.children[i].config.text ~= '' then'''
    -match_indent = true
    -
    -# remove hardcoded shop selection limit
    -[[patches]]
    -[patches.pattern]
    -target = "cardarea.lua"
    -pattern = "if self.highlighted[1] then"
    -position = "at"
    -payload = "if #self.highlighted >= self.config.highlighted_limit then"
    -match_indent = true
    -
    -# Deck unlock popup shows custom unlock descriptions
    -# create_UIBox_deck_unlock()
    -[[patches]]
    -[patches.pattern]
    -target = 'functions/UI_definitions.lua'
    -pattern = "if deck_center.unlock_condition.type == 'win_deck' then"
    -position = "before"
    -payload = """
    -if deck_center.check_for_unlock and type(deck_center.check_for_unlock) == "function" then
    -    local loc_args = {}
    -    local key_override
    -    if deck_center.locked_loc_vars and type(deck_center.locked_loc_vars) == 'function' then
    -        local res = deck_center:locked_loc_vars() or {}
    -        loc_args = res.vars or {}
    -        key_override = res.key
    -    end
    -    localize{type = 'unlocks', key = key_override or deck_center.key, set = "Back", nodes = deck_criteria, vars = loc_args, default_col = G.C.WHITE, shadow = true}
    -end
    -"""
    -match_indent = true
    
    From 7868d4867466ce26965d5f09afafe8bea4fea5ab Mon Sep 17 00:00:00 2001
    From: Gloompashcy 
    Date: Mon, 9 Feb 2026 18:36:59 +1000
    Subject: [PATCH 2/2] lovely directory folers + cleanup
    
    See pr for details
    ---
     lovely/better_calc/better_calc.toml           | 2069 +++++++++++++++++
     lovely/better_calc/calc_returns.toml          |   45 +
     lovely/better_calc/can_calculate.toml         |   63 +
     .../new_calculation/calculate_card_added.toml |   29 +
     .../new_calculation/joker_retriggers.toml     |  324 +++
     .../new_calculation/listed_probabilities.toml |  162 ++
     .../better_calc/new_calculation/scaling.toml  |  708 ++++++
     lovely/better_calc/new_calculation/shop.toml  |  271 +++
     .../better_calc/object_vals/card_limit.toml   |  389 ++++
     .../better_calc/object_vals/hand_limit.toml   |   93 +
     .../better_calc/object_vals/perma_bonus.toml  |  297 +++
     lovely/engine/event.toml                      |   64 +
     lovely/engine/threads.toml                    |   30 +
     lovely/fixes.toml                             |  853 +++++++
     lovely/mod.toml                               |   72 +
     lovely/objects/smods/jimboquip.toml           |  202 ++
     lovely/objects/smods/keybind.toml             |   82 +
     lovely/objects/smods/language.toml            |   59 +
     lovely/objects/smods/scoring_calculation.toml |  378 +++
     lovely/objects/smods/sound.toml               |  167 ++
     lovely/objects/vanilla_collection/blind.toml  |  552 +++++
     .../vanilla_collection/centers/back.toml      |  207 ++
     .../vanilla_collection/centers/booster.toml   |  367 +++
     .../vanilla_collection/centers/center.toml    |  639 +++++
     .../vanilla_collection/centers/edition.toml   |  219 ++
     .../centers/enhancement.toml                  |  364 +++
     lovely/objects/vanilla_collection/seal.toml   |  241 ++
     lovely/objects/vanilla_collection/stake.toml  |  281 +++
     .../objects/vanilla_collection/sticker.toml   |  111 +
     lovely/objects/vanilla_collection/tag.toml    |  166 ++
     lovely/objects/vanilla_other/achievement.toml |  109 +
     lovely/objects/vanilla_other/cardarea.toml    |   79 +
     lovely/objects/vanilla_other/challenge.toml   |   93 +
     .../objects/vanilla_other/playing_card.toml   |  378 +++
     lovely/objects/vanilla_other/poker_hand.toml  |  127 +
     lovely/objects/vanilla_other/rarity.toml      |   61 +
     lovely/objects/visual/atlas.toml              |   55 +
     lovely/objects/visual/deck_skin.toml          |  110 +
     lovely/objects/visual/gradient.toml           |   56 +
     lovely/pool.toml                              |  208 ++
     lovely/shaders/screen_shader_stack.toml       |   74 +
     lovely/shaders/shaders.toml                   |   18 +
     lovely/smods_core/compat_0_9_8.toml           |   38 +
     lovely/smods_core/core.toml                   |   28 +
     lovely/smods_core/crash_handler.toml          |   32 +
     lovely/smods_core/libs.toml                   |   32 +
     lovely/smods_core/loader.toml                 |   27 +
     lovely/smods_core/preflight.toml              |   47 +
     lovely/sprites/create_sprite.toml             |   97 +
     lovely/sprites/easings.toml                   |   52 +
     lovely/sprites/joker_size.toml                |   73 +
     lovely/ui/compact_cashout.toml                |   65 +
     lovely/ui/dollar_row.toml                     |   77 +
     lovely/ui/menu.toml                           |   60 +
     lovely/ui/object_ui/blind_ui.toml             |  176 ++
     lovely/ui/object_ui/poker_hand_screen.toml    |   54 +
     lovely/ui/object_ui/poker_hand_text.toml      |   42 +
     lovely/ui/text/multi_box_descriptions.toml    |  158 ++
     lovely/ui/text/number_formatting.toml         |  177 ++
     lovely/ui/text/text_effect.toml               |  126 +
     lovely/ui/text/ui_additional_text_props.toml  |  386 +++
     lovely/ui/ui.toml                             |  405 ++++
     lovely/ui/ui_elements.toml                    |  152 ++
     63 files changed, 13176 insertions(+)
     create mode 100644 lovely/better_calc/better_calc.toml
     create mode 100644 lovely/better_calc/calc_returns.toml
     create mode 100644 lovely/better_calc/can_calculate.toml
     create mode 100644 lovely/better_calc/new_calculation/calculate_card_added.toml
     create mode 100644 lovely/better_calc/new_calculation/joker_retriggers.toml
     create mode 100644 lovely/better_calc/new_calculation/listed_probabilities.toml
     create mode 100644 lovely/better_calc/new_calculation/scaling.toml
     create mode 100644 lovely/better_calc/new_calculation/shop.toml
     create mode 100644 lovely/better_calc/object_vals/card_limit.toml
     create mode 100644 lovely/better_calc/object_vals/hand_limit.toml
     create mode 100644 lovely/better_calc/object_vals/perma_bonus.toml
     create mode 100644 lovely/engine/event.toml
     create mode 100644 lovely/engine/threads.toml
     create mode 100644 lovely/fixes.toml
     create mode 100644 lovely/mod.toml
     create mode 100644 lovely/objects/smods/jimboquip.toml
     create mode 100644 lovely/objects/smods/keybind.toml
     create mode 100644 lovely/objects/smods/language.toml
     create mode 100644 lovely/objects/smods/scoring_calculation.toml
     create mode 100644 lovely/objects/smods/sound.toml
     create mode 100644 lovely/objects/vanilla_collection/blind.toml
     create mode 100644 lovely/objects/vanilla_collection/centers/back.toml
     create mode 100644 lovely/objects/vanilla_collection/centers/booster.toml
     create mode 100644 lovely/objects/vanilla_collection/centers/center.toml
     create mode 100644 lovely/objects/vanilla_collection/centers/edition.toml
     create mode 100644 lovely/objects/vanilla_collection/centers/enhancement.toml
     create mode 100644 lovely/objects/vanilla_collection/seal.toml
     create mode 100644 lovely/objects/vanilla_collection/stake.toml
     create mode 100644 lovely/objects/vanilla_collection/sticker.toml
     create mode 100644 lovely/objects/vanilla_collection/tag.toml
     create mode 100644 lovely/objects/vanilla_other/achievement.toml
     create mode 100644 lovely/objects/vanilla_other/cardarea.toml
     create mode 100644 lovely/objects/vanilla_other/challenge.toml
     create mode 100644 lovely/objects/vanilla_other/playing_card.toml
     create mode 100644 lovely/objects/vanilla_other/poker_hand.toml
     create mode 100644 lovely/objects/vanilla_other/rarity.toml
     create mode 100644 lovely/objects/visual/atlas.toml
     create mode 100644 lovely/objects/visual/deck_skin.toml
     create mode 100644 lovely/objects/visual/gradient.toml
     create mode 100644 lovely/pool.toml
     create mode 100644 lovely/shaders/screen_shader_stack.toml
     create mode 100644 lovely/shaders/shaders.toml
     create mode 100644 lovely/smods_core/compat_0_9_8.toml
     create mode 100644 lovely/smods_core/core.toml
     create mode 100644 lovely/smods_core/crash_handler.toml
     create mode 100644 lovely/smods_core/libs.toml
     create mode 100644 lovely/smods_core/loader.toml
     create mode 100644 lovely/smods_core/preflight.toml
     create mode 100644 lovely/sprites/create_sprite.toml
     create mode 100644 lovely/sprites/easings.toml
     create mode 100644 lovely/sprites/joker_size.toml
     create mode 100644 lovely/ui/compact_cashout.toml
     create mode 100644 lovely/ui/dollar_row.toml
     create mode 100644 lovely/ui/menu.toml
     create mode 100644 lovely/ui/object_ui/blind_ui.toml
     create mode 100644 lovely/ui/object_ui/poker_hand_screen.toml
     create mode 100644 lovely/ui/object_ui/poker_hand_text.toml
     create mode 100644 lovely/ui/text/multi_box_descriptions.toml
     create mode 100644 lovely/ui/text/number_formatting.toml
     create mode 100644 lovely/ui/text/text_effect.toml
     create mode 100644 lovely/ui/text/ui_additional_text_props.toml
     create mode 100644 lovely/ui/ui.toml
     create mode 100644 lovely/ui/ui_elements.toml
    
    diff --git a/lovely/better_calc/better_calc.toml b/lovely/better_calc/better_calc.toml
    new file mode 100644
    index 000000000..960793973
    --- /dev/null
    +++ b/lovely/better_calc/better_calc.toml
    @@ -0,0 +1,2069 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +## G.FUNCS.evaluate_play()
    +# evaluate main scoring
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/state_events.lua'
    +pattern = '''(?[\t ]*)(?if modded then update_hand_text\(\{sound = 'chips2', modded = modded\}, \{chips = hand_chips, mult = mult\}\) end)(.*\n)*?\s+(?--\++--)'''
    +position = 'at'
    +line_prepend = '$indent'
    +payload = '''$handtext
    +delay(0.3)
    +SMODS.calculate_context({initial_scoring_step = true, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands})
    +for _, v in ipairs(SMODS.get_card_areas('playing_cards')) do
    +    SMODS.calculate_main_scoring({cardarea = v, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands}, v == G.play and scoring_hand or nil)
    +    delay(0.3)
    +end
    +$delimiter'''
    +## eval_card()
    +# handle debuffed playing cards
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''
    +function eval_card(card, context)
    +    context = context or {}
    +    local ret = {}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +function eval_card(card, context)
    +    context = context or {}
    +    if not card:can_calculate(context.ignore_debuff, context.remove_playing_cards or context.joker_type_destroyed) then
    +        if card.ability.rental then
    +            local ret = {}
    +            ret[SMODS.Stickers.rental] = card:calculate_sticker(context, 'rental')
    +            return ret, {}
    +        end
    +        return {}, {}
    +    end
    +	local vanilla_debuff_handling
    +    for _, key in ipairs(SMODS.custom_debuff_handling) do if card.config.center_key == key then vanilla_debuff_handling = true; break end end
    +    if context.other_card and context.other_card.can_calculate and not context.other_card:can_calculate(context.ignore_other_debuff or context.ignore_debuff) and not vanilla_debuff_handling then return {}, {} end
    +    local ret = {}
    +'''
    +
    +# built in config values
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''
    +if context.cardarea == G.play then
    +    local chips = card:get_chip_bonus()
    +    if chips > 0 then
    +        ret.chips = chips
    +    end
    +
    +    local mult = card:get_chip_mult()
    +    if mult > 0 then
    +        ret.mult = mult
    +    end
    +
    +    local x_mult = card:get_chip_x_mult(context)
    +    if x_mult > 0 then
    +        ret.x_mult = x_mult
    +    end
    +
    +    local p_dollars = card:get_p_dollars()
    +    if p_dollars > 0 then
    +        ret.p_dollars = p_dollars
    +    end
    +
    +    local jokers = card:calculate_joker(context)
    +    if jokers then
    +        ret.jokers = jokers
    +    end
    +
    +    local edition = card:get_edition(context)
    +    if edition then
    +        ret.edition = edition
    +    end
    +end
    +'''
    +match_indent = true
    +position = "at"
    +payload = """
    +if context.cardarea == G.play and context.main_scoring then
    +    ret.playing_card = {}
    +    local chips = card:get_chip_bonus()
    +    if chips ~= 0 then
    +        ret.playing_card.chips = chips
    +    end
    +
    +    local mult = card:get_chip_mult()
    +    if mult ~= 0 then
    +        ret.playing_card.mult = mult
    +    end
    +
    +    local x_mult = card:get_chip_x_mult(context)
    +    if x_mult > 0 then
    +        ret.playing_card.x_mult = x_mult
    +    end
    +
    +    local p_dollars = card:get_p_dollars()
    +    if p_dollars ~= 0 then
    +        ret.playing_card.p_dollars = p_dollars
    +    end
    +
    +    local x_chips = card:get_chip_x_bonus()
    +    if x_chips > 0 then
    +        ret.playing_card.x_chips = x_chips
    +    end
    +
    +    -- TARGET: main scoring on played cards
    +end
    +if context.end_of_round and context.cardarea == G.hand and context.playing_card_end_of_round then
    +    local end_of_round = card:get_end_of_round_effect(context)
    +    if end_of_round then
    +        ret.end_of_round = end_of_round
    +    end
    +end
    +"""
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''
    +if context.cardarea == G.hand then
    +    local h_mult = card:get_chip_h_mult()
    +    if h_mult > 0 then
    +        ret.h_mult = h_mult
    +    end
    +
    +    local h_x_mult = card:get_chip_h_x_mult()
    +    if h_x_mult > 0 then
    +        ret.x_mult = h_x_mult
    +    end
    +
    +    local jokers = card:calculate_joker(context)
    +    if jokers then
    +        ret.jokers = jokers
    +    end
    +'''
    +match_indent = true
    +position = "at"
    +payload = """
    +if context.cardarea == G.hand and context.main_scoring then
    +    ret.playing_card = {}
    +    local h_mult = card:get_chip_h_mult()
    +    if h_mult ~= 0 then
    +        ret.playing_card.h_mult = h_mult
    +    end
    +
    +    local h_x_mult = card:get_chip_h_x_mult()
    +    if h_x_mult > 0 then
    +        ret.playing_card.x_mult = h_x_mult
    +    end
    +
    +    local h_chips = card:get_chip_h_bonus()
    +    if h_chips ~= 0 then
    +        ret.playing_card.h_chips = h_chips
    +    end
    +
    +    local h_x_chips = card:get_chip_h_x_bonus()
    +    if h_x_chips > 0 then
    +        ret.playing_card.x_chips = h_x_chips
    +    end
    +
    +    -- TARGET: main scoring on held cards
    +"""
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''
    +local seals = card:calculate_seal(context)
    +if seals then
    +    ret.seals = seals
    +end
    +'''
    +match_indent = true
    +position = "at"
    +payload = """
    +if card.ability.set == 'Enhanced' then
    +    local enhancement = card:calculate_enhancement(context)
    +    if enhancement then
    +        ret.enhancement = enhancement
    +    end
    +end
    +if context.extra_enhancement then return ret end
    +if card.edition then
    +    local edition = card:calculate_edition(context)
    +    if edition then
    +        ret.edition = edition
    +    end
    +end
    +if card.seal then
    +    local seals = card:calculate_seal(context)
    +    if seals then
    +        ret.seals = seals
    +    end
    +end
    +for _,k in ipairs(SMODS.Sticker.obj_buffer) do
    +    local v = SMODS.Stickers[k]
    +    local sticker = card:calculate_sticker(context, k)
    +    if sticker then
    +        ret[v] = sticker
    +    end
    +end
    +
    +-- TARGET: evaluate your own repetition effects
    +if card.ability.repetitions and card.ability.repetitions > 0 then
    +    ret.seals = ret.seals or { card = card, message = localize('k_again_ex') }
    +    ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card.ability.repetitions) or card.ability.repetitions
    +end
    +if card.ability.perma_repetitions and card.ability.perma_repetitions > 0 then
    +    ret.seals = ret.seals or { card = card, message = localize('k_again_ex') }
    +    ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card.ability.perma_repetitions) or card.ability.perma_repetitions
    +end
    +"""
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "if context.cardarea == G.jokers or context.card == G.consumeables then"
    +match_indent = true
    +position = "before"
    +payload = """
    +if card.ability.set == 'Enhanced' then
    +    local enhancement = card:calculate_enhancement(context)
    +    if enhancement then
    +        ret.enhancement = enhancement
    +    end
    +end
    +if context.extra_enhancement then return ret end
    +if card.edition then
    +    local edition = card:calculate_edition(context)
    +    if edition then
    +        ret.edition = edition
    +    end
    +end
    +if card.seal then
    +    local seals = card:calculate_seal(context)
    +    if seals then
    +        ret.seals = seals
    +    end
    +end
    +for _,k in ipairs(SMODS.Sticker.obj_buffer) do
    +    local v = SMODS.Stickers[k]
    +    local sticker = card:calculate_sticker(context, k)
    +    if sticker then
    +        ret[v] = sticker
    +    end
    +end
    +
    +-- TARGET: evaluate your own general effects
    +"""
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''
    +if context.cardarea == G.jokers or context.card == G.consumeables then
    +    local jokers = nil
    +    if context.edition then
    +        jokers = card:get_edition(context)
    +    elseif context.other_joker then
    +        jokers = context.other_joker:calculate_joker(context)
    +    else
    +        jokers = card:calculate_joker(context)
    +    end
    +    if jokers then
    +        ret.jokers = jokers
    +    end
    +end
    +
    +return ret'''
    +match_indent = true
    +position = "at"
    +payload = """
    +local post_trig = {}
    +local areas = SMODS.get_card_areas('jokers')
    +local area_set = {}
    +for _,v in ipairs(areas) do area_set[v] = true end
    +if card.area and area_set[card.area] then
    +    local jokers, triggered = card:calculate_joker(context)
    +    if jokers == true then jokers = { remove = true } end
    +    if type(jokers) ~= 'table' then jokers = nil end
    +    if jokers or triggered then
    +        ret.jokers = jokers
    +        if not (context.retrigger_joker_check or context.retrigger_joker) and not (jokers and jokers.no_retrigger) and not SMODS.is_getter_context(context) then
    +            local retriggers = SMODS.calculate_retriggers(card, context, ret)
    +            if next(retriggers) then
    +                ret.retriggers = retriggers
    +            end
    +        end
    +        if not context.post_trigger and not context.retrigger_joker_check and SMODS.optional_features.post_trigger then
    +            SMODS.calculate_context({blueprint_card = context.blueprint_card, post_trigger = true, other_card = card, other_context = context, other_ret = ret}, post_trig)
    +        end
    +    end
    +end
    +
    +return ret, post_trig
    +"""
    +# patch card_eval_status_text to allow G.deck usage
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''
    +elseif card.area == G.hand then'''
    +match_indent = true
    +position = "at"
    +payload = """
    +elseif card.area == G.hand or card.area == G.deck then
    +"""
    +
    +# card_eval_status_text alignment patches
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''elseif card.area == G.hand or card.area == G.deck then'''
    +payload = '''elseif card == G.deck then
    +    y_off = -0.05*G.CARD_H
    +    card_aligned = 'tm'
    +elseif card.area == G.discard or card.area == G.vouchers then
    +    y_off = card.area == G.discard and -0.35*G.CARD_H or -0.65*G.CARD_H
    +    card = G.deck.cards[1] or G.deck
    +    card_aligned = 'tm'
    +'''
    +
    +# G.FUNCS.evaluate_play()
    +
    +
    +# Add deck/discard individual contexts
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +position = 'before'
    +match_indent = true
    +pattern = '''
    +--+++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
    +--Joker Effects
    +--+++++++++++++++++++++++++++++++++++++++++++++++++++++++++--'''
    +payload = '''
    +
    +'''
    +
    +# Joker Effects
    +# Edition effects
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +--calculate the joker edition effects
    +local edition_effects = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, edition = true})
    +if edition_effects.jokers then
    +    edition_effects.jokers.edition = true
    +    if edition_effects.jokers.chip_mod then
    +        hand_chips = mod_chips(hand_chips + edition_effects.jokers.chip_mod)
    +        update_hand_text({delay = 0}, {chips = hand_chips})
    +        card_eval_status_text(_card, 'jokers', nil, percent, nil, {
    +            message = localize{type='variable',key='a_chips',vars={edition_effects.jokers.chip_mod}},
    +            chip_mod =  edition_effects.jokers.chip_mod,
    +            colour =  G.C.EDITION,
    +            edition = true})
    +    end
    +    if edition_effects.jokers.mult_mod then
    +        mult = mod_mult(mult + edition_effects.jokers.mult_mod)
    +        update_hand_text({delay = 0}, {mult = mult})
    +        card_eval_status_text(_card, 'jokers', nil, percent, nil, {
    +            message = localize{type='variable',key='a_mult',vars={edition_effects.jokers.mult_mod}},
    +            mult_mod =  edition_effects.jokers.mult_mod,
    +            colour = G.C.DARK_EDITION,
    +            edition = true})
    +    end
    +    percent = percent+percent_delta
    +end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +local effects = {}
    +-- remove base game joker edition calc
    +local eval = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, edition = true, pre_joker = true})
    +if eval.edition then effects[#effects+1] = eval end
    +
    +'''
    +# Edition mult effects
    +## extra end to fix syntax from adding joker-like areas
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +    if edition_effects.jokers then
    +        if edition_effects.jokers.x_mult_mod then
    +            mult = mod_mult(mult*edition_effects.jokers.x_mult_mod)
    +            update_hand_text({delay = 0}, {mult = mult})
    +            card_eval_status_text(_card, 'jokers', nil, percent, nil, {
    +                message = localize{type='variable',key='a_xmult',vars={edition_effects.jokers.x_mult_mod}},
    +                x_mult_mod =  edition_effects.jokers.x_mult_mod,
    +                colour =  G.C.EDITION,
    +                edition = true})
    +        end
    +        percent = percent+percent_delta
    +    end
    +end'''
    +match_indent = false
    +position = "at"
    +payload = '''
    +            -- calculate edition multipliers
    +            local eval = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, edition = true, post_joker = true})
    +            if eval.edition then effects[#effects+1] = eval end
    +
    +            SMODS.trigger_effects(effects, _card)
    +        end end
    +'''
    +# Joker effects
    +
    +# allows adding other areas (syntax is fixed further down)
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''for i=1, #G.jokers.cards + #G.consumeables.cards do
    +    local _card = G.jokers.cards[i] or G.consumeables.cards[i - #G.jokers.cards]'''
    +payload = '''for _, area in ipairs(SMODS.get_card_areas('jokers')) do for _, _card in ipairs(area.cards) do'''
    +
    +## I am NOT converting this to regex (yet)
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +            --calculate the joker effects
    +            local effects = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, joker_main = true})
    +
    +            --Any Joker effects
    +            if effects.jokers then
    +                local extras = {mult = false, hand_chips = false}
    +                if effects.jokers.mult_mod then mult = mod_mult(mult + effects.jokers.mult_mod);extras.mult = true end
    +                if effects.jokers.chip_mod then hand_chips = mod_chips(hand_chips + effects.jokers.chip_mod);extras.hand_chips = true end
    +                if effects.jokers.Xmult_mod then mult = mod_mult(mult*effects.jokers.Xmult_mod);extras.mult = true  end
    +                update_hand_text({delay = 0}, {chips = extras.hand_chips and hand_chips, mult = extras.mult and mult})
    +                card_eval_status_text(_card, 'jokers', nil, percent, nil, effects.jokers)
    +                percent = percent+percent_delta
    +            end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +-- Calculate context.joker_main
    +local joker_eval, post = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, joker_main = true})
    +if next(joker_eval) then
    +    if joker_eval.edition then joker_eval.edition = {} end
    +    table.insert(effects, joker_eval)
    +    for _, v in ipairs(post) do effects[#effects+1] = v end
    +    if joker_eval.retriggers then
    +        for rt = 1, #joker_eval.retriggers do
    +            local rt_eval, rt_post = eval_card(_card, {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, joker_main = true, retrigger_joker = true})
    +            if next(rt_eval) then
    +                table.insert(effects, {retriggers = joker_eval.retriggers[rt]})
    +                table.insert(effects, rt_eval)
    +                for _, v in ipairs(rt_post) do effects[#effects+1] = v end
    +            end
    +        end
    +    end
    +end'''
    +# Joker on Joker effects
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +--Joker on Joker effects
    +for _, v in ipairs(G.jokers.cards) do
    +    local effect = v:calculate_joker{full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, other_joker = _card}
    +    if effect then
    +        local extras = {mult = false, hand_chips = false}
    +        if effect.mult_mod then mult = mod_mult(mult + effect.mult_mod);extras.mult = true end
    +        if effect.chip_mod then hand_chips = mod_chips(hand_chips + effect.chip_mod);extras.hand_chips = true end
    +        if effect.Xmult_mod then mult = mod_mult(mult*effect.Xmult_mod);extras.mult = true  end
    +        if extras.mult or extras.hand_chips then update_hand_text({delay = 0}, {chips = extras.hand_chips and hand_chips, mult = extras.mult and mult}) end
    +        if extras.mult or extras.hand_chips then card_eval_status_text(v, 'jokers', nil, percent, nil, effect) end
    +        percent = percent+percent_delta
    +    end
    +end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +-- Calculate context.other_joker effects
    +for _, _area in ipairs(SMODS.get_card_areas('jokers')) do
    +    for _, _joker in ipairs(_area.cards) do
    +        local other_key = 'other_unknown'
    +        if _card.ability.set == 'Joker' then other_key = 'other_joker' end
    +        if _card.ability.consumeable then other_key = 'other_consumeable' end
    +        if _card.ability.set == 'Voucher' then other_key = 'other_voucher' end
    +        -- TARGET: add context.other_something identifier to your cards
    +        local joker_eval,post = eval_card(_joker, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, other_main = _card })
    +        if next(joker_eval) then
    +            if joker_eval.edition then joker_eval.edition = {} end
    +            joker_eval.jokers.juice_card = _joker
    +            table.insert(effects, joker_eval)
    +            for _, v in ipairs(post) do effects[#effects+1] = v end
    +            if joker_eval.retriggers then
    +                for rt = 1, #joker_eval.retriggers do
    +                    local rt_eval, rt_post = eval_card(_joker, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, retrigger_joker = true})
    +                    if next(rt_eval) then
    +                        table.insert(effects, {retriggers = joker_eval.retriggers[rt]})
    +                        table.insert(effects, rt_eval)
    +                        for _, v in ipairs(rt_post) do effects[#effects+1] = v end
    +                    end
    +                end
    +            end
    +        end
    +    end
    +end
    +for _, _area in ipairs(SMODS.get_card_areas('individual')) do
    +    local other_key = 'other_unknown'
    +    if _card.ability.set == 'Joker' then other_key = 'other_joker' end
    +    if _card.ability.consumeable then other_key = 'other_consumeable' end
    +    if _card.ability.set == 'Voucher' then other_key = 'other_voucher' end
    +    -- TARGET: add context.other_something identifier to your cards
    +    local _eval,post = SMODS.eval_individual(_area, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, other_main = _card })
    +    if next(_eval) then
    +        _eval.individual.juice_card = _area.scored_card
    +        table.insert(effects, _eval)
    +        for _, v in ipairs(post) do effects[#effects+1] = v end
    +        if _eval.retriggers then
    +            for rt = 1, #_eval.retriggers do
    +                local rt_eval, rt_post = SMODS.eval_individual(_area, {full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, retrigger_joker = true})
    +                if next(rt_eval) then
    +                    table.insert(effects, {_eval.retriggers[rt]})
    +                    table.insert(effects, rt_eval)
    +                    for _, v in ipairs(rt_post) do effects[#effects+1] = v end
    +                end
    +            end
    +        end
    +    end
    +end
    +'''
    +
    +## Fix other evaluations
    +# Discarding cards
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +for j = 1, #G.jokers.cards do
    +    local eval = nil
    +    eval = G.jokers.cards[j]:calculate_joker({discard = true, other_card =  G.hand.highlighted[i], full_hand = G.hand.highlighted})
    +    if eval then
    +        if eval.remove then removed = true end
    +        card_eval_status_text(G.jokers.cards[j], 'jokers', nil, 1, nil, eval)
    +    end
    +end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +local effects = {}
    +SMODS.calculate_context({discard = true, other_card =  G.hand.highlighted[i], full_hand = G.hand.highlighted, ignore_other_debuff = true}, effects)
    +SMODS.trigger_effects(effects)
    +for _, eval in pairs(effects) do
    +    if type(eval) == 'table' then
    +        for key, eval2 in pairs(eval) do
    +            if key == 'remove' or (type(eval2) == 'table' and eval2.remove) then removed = true end
    +        end
    +    end
    +end'''
    +
    +# context.before
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +for i=1, #G.jokers.cards do
    +    --calculate the joker effects
    +    local effects = eval_card(G.jokers.cards[i], {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, before = true})
    +    if effects.jokers then
    +        card_eval_status_text(G.jokers.cards[i], 'jokers', nil, percent, nil, effects.jokers)
    +        percent = percent + percent_delta
    +        if effects.jokers.level_up then
    +            level_up_hand(G.jokers.cards[i], text)
    +        end
    +    end
    +end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +-- context.before calculations
    +if SMODS.last_hand then
    +    for _, v in ipairs({'scoring_hand', 'full_hand'}) do
    +        for _, _c in ipairs(SMODS.last_hand[v]) do
    +            _c.ability['SMODS_'..v] = nil
    +        end
    +    end
    +end
    +SMODS.last_hand = {scoring_hand = scoring_hand, scoring_name = text, full_hand = G.play.cards}
    +SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, before = true})
    +
    +-- TARGET: effects before scoring starts
    +
    +SMODS.displayed_hand = nil'''
    +
    +# context.final_scoring_step
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''local nu_chip, nu_mult = G.GAME.selected_back:trigger_effect{context = 'final_scoring_step', chips = hand_chips, mult = mult}'''
    +match_indent = true
    +position = "before"
    +payload = '''
    +-- context.final_scoring_step calculations
    +SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, final_scoring_step = true})
    +
    +-- TARGET: effects before deck final_scoring_step
    +'''
    +
    +# context.destroying_card
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +for j = 1, #G.jokers.cards do
    +    destroyed = G.jokers.cards[j]:calculate_joker({destroying_card = scoring_hand[i], full_hand = G.play.cards})
    +    if destroyed then break end
    +end
    +
    +if scoring_hand[i].ability.name == 'Glass Card' and not scoring_hand[i].debuff and pseudorandom('glass') < G.GAME.probabilities.normal/scoring_hand[i].ability.extra then
    +    destroyed = true
    +end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +-- context.destroying_card calculations
    +for j = 1, #G.jokers.cards do
    +    local eval, post = eval_card(G.jokers.cards[j], {destroying_card = scoring_hand[i], full_hand = G.play.cards})
    +    SMODS.trigger_effects({eval, post}, scoring_hand[i])
    +    if eval.jokers then destroyed = true end
    +
    +end
    +
    +if SMODS.has_enhancement(scoring_hand[i], 'm_glass') and scoring_hand[i]:can_calculate() and SMODS.pseudorandom_probability(scoring_hand[i], 'glass', 1, scoring_hand[i].ability.name == 'Glass Card' and scoring_hand[i].ability.extra or G.P_CENTERS.m_glass.config.extra) then
    +    destroyed = true
    +end
    +
    +local eval, post = eval_card(scoring_hand[i], {destroying_card = scoring_hand[i], full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, cardarea = G.play})
    +local self_destroy = false
    +for key, effect in pairs(eval) do
    +    self_destroy = SMODS.calculate_effect(effect, scoring_hand[i])
    +end
    +SMODS.trigger_effects({post}, scoring_hand[i])
    +if self_destroy then destroyed = true end
    +
    +-- TARGET: card destroyed when played
    +'''
    +
    +# context.remove_playing_cards
    +[[patches]]
    +[patches.regex]
    +target = "functions/state_events.lua"
    +pattern = '''(?[\t ]*)local cards_destroyed = \{\}\n(.*\n)*?\s+for j=1, #G\.jokers\.cards do\n\s+eval_card\(G\.jokers\.cards\[j\], \{cardarea = G\.jokers, remove_playing_cards = true, removed = cards_destroyed\}\)\n\s+end'''
    +line_prepend = '$indent'
    +position = "at"
    +payload = '''
    +local cards_destroyed = {}
    +for _,v in ipairs(SMODS.get_card_areas('playing_cards', 'destroying_cards')) do
    +    SMODS.calculate_destroying_cards({ full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, cardarea = v }, cards_destroyed, v == G.play and scoring_hand or nil)
    +end
    +
    +-- context.remove_playing_cards calculations
    +if cards_destroyed[1] then
    +    SMODS.calculate_context({scoring_hand = scoring_hand, remove_playing_cards = true, removed = cards_destroyed})
    +end
    +
    +-- TARGET: effects when cards are removed
    +
    +'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +line_prepend = '$indent'
    +position = 'at'
    +pattern = '(?[\t ]*)for i = 1, #G.jokers.cards do[\n\s]*G.jokers.cards\[i\]:calculate_joker\(\{remove_playing_cards = true, removed = destroyed_cards\}\)[\s\n]*end'
    +payload = '''SMODS.calculate_context({ remove_playing_cards = true, removed = destroyed_cards })'''
    +
    +# context.remove_playing_cards from discard
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +if destroyed_cards[1] then
    +    for j=1, #G.jokers.cards do
    +        eval_card(G.jokers.cards[j], {cardarea = G.jokers, remove_playing_cards = true, removed = destroyed_cards})
    +    end
    +end
    +'''
    +position = "at"
    +match_indent = true
    +payload = '''
    +-- context.remove_playing_cards from discard
    +if destroyed_cards[1] then
    +    SMODS.calculate_context({remove_playing_cards = true, removed = destroyed_cards})
    +end
    +
    +-- TARGET: effects after cards destroyed in discard'''
    +
    +
    +# context.debuffed_hand
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +for i=1, #G.jokers.cards do
    +
    +    --calculate the joker effects
    +    local effects = eval_card(G.jokers.cards[i], {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, debuffed_hand = true})
    +
    +    --Any Joker effects
    +    if effects.jokers then
    +        card_eval_status_text(G.jokers.cards[i], 'jokers', nil, percent, nil, effects.jokers)
    +        percent = percent+percent_delta
    +    end
    +end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +-- context.debuffed_hand calculations
    +SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, debuffed_hand = true})
    +
    +-- TARGET: effects after hand debuffed by blind'''
    +
    +# context.after
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''
    +for i=1, #G.jokers.cards do
    +    --calculate the joker after hand played effects
    +    local effects = eval_card(G.jokers.cards[i], {cardarea = G.jokers, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, after = true})
    +    if effects.jokers then
    +        card_eval_status_text(G.jokers.cards[i], 'jokers', nil, percent, nil, effects.jokers)
    +        percent = percent + percent_delta
    +    end
    +end'''
    +match_indent = true
    +position = "at"
    +payload = '''
    +-- context.after calculations
    +SMODS.calculate_context({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, after = true})
    +
    +-- TARGET: effects after hand evaluation'''
    +
    +# calc_dollar_bonus call through consumeables
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +pattern = '''
    +for i = 1, #G.jokers.cards do
    +    local ret = G.jokers.cards[i]:calculate_dollar_bonus()
    +    if ret then
    +        add_round_eval_row({dollars = ret, bonus = true, name='joker'..i, pitch = pitch, card = G.jokers.cards[i]})
    +        pitch = pitch + 0.06
    +        dollars = dollars + ret
    +    end
    +end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +local i = 0
    +for _, area in ipairs(SMODS.get_card_areas('jokers')) do
    +        for _, _card in ipairs(area.cards) do
    +        local ret = _card:calculate_dollar_bonus()
    +
    +        -- TARGET: calc_dollar_bonus per card
    +        if ret then
    +            i = i+1
    +            add_round_eval_row({dollars = ret, bonus = true, name='joker'..i, pitch = pitch, card = _card})
    +            pitch = pitch + 0.06
    +            dollars = dollars + ret
    +        end
    +    end
    +end
    +'''
    +
    +# context.round_eval
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +pattern = '''
    +G.GAME.selected_back:trigger_effect({context = 'eval'})
    +'''
    +position = 'before'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context{round_eval = true}
    +'''
    +
    +# Add better `add_round_eval_row` support   
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +elseif string.find(config.name, 'joker') then
    +'''
    +payload = '''
    +elseif config.name == 'custom' then
    +    if config.number then table.insert(left_text, {n=G.UIT.T, config={text = config.number, scale = 0.8*scale, colour = config.number_colour or G.C.FILTER, shadow = true, juice = true}}) end
    +    table.insert(left_text, {n=G.UIT.O, config={object = DynaText({string = {" "..config.text}, colours = {config.text_colour or G.C.UI.TEXT_LIGHT}, shadow = true, pop_in = 0, scale = 0.4*scale, silent = true})}})
    +'''
    +
    +
    +# context.end_of_round
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +pattern = '''
    +for i = 1, #G.jokers.cards do
    +    local eval = nil
    +    eval = G.jokers.cards[i]:calculate_joker({end_of_round = true, game_over = game_over})
    +    if eval then
    +        if eval.saved then
    +            game_over = false
    +        end
    +        card_eval_status_text(G.jokers.cards[i], 'jokers', nil, nil, nil, eval)
    +    end
    +    G.jokers.cards[i]:calculate_rental()
    +    G.jokers.cards[i]:calculate_perishable()
    +end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +-- context.end_of_round calculations
    +SMODS.saved = false
    +G.GAME.saved_text = nil
    +SMODS.last_hand = SMODS.last_hand or {scoring_hand = {}, full_hand = {}}
    +SMODS.calculate_context({end_of_round = true, game_over = game_over, beat_boss = G.GAME.blind.boss, scoring_hand = SMODS.last_hand.scoring_hand, scoring_name = SMODS.last_hand.scoring_name, full_hand = SMODS.last_hand.full_hand })
    +if SMODS.saved then game_over = false end
    +-- TARGET: main end_of_round evaluation
    +'''
    +
    +# context.end_of_round individual effects
    +[[patches]]
    +[patches.regex]
    +target = 'functions/state_events.lua'
    +position = 'at'
    +pattern = '''(?[\t ]*)for i=1, #G\.hand\.cards do\n\s+--Check for hand doubling\n(.*\n)*?\s+delay\(0\.3\)'''
    +line_prepend = '$indent'
    +payload = '''for _,v in ipairs(SMODS.get_card_areas('playing_cards', 'end_of_round')) do
    +    SMODS.calculate_end_of_round_effects({ cardarea = v, end_of_round = true, beat_boss = G.GAME.blind.boss })
    +end
    +'''
    +
    +# store associated blind tag as a global object
    +[[patches]]
    +[patches.pattern]
    +target = "functions/button_callbacks.lua"
    +pattern = "G.GAME.round_resets.blind = e.config.ref_table"
    +position = "before"
    +payload = '''
    +local _tag = e.UIBox:get_UIE_by_ID('tag_container')
    +G.GAME.round_resets.blind_tag = _tag and _tag.config and _tag.config.ref_table or nil
    +'''
    +match_indent = true
    +
    +# context.setting_blind
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +pattern = '''
    +for i = 1, #G.jokers.cards do
    +    G.jokers.cards[i]:calculate_joker({setting_blind = true, blind = G.GAME.round_resets.blind})
    +end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({setting_blind = true, blind = G.GAME.round_resets.blind})
    +
    +-- TARGET: setting_blind effects
    +'''
    +
    +# context.pre_discard
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +pattern = '''
    +for j = 1, #G.jokers.cards do
    +    G.jokers.cards[j]:calculate_joker({pre_discard = true, full_hand = G.hand.highlighted, hook = hook})
    +end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({pre_discard = true, full_hand = G.hand.highlighted, hook = hook})
    +
    +-- TARGET: pre_discard
    +'''
    +
    +# context.selling_self in cards
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''
    +self:calculate_joker{selling_self = true}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +local eval, post = eval_card(self, {selling_self = true})
    +local effects = {eval}
    +for _,v in ipairs(post) do effects[#effects+1] = v end
    +if eval.retriggers then
    +    for rt = 1, #eval.retriggers do
    +        local rt_eval, rt_post = eval_card(self, { selling_self = true, retrigger_joker = true})
    +        if next(rt_eval) then
    +            table.insert(effects, {eval.retriggers[rt]})
    +            table.insert(effects, rt_eval)
    +            for _, v in ipairs(rt_post) do effects[#effects+1] = v end
    +        end
    +    end
    +end
    +SMODS.trigger_effects(effects, self)
    +'''
    +
    +# context.open_booster
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''
    +            for i = 1, #G.jokers.cards do
    +                G.jokers.cards[i]:calculate_joker({open_booster = true, card = self})
    +            end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({open_booster = true, card = self, booster = booster_obj})
    +'''
    +
    +# context.buying_card
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''
    +        for i = 1, #G.jokers.cards do
    +            G.jokers.cards[i]:calculate_joker({buying_card = true, card = self})
    +        end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({buying_card = true, card = self})
    +'''
    +
    +# context.first_hand_drawn
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = '''
    +if G.GAME.current_round.hands_played == 0 and
    +    G.GAME.current_round.discards_used == 0 and G.GAME.facing_blind then
    +    for i = 1, #G.jokers.cards do
    +        G.jokers.cards[i]:calculate_joker({first_hand_drawn = true})
    +    end
    +end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +-- removed first hand drawn context
    +'''
    +# Hand Drawn Contexts
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self:emplace(card, nil, stay_flipped)
    +return true
    +'''
    +payload = '''
    +self:emplace(card, nil, stay_flipped)
    +return card
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if to:draw_card_from(from, stay_flipped, discarded_only) then drawn = true end
    +'''
    +payload = '''
    +card = to:draw_card_from(from, stay_flipped, discarded_only)
    +if card then drawn = true end
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +if sort then
    +    to:sort()
    +end
    +'''
    +payload = '''
    +SMODS.drawn_cards = SMODS.drawn_cards or {}
    +if card and card.playing_card then SMODS.drawn_cards[#SMODS.drawn_cards+1] = card end
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +for i=1, hand_space do --draw cards from deckL
    +    if G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK then
    +        draw_card(G.deck,G.hand, i*100/hand_space,'up', true)
    +    else
    +        draw_card(G.deck,G.hand, i*100/hand_space,'up', true)
    +    end
    +end
    +'''
    +payload = '''
    +SMODS.cards_to_draw = (SMODS.cards_to_draw or 0) + hand_space
    +SMODS.drawn_cards = {}
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +for i=1, hand_space do --draw cards from deckL
    +    if G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK then
    +        draw_card(G.deck,G.hand, i*100/hand_space,'up', true)
    +    else
    +        draw_card(G.deck,G.hand, i*100/hand_space,'up', true)
    +    end
    +end
    +'''
    +payload = '''
    +G.E_MANAGER:add_event(Event({
    +    trigger = 'immediate',
    +    func = function()                
    +        SMODS.cards_to_draw = SMODS.cards_to_draw - hand_space
    +        return true
    +    end
    +}))
    +G.E_MANAGER:add_event(Event({
    +    trigger = 'before',
    +    delay = 0.4,
    +    func = function()
    +        if #SMODS.drawn_cards > 0 then
    +            SMODS.calculate_context({first_hand_drawn = not G.GAME.current_round.any_hand_drawn and G.GAME.facing_blind,
    +                                    hand_drawn = G.GAME.facing_blind and SMODS.drawn_cards,
    +                                    other_drawn = not G.GAME.facing_blind and SMODS.drawn_cards})
    +            SMODS.drawn_cards = {}
    +            if G.GAME.facing_blind then G.GAME.current_round.any_hand_drawn = true end
    +        end
    +        return true
    +    end
    +}))
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +draw_card(G.deck,G.hand, i*100/hand_space,'up', true)
    +'''
    +payload = '''
    +draw_card(G.deck,G.hand, i*100/hand_space,'up', true, cards_to_draw[i])
    +'''
    +
    +
    +# Used to identify first hand of round
    +# new_round
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = 'G.GAME.current_round.discards_used = 0'
    +payload = '''
    +G.GAME.current_round.any_hand_drawn = nil
    +'''
    +
    +
    +# context.using_consumeable
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +      for i = 1, #G.jokers.cards do
    +        G.jokers.cards[i]:calculate_joker({using_consumeable = true, consumeable = card})
    +      end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({using_consumeable = true, consumeable = card, area = card.from_area})
    +'''
    +# save area of used card
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +c1.area:remove_card(c1)
    +'''
    +payload = '''
    +c1.from_area = c1.area
    +'''
    +
    +# context.selling_card
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +    for i = 1, #G.jokers.cards do
    +      if G.jokers.cards[i] ~= card then
    +        G.jokers.cards[i]:calculate_joker({selling_card = true, card = card})
    +      end
    +    end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({selling_card = true, card = card})
    +'''
    +
    +
    +# context.buying_card
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +G.E_MANAGER:add_event(Event({func = function() c1:calculate_joker({buying_card = true, card = c1}) return true end}))
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +G.E_MANAGER:add_event(Event({func = function()
    +    local eval, post = eval_card(c1, {buying_card = true, buying_self = true, card = c1}) -- buying_card left for back compat, buying_self recommended to use
    +    SMODS.trigger_effects({eval, post}, c1)
    +    return true
    +    end}))
    +'''
    +
    +# context.buying_card
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +          for i = 1, #G.jokers.cards do
    +            G.jokers.cards[i]:calculate_joker({buying_card = true, card = c1})
    +          end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({buying_card = true, card = c1})
    +'''
    +# context.starting_shop
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = '''
    +G.CONTROLLER:snap_to({node = G.shop:get_UIE_by_ID('next_round_button')})
    +'''
    +position = 'before'
    +match_indent = true
    +payload = '''
    +if not nosave_shop then SMODS.calculate_context({starting_shop = true}) end
    +'''
    +# context.ending_shop
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +      for i = 1, #G.jokers.cards do
    +        G.jokers.cards[i]:calculate_joker({ending_shop = true})
    +      end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({ending_shop = true})
    +'''
    +
    +# context.skipping_booster
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +    for i = 1, #G.jokers.cards do
    +      G.jokers.cards[i]:calculate_joker({skipping_booster = true})
    +    end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({skipping_booster = true, booster = booster_obj})
    +'''
    +# context.skip_blind
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +          for i = 1, #G.jokers.cards do
    +            G.jokers.cards[i]:calculate_joker({skip_blind = true})
    +          end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({skip_blind = true})
    +'''
    +
    +# context.reroll_shop
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''
    +            for i = 1, #G.jokers.cards do
    +              G.jokers.cards[i]:calculate_joker({reroll_shop = true})
    +            end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.calculate_context({reroll_shop = true, cost = reroll_cost})
    +'''
    +# reroll_cost for context
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +if G.GAME.current_round.reroll_cost > 0 then
    +'''
    +payload = '''
    +local reroll_cost = G.GAME.current_round.reroll_cost
    +'''
    +
    +
    +# Fix purple seal calc
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''
    +if context.discard then
    +    if self.seal == 'Purple' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if context.discard and context.other_card == self then
    +    if self.seal == 'Purple' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then'''
    +
    +
    +# Auto deal with negative chips card_eval_status_text()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +text = localize{type='variable',key='a_chips',vars={amt}}
    +'''
    +payload = '''
    +text = localize{type='variable',key='a_chips'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}}
    +'''
    +
    +# Auto deal with negative mult card_eval_status_text()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +text = localize{type='variable',key='a_mult',vars={amt}}
    +'''
    +payload = '''
    +text = localize{type='variable',key='a_mult'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}}
    +'''
    +
    +# Auto deal with negative xmult card_eval_status_text()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +text = localize{type='variable',key='a_xmult',vars={amt}}
    +'''
    +payload = '''
    +text = localize{type='variable',key='a_xmult'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}}
    +'''
    +# Make percent and percent_delta globals
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''local percent = 0.3
    +local percent_delta = 0.08
    +'''
    +payload = '''percent = 0.3
    +percent_delta = 0.08
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''local percent = (i-0.999)/(#G.hand.cards-0.998) + (j-1)*0.1'''
    +payload = '''percent = (i-0.999)/(#G.hand.cards-0.998) + (j-1)*0.1'''
    +
    +# Add support for pitch and volume returns in effects
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = 'local volume = 1'
    +payload = '''
    +local trigger = 'before'
    +local blocking = nil
    +local blockable = nil
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +volume = extra.edition and 0.3 or sound == 'multhit2' and 0.7 or 1
    +'''
    +payload = '''
    +sound = extra.sound or sound
    +percent = extra.pitch or percent
    +volume = extra.volume or volume
    +trigger = extra.trigger or 'before'
    +blocking = extra.blocking
    +blockable = extra.blockable
    +'''
    +
    +# Voucher cardarea
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'before'
    +pattern = 'self.deck = CardArea('
    +payload = '''self.vouchers = CardArea(
    +    G.discard.T.x, G.discard.T.y,
    +    G.discard.T.w, G.discard.T.h,
    +    { type = "discard", card_limit = 1e308 }
    +)
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'after'
    +pattern = 'function Card:apply_to_run(center)'
    +payload = '''
    +    local card_to_save = self and copy_card(self) or Card(0, 0, G.CARD_W, G.CARD_H, G.P_CARDS.empty, center)
    +    card_to_save.VT.x, card_to_save.VT.y = G.vouchers.T.x, G.vouchers.T.y
    +    G.vouchers:emplace(card_to_save)
    +    SMODS.enh_cache:clear()
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +position = 'at'
    +match_indent = false
    +pattern = '''if self.ability.set == "Planet" and not self.debuff then
    +    if context.joker_main then
    +        if G.GAME.used_vouchers.v_observatory and self.ability.consumeable.hand_type == context.scoring_name then
    +            return {
    +                message = localize{type = 'variable', key = 'a_xmult', vars = {G.P_CENTERS.v_observatory.config.extra}},
    +                Xmult_mod = G.P_CENTERS.v_observatory.config.extra
    +            }
    +        end
    +    end
    +end'''
    +payload = ''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'back.lua'
    +position = 'at'
    +line_prepend = '$indent'
    +pattern = '(?[\t ]*)(?Card.apply_to_run\(nil, G\.P_CENTERS\[.*?\]\))'
    +payload = '''G.E_MANAGER:add_event(Event({
    +    func = function()
    +        $line
    +        return true
    +    end
    +}))'''
    +
    +#wtf
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''self.discard_pos = {
    +    r = 3.6*(math.random()-0.5),
    +    x = math.random(),
    +    y = math.random()
    +}'''
    +payload = '''self.discard_pos = {
    +    r = 0,
    +    x = 0,
    +    y = 0,
    +}
    +'''
    +
    +# xchip support
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +elseif (eval_type == 'x_mult') or (eval_type == 'h_x_mult') then
    +'''
    +payload = '''
    +elseif eval_type == 'x_chips' then
    +        sound = 'xchips'
    +        volume = 0.7
    +        amt = amt
    +        text = localize{type='variable',key='a_xchips'..(amt<0 and '_minus' or ''),vars={math.abs(amt)}}
    +        colour = G.C.BLUE
    +        config.type = 'fade'
    +        config.scale = 0.7
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if extra.chip_mod then
    +'''
    +payload = '''
    +if extra.chip_mod or extra.Xchip_mod then
    +'''
    +
    +# Modify scoring hand context
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +--Add all the pure bonus cards to the scoring hand
    +local pures = {}
    +for i=1, #G.play.cards do
    +    if next(find_joker('Splash')) then
    +        scoring_hand[i] = G.play.cards[i]
    +    else
    +        if G.play.cards[i].ability.effect == 'Stone Card' then
    +            local inside = false
    +            for j=1, #scoring_hand do
    +                if scoring_hand[j] == G.play.cards[i] then
    +                    inside = true
    +                end
    +            end
    +            if not inside then table.insert(pures, G.play.cards[i]) end
    +        end
    +    end
    +end
    +for i=1, #pures do
    +    table.insert(scoring_hand, pures[i])
    +end
    +table.sort(scoring_hand, function (a, b) return a.T.x < b.T.x end )
    +'''
    +payload = '''
    +local final_scoring_hand = {}
    +for i=1, #G.play.cards do
    +    local splashed = SMODS.always_scores(G.play.cards[i]) or next(find_joker('Splash'))
    +    local unsplashed = SMODS.never_scores(G.play.cards[i])
    +    if not splashed then
    +        for _, card in pairs(scoring_hand) do
    +            if card == G.play.cards[i] then splashed = true end
    +        end
    +    end
    +    local effects = {}
    +    SMODS.calculate_context({modify_scoring_hand = true, other_card =  G.play.cards[i], full_hand = G.play.cards, scoring_hand = scoring_hand, in_scoring = true, ignore_other_debuff = true}, effects)
    +    local flags = SMODS.trigger_effects(effects, G.play.cards[i])
    +    if flags.add_to_hand then splashed = true end
    +	if flags.remove_from_hand then unsplashed = true end
    +    if splashed and not unsplashed then table.insert(final_scoring_hand, G.play.cards[i]) end
    +end
    +-- TARGET: adding to hand effects
    +scoring_hand = final_scoring_hand
    +'''
    +
    +# Add ending_booster calculation context
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +for i = 1, #G.GAME.tags do
    +    if G.GAME.tags[i]:apply_to_run({type = 'new_blind_choice'}) then break end
    +end
    +
    +G.E_MANAGER:add_event(Event({trigger = 'after',delay = 0.2*delayfac,
    +          func = function()
    +            save_run()
    +            return true
    +      end}))
    +'''
    +payload = '''
    +SMODS.calculate_context({ending_booster = true, booster = booster_obj})
    +'''
    +
    +# Fix Certificate to use SMODS.poll_seal and use playing_card_added context correctly
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.E_MANAGER:add_event(Event({
    +    func = function()
    +        local _card = create_playing_card({
    +            front = pseudorandom_element(G.P_CARDS, pseudoseed('cert_fr')),
    +            center = G.P_CENTERS.c_base}, G.hand, nil, nil, {G.C.SECONDARY_SET.Enhanced})
    +        local seal_type = pseudorandom(pseudoseed('certsl'))
    +        if seal_type > 0.75 then _card:set_seal('Red', true)
    +        elseif seal_type > 0.5 then _card:set_seal('Blue', true)
    +        elseif seal_type > 0.25 then _card:set_seal('Gold', true)
    +        else _card:set_seal('Purple', true)
    +        end
    +        G.GAME.blind:debuff_card(_card)
    +        G.hand:sort()
    +        if context.blueprint_card then context.blueprint_card:juice_up() else self:juice_up() end
    +        return true
    +    end}))
    +
    +playing_card_joker_effects({true})
    +'''
    +payload = '''
    +G.E_MANAGER:add_event(Event({
    +    func = function()
    +        local _card = create_playing_card({
    +            front = pseudorandom_element(G.P_CARDS, pseudoseed('cert_fr')),
    +            center = G.P_CENTERS.c_base}, G.hand, nil, nil, {G.C.SECONDARY_SET.Enhanced})
    +        _card:set_seal(SMODS.poll_seal({type_key = 'certsl', guaranteed = true}), nil, true)
    +        G.GAME.blind:debuff_card(_card)
    +        G.hand:sort()
    +        if context.blueprint_card then context.blueprint_card:juice_up() else self:juice_up() end
    +        playing_card_joker_effects({_card})
    +        save_run()
    +        return true
    +    end}))
    +
    +'''
    +# create_playing_card
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +function create_playing_card(card_init, area, skip_materialize, silent, colours)
    +'''
    +payload = '''
    +function create_playing_card(card_init, area, skip_materialize, silent, colours, skip_emplace)
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if area then area:emplace(card) end
    +'''
    +payload = '''
    +if area and not skip_emplace then area:emplace(card) end
    +'''
    +
    +## Fix Marble Joker to send the created card to playing_card_added correctly
    +# Marble Joker
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.E_MANAGER:add_event(Event({
    +    func = function()
    +        local front = pseudorandom_element(G.P_CARDS, pseudoseed('marb_fr'))
    +        G.playing_card = (G.playing_card and G.playing_card + 1) or 1
    +        local card = Card(G.play.T.x + G.play.T.w/2, G.play.T.y, G.CARD_W, G.CARD_H, front, G.P_CENTERS.m_stone, {playing_card = G.playing_card})
    +        card:start_materialize({G.C.SECONDARY_SET.Enhanced})
    +        G.play:emplace(card)
    +        table.insert(G.playing_cards, card)
    +        return true
    +    end}))
    +card_eval_status_text(context.blueprint_card or self, 'extra', nil, nil, nil, {message = localize('k_plus_stone'), colour = G.C.SECONDARY_SET.Enhanced})
    +
    +G.E_MANAGER:add_event(Event({
    +    func = function()
    +        G.deck.config.card_limit = G.deck.config.card_limit + 1
    +        return true
    +    end}))
    +    draw_card(G.play,G.deck, 90,'up', nil)
    +
    +playing_card_joker_effects({true})
    +'''
    +payload = '''
    +G.E_MANAGER:add_event(Event({
    +    func = function()
    +        local card = create_playing_card({
    +            front = pseudorandom_element(G.P_CARDS, pseudoseed('marb_fr')),
    +            center = G.P_CENTERS.m_stone}, G.play, nil, nil, {G.C.SECONDARY_SET.Enhanced})
    +        SMODS.calculate_effect({message = localize('k_plus_stone'), colour = G.C.SECONDARY_SET.Enhanced}, context.blueprint_card or self)
    +        G.E_MANAGER:add_event(Event({
    +        func = function()
    +            draw_card(G.play,G.deck, 90,'up', nil)
    +            return true
    +        end}))
    +        playing_card_joker_effects({card})
    +        return true
    +    end}))
    +'''
    +# DNA
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = 'playing_cards_created = {true}'
    +payload = 'playing_cards_created = {_card}'
    +
    +## Remove unneeded area check
    +# Card:calculate_joker
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = 'if context.cardarea == G.jokers then'
    +payload = 'do'
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +position = 'at'
    +pattern = '{n=G.UIT.O, config={object = DynaText({scale = 0.6, string = G.GAME.blind:get_loc_debuff_text(), maxw = 9, colours = {G.C.WHITE},float = true, shadow = true, silent = true, pop_in = 0, pop_in_rate = 6})}},'
    +payload = '{n=G.UIT.O, config={func = "update_blind_debuff_text", object = DynaText({scale = 0.6, string = SMODS.debuff_text or G.GAME.blind:get_loc_debuff_text(), maxw = 9, colours = {G.C.WHITE},float = true, shadow = true, silent = true, pop_in = 0, pop_in_rate = 6})}},'
    +match_indent = true
    +
    +# Custom saved message
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{n=G.UIT.O, config={object = DynaText({string = {' '..localize('ph_mr_bones')..' '}, colours = {G.C.FILTER}, shadow = true, pop_in = 0, scale = 0.5*scale, silent = true})}}
    +'''
    +payload = '''
    +{n=G.UIT.O, config={object = DynaText({string = {' '..(type(G.GAME.saved_text) == 'string' and (G.localization.misc.dictionary[G.GAME.saved_text] and localize(G.GAME.saved_text) or G.GAME.saved_text) or localize('ph_mr_bones'))..' '}, colours = {G.C.FILTER}, shadow = true, pop_in = 0, scale = 0.5*scale, silent = true})}}
    +'''
    +
    +# Customise dissolve colours from calculate
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +local dissolve_time = 0.7*(dissolve_time_fac or 1)
    +'''
    +payload = '''
    +dissolve_colours = dissolve_colours or (type(self.destroyed) == 'table' and self.destroyed.colours) or nil
    +dissolve_time_fac = dissolve_time_fac or (type(self.destroyed) == 'table' and self.destroyed.time) or nil
    +'''
    +
    +# Smart level up functionality
    +# G.FUNCS.evaluate_play()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = false
    +position = 'after'
    +pattern = '''delay = G.GAME.current_round.current_hand.handname ~= disp_text and 0.4 or 0}, {handname=disp_text, level=G.GAME.hands[text].level, mult = G.GAME.hands[text].mult, chips = G.GAME.hands[text].chips})'''
    +payload = '''    
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    if name ~= 'chips' and name ~= 'mult' then update_hand_text({immediate = true}, {[name] = parameter.current}) end
    +end
    +SMODS.displayed_hand = text
    +SMODS.displaying_scoring = true'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''hand_chips = mod_chips(0)'''
    +payload = '''SMODS.displayed_hand = nil'''
    +times = 1
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = false
    +position = 'after'
    +pattern = '''
    +  func = (function() G.GAME.current_round.current_hand.handname = '';return true end)
    +}))
    +delay(0.3)'''
    +payload = '''    SMODS.displaying_scoring = nil'''
    +
    +# Adjust food jokers
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.E_MANAGER:add_event(Event({
    +    func = function()
    +        play_sound('tarot1')
    +        self.T.r = -0.2
    +        self:juice_up(0.3, 0.4)
    +        self.states.drag.is = true
    +        self.children.center.pinch.x = true
    +        G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.3, blockable = false,
    +            func = function()
    +                    G.jokers:remove_card(self)
    +                    self:remove()
    +                    self = nil
    +                return true; end})) 
    +        return true
    +    end
    +})) 
    +'''
    +payload = '''
    +SMODS.destroy_cards(self, nil, nil, true)
    +'''
    +
    +# Joker-type removed context added
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +function Card:start_dissolve(dissolve_colours, silent, dissolve_time_fac, no_juice)
    +'''
    +payload = '''
    +    if self.getting_sliced and not (self.ability.set == 'Default' or self.ability.set == 'Enhanced') then
    +        local flags = SMODS.calculate_context({joker_type_destroyed = true, card = self})
    +        if flags.no_destroy then self.getting_sliced = nil; return end
    +    end
    +    if self.skip_destroy_animation then
    +        G.E_MANAGER:add_event(Event({
    +            func = function()
    +                play_sound('tarot1')
    +                self.T.r = -0.2
    +                self:juice_up(0.3, 0.4)
    +                self.states.drag.is = true
    +                self.children.center.pinch.x = true
    +                G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.3, blockable = false,
    +                    func = function()
    +                            G.jokers:remove_card(self)
    +                            self:remove()
    +                            self = nil
    +                        return true; end})) 
    +                return true
    +            end
    +        })) 
    +        return
    +    end
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +function Card:shatter()
    +'''
    +payload = '''
    +    if self.getting_sliced and not (self.ability.set == 'Default' or self.ability.set == 'Enhanced') then
    +        local flags = SMODS.calculate_context({joker_type_destroyed = true, card = self, shatters = true})
    +        if flags.no_destroy then self.getting_sliced = nil; return end
    +    end
    +    if self.skip_destroy_animation then
    +        G.E_MANAGER:add_event(Event({
    +            func = function()
    +                play_sound('tarot1')
    +                self.T.r = -0.2
    +                self:juice_up(0.3, 0.4)
    +                self.states.drag.is = true
    +                self.children.center.pinch.x = true
    +                G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.3, blockable = false,
    +                    func = function()
    +                            G.jokers:remove_card(self)
    +                            self:remove()
    +                            self = nil
    +                        return true; end})) 
    +                return true
    +            end
    +        })) 
    +        return
    +    end
    +'''
    +
    +
    +## Change eternal checks
    +# Ceremonial Dagger
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if my_pos and G.jokers.cards[my_pos+1] and not self.getting_sliced and not G.jokers.cards[my_pos+1].ability.eternal and not G.jokers.cards[my_pos+1].getting_sliced then 
    +'''
    +payload = '''
    +if my_pos and G.jokers.cards[my_pos+1] and not self.getting_sliced and not SMODS.is_eternal(G.jokers.cards[my_pos+1], self) and not G.jokers.cards[my_pos+1].getting_sliced then
    +'''
    +# Madness
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if G.jokers.cards[i] ~= self and not G.jokers.cards[i].ability.eternal and not G.jokers.cards[i].getting_sliced then destructable_jokers[#destructable_jokers+1] = G.jokers.cards[i] end
    +'''
    +payload = '''
    +if G.jokers.cards[i] ~= self and not SMODS.is_eternal(G.jokers.cards[i], self) and not G.jokers.cards[i].getting_sliced then destructable_jokers[#destructable_jokers+1] = G.jokers.cards[i] end
    +'''
    +# Ankh
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if not v.ability.eternal then deletable_jokers[#deletable_jokers + 1] = v end
    +'''
    +payload = '''
    +if not SMODS.is_eternal(v, self) then deletable_jokers[#deletable_jokers + 1] = v end
    +'''
    +# Hex SMODS.is_eternal and card.getting_sliced
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if v ~= eligible_card and (not v.ability.eternal) then v:start_dissolve(nil, _first_dissolve);_first_dissolve = true end
    +'''
    +payload = '''
    +if v ~= eligible_card and (not SMODS.is_eternal(v, self)) then v.getting_sliced = true; v:start_dissolve(nil, _first_dissolve);_first_dissolve = true end
    +'''
    +# Card:can_sell()
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +not self.ability.eternal then
    +'''
    +payload = '''
    +not SMODS.is_eternal(self, {from_sell = true}) then
    +'''
    +
    +# Adds tag_added context
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +G.GAME.tags[#G.GAME.tags+1] = _tag
    +'''
    +payload = '''
    +if not _tag.from_load then SMODS.calculate_context({tag_added = _tag}) end
    +_tag.from_load = nil
    +'''
    +
    +# Prevent tag added context on reloading
    +[[patches]]
    +[patches.pattern]
    +target = 'tag.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +G.GAME.tag_tally = math.max(self.tally, G.GAME.tag_tally) + 1
    +'''
    +payload = '''
    +self.from_load = true
    +'''
    +
    +# Fix Campfire 'upgrading' when it is sold
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self.ability.name == 'Campfire' and not context.blueprint then
    +'''
    +payload = '''
    +if self.ability.name == 'Campfire' and not context.blueprint and self ~= context.card then
    +'''
    +
    +# Support for ante contexts
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +pattern = '''
    +delay(0.4); ease_ante(1); delay(0.4); check_for_unlock({type = 'ante_up', ante = G.GAME.round_resets.ante + 1})
    +'''
    +position = 'at'
    +payload = '''
    +delay(0.4); SMODS.ante_end = true; ease_ante(1); SMODS.ante_end = nil; delay(0.4); check_for_unlock({type = 'ante_up', ante = G.GAME.round_resets.ante + 1})
    +'''
    +
    +# Sixth Sense only triggers once with multiple copies
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self.ability.name == 'Sixth Sense' and #context.full_hand == 1 and context.full_hand[1]:get_id() == 6 and G.GAME.current_round.hands_played == 0 then
    +'''
    +payload = '''
    +if self.ability.name == 'Sixth Sense' and #context.full_hand == 1 and context.full_hand[1]:get_id() == 6 and not context.full_hand[1].sixth_sense and G.GAME.current_round.hands_played == 0 then
    +    context.full_hand[1].sixth_sense = true
    +'''
    +
    +# Blueprint uses SMODS.blueprint_effect
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if other_joker and other_joker ~= self then
    +    context.blueprint = (context.blueprint and (context.blueprint + 1)) or 1
    +    context.blueprint_card = context.blueprint_card or self
    +    if context.blueprint > #G.jokers.cards + 1 then return end
    +    local other_joker_ret = other_joker:calculate_joker(context)
    +    if other_joker_ret then 
    +        other_joker_ret.card = context.blueprint_card or self
    +        other_joker_ret.colour = G.C.BLUE
    +        return other_joker_ret
    +    end
    +end
    +'''
    +payload = '''
    +local ret = SMODS.blueprint_effect(self, other_joker, context)
    +if ret then
    +    ret.colour = G.C.BLUE
    +    return ret
    +end
    +'''
    +
    +# Brainstorm uses SMODS.blueprint_effect
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if other_joker and other_joker ~= self then
    +    context.blueprint = (context.blueprint and (context.blueprint + 1)) or 1
    +    context.blueprint_card = context.blueprint_card or self
    +    if context.blueprint > #G.jokers.cards + 1 then return end
    +    local other_joker_ret = other_joker:calculate_joker(context)
    +    if other_joker_ret then 
    +        other_joker_ret.card = context.blueprint_card or self
    +        other_joker_ret.colour = G.C.RED
    +        return other_joker_ret
    +    end
    +end
    +'''
    +payload = '''
    +local ret = SMODS.blueprint_effect(self, other_joker, context)
    +if ret then
    +    ret.colour = G.C.RED
    +    return ret
    +end
    +'''
    +
    +# Reload SMODS.last_hand
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +table.sort(G.playing_cards, function (a, b) return a.playing_card > b.playing_card end )
    +'''
    +payload = '''
    +if saveTable then
    +    if saveTable.SMODS 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
    +                if card.ability['SMODS_'..v] then
    +                    SMODS.last_hand[v][card.ability['SMODS_'..v]] = card
    +                end
    +            end
    +        end
    +	end
    +end
    +'''
    +
    +
    +
    +### From old better_calc_fixes.toml
    +## Delayed references to context.blueprint_card should be saved in a variable
    +## because context is modified in better calc
    +## (this patch just changes all references)
    +# Card:calculate_joker(context)
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +position = 'after'
    +pattern = '''function Card:calculate_joker(context)
    +    if self.debuff then return nil end
    +'''
    +payload = '    local context_blueprint_card = context.blueprint_card'
    +match_indent = true
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +position = 'at'
    +pattern = 'card_eval_status_text\(context\.blueprint_card or self'
    +payload = 'card_eval_status_text(context_blueprint_card or self'
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +position = 'at'
    +pattern = 'if context.blueprint_card then context.blueprint_card:juice_up() else self:juice_up() end'
    +payload = 'if context_blueprint_card then context_blueprint_card:juice_up() else self:juice_up() end'
    +match_indent = true
    +
    diff --git a/lovely/better_calc/calc_returns.toml b/lovely/better_calc/calc_returns.toml
    new file mode 100644
    index 000000000..e403b5b6b
    --- /dev/null
    +++ b/lovely/better_calc/calc_returns.toml
    @@ -0,0 +1,45 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Add support to attention text for UI nodes as a message
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +{n=G.UIT.O, config={draw_layer = 1, object = DynaText({scale = args.scale, string = args.text, maxw = args.maxw, colours = {args.colour},float = true, shadow = true, silent = not args.noisy, args.scale, pop_in = 0, pop_in_rate = 6, rotate = args.rotate or nil})}},
    +'''
    +payload = '''
    +type(args.text) == "table" and args.text.n and args.text or
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +args.text = args.AT.UIRoot.children[1].config.object
    +args.text:pulse(0.5)
    +'''
    +payload = '''
    +if type(args.text) == 'string' then
    +    args.text = args.AT.UIRoot.children[1].config.object
    +    args.text:pulse(0.5)
    +end
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +args.text:pop_out(3)
    +'''
    +payload = '''
    +if Object.is(args.text, DynaText) then
    +    args.text:pop_out(3)
    +end
    +'''
    diff --git a/lovely/better_calc/can_calculate.toml b/lovely/better_calc/can_calculate.toml
    new file mode 100644
    index 000000000..3b2c01eb2
    --- /dev/null
    +++ b/lovely/better_calc/can_calculate.toml
    @@ -0,0 +1,63 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''function Card:get_end_of_round_effect(context)
    +    if self.debuff then return {} end'''
    +position = "at"
    +match_indent = true
    +payload = '''
    +function Card:get_end_of_round_effect(context)'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''function Card:calculate_dollar_bonus()
    +    if self.debuff then return end'''
    +position = "at"
    +match_indent = true
    +payload = '''
    +function Card:calculate_dollar_bonus()
    +    if not self:can_calculate() then return end'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''function Card:calculate_seal(context)
    +    if self.debuff then return nil end'''
    +position = "at"
    +match_indent = true
    +payload = '''
    +function Card:calculate_seal(context)'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''function Card:calculate_joker(context)
    +    if self.debuff then return nil end'''
    +position = "at"
    +match_indent = true
    +payload = '''
    +function Card:calculate_joker(context)'''
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''if self.ability.set == "Joker" and not self.debuff then'''
    +position = "at"
    +match_indent = true
    +payload = '''if self.ability.set == "Joker" then'''
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''if self.debuff then return 0 end'''
    +position = "at"
    +match_indent = true
    +payload = ''
    \ No newline at end of file
    diff --git a/lovely/better_calc/new_calculation/calculate_card_added.toml b/lovely/better_calc/new_calculation/calculate_card_added.toml
    new file mode 100644
    index 000000000..caeac142b
    --- /dev/null
    +++ b/lovely/better_calc/new_calculation/calculate_card_added.toml
    @@ -0,0 +1,29 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +# Add card_added context
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +(?[\t ]*).*then G\.E_MANAGER:add_event\(Event\(\{ func = function\(\) G\.GAME\.blind:set_blind\(nil, true, nil\); return true end \}\)\) end
    +(?)[\s\S]*
    +function Card:remove_from_deck'''
    +position = "after"
    +line_prepend = "$indent"
    +payload = '''
    +if not from_debuff and G.hand then
    +    local is_playing_card = self.ability.set == 'Default' or self.ability.set == 'Enhanced'
    +    
    +    -- TARGET: calculate card_added
    +
    +    if not is_playing_card then
    +        SMODS.calculate_context({card_added = true, card = self})
    +        SMODS.enh_cache:clear()
    +    end
    +end
    +'''
    +root_capture = '$root'
    +times = 1 
    diff --git a/lovely/better_calc/new_calculation/joker_retriggers.toml b/lovely/better_calc/new_calculation/joker_retriggers.toml
    new file mode 100644
    index 000000000..03343f479
    --- /dev/null
    +++ b/lovely/better_calc/new_calculation/joker_retriggers.toml
    @@ -0,0 +1,324 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +
    +# Luchador
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''[ \t]*G\.GAME\.blind:disable\(\)
    +(?[ \t]*)end'''
    +position = "at"
    +line_prepend = '$indent'
    +payload = '''   G.GAME.blind:disable()
    +    return nil, true
    +end'''
    +
    +# Diet Cola
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*return true
    +[ \t]*end\)
    +[ \t]*\}\)\)
    +(?[ \t]*)end
    +'''
    +position = "at"
    +line_prepend = '$indent'
    +payload = '''           return true
    +       end)
    +    }))
    +    return nil, true
    +end
    +'''
    +
    +# Invisible Joker
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''[ \t]*if card\.ability\.invis_rounds then card\.ability\.invis_rounds = 0 end
    +[ \t]*card:add_to_deck\(\)
    +(?[ \t]*)G\.jokers:emplace\(card\)'''
    +position = "after"
    +line_prepend = '$indent'
    +payload = "\nreturn nil, true"
    +
    +# Perkeo
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''[ \t]*card_eval_status_text\(context[._]blueprint_card or self, 'extra', nil, nil, nil, \{message = localize\('k_duplicated_ex'\)\}\)
    +(?[ \t]*)end'''
    +position = "at"
    +line_prepend = '$indent'
    +payload = '''    card_eval_status_text(context_blueprint_card or self, 'extra', nil, nil, nil, {message = localize('k_duplicated_ex')})
    +    return nil, true
    +end'''
    +
    +# Throwback
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +[ \t]*return
    +(?[ \t]*)elseif context\.skipping_booster'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        return nil, true\n"
    +
    +# Red Card
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +[ \t]*return
    +(?[ \t]*)elseif context\.playing_card_added'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        return nil, true\n"
    +
    +# Hologram
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)elseif context\.first_hand_drawn'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        return nil, true\n"
    +
    +# Certificate
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'DNA' and not context\.blueprint'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "    return nil, true\n"
    +
    +# Chicot
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Madness' '''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "    return nil, true\n"
    +
    +# Madness
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Burglar' '''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "    return nil, true\n"
    +
    +# Burglar
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Riff-raff' '''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "    return nil, true\n"
    +
    +# Riff-raff
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Cartomancer' '''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "    return nil, true\n"
    +
    +# Cartomancer
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Ceremonial Dagger' and not context.blueprint'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "    return nil, true\n"
    +
    +# Ceremonial Dagger
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Marble Joker' '''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        return nil, true\n"
    +
    +# Marble Joker
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +[ \t]*return
    +(?[ \t]*)elseif context.destroying_card'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        return nil, true\n"
    +
    +# Glass Joker
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +if glass_cards > 0 then(.*\n){0,20}?.*
    +[ \t]*return true
    +[ \t]*end
    +(?[ \t]*)\}\)\)'''
    +position = "after"
    +line_prepend = '$indent'
    +payload = "\nreturn nil, true"
    +
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +[ \t]*return
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Fortune Teller' and not context\.blueprint'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        return nil, true\n"
    +
    +# Fortune Teller
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)if self\.ability\.name == 'Constellation' and not context\.blueprint'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "    return nil, true\n"
    +
    +# Constellation
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +[ \t]*return
    +(?[ \t]*)elseif context.debuffed_hand'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        nil, true\n"
    +
    +# Burnt Joker
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +(?[ \t]*)elseif context.discard'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "        return nil, true\n"
    +
    +# Faceless Joker
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''
    +[ \t]*end
    +[ \t]*end
    +[ \t]*return
    +(?[ \t]*)elseif context.end_of_round'''
    +position = "before"
    +line_prepend = '$indent'
    +payload = "            nil, true\n"
    +
    +# Yorick
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "self.ability.yorick_discards = self.ability.yorick_discards - 1"
    +position = "after"
    +match_indent = true
    +payload = "return nil, true"
    +
    +# Hallucination
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize('k_plus_tarot'), colour = G.C.PURPLE})"
    +position = "after"
    +match_indent = true
    +payload = "return nil, true"
    +
    +## Change card returns
    +# Ramen
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +message = localize('k_eaten_ex'),
    +colour = G.C.FILTER'''
    +position = "before"
    +match_indent = true
    +payload = "card = self,"
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''message = localize{type='variable',key='a_xmult_minus',vars={self.ability.extra}},'''
    +position = "before"
    +match_indent = true
    +payload = "card = self,"
    +
    +# Yorick
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +delay = 0.2,
    +message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},'''
    +position = "before"
    +match_indent = true
    +payload = "card = self,"
    +
    +# To Do List
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''message = localize('$')..self.ability.extra.dollars,
    +    dollars = self.ability.extra.dollars,'''
    +position = "at"
    +match_indent = true
    +payload = '''message = localize('$')..self.ability.extra.dollars,'''
    +
    +# Matador
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''message = localize('$')..self.ability.extra,
    +    dollars = self.ability.extra,'''
    +position = "at"
    +match_indent = true
    +payload = '''message = localize('$')..self.ability.extra,'''
    diff --git a/lovely/better_calc/new_calculation/listed_probabilities.toml b/lovely/better_calc/new_calculation/listed_probabilities.toml
    new file mode 100644
    index 000000000..65698901b
    --- /dev/null
    +++ b/lovely/better_calc/new_calculation/listed_probabilities.toml
    @@ -0,0 +1,162 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +# Listed pseudorandom probabilities in cards
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +pattern = '''pseudorandom\((.*?)\) ?< ?G\.GAME\.probabilities\.normal ?\/ ?(.*?)( |\)|$)'''
    +position = 'at'
    +payload = "SMODS.pseudorandom_probability(self, $1, 1, $2)$3"
    +
    +# The Wheel
    +# Don't have to modify loc_debuff_text since that's already done via override
    +# Also don't have for HUD_blind_debuff_prefix since SMODS makes it unused
    +[[patches]]
    +[patches.pattern]
    +target = "blind.lua"
    +pattern = '''if self.name == 'The Wheel' and pseudorandom(pseudoseed('wheel')) < G.GAME.probabilities.normal/7 then'''
    +position = "at"
    +match_indent = true
    +payload = '''if self.name == 'The Wheel' and SMODS.pseudorandom_probability(self, pseudoseed('wheel'), 1, 7, 'wheel') then'''
    +
    +# Space Joker prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == 'Space Joker' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == 'Space Joker' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, 'space')}'''
    +match_indent = true
    +
    +# 8 ball prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == '8 Ball' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1),self.ability.extra}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == '8 Ball' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, '8ball')}'''
    +match_indent = true
    +
    +# Gros michel prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == 'Gros Michel' then loc_vars = {self.ability.extra.mult, ''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == 'Gros Michel' then loc_vars = {self.ability.extra.mult, SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'gros_michel')}'''
    +match_indent = true
    +
    +# Business card prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == 'Business Card' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == 'Business Card' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, 'business')}'''
    +match_indent = true
    +
    +# Bloodstone prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == 'Bloodstone' then loc_vars = {''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds, self.ability.extra.Xmult}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == 'Bloodstone' then 
    +    local a, b = SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'bloodstone')
    +    loc_vars = {a, b, self.ability.extra.Xmult}'''
    +match_indent = true
    +
    +# Cavendish prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == 'Cavendish' then loc_vars = {self.ability.extra.Xmult, ''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == 'Cavendish' then loc_vars = {self.ability.extra.Xmult, SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'cavendish')}'''
    +match_indent = true
    +
    +# Reserved parking prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == 'Reserved Parking' then loc_vars = {self.ability.extra.dollars, ''..(G.GAME and G.GAME.probabilities.normal or 1), self.ability.extra.odds}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == 'Reserved Parking' then loc_vars = {self.ability.extra.dollars, SMODS.get_probability_vars(self, 1, self.ability.extra.odds, 'parking')}'''
    +match_indent = true
    +
    +# Hallucination prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = '''elseif self.ability.name == 'Hallucination' then loc_vars = {G.GAME.probabilities.normal, self.ability.extra}'''
    +position = 'at'
    +payload = '''elseif self.ability.name == 'Hallucination' then loc_vars = {SMODS.get_probability_vars(self, 1, self.ability.extra, 'halu'..G.GAME.round_resets.ante)}'''
    +match_indent = true
    +
    +# Glass card prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = '''elseif _c.effect == 'Glass Card' then loc_vars = {cfg.Xmult, G.GAME.probabilities.normal, cfg.extra}'''
    +position = 'at'
    +payload = '''elseif _c.effect == 'Glass Card' then loc_vars = {cfg.Xmult, SMODS.get_probability_vars(card, 1, cfg.extra, 'glass')}'''
    +match_indent = true
    +
    +# Wheel prob vars
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = '''elseif _c.name == "The Wheel of Fortune" then loc_vars = {G.GAME.probabilities.normal, cfg.extra};  info_queue[#info_queue+1] = G.P_CENTERS.e_foil; info_queue[#info_queue+1] = G.P_CENTERS.e_holo; info_queue[#info_queue+1] = G.P_CENTERS.e_polychrome;'''
    +position = 'at'
    +payload = '''elseif _c.name == "The Wheel of Fortune" then 
    +    loc_vars = {SMODS.get_probability_vars(card, 1, cfg.extra, 'wheel_of_fortune')}
    +    info_queue[#info_queue+1] = G.P_CENTERS.e_foil
    +    info_queue[#info_queue+1] = G.P_CENTERS.e_holo
    +    info_queue[#info_queue+1] = G.P_CENTERS.e_polychrome'''
    +match_indent = true
    +
    +# Modify Oops All 6s! behaviour - removed for back compat for now
    +# [[patches]]
    +# [patches.pattern]
    +# target = 'card.lua'
    +# match_indent = true
    +# position = 'at'
    +# pattern = '''
    +# for k, v in pairs(G.GAME.probabilities) do 
    +#     G.GAME.probabilities[k] = v*2
    +# end
    +# '''
    +# payload = '''
    +# -- removed by smods
    +# '''
    +# [[patches]]
    +# [patches.pattern]
    +# target = 'card.lua'
    +# match_indent = true
    +# position = 'at'
    +# pattern = '''
    +# for k, v in pairs(G.GAME.probabilities) do 
    +#     G.GAME.probabilities[k] = v/2
    +# end
    +# '''
    +# payload = '''
    +# -- removed by smods
    +# '''
    +# Add Opps All 6s! calculation
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +elseif context.buying_card then
    +'''
    +payload = '''
    +elseif context.mod_probability and not context.blueprint and self.config.center_key == 'j_oops' then
    +    return {
    +        numerator = context.numerator * 2
    +    }
    +'''
    diff --git a/lovely/better_calc/new_calculation/scaling.toml b/lovely/better_calc/new_calculation/scaling.toml
    new file mode 100644
    index 000000000..f0f1d80c5
    --- /dev/null
    +++ b/lovely/better_calc/new_calculation/scaling.toml
    @@ -0,0 +1,708 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Ceremonial Dagger
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.ability.mult = self.ability.mult + sliced_card.sell_cost*2
    +'''
    +payload = '''
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.mult+2*sliced_card.sell_cost}}, colour = G.C.RED, no_juice = true})'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "mult",
    +    scalar_table = sliced_card,
    +    scalar_value = "sell_cost",
    +    operation = function(ref_table, ref_value, initial, scaling)
    +        ref_table[ref_value] = initial + 2*scaling
    +    end,
    +    scaling_message = {
    +        message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.mult+2*sliced_card.sell_cost}},
    +        colour = G.C.RED,
    +        no_juice = true
    +    }
    +})
    +'''
    +
    +# Flash Card
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.ability.mult = self.ability.mult + self.ability.extra
    +G.E_MANAGER:add_event(Event({
    +    func = (function()
    +        card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.mult}}, colour = G.C.MULT})
    +    return true
    +end)}))
    +'''
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "mult",
    +    scalar_value = "extra",
    +    message_key = 'a_mult',
    +    message_colour = G.C.RED
    +})
    +return nil, true
    +'''
    +# Red Card
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.ability.mult = self.ability.mult + self.ability.extra
    +                G.E_MANAGER:add_event(Event({
    +    func = function() 
    +        card_eval_status_text(self, 'extra', nil, nil, nil, {
    +            message = localize{type = 'variable', key = 'a_mult', vars = {self.ability.extra}},
    +            colour = G.C.RED,
    +            delay = 0.45, 
    +            card = self
    +        }) 
    +        return true
    +    end}))
    +'''
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "mult",
    +    scalar_value = "extra",
    +    message_key = 'a_mult',
    +    message_colour = G.C.RED,
    +    message_delay = 0.45,
    +})
    +'''
    +# Spare Trousers
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.ability.mult = self.ability.mult + self.ability.extra
    +return {
    +    message = localize('k_upgrade_ex'),
    +    colour = G.C.RED,
    +    card = self
    +}
    +'''
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "mult",
    +    scalar_value = "extra",
    +    message_colour = G.C.RED
    +})
    +return nil, true
    +'''
    +# Ride The Bus
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +else
    +    self.ability.mult = self.ability.mult + self.ability.extra
    +end
    +'''
    +payload = '''
    +else
    +    SMODS.scale_card(self, {
    +        ref_table = self.ability,
    +        ref_value = "mult",
    +        scalar_value = "extra",
    +        no_message = true
    +    })
    +    return nil, true
    +end
    +'''
    +
    +# Egg
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.extra_value = self.ability.extra_value + self.ability.extra
    +self:set_cost()
    +return {
    +    message = localize('k_val_up'),
    +    colour = G.C.MONEY
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "extra_value",
    +    scalar_value = "extra",
    +    scaling_message = {
    +        message = localize('k_val_up'),
    +        colour = G.C.MONEY
    +    }
    +})
    +self:set_cost()
    +return nil, true
    +'''
    +
    +# Runner/Square Joker
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.extra.chips = self.ability.extra.chips + self.ability.extra.chip_mod
    +return {
    +    message = localize('k_upgrade_ex'),
    +    colour = G.C.CHIPS,
    +    card = self
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability.extra,
    +    ref_value = "chips",
    +    scalar_value = "chip_mod",
    +})
    +return nil, true
    +'''
    +
    +# Castle
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.extra.chips = self.ability.extra.chips + self.ability.extra.chip_mod
    +                  
    +return {
    +    message = localize('k_upgrade_ex'),
    +    card = self,
    +    colour = G.C.CHIPS
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability.extra,
    +    ref_value = "chips",
    +    scalar_value = "chip_mod",
    +})
    +return nil, true
    +'''
    +
    +# Wee Joker
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.extra.chips = self.ability.extra.chips + self.ability.extra.chip_mod
    +
    +return {
    +    extra = {focus = self, message = localize('k_upgrade_ex')},
    +    card = self,
    +    colour = G.C.CHIPS
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability.extra,
    +    ref_value = "chips",
    +    scalar_value = "chip_mod",
    +    no_message = true
    +})
    +return {
    +    extra = {focus = self, message = localize('k_upgrade_ex')},
    +    card = self
    +}
    +'''
    +
    +# Ice Cream
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.extra.chips = self.ability.extra.chips - self.ability.extra.chip_mod
    +return {
    +    message = localize{type='variable',key='a_chips_minus',vars={self.ability.extra.chip_mod}},
    +    colour = G.C.CHIPS
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability.extra,
    +    ref_value = "chips",
    +    scalar_value = "chip_mod",
    +    operation = "-",
    +    message_key = 'a_chips_minus'
    +})
    +return nil, true
    +'''
    +
    +# Madness
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +if self.ability.name == 'Madness' and not context.blueprint and not context.blind.boss then
    +    self.ability.x_mult = self.ability.x_mult + self.ability.extra
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if self.ability.name == 'Madness' and not context.blueprint and not context.blind.boss then
    +'''
    +# Madness pt2
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if not (context.blueprint_card or self).getting_sliced then
    +    card_eval_status_text((context.blueprint_card or self), 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.x_mult}}})
    +end
    +'''
    +payload = '''
    +if not (context.blueprint_card or self).getting_sliced then
    +    SMODS.scale_card(context.blueprint_card or self, {
    +        ref_table = self.ability,
    +        ref_value = "x_mult",
    +        scalar_value = "extra",
    +        message_key = 'a_xmult'
    +    })
    +end
    +'''
    +
    +# Constellation
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +if self.ability.name == 'Constellation' and not context.blueprint and context.consumeable.ability.set == 'Planet' then
    +    self.ability.x_mult = self.ability.x_mult + self.ability.extra
    +    G.E_MANAGER:add_event(Event({
    +        func = function() card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}}}); return true
    +        end}))
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if self.ability.name == 'Constellation' and not context.blueprint and context.consumeable.ability.set == 'Planet' then
    +    SMODS.scale_card(self, {
    +        ref_table = self.ability,
    +        ref_value = "x_mult",
    +        scalar_value = "extra",
    +        message_key = 'a_xmult'
    +    })
    +'''
    +# Campfire
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.ability.x_mult = self.ability.x_mult + self.ability.extra
    +G.E_MANAGER:add_event(Event({
    +    func = function() card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize('k_upgrade_ex')}); return true
    +    end}))
    +'''
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "x_mult",
    +    scalar_value = "extra",
    +    message_colour = G.C.FILTER
    +})
    +return nil, true
    +'''
    +# Lucky Cat
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.ability.x_mult = self.ability.x_mult + self.ability.extra
    +return {
    +    extra = {focus = self, message = localize('k_upgrade_ex'), colour = G.C.MULT},
    +    card = self
    +}
    +'''
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "x_mult",
    +    scalar_value = "extra",
    +    no_message = true
    +})
    +return {
    +    extra = {focus = self, message = localize('k_upgrade_ex'), colour = G.C.MULT},
    +    card = self
    +}
    +'''
    +# Obelisk
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +else
    +    self.ability.x_mult = self.ability.x_mult + self.ability.extra
    +end
    +'''
    +payload = '''
    +else
    +    SMODS.scale_card(self, {
    +        ref_table = self.ability,
    +        ref_value = "x_mult",
    +        scalar_value = "extra",
    +        no_message = true
    +    })
    +    return nil, true
    +end
    +'''
    +
    +# Hit the Road/Lucky Cat
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.x_mult = self.ability.x_mult + self.ability.extra
    +return {
    +    message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},
    +        colour = G.C.RED,
    +        delay = 0.45, 
    +    card = self
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "x_mult",
    +    scalar_value = "extra",
    +    message_key = 'a_xmult',
    +    message_colour = G.C.RED,
    +    message_delay = 0.45,
    +})
    +return nil, true
    +'''
    +
    +# Green Joker: Subtraction
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.mult = math.max(0, self.ability.mult - self.ability.extra.discard_sub)
    +if self.ability.mult ~= prev_mult then 
    +    return {
    +        message = localize{type='variable',key='a_mult_minus',vars={self.ability.extra.discard_sub}},
    +        colour = G.C.RED,
    +        card = self
    +    }
    +end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if self.ability.mult ~= 0 then
    +    SMODS.scale_card(self, {
    +        ref_table = self.ability,
    +        ref_value = "mult",
    +        scalar_table = self.ability.extra,
    +        scalar_value = "discard_sub",
    +        operation = function(ref_table, ref_value, initial, change)
    +            ref_table[ref_value] = math.max(0, initial - change)
    +        end,
    +        message_key = 'a_mult_minus',
    +        message_colour = G.C.RED
    +    })
    +    return nil, true
    +end
    +'''
    +
    +# Green Joker: Addition
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.mult = self.ability.mult + self.ability.extra.hand_add
    +return {
    +    card = self,
    +    message = localize{type='variable',key='a_mult',vars={self.ability.extra.hand_add}}
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "mult",
    +    scalar_table = self.ability.extra,
    +    scalar_value = "hand_add"
    +})
    +return nil, true
    +'''
    +
    +# Vampire
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.x_mult = self.ability.x_mult + self.ability.extra*#enhanced
    +return {
    +    message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},
    +    colour = G.C.MULT,
    +    card = self
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "x_mult",
    +    scalar_value = "extra",
    +    message_key = 'a_xmult',
    +    message_colour = G.C.MULT,
    +    operation = function(ref_table, ref_value, initial, scaling)
    +        ref_table[ref_value] = initial + scaling*#enhanced
    +    end
    +})
    +return nil, true
    +'''
    +
    +# Hologram
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.x_mult = self.ability.x_mult + #context.cards*self.ability.extra
    +card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.x_mult}}})
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "x_mult",
    +    scalar_value = "extra",
    +    message_key = 'a_xmult',
    +    operation = function(ref_table, ref_value, initial, scaling)
    +        ref_table[ref_value] = initial + scaling*#context.cards
    +    end
    +})
    +'''
    +
    +# Rocket
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.extra.dollars = self.ability.extra.dollars + self.ability.extra.increase
    +return {
    +    message = localize('k_upgrade_ex'),
    +    colour = G.C.MONEY
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability.extra,
    +    ref_value = "dollars",
    +    scalar_value = "increase",
    +    message_colour = G.C.MONEY
    +})
    +return nil, true
    +'''
    +
    +# Turtle Bean
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.extra.h_size = self.ability.extra.h_size - self.ability.extra.h_mod
    +G.hand:change_size(- self.ability.extra.h_mod)
    +return {
    +    message = localize{type='variable',key='a_handsize_minus',vars={self.ability.extra.h_mod}},
    +    colour = G.C.FILTER
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability.extra,
    +    ref_value = "h_size",
    +    scalar_value = "h_mod",
    +    message_key = 'a_handsize_minus',
    +    operation = function(ref_table, ref_value, initial, change)
    +        ref_table[ref_value] = initial - change
    +        G.hand:change_size(- change)
    +    end
    +})
    +return nil, true
    +'''
    +
    +# Popcorn
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.mult = self.ability.mult - self.ability.extra
    +return {
    +    message = localize{type='variable',key='a_mult_minus',vars={self.ability.extra}},
    +    colour = G.C.MULT
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "mult",
    +    scalar_value = "extra",
    +    message_key = 'a_mult_minus',
    +    colour = G.C.MULT,
    +    operation = '-'
    +})
    +return nil, true
    +'''
    +
    +# Ramen
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.x_mult = self.ability.x_mult - self.ability.extra
    +return {
    +    delay = 0.2,
    +    card = self,
    +    message = localize{type='variable',key='a_xmult_minus',vars={self.ability.extra}},
    +    colour = G.C.RED
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "x_mult",
    +    scalar_value = "extra",
    +    operation = "-",
    +    message_key = 'a_xmult_minus',
    +    colour = G.C.RED,
    +    message_delay = 0.2,
    +})
    +return nil, true
    +'''
    +
    +# Yorick
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.ability.x_mult = self.ability.x_mult + self.ability.extra.xmult
    +return {
    +    card = self,
    +    delay = 0.2,
    +    message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},
    +    colour = G.C.RED
    +}
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = "x_mult",
    +    scalar_table = self.ability.extra,
    +    scalar_value = "xmult",
    +    message_key = 'a_xmult',
    +    message_colour = G.C.RED,
    +    message_delay = 0.2,
    +})
    +return nil, true
    +'''
    +
    +# Canio
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.ability.caino_xmult = self.ability.caino_xmult + face_cards*self.ability.extra
    +G.E_MANAGER:add_event(Event({
    +func = function() card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.caino_xmult}}}); return true
    +end}))
    +'''
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = 'caino_xmult',
    +    scalar_value = 'extra',
    +    operation = function(ref_table, ref_value, initial, change)
    +        ref_table[ref_value] = initial + face_cards*change
    +    end,
    +    message_key = 'a_xmult'
    +})
    +return nil, true
    +'''
    +
    +# Glass Joker
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.E_MANAGER:add_event(Event({
    +    func = function()
    +        self.ability.x_mult = self.ability.x_mult + self.ability.extra*glass_cards
    +    return true
    +    end
    +}))
    +card_eval_status_text(self, 'extra', nil, nil, nil, {message = localize{type = 'variable', key = 'a_xmult', vars = {self.ability.x_mult + self.ability.extra*glass_cards}}})
    +'''
    +payload = '''
    +SMODS.scale_card(self, {
    +    ref_table = self.ability,
    +    ref_value = 'x_mult',
    +    scalar_value = 'extra',
    +    operation = function(ref_table, ref_value, initial, change)
    +        ref_table[ref_value] = initial + glass_cards*change
    +    end,
    +    message_key = 'a_xmult'
    +})
    +'''
    diff --git a/lovely/better_calc/new_calculation/shop.toml b/lovely/better_calc/new_calculation/shop.toml
    new file mode 100644
    index 000000000..89ec1f4e4
    --- /dev/null
    +++ b/lovely/better_calc/new_calculation/shop.toml
    @@ -0,0 +1,271 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Set defaults
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +erratic_suits_and_ranks = false,
    +'''
    +payload = '''
    +boosters_in_shop = 2,
    +vouchers_in_shop = 1,
    +'''
    +
    +# Allow booster count to be controlled by G.GAME.modifiers.extra_boosters
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +for i = 1, 2 do
    +    G.GAME.current_round.used_packs = G.GAME.current_round.used_packs or {}
    +'''
    +payload = '''
    +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 {}
    +'''
    +# Custom deck functionality
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +if self.effect.config.no_interest then
    +'''
    +payload = '''
    +if self.effect.config.boosters_in_shop then
    +    G.GAME.starting_params.boosters_in_shop = self.effect.config.boosters_in_shop
    +end
    +'''
    +
    +# Allow voucher count to be controlled by G.GAME.modifiers.extra_vouchers
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if G.GAME.current_round.voucher and G.P_CENTERS[G.GAME.current_round.voucher] then
    +    local card = Card(G.shop_vouchers.T.x + G.shop_vouchers.T.w/2,
    +    G.shop_vouchers.T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, G.P_CENTERS[G.GAME.current_round.voucher],{bypass_discovery_center = true, bypass_discovery_ui = true})
    +    card.shop_voucher = true
    +    create_shop_card_ui(card, 'Voucher', G.shop_vouchers)
    +    card:start_materialize()
    +    G.shop_vouchers:emplace(card)
    +end
    +'''
    +payload = '''
    +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
    +'''
    +# Modify generating vouchers
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.GAME.current_round.voucher = get_next_voucher_key()
    +'''
    +payload = '''
    +G.GAME.current_round.voucher = SMODS.get_next_vouchers()
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.GAME.current_round.voucher = G.SETTINGS.tutorial_progress and G.SETTINGS.tutorial_progress.forced_voucher or get_next_voucher_key()
    +'''
    +payload = '''
    +local forced_voucher = (G.SETTINGS.tutorial_progress or {}).forced_voucher
    +self.GAME.current_round.voucher = forced_voucher and {forced_voucher, spawn = {[forced_voucher] = true }} or SMODS.get_next_vouchers()
    +'''
    +# Stop redeeming vouchers deleting the table of vouchers
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self.shop_voucher then G.GAME.current_round.voucher = nil end
    +'''
    +payload = '''
    +if self.shop_voucher then G.GAME.current_round.voucher.spawn[self.config.center_key] = false end
    +if self.from_tag then G.GAME.current_round.voucher.spawn[G.GAME.current_round.voucher[1]] = false end
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.GAME.current_round.voucher = nil
    +'''
    +payload = '''
    +--G.GAME.current_round.voucher = nil
    +'''
    +# Add voucher restock message
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +  if (G.shop_vouchers and G.shop_vouchers.cards and (G.shop_vouchers.cards[1] or G.GAME.current_round.voucher)) then
    +'''
    +payload = '''
    +  if (G.shop_vouchers and G.shop_vouchers.cards and G.shop_vouchers.cards[1]) then
    +'''
    +# Maintain voucher tag jank interaction
    +[[patches]]
    +[patches.pattern]
    +target = 'tag.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +create_shop_card_ui(card, 'Voucher', G.shop_vouchers)
    +'''
    +payload = '''
    +card.from_tag = true
    +'''
    +
    +
    +# Free Rerolls
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +local chaos = find_joker('Chaos the Clown')
    +G.GAME.current_round.free_rerolls = #chaos
    +'''
    +payload = '''
    +G.GAME.current_round.free_rerolls = G.GAME.round_resets.free_rerolls
    +'''
    +# G.GAME.round_resets.free_rerolls
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +reroll_cost = 1,
    +'''
    +payload = '''
    +free_rerolls = 0,
    +'''
    +# Adjust Chaos the Clown
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.GAME.current_round.free_rerolls = G.GAME.current_round.free_rerolls + 1
    +'''
    +payload = '''
    +SMODS.change_free_rerolls(1)
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.GAME.current_round.free_rerolls = G.GAME.current_round.free_rerolls - 1
    +'''
    +payload = '''
    +SMODS.change_free_rerolls(-1)
    +'''
    +
    +## Shop Card Area Width
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''G.GAME.shop.joker_max*1.02*G.CARD_W,'''
    +position = 'at'
    +match_indent = true
    +payload = '''math.min(G.GAME.shop.joker_max*1.02*G.CARD_W,4.08*G.CARD_W),'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = '''G.shop_jokers.T.w = G.GAME.shop.joker_max*1.01*G.CARD_W'''
    +position = 'at'
    +match_indent = true
    +payload = '''G.shop_jokers.T.w = math.min(G.GAME.shop.joker_max*1.02*G.CARD_W,4.08*G.CARD_W)'''
    +
    +# for some reason shop_voucher is not saved/loaded so... that's what's gonna happen
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''ability = self.ability,'''
    +position = "before"
    +payload = '''
    +shop_voucher = self.shop_voucher,
    +'''
    +match_indent = true
    +
    +# water is wet
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''self.ability = cardTable.ability'''
    +position = "before"
    +payload = '''
    +self.shop_voucher = cardTable.shop_voucher
    +'''
    +match_indent = true
    +
    +# poll_edition for playing cards in shop
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''
    +local edition_poll = pseudorandom(pseudoseed('illusion'))
    +local edition = {}
    +if edition_poll > 1 - 0.15 then edition.polychrome = true
    +elseif edition_poll > 0.5 then edition.holo = true
    +else edition.foil = true
    +end
    +card:set_edition(edition)
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''card:set_edition(poll_edition('illusion', nil, true, true))'''
    +
    +
    +# modify_shop_card context for allowing shop cards to be modified (duh)
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''
    +local card = create_card(v.type, area, nil, nil, nil, nil, nil, 'sho')
    +'''
    +position = 'at'
    +payload = '''
    +local args = {set = v.type, area = area, key_append = 'sho'}
    +local flags = SMODS.calculate_context({create_shop_card = true, set = v.type})
    +local create_flags = SMODS.merge_defaults(flags.shop_create_flags or {}, args)
    +local card = SMODS.create_card(create_flags)
    +SMODS.calculate_context({modify_shop_card = true, card = card})
    +'''
    +match_indent = true
    \ No newline at end of file
    diff --git a/lovely/better_calc/object_vals/card_limit.toml b/lovely/better_calc/object_vals/card_limit.toml
    new file mode 100644
    index 000000000..fe0e54ae7
    --- /dev/null
    +++ b/lovely/better_calc/object_vals/card_limit.toml
    @@ -0,0 +1,389 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Reset SMODS.cards_to_draw on game start
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +local saveTable = args.savetext or nil
    +'''
    +payload = '''
    +SMODS.cards_to_draw = nil
    +'''
    +
    +
    +# Set metatable
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.config = config or {}
    +'''
    +payload = '''
    +self.config = setmetatable({card_limits = {card_limit}}, {
    +    __index = function(t, key)
    +        if key == "card_limit" then
    +            return (t.card_limits.total_slots or 0) - (t.card_limits.extra_slots_used or 0)
    +        end
    +    end,
    +    __newindex = function(t, key, value)
    +        if key == 'card_limit' then
    +            if not t.card_limits.base then rawset(t.card_limits, 'base', value) end
    +            if not t.card_limits.total_slots then rawset(t.card_limits, 'total_slots', value) end
    +            rawset(t.card_limits, 'mod', value - t.card_limits.base - (t.card_limits.extra_slots or 0))
    +        else
    +            rawset(t, key, value)
    +        end
    +    end
    +})
    +
    +SMODS.merge_defaults(self.config, config)
    +'''
    +
    +# Load metatable
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.config = cardAreaTable.config
    +'''
    +payload = '''
    +self.config = setmetatable(cardAreaTable.config, {
    +    __index = function(t, key)
    +        if key == "card_limit" then
    +            return (t.card_limits.total_slots or 0) - (t.card_limits.extra_slots_used or 0)
    +        end
    +    end,
    +    __newindex = function(t, key, value)
    +        if key == 'card_limit' then
    +            if not t.card_limits.base then rawset(t.card_limits, 'base', value) end
    +            rawset(t.card_limits, 'mod', value - t.card_limits.base - (t.card_limits.extra_slots or 0))
    +        else
    +            rawset(t, key, value)
    +        end
    +    end
    +})
    +
    +'''
    +
    +# Bypass CardArea:change_size()
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +function CardArea:change_size(delta)
    +'''
    +payload = '''
    +    if true then
    +        self.config.card_limits.mod = (self.config.card_limits.mod or 0) + delta
    +        return
    +    end
    +'''
    +
    +# CardArea:update()
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self == G.deck and self.config.card_limit > #G.playing_cards then self.config.card_limit = #G.playing_cards end
    +'''
    +payload = '''
    +if self == G.deck and self.config.card_limit ~= #G.playing_cards then
    +    self.config.card_limit = #G.playing_cards
    +    self.config.card_limits.total_slots = #G.playing_cards
    +end
    +'''
    +
    +# Card:add_to_deck()
    +# remove vanilla functionality
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self.edition and self.edition.negative then 
    +            if from_debuff then 
    +                self.ability.queue_negative_removal = nil
    +            else
    +                if self.ability.consumeable then
    +                    G.consumeables.config.card_limit = G.consumeables.config.card_limit + 1
    +                else
    +                    G.jokers.config.card_limit = G.jokers.config.card_limit + 1
    +                end
    +            end
    +        end
    +'''
    +payload = '''
    +-- removed by SMODS
    +'''
    +
    +# Card:remove_from_deck()
    +# remove vanilla functionality
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self.edition and self.edition.negative and G.jokers then
    +            if from_debuff then
    +                self.ability.queue_negative_removal = true 
    +            else
    +                if self.ability.consumeable then
    +                    G.consumeables.config.card_limit = G.consumeables.config.card_limit - 1
    +                else
    +                    G.jokers.config.card_limit = G.jokers.config.card_limit - 1
    +                end 
    +            end
    +        end
    +'''
    +payload = '''
    +-- removed by SMODS
    +'''
    +
    +# CardArea:update()
    +# Add support for extra_slots_used parameter
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.config.temp_limit = math.max(#self.cards, self.config.card_limit)
    +self.config.card_count = #self.cards
    +'''
    +payload = '''
    +self:handle_card_limit()
    +self.config.temp_limit = math.max(#self.cards, self.config.card_limit or 0)
    +'''
    +
    +# CardArea:draw()
    +# Change count display to use true_card_limit
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{n=G.UIT.T, config={ref_table = self.config, ref_value = 'card_limit', scale = 0.3, colour = G.C.WHITE}},
    +'''
    +payload = '''
    +{n=G.UIT.T, config={ref_table = self.config.card_limits, ref_value = 'total_slots', scale = 0.3, colour = G.C.WHITE}},
    +'''
    +
    +# Add negative info_queue support
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{card_limit = self.GAME.starting_params.consumable_slots, type = 'joker', highlight_limit = 1})
    +'''
    +payload = '''
    +{card_limit = self.GAME.starting_params.consumable_slots, type = 'joker', highlight_limit = 1, negative_info = 'consumable'})
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{card_limit = self.GAME.starting_params.joker_slots, type = 'joker', highlight_limit = 1})
    +'''
    +payload = '''
    +{card_limit = self.GAME.starting_params.joker_slots, type = 'joker', highlight_limit = 1, negative_info = 'joker'})
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{card_limit = self.GAME.starting_params.hand_size, type = 'hand'})
    +'''
    +payload = '''
    +{card_limit = self.GAME.starting_params.hand_size, type = 'hand', negative_info = 'playing_card'})
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{card_limit = G.GAME.shop.joker_max, type = 'shop', highlight_limit = 1})
    +'''
    +payload = '''
    +{card_limit = G.GAME.shop.joker_max, type = 'shop', highlight_limit = 1, negative_info = true})
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{card_limit = 5, type = 'play'})
    +'''
    +payload = '''
    +{card_limit = 5, type = 'play', negative_info = 'playing_card'})
    +'''
    +
    +# generate_card_ui()
    +# Adds info queue tooltips for generic card limit and slots used
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +for _, v in ipairs(info_queue) do
    +'''
    +payload = '''
    +if card and card.ability and (card.ability.extra_slots_used or 0) ~= 0 then
    +    local str = 'generic_extra_slots'
    +    if card.ability.set == 'Default' or card.ability.set == 'Enhanced' then str = str .. '_pc' end
    +    info_queue[#info_queue + 1] = {set = 'Other', key = str, vars = {card.ability.extra_slots_used + 1}}
    +end
    +if card and card.ability and (card.ability.card_limit or 0) ~= 0 then
    +    if not (card.edition and card.edition.card_limit == card.ability.card_limit) then
    +        local amount = card.ability.card_limit - (card.edition and card.edition.card_limit or 0)
    +        local str = 'generic_card_limit'
    +        if card.ability.set == 'Default' or card.ability.set == 'Enhanced' then str = str .. '_pc' end
    +        info_queue[#info_queue + 1] = {set = 'Other', key = amount == 1 and str or str..'_plural', vars = {localize({type='variable', key= amount > 0 and 'a_chips' or 'a_chips_minus', vars ={math.abs(amount)}})}}
    +    end
    +end
    +'''
    +
    +
    +
    +# G.FUNCS.draw_from_deck_to_hand()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = "local hand_space = e or*"
    +position = "at"
    +payload = """local hand_space = e
    +local cards_to_draw = {}
    +local space_taken = 0
    +local limit = G.hand.config.card_limit - #G.hand.cards - (SMODS.cards_to_draw or 0)
    +local flags = SMODS.calculate_context({drawing_cards = true, amount = limit})
    +limit = flags.cards_to_draw or flags.modify or limit
    +local unfixed = not G.hand.config.fixed_limit
    +local n = 0
    +while n < #G.deck.cards and limit > 0 do
    +    local card = G.deck.cards[#G.deck.cards-n]
    +    local mod = unfixed and (card.ability.card_limit - card.ability.extra_slots_used) or 0
    +    if limit - 1 + mod < 0 then
    +    else    
    +        limit = limit - 1 + mod
    +        table.insert(cards_to_draw, card)
    +        space_taken = space_taken + (1 - mod)
    +    end
    +    n = n + 1
    +end
    +hand_space = #cards_to_draw
    +"""
    +match_indent = true
    +
    +# Handle The Serpent blind drawing
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +hand_space = math.min(#G.deck.cards, 3)
    +'''
    +payload = '''
    +G.hand.config.card_limits.blind_restriction = hand_space - math.min(#G.deck.cards, 3)
    +'''
    +# The Serpent only affects refilling the hand
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +if G.GAME.blind.name == 'The Serpent' and
    +'''
    +payload = '''
    +    G.STATE == G.STATES.DRAW_TO_HAND and
    +'''
    +
    +
    +# check_for_unlock
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "if args.type == 'min_hand_size' and G.hand and G.hand.config.card_limit <= card.unlock_condition.extra then"
    +position = "at"
    +payload = "if args.type == 'min_hand_size' and G.hand and G.hand.config.true_card_limit <= card.unlock_condition.extra then"
    +match_indent = true
    +
    +# Challenge UI tooltip support
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +card_w = joker_size*G.CARD_W, type = 'title_2', highlight_limit = 0})
    +'''
    +payload = '''
    +negative_info = 'joker',
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +card_w = joker_size*G.CARD_W, type = 'title_2', spread = true, highlight_limit = 0})
    +'''
    +payload = '''
    +negative_info = 'consumable',
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{card_limit = #SUITS[suit_map[j]], type = 'title_2', view_deck = true, highlight_limit = 0, card_w = G.CARD_W*0.5, draw_layers = {'card'}})'''
    +payload = '''
    +{card_limit = #SUITS[suit_map[j]], type = 'title_2', view_deck = true, highlight_limit = 0, card_w = G.CARD_W*0.5, draw_layers = {'card'}, negative_info = 'playing_card'})'''
    +
    +# Stop updating during run initialisation
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if #self.cards > self.config.card_limit then
    +    if self == G.deck then
    +        self.config.card_limit = #self.cards
    +    end
    +end
    +'''
    +payload = '''
    +if #self.cards > self.config.card_limit then
    +    if self == G.deck and G.GAME.blind then
    +        self.config.card_limit = #self.cards
    +    end
    +end
    +'''
    diff --git a/lovely/better_calc/object_vals/hand_limit.toml b/lovely/better_calc/object_vals/hand_limit.toml
    new file mode 100644
    index 000000000..d6d8fb57e
    --- /dev/null
    +++ b/lovely/better_calc/object_vals/hand_limit.toml
    @@ -0,0 +1,93 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Add starting params
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +consumable_slots = 2,
    +'''
    +payload = '''
    +play_limit = 5,
    +discard_limit = 5,
    +no_limit = '',
    +'''
    +# Reset visual indicators
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +self.GAME = saveTable and saveTable.GAME or self:init_game_object()
    +'''
    +payload = '''
    +SMODS.update_hand_limit_text(true, true)
    +'''
    +
    +
    +# Change hand limit
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if #G.hand.highlighted <= 0 or G.GAME.blind.block_play or #G.hand.highlighted > 5 then 
    +'''
    +payload = '''
    +if #G.hand.highlighted <= 0 or G.GAME.blind.block_play or #G.hand.highlighted > math.max(G.GAME.starting_params.play_limit, 1) then 
    +'''
    +
    +# Change discard limit
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if G.GAME.current_round.discards_left <= 0 or #G.hand.highlighted <= 0 then 
    +'''
    +payload = '''
    +if G.GAME.current_round.discards_left <= 0 or #G.hand.highlighted <= 0 or #G.hand.highlighted > math.max(G.GAME.starting_params.discard_limit, 0) then 
    +'''
    +
    +# Add play limit indicator to UI
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +{n=G.UIT.R, config={align = "bcm", padding = 0}, nodes={
    +    {n=G.UIT.T, config={text = localize('b_play_hand'), scale = text_scale, colour = G.C.UI.TEXT_LIGHT, focus_args = {button = 'x', orientation = 'bm'}, func = 'set_button_pip'}}
    +}},
    +'''
    +payload = '''
    +{n=G.UIT.R, config={align = "bcm", padding = 0}, nodes = {
    +    {n=G.UIT.T, config={ref_table = SMODS.hand_limit_strings, ref_value = 'play', scale = text_scale * 0.65, colour = G.C.UI.TEXT_LIGHT}}
    +}},
    +'''
    +# Add discard limit indicator to UI
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +{n=G.UIT.R, config={align = "cm", padding = 0}, nodes={
    +    {n=G.UIT.T, config={text = localize('b_discard'), scale = text_scale, colour = G.C.UI.TEXT_LIGHT, focus_args = {button = 'y', orientation = 'bm'}, func = 'set_button_pip'}}
    +}}'''
    +payload = '''
    +{n=G.UIT.R, config={align = "cm", padding = 0}, nodes={
    +    {n=G.UIT.T, config={text = localize('b_discard'), scale = text_scale, colour = G.C.UI.TEXT_LIGHT, focus_args = {button = 'y', orientation = 'bm'}, func = 'set_button_pip'}}
    +}},
    +{n=G.UIT.R, config={align = "cm", padding = 0}, nodes={
    +    {n=G.UIT.T, config={ref_table = SMODS.hand_limit_strings, ref_value = 'discard', scale = text_scale * 0.65, colour = G.C.UI.TEXT_LIGHT}}
    +}},
    +'''
    diff --git a/lovely/better_calc/object_vals/perma_bonus.toml b/lovely/better_calc/object_vals/perma_bonus.toml
    new file mode 100644
    index 000000000..8a87adff0
    --- /dev/null
    +++ b/lovely/better_calc/object_vals/perma_bonus.toml
    @@ -0,0 +1,297 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +### Permanent card values implementations
    +## Adapted from AMM (https://github.com/AutumnMood924/AutumnMoodMechanics)
    +
    +# generate_card_ui(): mult card special case
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "if _c.effect == 'Mult Card' then loc_vars = {cfg.mult}"
    +position = "at"
    +payload = "if _c.effect == 'Mult Card' then loc_vars = {SMODS.signed(cfg.mult)}"
    +match_indent = true
    +
    +# generate_card_ui(): gold card special case
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "elseif _c.effect == 'Gold Card' then loc_vars = {cfg.h_dollars}"
    +position = "at"
    +payload = "elseif _c.effect == 'Gold Card' then loc_vars = {SMODS.signed_dollars(cfg.h_dollars)}"
    +match_indent = true
    +
    +# generate_card_ui(): stone card special case
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "elseif _c.effect == 'Stone Card' then loc_vars = {((specific_vars and specific_vars.bonus_chips) or cfg.bonus)}"
    +position = "at"
    +payload = "elseif _c.effect == 'Stone Card' then loc_vars = {((specific_vars and SMODS.signed(specific_vars.bonus_chips)) or cfg.bonus and SMODS.signed(cfg.bonus) or 0)}"
    +match_indent = true
    +
    +# generate_card_ui(): show permanent bonuses on default playing cards
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "elseif _c.set == 'Enhanced' then"
    +position = "before"
    +payload = '''SMODS.localize_perma_bonuses(specific_vars, desc_nodes)'''
    +match_indent = true
    +overwrite = false
    +
    +# generate_card_ui(): signed(extra_chips)
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {specific_vars.bonus_chips}}"
    +position = "at"
    +match_indent = true
    +payload = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_chips)}}"
    +
    +# generate_card_ui(): signed(extra_chips)
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {((specific_vars and specific_vars.bonus_chips) or cfg.bonus)}}"
    +position = "at"
    +match_indent = true
    +payload = "localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {SMODS.signed((specific_vars and specific_vars.bonus_chips) or cfg.bonus)}}"
    +
    +# generate_card_ui(): show permanent bonuses on enhanced playing cards
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "elseif _c.set == 'Booster' then"
    +position = "before"
    +payload = '''SMODS.localize_perma_bonuses(specific_vars, desc_nodes)'''
    +match_indent = true
    +overwrite = false
    +
    +# generate_UIBox_ability_table(): prime locals for easier boolean magic
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "loc_vars = { playing_card = not not self.base.colour, value = self.base.value, suit = self.base.suit, colour = self.base.colour,"
    +position = "before"
    +payload = '''local bonus_chips = self.ability.bonus + (self.ability.perma_bonus or 0)
    +local total_h_dollars = self:get_h_dollars()'''
    +match_indent = true
    +overwrite = false
    +
    +# generate_UIBox_ability_table(): prime specific_vars for playing cards
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "bonus_chips = (self.ability.bonus + (self.ability.perma_bonus or 0)) > 0 and (self.ability.bonus + (self.ability.perma_bonus or 0)) or nil,"
    +position = "at"
    +payload = '''
    +bonus_x_chips = self.ability.perma_x_chips ~= 0 and (self.ability.perma_x_chips + 1) or nil,
    +bonus_mult = self.ability.perma_mult ~= 0 and self.ability.perma_mult or nil,
    +bonus_x_mult = self.ability.perma_x_mult ~= 0 and (self.ability.perma_x_mult + 1) or nil,
    +bonus_h_chips = self.ability.perma_h_chips ~= 0 and self.ability.perma_h_chips or nil,
    +bonus_h_x_chips = self.ability.perma_h_x_chips ~= 0 and (self.ability.perma_h_x_chips + 1) or nil,
    +bonus_h_mult = self.ability.perma_h_mult ~= 0 and self.ability.perma_h_mult or nil,
    +bonus_h_x_mult = self.ability.perma_h_x_mult ~= 0 and (self.ability.perma_h_x_mult + 1) or nil,
    +bonus_p_dollars = self.ability.perma_p_dollars ~= 0 and self.ability.perma_p_dollars or nil,
    +bonus_h_dollars = self.ability.perma_h_dollars ~= 0 and self.ability.perma_h_dollars or nil,
    +total_h_dollars = total_h_dollars ~= 0 and total_h_dollars or nil,
    +bonus_chips = bonus_chips ~= 0 and bonus_chips or nil,
    +bonus_repetitions = self.ability.perma_repetitions ~= 0 and self.ability.perma_repetitions or nil,'''
    +match_indent = true
    +overwrite = false
    +
    +# set_ability: set defaults for temporary bonuses
    +# Also add conformance with SMODS documentation.
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "x_mult = center.config.Xmult or 1,"
    +position = "at"
    +payload = '''
    +x_mult = center.config.Xmult or center.config.x_mult or 1,
    +h_chips = center.config.h_chips or 0,
    +x_chips = center.config.x_chips or 1,
    +h_x_chips = center.config.h_x_chips or 1,
    +repetitions = center.config.repetitions or 0,
    +'''
    +match_indent = true
    +overwrite = false
    +
    +# set_ability: set defaults for permanent bonuses
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "perma_bonus = self.ability and self.ability.perma_bonus or 0,"
    +position = "after"
    +payload = '''
    +perma_x_chips = self.ability and self.ability.perma_x_chips or 0,
    +perma_mult = self.ability and self.ability.perma_mult or 0,
    +perma_x_mult = self.ability and self.ability.perma_x_mult or 0,
    +perma_h_chips = self.ability and self.ability.perma_h_chips or 0,
    +perma_h_x_chips = self.ability and self.ability.perma_h_x_chips or 0,
    +perma_h_mult = self.ability and self.ability.perma_h_mult or 0,
    +perma_h_x_mult = self.ability and self.ability.perma_h_x_mult or 0,
    +perma_p_dollars = self.ability and self.ability.perma_p_dollars or 0,
    +perma_h_dollars = self.ability and self.ability.perma_h_dollars or 0,
    +perma_repetitions = self.ability and self.ability.perma_repetitions or 0,
    +card_limit = self.ability and self.ability.card_limit or 0,
    +extra_slots_used = self.ability and self.ability.extra_slots_used or 0,
    +'''
    +match_indent = true
    +overwrite = false
    +
    +# Card:get_chip_bonus
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''function Card:get_chip_bonus(*'''
    +position = "after"
    +match_indent = true
    +payload = '''
    +    if self.ability.extra_enhancement then return self.ability.bonus end'''
    +
    +# Card:get_chip_mult
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''if self.ability.effect == "Lucky Card" then
    +    if pseudorandom('lucky_mult') < G.GAME.probabilities.normal/5 then
    +        self.lucky_trigger = true
    +        return self.ability.mult
    +    else
    +        return 0
    +    end
    +else
    +    return self.ability.mult
    +end'''
    +position = "at"
    +match_indent = true
    +payload = '''local ret = (not self.ability.extra_enhancement and self.ability.perma_mult) or 0
    +if self.ability.effect == "Lucky Card" then
    +    if SMODS.pseudorandom_probability(self, 'lucky_mult', 1, 5) then
    +        self.lucky_trigger = true
    +        ret = ret + self.ability.mult
    +    end
    +else
    +    ret = ret + self.ability.mult
    +end
    +-- TARGET: get_chip_mult
    +return ret'''
    +
    +# Card:get_chip_x_mult
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''if self.ability.x_mult <= 1 then return 0 end
    +return self.ability.x_mult'''
    +position = "at"
    +match_indent = true
    +payload = '''local ret = SMODS.multiplicative_stacking(self.ability.x_mult or 1, (not self.ability.extra_enhancement and self.ability.perma_x_mult) or 0)
    +-- TARGET: get_chip_x_mult
    +return ret
    +'''
    +
    +# Card:get_chip_h_mult
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = 'return self.ability.h_mult'
    +position = "at"
    +match_indent = true
    +payload = '''local ret = (self.ability.h_mult or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_mult) or 0)
    +-- TARGET: get_chip_h_mult
    +return ret
    +'''
    +
    +# Card:get_chip_h_x_mult
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = 'return self.ability.h_x_mult'
    +position = "at"
    +match_indent = true
    +payload = '''local ret = SMODS.multiplicative_stacking(self.ability.h_x_mult or 1, (not self.ability.extra_enhancement and self.ability.perma_h_x_mult) or 0)
    +-- TARGET: get_chip_h_x_mult
    +return ret
    +'''
    +
    +# Card:get_chip_x_bonus
    +# Card:get_chip_h_bonus
    +# Card:get_chip_h_x_bonus
    +# Card:get_h_dollars
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = 'function Card:get_edition()'
    +position = "before"
    +match_indent = true
    +payload = '''
    +function Card:get_chip_x_bonus()
    +    if self.debuff then return 0 end
    +    local ret = SMODS.multiplicative_stacking(self.ability.x_chips or 1, (not self.ability.extra_enhancement and self.ability.perma_x_chips) or 0)
    +    -- TARGET: get_chip_x_bonus
    +    return ret
    +end
    +
    +function Card:get_chip_h_bonus()
    +    if self.debuff then return 0 end
    +    local ret = (self.ability.h_chips or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_chips) or 0)
    +    -- TARGET: get_chip_h_bonus
    +    return ret
    +end
    +
    +function Card:get_chip_h_x_bonus()
    +    if self.debuff then return 0 end
    +    local ret = SMODS.multiplicative_stacking(self.ability.h_x_chips or 1, (not self.ability.extra_enhancement and self.ability.perma_h_x_chips) or 0)
    +    -- TARGET: get_chip_h_x_bonus
    +    return ret
    +end
    +
    +function Card:get_h_dollars()
    +    if self.debuff then return 0 end
    +    local ret = (self.ability.h_dollars or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_dollars) or 0)
    +    -- TARGET: get_h_dollars
    +    return ret
    +end
    +'''
    +
    +# Card:get_p_dollars
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''end
    +if ret > 0 then
    +    G.GAME.dollar_buffer = (G.GAME.dollar_buffer or 0) + ret'''
    +position = "at"
    +match_indent = true
    +payload = '''elseif self.ability.p_dollars < 0 then
    +    ret = ret + self.ability.p_dollars
    +end
    +ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_p_dollars) or 0)
    +-- TARGET: get_p_dollars
    +if ret ~= 0 then
    +    G.GAME.dollar_buffer = (G.GAME.dollar_buffer or 0) + ret'''
    +
    +# Card:get_end_of_round_effect
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +if self.ability.h_dollars > 0 then
    +    ret.h_dollars = self.ability.h_dollars
    +    ret.card = self
    +end
    +'''
    +position = "at"
    +match_indent = true
    +payload = '''
    +local h_dollars = self:get_h_dollars()
    +if h_dollars ~= 0 then
    +    ret.h_dollars = h_dollars
    +    ret.card = self
    +end
    +'''
    \ No newline at end of file
    diff --git a/lovely/engine/event.toml b/lovely/engine/event.toml
    new file mode 100644
    index 000000000..528011d71
    --- /dev/null
    +++ b/lovely/engine/event.toml
    @@ -0,0 +1,64 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# These 4 patches fix a bug when appending events to the queue while it's processing an event
    +# Initalize a count in the class
    +# EventManager:init()
    +[[patches]]
    +[patches.pattern]
    +target = 'event.lua'
    +pattern = '''
    +self.queue_last_processed = G.TIMERS.REAL
    +'''
    +position = 'after'
    +payload = '''
    +self.append_count = 0
    +'''
    +match_indent = true
    +
    +# Reset the count for use
    +# EventManager:update(dt, forced)
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/event.lua'
    +pattern = '''
    +if (not blocked or not v[i].blockable) then v[i]:handle(results) end
    +'''
    +position = 'before'
    +payload = '''
    +self.append_count = 0
    +self.append_queue = k
    +'''
    +match_indent = true
    +
    +# Increment i to fix our place in the queue
    +# EventManager:update(dt, forced)
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/event.lua'
    +pattern = '''
    +if (not blocked or not v[i].blockable) then v[i]:handle(results) end
    +'''
    +position = 'after'
    +payload = '''
    +i = i + self.append_count
    +'''
    +match_indent = true
    +
    +# Increment the count when an event is added to the front
    +# EventManager:add_event(event, queue, front)
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/event.lua'
    +pattern = '''
    +if front then
    +'''
    +position = 'after'
    +payload = '''
    +if self.append_queue == queue then
    +    self.append_count = self.append_count + 1
    +end
    +'''
    +match_indent = true
    diff --git a/lovely/engine/threads.toml b/lovely/engine/threads.toml
    new file mode 100644
    index 000000000..85c258013
    --- /dev/null
    +++ b/lovely/engine/threads.toml
    @@ -0,0 +1,30 @@
    +# Necessary to kill threads which lets us restart the game.
    +
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +[[patches]]
    +[patches.pattern]
    +target = "engine/save_manager.lua"
    +pattern = "if request then"
    +position = "after"
    +payload = "if request.type == 'kill' then return end"
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "engine/http_manager.lua"
    +pattern = "if request then"
    +position = "after"
    +payload = "if request.type == 'kill' then return end"
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "engine/sound_manager.lua"
    +pattern = "if request then"
    +position = "after"
    +payload = "if request.type == 'kill' then return end"
    +match_indent = true
    diff --git a/lovely/fixes.toml b/lovely/fixes.toml
    new file mode 100644
    index 000000000..0ff85bafa
    --- /dev/null
    +++ b/lovely/fixes.toml
    @@ -0,0 +1,853 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Fixes for either base game code or general mod compatibility
    +
    +## Mods assume Game:start_run() is called with non-nil argument
    +# G.FUNCS.start_run()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = "G.FUNCS.start_run = function(e, args)"
    +position = 'after'
    +match_indent = true
    +payload = "args = args or {}"
    +
    +## Allows running the game without Steam being active
    +# love.load()
    +[[patches]]
    +[patches.regex]
    +target = 'main.lua'
    +pattern = "(?[\t ]*)if not \\(st.init and st:init\\(\\)\\) then\n[\t ]*(?love.event.quit\\(\\))"
    +position = 'at'
    +root_capture = 'quit'
    +payload = 'st = nil'
    +
    +
    +## Prevents the game from crashing when hitting play with a corrupt/invalid save file
    +# G.FUNCS.can_continue(e)
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = "if G.SAVED_GAME ~= nil then G.SAVED_GAME = STR_UNPACK(G.SAVED_GAME) end"
    +position = 'after'
    +match_indent = true
    +payload = """
    +if G.SAVED_GAME == nil then
    +    e.config.colour = G.C.UI.BACKGROUND_INACTIVE
    +    e.config.button = nil
    +    return _can_continue
    +end
    +"""
    +
    +## Fix loading a blind with $0 reward
    +# Blind:load()
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = '''
    +(?[\t ]*)    G\.HUD_blind\.alignment\.offset\.y = 0
    +[\t ]*end'''
    +position = 'at'
    +payload = '''
    +end
    +if G.GAME.blind.name and G.GAME.blind.name ~= '' then
    +    G.HUD_blind.alignment.offset.y = 0
    +end'''
    +line_prepend = '$indent'
    +
    +## Remove incorrect check for Moveable alignment change
    +# Moveable:align_to_major()
    +[[patches]]
    +[patches.regex]
    +target = 'engine/moveable.lua'
    +pattern = '''
    +(?[\t ]*)if +self\.alignment\.prev_offset\.x == self\.alignment\.offset\.x[\s\S]*?return end
    +'''
    +position = 'at'
    +payload = 'if not self.alignment.type_list then return end'
    +line_prepend = '$indent'
    +
    +## Don't save run while booster pack opened (always softlocks)
    +# save_run()
    +[[patches]]
    +[patches.pattern]
    +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"""
    +match_indent = true
    +
    +## Set `G.your_collection.config.collection` to true in all cases
    +# create_UIBox_your_collection_seals()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''\{card_limit = 4, type = 'title', highlight_limit = 0\}'''
    +position = 'at'
    +payload = '''{card_limit = 4, type = 'title', highlight_limit = 0, collection = true}'''
    +
    +## Save and load Card.unique_val, edition options
    +# Card:save()
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = "bypass_lock = self.bypass_lock,"
    +position = "after"
    +payload = """
    +unique_val = self.unique_val,
    +unique_val__saved_ID = self.unique_val__saved_ID or self.ID,
    +ignore_base_shader = self.ignore_base_shader,
    +ignore_shadow = self.ignore_shadow,"""
    +match_indent = true
    +
    +# Card:load()
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = "self.bypass_lock = cardTable.bypass_lock"
    +position = "after"
    +payload = """
    +self.unique_val = cardTable.unique_val or self.unique_val
    +self.unique_val__saved_ID = cardTable.unique_val__saved_ID or self.unique_val__saved_ID
    +if cardTable.unique_val__saved_ID and G.ID <= cardTable.unique_val__saved_ID then
    +    G.ID = cardTable.unique_val__saved_ID + 1
    +end
    +
    +self.ignore_base_shader = cardTable.ignore_base_shader or {}
    +self.ignore_shadow = cardTable.ignore_shadow or {}"""
    +match_indent = true
    +
    +## Vars in card descriptions should use `card.ability` instead of `_c.config` where possible
    +## Allow passing in custom vars
    +# generate_card_ui()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = 'function generate_card_ui(_c, full_UI_table, specific_vars, card_type, badges, hide_desc, main_start, main_end)'
    +position = 'at'
    +match_indent = true
    +payload = '''function generate_card_ui(_c, full_UI_table, specific_vars, card_type, badges, hide_desc, main_start, main_end, card)
    +    if _c.specific_vars then specific_vars = _c.specific_vars end
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = "if _c.set == 'Other' then"
    +position = 'before'
    +match_indent = true
    +payload = "local cfg = (card and card.ability) or _c['config']" # string index to make sure the next patch doesn't eat it
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = "if _c.name ~= 'Stone Card' and ((specific_vars and specific_vars.bonus_chips) or _c.config.bonus) then"
    +position = 'at'
    +match_indent = true
    +payload = "if _c.name ~= 'Stone Card' and ((specific_vars and specific_vars.bonus_chips) or (cfg.bonus ~= 0 and cfg.bonus)) then"
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/common_events.lua'
    +pattern = '_c.config'
    +position = 'at'
    +payload = 'cfg'
    +
    +## When overriding with set_ability and card is added to deck, call add / remove effects
    +# Card:set_ability()
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = "self.config.center = center"
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if delay_sprites == 'quantum' then self.from_quantum = true end
    +local was_added_to_deck = false
    +if self.added_to_deck and old_center and not self.debuff then
    +    self:remove_from_deck()
    +    was_added_to_deck = true
    +end
    +if type(center) == 'string' then
    +    assert(G.P_CENTERS[center], ("Could not find center \"%s\""):format(center))
    +    center = G.P_CENTERS[center]
    +end
    +self.config.center = center
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = "if G.consumeables and self.area == G.consumeables then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +if was_added_to_deck and not self.debuff then
    +    self:add_to_deck()
    +end
    +self.from_quantum = nil'''
    +
    +
    +## set_ability() transfers over old fields
    +# special cases:
    +# extra_value should be transferred
    +# name, effect, set, extra should always be overwritten
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +pattern = '''
    +(?[\t ]*)self\.ability = (\{[\s\S]*?
    +[\t ]*\})
    +'''
    +position = 'at'
    +line_prepend = '$indent'
    +payload = '''
    +local new_ability = $2
    +self.ability = self.ability or {}
    +new_ability.extra_value = nil
    +new_ability.debuff_sources = {}
    +self.ability.extra_value = self.ability.extra_value or 0
    +for k, v in pairs(new_ability) do
    +    self.ability[k] = v
    +end
    +
    +-- handles card_limit/extra_slots_used changes
    +self.ability.card_limit = self.ability.card_limit + (center.config.card_limit or 0)
    +self.ability.extra_slots_used = self.ability.extra_slots_used + (center.config.extra_slots_used or 0)
    +
    +
    +-- reset keys do not persist on ability change
    +local reset_keys = {'name', 'effect', 'set', 'extra', 'played_this_ante', 'perma_debuff'}
    +for _, mod in ipairs(SMODS.mod_list) do
    +    if mod.set_ability_reset_keys then
    +        local keys = mod.set_ability_reset_keys()
    +        for _, v in pairs(keys) do table.insert(reset_keys, v) end
    +    end
    +end
    +for _, k in ipairs(reset_keys) do
    +    self.ability[k] = new_ability[k]
    +end
    +'''
    +
    +## Fix crash if self.config.card == nil for non-vanilla set_ability() calls
    +# Card:set_ability()
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = "self.label = center.label or self.config.card.label or self.ability.set"
    +position = 'at'
    +match_indent = true
    +payload = "self.label = center.label or self.config.card and self.config.card.label or self.ability.set"
    +
    +### Fix Matador
    +
    +# These patches have been removed for altering vanilla behavior. Git blame this line to see what they were
    +
    +### Debuffing boss blinds and Verdant Leaf don't debuff things
    +### that aren't a Joker or playing card
    +
    +## Blind:debuff_card
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = '''card\.area ~= G.jokers'''
    +position = 'at'
    +payload = """card.playing_card"""
    +
    +### Fix Crimson Heart
    +
    +## Blind:drawn_to_hand()
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "if self.name == 'Crimson Heart' and self.prepped and G.jokers.cards\\[1\\] then"
    +position = 'after'
    +payload = """
    +
    +            local prev_chosen_set = {}
    +            local fallback_jokers = {}"""
    +# Fix bad logic if not enough choices for debuff
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = '''
    +(?[\t ]*)for i = 1, #G\.jokers\.cards do
    +[\t ]*if not G\.jokers\.cards\[i\]\.debuff or #G\.jokers\.cards < 2 then jokers\[#jokers\+1\] = ?G\.jokers\.cards\[i\] end
    +[\t ]*G\.jokers\.cards\[i\]:set_debuff\(false\)
    +[\t ]*end'''
    +position = 'at'
    +line_prepend = '$indent'
    +payload = """
    +for i = 1, #G.jokers.cards do
    +    if G.jokers.cards[i].ability.crimson_heart_chosen then
    +        prev_chosen_set[G.jokers.cards[i]] = true
    +        G.jokers.cards[i].ability.crimson_heart_chosen = nil
    +        if G.jokers.cards[i].debuff then SMODS.recalc_debuff(G.jokers.cards[i]) end
    +    end
    +end
    +for i = 1, #G.jokers.cards do
    +    if not G.jokers.cards[i].debuff then
    +        if not prev_chosen_set[G.jokers.cards[i]] then
    +            jokers[#jokers+1] = G.jokers.cards[i]
    +        end
    +        table.insert(fallback_jokers, G.jokers.cards[i])
    +    end
    +end
    +if #jokers == 0 then jokers = fallback_jokers end"""
    +# Add variable for Crimson Heart's choice
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "_card:set_debuff(true)"
    +position = "at"
    +match_indent = true
    +payload = """
    +_card.ability.crimson_heart_chosen = true
    +SMODS.recalc_debuff(_card)"""
    +
    +## Blind:debuff_card()
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = '''
    +if self\.name == 'Crimson Heart' and not self\.disabled and card\.area == G\.jokers then\s+
    +((?[\t ]*)return)'''
    +root_capture = '$1'
    +position = "at"
    +line_prepend = '$indent'
    +payload = """
    +if card.ability.crimson_heart_chosen then
    +    card:set_debuff(true);
    +    if card.debuff then card.debuffed_by_blind = true end
    +    return
    +end"""
    +
    +## Blind:press_play()
    +# Shouldn't work with Matador
    +# yes it should
    +
    +## Blind:disable()
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "elseif self.name == 'The Water' then"
    +position = 'at'
    +payload = """
    +elseif self.name == 'Crimson Heart' then
    +        for _, v in ipairs(G.jokers.cards) do
    +            v.ability.crimson_heart_chosen = nil
    +        end
    +    elseif self.name == 'The Water' then"""
    +
    +## Blind:defeat()
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "elseif self.name == 'The Manacle' and not self.disabled then"
    +position = 'at'
    +payload = """
    +elseif self.name == 'Crimson Heart' then
    +        for _, v in ipairs(G.jokers.cards) do
    +            v.ability.crimson_heart_chosen = nil
    +        end
    +    elseif self.name == 'The Manacle' and not self.disabled then"""
    +
    +
    +## Fix Manacle's unnecessary card draw after positive G.hand:change_size()
    +# Blind:disable()
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = 'G\.hand:change_size\(1\)(\s+G\.FUNCS\.draw_from_deck_to_hand\(1\))'
    +root_capture = '$1'
    +position = 'at'
    +payload = ""
    +
    +#
    +# Money scaling fix
    +#
    +
    +## create_UIBox_HUD
    +[[patches]]
    +[patches.regex]
    +target = "functions/UI_definitions.lua"
    +pattern = '''
    +string = \{\{ref_table = G\.GAME\, ref_value = 'dollars'\, prefix = localize\('\$'\)\}\}\,'''
    +position = "after"
    +payload = '''
    +
    +                            scale_function = function ()
    +                                return scale_number(G.GAME.dollars, 2.2 * scale, 99999, 1000000)
    +                            end,'''
    +
    +## DynaText:update_text
    +[[patches]]
    +[patches.pattern]
    +target = "engine/text.lua"
    +pattern = 'self.config.H = 0'
    +position = "after"
    +payload = "self.scale = self.config.scale_function and self.config.scale_function() or self.scale"
    +match_indent = true
    +
    +
    +#
    +# Fix gold stake legendary infloop
    +# Do not try to roll for jokers that are not in_pool
    +#
    +
    +# generate_starting_seed()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''if win_ante and (win_ante >= 8) then'''
    +match_indent = true
    +position = "at"
    +payload = '''if win_ante and (win_ante >= 8) or not SMODS.add_to_pool(v) then'''
    +
    +#
    +# Fix G.GAME.blind:set_blind(nil, true, nil)
    +# being called when not in blind.
    +#
    +
    +# Card:add_to_deck
    +# Card:remove_from_deck
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = 'if G\.GAME\.blind then'
    +position = "at"
    +payload = "if G.GAME.blind and G.GAME.blind.in_blind and not self.from_quantum then"
    +
    +# end_round()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = "local game_over = true"
    +position = "before"
    +payload = "G.GAME.blind.in_blind = false"
    +match_indent = true
    +
    +# Allow winning game if winning ante is skipped
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = "if G.GAME.round_resets.ante == G.GAME.win_ante and G.GAME.blind:get_type() == 'Boss' then"
    +position = "at"
    +payload = "if not G.GAME.won and G.GAME.round_resets.ante >= G.GAME.win_ante and G.GAME.blind:get_type() == 'Boss' then"
    +match_indent = true
    +
    +
    +
    +# Make sure new param is loaded
    +[[patches]]
    +[patches.pattern]
    +target = "blind.lua"
    +match_indent = true
    +pattern = "function Blind:load(blindTable)"
    +position = "after"
    +payload = '''
    +    self.in_blind = blindTable.in_blind'''
    +
    +# Make sure new param is saved
    +[[patches]]
    +[patches.pattern]
    +target = "blind.lua"
    +match_indent = true
    +pattern = "local blindTable = {"
    +position = "after"
    +payload = '''
    +    in_blind = self.in_blind,'''
    +
    +# Cartomancer and astronomer unlock when *actually all* Tarot/Planet cards are discovered
    +# check_for_unlock()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +match_indent = true
    +pattern = "if card.unlock_condition.tarot_count <= args.tarot_count then"
    +position = "at"
    +payload = 'if #G.P_CENTER_POOLS.Tarot <= args.tarot_count then'
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +match_indent = true
    +pattern = "if card.unlock_condition.planet_count <= args.planet_count then"
    +position = "at"
    +payload = 'if #G.P_CENTER_POOLS.Planet <= args.planet_count then'
    +
    +# wtf
    +[[patches]]
    +[patches.pattern]
    +target = "engine/animatedsprite.lua"
    +match_indent = true
    +pattern = "for _, v in pairs(G.ANIMATIONS) do"
    +position = "at"
    +payload = 'for k, v in pairs(G.ANIMATIONS) do'
    +
    +[[patches]]
    +[patches.pattern]
    +target = "engine/animatedsprite.lua"
    +match_indent = true
    +pattern = "for _, v in pairs(G.I.SPRITE) do"
    +position = "at"
    +payload = 'for k, v in pairs(G.I.SPRITE) do'
    +
    +## Make vanilla enhancement jokers work with extra enhancements
    +
    +# Steel Joker
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +match_indent = true
    +pattern = "if v.config.center == G.P_CENTERS.m_steel then self.ability.steel_tally = self.ability.steel_tally+1 end"
    +position = "at"
    +payload = "if SMODS.has_enhancement(v, 'm_steel') then self.ability.steel_tally = self.ability.steel_tally+1 end"
    +
    +# Stone Joker
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +match_indent = true
    +pattern = "if v.config.center == G.P_CENTERS.m_stone then self.ability.stone_tally = self.ability.stone_tally+1 end"
    +position = "at"
    +payload = "if SMODS.has_enhancement(v, 'm_stone') then self.ability.stone_tally = self.ability.stone_tally+1 end"
    +
    +# Golden Ticket
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +match_indent = true
    +pattern = "context.other_card.ability.name == 'Gold Card' then"
    +position = "at"
    +payload = "SMODS.has_enhancement(context.other_card, 'm_gold') then"
    +
    +# Golden Ticket Unlock
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +match_indent = true
    +pattern = "if args.cards[j].ability.name == 'Gold Card' then"
    +position = "at"
    +payload = "if SMODS.has_enhancement(args.cards[j], 'm_gold') then"
    +
    +# Glass Joker
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +match_indent = true
    +pattern = "if val.ability.name == 'Glass Card' then shattered_glass = shattered_glass + 1 end"
    +position = "at"
    +payload = "if SMODS.has_enhancement(val, 'm_glass') then shattered_glass = shattered_glass + 1 end"
    +
    +# Driver's License
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +match_indent = true
    +pattern = "if v.config.center ~= G.P_CENTERS.c_base then self.ability.driver_tally = self.ability.driver_tally+1 end"
    +position = "at"
    +payload = "if next(SMODS.get_enhancements(v)) then self.ability.driver_tally = self.ability.driver_tally+1 end"
    +# Basegame fix for the reroll vouchers redeem function when only the center but no card object exists
    +
    +# Card:apply_to_run
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = """G.GAME.round_resets.reroll_cost = G.GAME.round_resets.reroll_cost - self.ability.extra"""
    +position = 'at'
    +match_indent = true
    +payload = """G.GAME.round_resets.reroll_cost = G.GAME.round_resets.reroll_cost - center_table.extra"""
    +
    +# Card:apply_to_run
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = """G.GAME.current_round.reroll_cost = math.max(0, G.GAME.current_round.reroll_cost - self.ability.extra)"""
    +position = 'at'
    +match_indent = true
    +payload = """G.GAME.current_round.reroll_cost = math.max(0, G.GAME.current_round.reroll_cost - center_table.extra)"""
    +
    +
    +# Fix booster skip issues maybe?
    +[[patches]]
    +[patches.pattern]
    +target = "functions/button_callbacks.lua"
    +pattern = "if G.pack_cards and (G.pack_cards.cards[1]) and"
    +position = "at"
    +payload = '''
    +if G.pack_cards and (not (G.GAME.STOP_USE and G.GAME.STOP_USE > 0)) and
    +'''
    +match_indent = true
    +
    +# Due to STOP_USE being used we can remove this dumb check
    +# could probably remove the rest of it since it serves no purpose otherwise, but whatever
    +[[patches]]
    +[patches.regex]
    +target = "functions/button_callbacks.lua"
    +pattern = '''and \(G\.hand\.cards\[1\] or \(G\.hand\.config\.card_limit <= 0\)\)'''
    +position = "at"
    +payload = ''' '''
    +
    +# Fix prng calls on collection advancing seeds
    +# Keep vanilla behaviour for to-do list
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = "if key == 'seed' then return math.random() end"
    +position = "after"
    +payload = """
    +if G.SETTINGS.paused and key ~= 'to_do' then return math.random() end
    +"""
    +overwrite = true
    +match_indent = true
    +
    +# Fixes Steam API not loading on unix
    +[[patches]]
    +[patches.pattern]
    +target = 'main.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '--To control when steam communication happens, make sure to send updates to steam as little as possible'
    +payload = '''local cwd = SMODS.NFS.getWorkingDirectory()
    +SMODS.NFS.setWorkingDirectory(love.filesystem.getSourceBaseDirectory())
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'main.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '--Set up the render window and the stage for the splash screen, then enter the gameloop with :update'
    +payload = '''SMODS.NFS.setWorkingDirectory(cwd)
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "main.lua"
    +pattern = '''if os == 'OS X' or os == 'Windows' then'''
    +position = "at"
    +payload = '''if os == 'OS X' or os == 'Windows' or os == 'Linux' then'''
    +overwrite = true
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "main.lua"
    +pattern = '''if os == 'OS X' then'''
    +position = "at"
    +payload = '''if os == 'OS X' or os == 'Linux' then'''
    +overwrite = true
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "main.lua"
    +pattern = "st = require 'luasteam'"
    +position = "at"
    +payload = """local success, _st = pcall(require, 'luasteam')
    +if success then st = _st else sendWarnMessage(_st, "LuaSteam"); st = {} end"""
    +overwrite = true
    +match_indent = true
    +
    +# copy_card: Subtract from .ability.card_limit
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +position = 'before'
    +match_indent = true
    +pattern = 'if not strip_edition then'
    +payload = '''
    +if other.edition then
    +    new_card.ability.card_limit = new_card.ability.card_limit - (other.edition.card_limit or 0)
    +    new_card.ability.extra_slots_used = new_card.ability.extra_slots_used - (other.edition.extra_slots_used or 0)
    +end
    +if other.seal then
    +    new_card.ability.card_limit = new_card.ability.card_limit - (other.ability.seal.card_limit or 0)
    +    new_card.ability.extra_slots_used = new_card.ability.extra_slots_used - (other.ability.seal.extra_slots_used or 0)
    +end'''
    +
    +# copy_card edition config
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +position = 'after'
    +match_indent = true
    +pattern = 'new_card:set_edition(other.edition or {}, nil, true)'
    +payload = '''
    +for k,v in pairs(other.edition or {}) do
    +    if type(v) == 'table' then
    +        new_card.edition[k] = copy_table(v)
    +    else
    +        new_card.edition[k] = v
    +    end
    +end'''
    +
    +# fix Smeared Joker compat issues with modded suits
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "if next(find_joker('Smeared Joker')) and (self.base.suit == 'Hearts' or self.base.suit == 'Diamonds') == (suit == 'Hearts' or suit == 'Diamonds') then"
    +position = "at"
    +payload = "if SMODS.smeared_check(self, suit) then"
    +overwrite = true
    +match_indent = true
    +
    +# fix Seeing Double compat issues with modded suits
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = """local suits = {
    +    ['Hearts'] = 0,
    +    ['Diamonds'] = 0,
    +    ['Spades'] = 0,
    +    ['Clubs'] = 0
    +}
    +for i = 1, #context.scoring_hand do
    +    if not SMODS.has_any_suit(context.scoring_hand[i]) then
    +        if context.scoring_hand[i]:is_suit('Hearts') then suits["Hearts"] = suits["Hearts"] + 1 end
    +        if context.scoring_hand[i]:is_suit('Diamonds') then suits["Diamonds"] = suits["Diamonds"] + 1 end
    +        if context.scoring_hand[i]:is_suit('Spades') then suits["Spades"] = suits["Spades"] + 1 end
    +        if context.scoring_hand[i]:is_suit('Clubs') then suits["Clubs"] = suits["Clubs"] + 1 end
    +    end
    +end
    +for i = 1, #context.scoring_hand do
    +    if SMODS.has_any_suit(context.scoring_hand[i]) then
    +        if context.scoring_hand[i]:is_suit('Clubs') and suits["Clubs"] == 0 then suits["Clubs"] = suits["Clubs"] + 1
    +        elseif context.scoring_hand[i]:is_suit('Diamonds') and suits["Diamonds"] == 0  then suits["Diamonds"] = suits["Diamonds"] + 1
    +        elseif context.scoring_hand[i]:is_suit('Spades') and suits["Spades"] == 0  then suits["Spades"] = suits["Spades"] + 1
    +        elseif context.scoring_hand[i]:is_suit('Hearts') and suits["Hearts"] == 0  then suits["Hearts"] = suits["Hearts"] + 1 end
    +    end
    +end
    +if (suits["Hearts"] > 0 or
    +suits["Diamonds"] > 0 or
    +suits["Spades"] > 0) and
    +suits["Clubs"] > 0 then
    +    return {
    +        message = localize{type='variable',key='a_xmult',vars={self.ability.extra}},
    +        Xmult_mod = self.ability.extra
    +    }
    +end"""
    +position = "at"
    +payload = """if SMODS.seeing_double_check(context.scoring_hand, 'Clubs') then
    +    return {
    +        message = localize{type='variable',key='a_xmult',vars={self.ability.extra}},
    +        Xmult_mod = self.ability.extra
    +    }
    +end"""
    +overwrite = true
    +match_indent = true
    +
    +# Card:get_end_of_round_effect
    +# prevents Blue Seal crash from a hand with no matching planet card (spawns random planet card)
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = """local card = create_card(card_type,G.consumeables, nil, nil, nil, nil, _planet, 'blusl')"""
    +position = 'before'
    +payload = """if _planet == 0 then _planet = nil end"""
    +match_indent = true
    +
    +## Fix remove_from_deck being called and unlocking stuff when exiting a run and
    +## clearing screen
    +# Game:delete_run()
    +[[patches]]
    +[patches.pattern]
    +target = "game.lua"
    +pattern = 'function Game:delete_run()'
    +position = 'after'
    +payload = '''    G.in_delete_run = true
    +    booster_obj = nil'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = "game.lua"
    +pattern = 'G.STATE = -1'
    +position = 'after'
    +payload = 'G.in_delete_run = false'
    +match_indent = true
    +# Card:remove()
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = 'if self.area then self.area:remove_card(self) end'
    +position = 'after'
    +payload = 'if G.in_delete_run then goto skip_game_actions_during_remove end'
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = 'if G.playing_cards then'
    +position = 'before'
    +payload = '::skip_game_actions_during_remove::'
    +match_indent = true
    +# CardArea:remove_card()
    +[[patches]]
    +[patches.pattern]
    +target = "cardarea.lua"
    +pattern = '''
    +self:set_ranks()
    +if self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end'''
    +position = 'at'
    +payload = '''
    +self:set_ranks()
    +if not G.in_delete_run and self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end'''
    +match_indent = true
    +
    +# Assign `getting_sliced` to destruction events
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +if v ~= chosen_joker then 
    +'''
    +payload = '''
    +v.getting_sliced = true
    +'''
    +
    +# Allow pseudoshuffle to take a string as a seed
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +function pseudoshuffle(list, seed)
    +'''
    +payload = '''
    +    if seed and type(seed) == "string" then seed = pseudoseed(seed) end
    +'''
    +
    +## Add initial playing cards to deck when starting run
    +# card_from_control
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +local _card = Card(G.deck.T.x, G.deck.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[control.s..'_'..control.r], G.P_CENTERS[control.e or 'c_base'], {playing_card = G.playing_card})
    +if control.d then _card:set_edition({[control.d] = true}, true, true) end
    +if control.g then _card:set_seal(control.g, true, true) end
    +'''
    +payload = '''
    +_card:add_to_deck()
    +'''
    +## Add cards created by create_playing_card to deck
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +local card = Card(_area.T.x, _area.T.y, G.CARD_W, G.CARD_H, card_init.front, card_init.center, {playing_card = G.playing_card})
    +'''
    +payload = '''
    +card:add_to_deck()
    +'''
    diff --git a/lovely/mod.toml b/lovely/mod.toml
    new file mode 100644
    index 000000000..8feb0d80f
    --- /dev/null
    +++ b/lovely/mod.toml
    @@ -0,0 +1,72 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +### Per-mod functions
    +
    +# end_round()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/state_events.lua'
    +pattern = '''(?[\t ]*)reset_castle_card\(\)'''
    +line_prepend = '$indent'
    +position = 'after'
    +payload = '''
    +
    +for _, mod in ipairs(SMODS.mod_list) do
    +	if mod.reset_game_globals and type(mod.reset_game_globals) == 'function' then
    +		mod.reset_game_globals(false)
    +	end
    +end'''
    +
    +# Game:start_run()
    +[[patches]]
    +[patches.regex]
    +target = 'game.lua'
    +pattern = '''(?[\t ]*)reset_castle_card\(\)'''
    +line_prepend = '$indent'
    +position = 'after'
    +payload = '''
    +
    +for _, mod in ipairs(SMODS.mod_list) do
    +	if mod.reset_game_globals and type(mod.reset_game_globals) == 'function' then
    +		mod.reset_game_globals(true)
    +	end
    +end'''
    +
    +# Card:set_debuff()
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = "function Card:set_debuff(should_debuff)"
    +position = 'after'
    +match_indent = true
    +payload = '''
    +	for _, mod in ipairs(SMODS.mod_list) do
    +		if mod.set_debuff and type(mod.set_debuff) == 'function' then
    +            local res = mod.set_debuff(self)
    +            if res == 'prevent_debuff' then
    +                if self.debuff then
    +                    self.debuff = false
    +                    if self.area == G.jokers then self:add_to_deck(true) end
    +					self.debuffed_by_blind = false
    +                end
    +                return
    +            end
    +			should_debuff = should_debuff or res
    +		end
    +	end
    +	for k, v in pairs(self.ability.debuff_sources or {}) do
    +		if v == 'prevent_debuff' then
    +			if self.debuff then
    +				self.debuff = false
    +				if self.area == G.jokers then self:add_to_deck(true) end
    +			end
    +			self.debuffed_by_blind = false
    +			return
    +		end
    +		should_debuff = should_debuff or v
    +	end
    +	
    +'''
    diff --git a/lovely/objects/smods/jimboquip.toml b/lovely/objects/smods/jimboquip.toml
    new file mode 100644
    index 000000000..3d150b6ed
    --- /dev/null
    +++ b/lovely/objects/smods/jimboquip.toml
    @@ -0,0 +1,202 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Select custom win quips
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = '''Jimbo = Card_Character({x = 0, y = 5})
    +local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    +spot.config.object:remove()
    +spot.config.object = Jimbo
    +Jimbo.ui_object_updated = true
    +Jimbo:add_speech_bubble('wq_'..math.random(1,7), nil, {quip = true})
    +Jimbo:say_stuff(5)
    +'''
    +position = "at"
    +payload = '''
    +local quip, extra = SMODS.quip("win")
    +extra.x = 0
    +extra.y = 5
    +Jimbo = Card_Character(extra)
    +local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    +spot.config.object:remove()
    +spot.config.object = Jimbo
    +Jimbo.ui_object_updated = true
    +Jimbo:add_speech_bubble(quip, nil, {quip = true}, extra)
    +Jimbo:say_stuff((extra and extra.times) or 5, false, quip)
    +'''
    +match_indent = true
    +
    +# Select custom loss quips
    +[[patches]]
    +[patches.pattern]
    +target = "game.lua"
    +pattern = '''Jimbo = Card_Character({x = 0, y = 5})
    +local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    +spot.config.object:remove()
    +spot.config.object = Jimbo
    +Jimbo.ui_object_updated = true
    +Jimbo:add_speech_bubble('lq_'..math.random(1,10), nil, {quip = true})
    +Jimbo:say_stuff(5)
    +'''
    +position = "at"
    +payload = '''
    +local quip, extra = SMODS.quip("loss")
    +extra.x = 0
    +extra.y = 5
    +Jimbo = Card_Character(extra)
    +local spot = G.OVERLAY_MENU:get_UIE_by_ID('jimbo_spot')
    +spot.config.object:remove()
    +spot.config.object = Jimbo
    +Jimbo.ui_object_updated = true
    +Jimbo:add_speech_bubble(quip, nil, {quip = true}, extra)
    +Jimbo:say_stuff((extra and extra.times) or 5, false, quip)
    +'''
    +match_indent = true
    +
    +# Allow custom materialize colours
    +[[patches]]
    +[patches.pattern]
    +target = "card_character.lua"
    +pattern = '''self.children.card:start_materialize({G.C.BLUE, G.C.WHITE, G.C.RED})'''
    +position = "at"
    +payload = '''self.children.card:start_materialize({args.materialize_colours and args.materialize_colours[1] or G.C.BLUE, args.materialize_colours and args.materialize_colours[2] or G.C.WHITE, args.materialize_colours and args.materialize_colours[3] or G.C.RED})'''
    +match_indent = true
    +
    +# Allow custom particle_colours
    +[[patches]]
    +[patches.pattern]
    +target = "card_character.lua"
    +pattern = '''colours = {G.C.RED, G.C.BLUE, G.C.ORANGE},'''
    +position = "at"
    +payload = '''colours = {args.particle_colours and args.particle_colours[1] or G.C.RED, args.particle_colours and args.particle_colours[2] or G.C.BLUE, args.particle_colours and args.particle_colours[3] or G.C.ORANGE},'''
    +match_indent = true
    +
    +# Allow custom centers to be used for quips
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +self.children.card = Card(self.T.x, self.T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, args.center or G.P_CENTERS.j_joker, {bypass_discovery_center = true})
    +'''
    +payload = '''
    +self.children.card = Card(self.T.x, self.T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, args.center and G.P_CENTERS[args.center] or G.P_CENTERS.j_joker, {bypass_discovery_center = true})
    +'''
    +
    +# Pass args into add_speech_bubble
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +function Card_Character:add_speech_bubble(text_key, align, loc_vars)
    +'''
    +payload = '''
    +function Card_Character:add_speech_bubble(text_key, align, loc_vars, quip_args)
    +    if quip_args and quip_args.text_key then text_key = quip_args.text_key end
    +'''
    +
    +
    +
    +
    +# Pass extra table into say_stuff
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +pattern = '''
    +function Card_Character:say_stuff(n, not_first)
    +'''
    +position = 'at'
    +payload = '''
    +function Card_Character:say_stuff(n, not_first, quip_key)
    +    local quip = SMODS.JimboQuips[quip_key] or {}
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +pattern = '''
    +self:say_stuff(n, true)
    +'''
    +position = 'at'
    +payload = '''
    +self:say_stuff(n, true, quip_key)
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +pattern = '''
    +self:say_stuff(n-1, true)
    +'''
    +position = 'at'
    +payload = '''
    +self:say_stuff(n-1, true, quip_key)
    +'''
    +match_indent = true
    +
    +
    +# Allow custom sounds to be used for the voice
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +pattern = '''
    +play_sound('voice'..math.random(1, 11), G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    +'''
    +position = 'at'
    +payload = '''
    +if quip.play_sounds and type(quip.play_sounds) == 'function' then
    +    quip:play_sounds(n)
    +elseif quip.extra and quip.extra.sound then
    +    local custom_pitch = quip.extra.pitch
    +    if type(quip.extra.sound) == 'table' then
    +        for k, v in pairs(quip.extra.sound) do
    +            play_sound(v, custom_pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    +        end
    +    elseif type(quip.extra.sound) == 'string' then
    +        play_sound(quip.extra.sound, custom_pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    +    else
    +        play_sound('voice'..math.random(1, 11), custom_pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    +    end
    +else
    +    play_sound('voice'..math.random(1, 11), quip.extra and quip.extra.pitch or G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
    +end
    +'''
    +match_indent = true
    +
    +
    +# Allow custom delay between talking sounds
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +pattern = '''
    +delay = 0.13,
    +'''
    +position = 'at'
    +payload = '''
    +delay = quip.extra and quip.extra.delay or 0.13,
    +'''
    +match_indent = true
    +
    +
    +# Allow custom juice parameters
    +[[patches]]
    +[patches.pattern]
    +target = 'card_character.lua'
    +pattern = '''
    +self.children.card:juice_up()
    +'''
    +position = 'at'
    +payload = '''
    +local juice_params = quip.extra and quip.extra.juice or {nil, nil}
    +self.children.card:juice_up(juice_params[1], juice_params[2])
    +'''
    +match_indent = true
    \ No newline at end of file
    diff --git a/lovely/objects/smods/keybind.toml b/lovely/objects/smods/keybind.toml
    new file mode 100644
    index 000000000..80272b8cc
    --- /dev/null
    +++ b/lovely/objects/smods/keybind.toml
    @@ -0,0 +1,82 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +# Check all registered keybinds
    +# inserted inside Controller:key_press_update
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/controller.lua'
    +pattern = "if not _RELEASE_MODE then"
    +position = "before"
    +payload = '''
    +for _, keybind in pairs(SMODS.Keybinds) do
    +    if keybind.action and keybind.key_pressed == key and keybind.event == 'pressed' then
    +        local execute = true
    +        for _, other_key in pairs(keybind.held_keys) do
    +            if not self.held_keys[other_key] then
    +                execute = false
    +                break
    +            end
    +        end
    +        if execute then
    +            keybind:action()
    +        end
    +    end
    +end
    +'''
    +match_indent = true
    +
    +# Controller:key_release_update
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/controller.lua'
    +pattern = "function Controller:key_release_update(key, dt)"
    +position = "after"
    +payload = '''
    +for _, keybind in pairs(SMODS.Keybinds) do
    +    if keybind.action and keybind.key_pressed == key and keybind.event == 'released' then
    +        local execute = true
    +        for _, other_key in pairs(keybind.held_keys) do
    +            if not self.held_keys[other_key] then
    +                execute = false
    +                break
    +            end
    +        end
    +        if execute then
    +            keybind:action()
    +        end
    +    end
    +end
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.regex]
    +target = 'engine/controller.lua'
    +pattern = 'if key == "r"'
    +position = 'before'
    +line_prepend = '$indent'
    +payload = '''
    +for _, keybind in pairs(SMODS.Keybinds) do
    +    if keybind.key_pressed == key and keybind.event == 'held' and keybind.held_duration then
    +        if self.held_key_times[key] > keybind.held_duration then
    +            local execute = true
    +            for _, other_key in pairs(keybind.held_keys) do
    +                if not self.held_keys[other_key] then
    +                    execute = false
    +                    break
    +                end
    +            end
    +            if execute then
    +                keybind:action()
    +                self.held_key_times[key] = nil
    +            end
    +        else
    +            self.held_key_times[key] = self.held_key_times[key] + dt
    +        end
    +    end
    +end
    +'''
    diff --git a/lovely/objects/smods/language.toml b/lovely/objects/smods/language.toml
    new file mode 100644
    index 000000000..b89ebff4b
    --- /dev/null
    +++ b/lovely/objects/smods/language.toml
    @@ -0,0 +1,59 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Language API
    +
    +# Game:set_language()
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = "if not (love.filesystem.read('localization/'..G.SETTINGS.language..'.lua')) or G.F_ENGLISH_ONLY then"
    +position = 'at'
    +payload = 'if false then'
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = "local localization = love.filesystem.getInfo('localization/'..G.SETTINGS.language..'.lua')"
    +position = 'at'
    +payload = "local localization = love.filesystem.getInfo('localization/'..G.SETTINGS.language..'.lua') or love.filesystem.getInfo('localization/en-us.lua')"
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = "self.localization = assert(loadstring(love.filesystem.read('localization/'..G.SETTINGS.language..'.lua')))()"
    +position = 'at'
    +payload = """self.localization = assert(loadstring(love.filesystem.read('localization/'..G.SETTINGS.language..'.lua') or love.filesystem.read('localization/en-us.lua'), '=[localization "'..G.SETTINGS.language..'.lua"]'))()"""
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = "self.LANG = self.LANGUAGES[self.SETTINGS.language] or self.LANGUAGES['en-us']"
    +position = 'at'
    +payload = "self.LANG = self.LANGUAGES[self.SETTINGS.real_language or self.SETTINGS.language] or self.LANGUAGES['en-us']"
    +match_indent = true
    +
    +# G.FUNCS.change_lang
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = "G.SETTINGS.language = lang.key"
    +position = 'at'
    +payload = """G.SETTINGS.language = lang.loc_key or lang.key
    +G.SETTINGS.real_language = lang.key"""
    +match_indent = true
    +
    +# G.FUNCS.warn_lang (wtf)
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = 'if (_infotip_object.config.set ~= e.config.ref_table.label) and (not G.F_NO_ACHIEVEMENTS) then'
    +position = 'at'
    +payload = 'if (_infotip_object.config.set ~= e.config.ref_table.label) then'
    +match_indent = true
    diff --git a/lovely/objects/smods/scoring_calculation.toml b/lovely/objects/smods/scoring_calculation.toml
    new file mode 100644
    index 000000000..b0b6d7a41
    --- /dev/null
    +++ b/lovely/objects/smods/scoring_calculation.toml
    @@ -0,0 +1,378 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/state_events.lua"
    +pattern = "^(.*)hand_chips\\s*\\*\\s*mult(.*)$"
    +position = "at"
    +payload = '''$1 SMODS.calculate_round_score() $2'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = "G.ARGS.score_intensity.earned_score = G.GAME.current_round.current_hand.chips*G.GAME.current_round.current_hand.mult"
    +position = "at"
    +payload = '''
    +local all_numbers = true
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    if type(G.GAME.current_round.current_hand[name]) ~= 'number' then all_numbers = false end
    +end
    +G.ARGS.score_intensity.earned_score = all_numbers and SMODS.calculate_round_score(true) or 0
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''contents.hand ='''
    +position = "before"
    +payload = '''--[['''
    +match_indent = false
    +
    +# Replace contexts.hand
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +contents.dollars_chips = {n=G.UIT.R, config={align = "cm",r=0.1, padding = 0,colour = G.C.DYN_UI.BOSS_MAIN, emboss = 0.05, id = 'row_dollars_chips'}, nodes={
    +'''
    +payload = '''
    +--]]
    +contents.hand = SMODS.GUI.hand_score_display_ui(scale)
    +'''
    +
    +# Add operator set up/loading
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +self.GAME.selected_back_key = selected_back
    +'''
    +payload = '''
    +
    +if saveTable then
    +    self.GAME.current_scoring_calculation = SMODS.Scoring_Calculations[saveTable.SCORING_CALC.key]:load({
    +        config = saveTable.SCORING_CALC.config
    +    })
    +else
    +    self.GAME.current_scoring_calculation = SMODS.Scoring_Calculations['multiply']:new()
    +end
    +'''
    +
    +# Operator saving
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +BLIND = G.GAME.blind:save(),
    +'''
    +payload = '''
    +SCORING_CALC = G.GAME.current_scoring_calculation:save(),
    +'''
    +
    +# UIElement remove functions
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/ui.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +if self == G.CONTROLLER.text_input_hook then 
    +'''
    +payload = '''
    +if self.config and self.config.func then
    +    self.config.func = nil
    +end
    +
    +'''
    +
    +# Add Scoring_Parameters to update_hand_text
    +[[patches]]
    +[patches.regex]
    +target = 'functions/common_events.lua'
    +position = 'at'
    +line_prepend = '$indent'
    +pattern = '''(?[\t ]*)if vals\.[A-z0-9\n\t .~=()'\-+,<;>:{}]+mult:juice_up\(\) end\n[\t ]+end'''
    +payload = '''
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    if vals[name] and G.GAME.current_round.current_hand[name] ~= vals[name] then
    +        local delta = (type(vals[name]) == 'number' and type(G.GAME.current_round.current_hand[name]) == 'number') and (vals[name] - G.GAME.current_round.current_hand[name]) or 0
    +        if delta < 0 then delta = ''..delta; col = G.C.RED
    +        elseif delta > 0 then delta = '+'..delta
    +        else delta = ''..delta
    +        end
    +        if type(vals[name]) == 'string' then delta = vals[name] end
    +        G.GAME.current_round.current_hand[name] = vals[name]
    +        G.hand_text_area[name] = G.hand_text_area[name] or G.HUD:get_UIE_by_ID('hand_'..name) or nil
    +        if G.hand_text_area[name] then
    +            G.hand_text_area[name]:update(0)
    +            if vals.StatusText then 
    +                attention_text({
    +                    text =delta,
    +                    scale = 0.8, 
    +                    hold = 1,
    +                    cover = G.hand_text_area[name].parent,
    +                    cover_colour = mix_colours(parameter.colour, col, 0.1),
    +                    emboss = 0.05,
    +                    align = 'cm',
    +                    cover_align = G.hand_text_area[name].parent.config.align
    +                })
    +            end
    +            if (vals[name.."_juice"] or parameter.juice_on_update) and not G.TAROT_INTERRUPT then G.hand_text_area[name]:juice_up() end
    +        end
    +    end
    +end
    +'''
    +
    +# Reset other values
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +update_hand_text({immediate = true, nopulse = true, delay = 0}, {mult = 0, chips = 0, level = '', handname = ''})
    +'''
    +payload = '''
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    update_hand_text({immediate = true, nopulse = true, delay = 0}, {[name] = parameter.default_value})
    +end
    +'''
    +# Reset when discard
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +update_hand_text({immediate = true, nopulse = true, delay = 0}, {mult = 0, chips = 0, level = '', handname = ''})
    +'''
    +payload = '''
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    update_hand_text({immediate = true, nopulse = true, delay = 0}, {[name] = parameter.default_value})
    +end
    +'''
    +
    +
    +# Reset display after hand played
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +check_and_set_high_score('hand', hand_chips*mult)
    +'''
    +payload = '''
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    update_hand_text({delay = 0}, {[name] = parameter.default_value})
    +end
    +'''
    +
    +# Set based on hand selected
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname=disp_text, level=G.GAME.hands[text].level, mult = G.GAME.hands[text].mult, chips = G.GAME.hands[text].chips})
    +'''
    +payload = '''
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    parameter.current = G.GAME.hands[text][name] or parameter.default_value
    +    update_hand_text({immediate = true, nopulse = nil, delay = 0}, {[name] = parameter.current})
    +end
    +'''
    +
    +# Reset values after hand played
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +match_indent = false
    +position = 'after'
    +pattern = '''
    +  func = (function() G.GAME.current_round.current_hand.handname = '';return true end)
    +}))
    +delay(0.3)
    +'''
    +payload = '''
    +    SMODS.last_hand_oneshot = SMODS.calculate_round_score() > G.GAME.blind.chips
    +    G.E_MANAGER:add_event(Event({
    +      trigger = 'immediate',
    +      func = (function() 
    +        for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +            parameter.current = parameter.default_value
    +        end
    +        return true 
    +      end)
    +    }))
    +    
    +'''
    +
    +# Level Up Hand uses scoring parameters
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +G.GAME.hands[hand].level = math.max(0, G.GAME.hands[hand].level + amount)
    +G.GAME.hands[hand].mult = math.max(G.GAME.hands[hand].s_mult + G.GAME.hands[hand].l_mult*(G.GAME.hands[hand].level - 1), 1)
    +G.GAME.hands[hand].chips = math.max(G.GAME.hands[hand].s_chips + G.GAME.hands[hand].l_chips*(G.GAME.hands[hand].level - 1), 0)
    +'''
    +payload = '''
    +SMODS.upgrade_poker_hands({
    +    hands = hand,
    +    func = function(base, hand, parameter)
    +            return base + G.GAME.hands[hand]['l_' .. parameter] * amount
    +    end,
    +    level_up = amount,
    +    from = card,
    +    instant = instant,
    +    StatusText = statustext
    +})
    +--[[
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +G.E_MANAGER:add_event(Event({
    +    trigger = 'immediate',
    +    func = (function() check_for_unlock{type = 'upgrade_hand', hand = hand, level = G.GAME.hands[hand].level} return true end)
    +}))
    +'''
    +payload = '''
    +]]
    +'''
    +
    +# Remove update_hand_text duplicates
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +update_hand_text({sound = 'button', volume = 0.7, pitch = 0.8, delay = 0.3}, {handname=localize(self.ability.consumeable.hand_type, 'poker_hands'),chips = G.GAME.hands[self.ability.consumeable.hand_type].chips, mult = G.GAME.hands[self.ability.consumeable.hand_type].mult, level=G.GAME.hands[self.ability.consumeable.hand_type].level})
    +level_up_hand(used_tarot, self.ability.consumeable.hand_type)
    +update_hand_text({sound = 'button', volume = 0.7, pitch = 1.1, delay = 0}, {mult = 0, chips = 0, handname = '', level = ''})
    +'''
    +payload = '''
    +level_up_hand(used_tarot, self.ability.consumeable.hand_type)
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +update_hand_text({sound = 'button', volume = 0.7, pitch = 0.8, delay = 0.3}, {handname=localize(text, 'poker_hands'),chips = G.GAME.hands[text].chips, mult = G.GAME.hands[text].mult, level=G.GAME.hands[text].level})
    +level_up_hand(context.blueprint_card or self, text, nil, 1)
    +update_hand_text({sound = 'button', volume = 0.7, pitch = 1.1, delay = 0}, {mult = 0, chips = 0, handname = '', level = ''})
    +'''
    +payload = '''
    +level_up_hand(context.blueprint_card or self, text, nil, 1)
    +'''
    +
    +# Face down hands
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname='????', level='?', mult = '?', chips = '?'})
    +'''
    +payload = '''
    +for name, parameter in pairs(SMODS.Scoring_Parameters) do
    +    update_hand_text({immediate = true, nopulse = nil, delay = 0}, {[name] = '?'})
    +end
    +'''
    +
    +# Handle flame_handler
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +G.C.UI_CHIPLICK = G.C.UI_CHIPLICK or {1, 1, 1, 1}
    +  G.C.UI_MULTLICK = G.C.UI_MULTLICK or {1, 1, 1, 1}
    +  for i=1, 3 do
    +    G.C.UI_CHIPLICK[i] = math.min(math.max(((G.C.UI_CHIPS[i]*0.5+G.C.YELLOW[i]*0.5) + 0.1)^2, 0.1), 1)
    +    G.C.UI_MULTLICK[i] = math.min(math.max(((G.C.UI_MULT[i]*0.5+G.C.YELLOW[i]*0.5) + 0.1)^2, 0.1), 1)
    +  end
    +
    +  G.ARGS.flame_handler = G.ARGS.flame_handler or {
    +    chips = {
    +      id = 'flame_chips', 
    +      arg_tab = 'chip_flames',
    +      colour = G.C.UI_CHIPS,
    +      accent = G.C.UI_CHIPLICK
    +    },
    +    mult = {
    +      id = 'flame_mult', 
    +      arg_tab = 'mult_flames',
    +      colour = G.C.UI_MULT,
    +      accent = G.C.UI_MULTLICK
    +    }
    +  }
    +'''
    +payload = '''
    +G.ARGS.flame_handler = G.ARGS.flame_handler or {}
    +
    +for key, parameter in pairs(SMODS.Scoring_Parameters) do
    +    for i=1, 3 do
    +        parameter.lick[i] = math.min(math.max(((parameter.colour[i]*0.5+G.C.YELLOW[i]*0.5) + 0.1)^2, 0.1), 1)
    +    end
    +    G.ARGS.flame_handler[key] = G.ARGS.flame_handler[key] or parameter:flame_handler()
    +end
    +'''
    +# pt2
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +e.config.object = Sprite(0, 0, 2.5, 2.5, G.ASSET_ATLAS["ui_1"], {x = 2, y = 0})
    +'''
    +payload = '''
    +e.config.object = Sprite(0, 0, e.config._w, e.config._h, G.ASSET_ATLAS["ui_1"], {x = 2, y = 0})
    +'''
    +
    +# Patch mod_chips/mod_mult
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +return _mult
    +'''
    +payload = '''
    +SMODS.Scoring_Parameters.mult:modify(nil, _mult - (mult or 0))
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +return _chips
    +'''
    +payload = '''
    +SMODS.Scoring_Parameters.chips:modify(nil, _chips - (hand_chips or 0))
    +'''
    diff --git a/lovely/objects/smods/sound.toml b/lovely/objects/smods/sound.toml
    new file mode 100644
    index 000000000..481300eee
    --- /dev/null
    +++ b/lovely/objects/smods/sound.toml
    @@ -0,0 +1,167 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +#modulate_sound()
    +[[patches]]
    +[patches.pattern] 
    +target = 'functions/misc_functions.lua'
    +pattern = 'G.SOUND_MANAGER.channel:push(G.ARGS.push)'
    +match_indent = true
    +position = 'after'
    +payload = '''
    +SMODS.previous_track = SMODS.previous_track or ''
    +local in_sync = (SMODS.Sounds[desired_track] or {}).sync
    +local out_sync = (SMODS.Sounds[SMODS.previous_track] or {}).sync
    +local should_sync = true
    +if (type(in_sync) == 'table' and not in_sync[SMODS.previous_track]) or in_sync == false then should_sync = false end
    +if (type(out_sync) == 'table' and not out_sync[desired_track]) or out_sync == false then should_sync = false end
    +if 
    +    SMODS.previous_track and SMODS.previous_track ~= desired_track and
    +    not should_sync
    +then
    +    G.ARGS.push.type = 'restart_music'
    +    G.SOUND_MANAGER.channel:push(G.ARGS.push)
    +end
    +SMODS.previous_track = desired_track'''
    +
    +[[patches]]
    +[patches.pattern] 
    +target = 'functions/misc_functions.lua'
    +pattern = 'G.ARGS.push.ambient_control = G.SETTINGS.ambient_control'
    +match_indent = true
    +position = 'after'
    +payload = '''
    +if SMODS.remove_replace_sound and SMODS.remove_replace_sound ~= desired_track then
    +    SMODS.Sound.replace_sounds[SMODS.remove_replace_sound] = nil
    +    SMODS.remove_replace_sound = nil
    +end
    +local replace_sound = SMODS.Sound.replace_sounds[desired_track]
    +if replace_sound then
    +    local replaced_track = desired_track
    +    desired_track = replace_sound.key
    +    G.ARGS.push.desired_track = desired_track
    +    if SMODS.previous_track ~= desired_track then
    +        if replace_sound.times > 0 then replace_sound.times = replace_sound.times - 1 end
    +        if replace_sound.times == 0 then SMODS.remove_replace_sound = replaced_track end
    +    end
    +end
    +local stop_sound = SMODS.Sound.stop_sounds[desired_track]
    +if SMODS.Sound.stop_sounds[desired_track] then
    +    if SMODS.previous_track ~= '' and stop_sound > 0 then stop_sound = stop_sound - 1 end
    +    SMODS.Sound.stop_sounds[desired_track] = stop_sound ~= 0 and stop_sound or nil
    +    SMODS.previous_track = ''
    +    return
    +end
    +'''
    +
    +[[patches]]
    +[patches.pattern] 
    +target = 'functions/misc_functions.lua'
    +pattern = "(G.STATE == G.STATES.SPLASH and '') or"
    +match_indent = true
    +position = 'after'
    +payload = 'SMODS.Sound:get_current_music() or'
    +
    +# PLAY_SOUND 
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/sound_manager.lua'
    +pattern = '''local s = {sound = love.audio.newSource("resources/sounds/"..args.sound_code..'.ogg', should_stream and "stream" or 'static')}'''
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local c = SMODS_Sounds[args.sound_code]
    +local s = c and
    +{sound = love.audio.newSource(love.sound.newDecoder(c.data), c.should_stream and 'stream' or 'static'), per = c.per, vol = c.vol } or
    +{sound = love.audio.newSource("resources/sounds/"..args.sound_code..'.ogg', should_stream and "stream" or 'static')}''' 
    +
    +# pass in custom sounds
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/sound_manager.lua'
    +pattern = "DISABLE_SFX = false"
    +match_indent = true
    +position = 'after'
    +payload = '''
    +SMODS_Sounds = {}
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/sound_manager.lua'
    +pattern = "elseif request.type == 'stop' then"
    +match_indent = true
    +position = 'before'
    +payload = '''
    +elseif request.type == 'sound_source' then
    +    SMODS_Sounds[request.sound_code] = {
    +        sound_code = request.sound_code,
    +        data = request.data,
    +        sound = sound,
    +        per = request.per,
    +        vol = request.vol,
    +    }
    +    SOURCES[request.sound_code] = {}
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/sound_manager.lua'
    +pattern = "s.original_pitch = args.per or 1"
    +match_indent = true
    +position = 'at'
    +payload = 's.original_pitch = ((args.type ~= "sound") and s.per) or args.per or 1'
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/sound_manager.lua'
    +pattern = "s.original_volume = args.vol or 1"
    +match_indent = true
    +position = 'at'
    +payload = 's.original_volume = ((args.type ~= "sound") and s.vol) or args.vol or 1'
    +
    +# don't crash RESTART_MUSIC
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/sound_manager.lua'
    +pattern = "RESTART_MUSIC()"
    +match_indent = true
    +position = 'at'
    +payload = 'RESTART_MUSIC(request)'
    +
    +# fix looping for music of different length
    +
    +[[patches]]
    +[patches.regex]
    +target = 'engine/sound_manager.lua'
    +pattern = """(?[\t ]*)function MODULATE\\(args\\)(\n.*){9}"""
    +line_prepend = '$indent'
    +position = 'at'
    +payload = """function MODULATE(args)
    +    if args.desired_track ~= '' then
    +        local sound = ((SOURCES[current_track or {}] or {})[1] or {}).sound
    +        if not sound or not sound:isPlaying() then
    +            RESTART_MUSIC(args)
    +        end
    +    end
    +"""
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/sound_manager.lua'
    +pattern = "for _, s in pairs(v) do"
    +match_indent = true
    +position = 'at'
    +payload = """current_track = args.desired_track
    +for _, s in pairs(v) do""" 
    +
    +# [[patches]]
    +# [patches.pattern]
    +# target = 'engine/sound_manager.lua'
    +# pattern = 'if s.sound and not s.sound:isPlaying() then'
    +# match_indent = true
    +# position = 'at'
    +# payload = '''if s.sound and s.sound:isPlaying() then
    +# 	s.sound:stop()
    +# elseif s.sound and not s.sound:isPlaying() then'''
    diff --git a/lovely/objects/vanilla_collection/blind.toml b/lovely/objects/vanilla_collection/blind.toml
    new file mode 100644
    index 000000000..ada510652
    --- /dev/null
    +++ b/lovely/objects/vanilla_collection/blind.toml
    @@ -0,0 +1,552 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Blind API
    +
    +## Set debuffed_by_blind, use it for Matador behavior
    +## Blind:debuff_card()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = 'card:set_debuff(true)'
    +position = 'after'
    +payload = "if card.debuff then card.debuffed_by_blind = true end"
    +match_indent = true
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = 'card:set_debuff\(true\); return end'
    +position = 'at'
    +payload = """
    +card:set_debuff(true); if card.debuff then card.debuffed_by_blind = true end; return end"""
    +
    +## Card:set_debuff()
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +pattern = '''
    +self\.debuff = should_debuff
    +(?[\t ]*)end
    +'''
    +position = 'after'
    +payload = """if not self.debuff then self.debuffed_by_blind = false end
    +
    +"""
    +line_prepend = '$indent'
    +
    +## Blind functions
    +
    +# Blind:set_blind()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "G.GAME.last_blind = G.GAME.last_blind or {}"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +self.children.animatedSprite = SMODS.create_sprite(self.T.x, self.T.y, self.T.w, self.T.h, obj.atlas or 'blind_chips', obj.pos or G.P_BLINDS.bl_small.pos)
    +self.children.animatedSprite.states = self.states
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "if self.name == 'The Eye' and not reset then"
    +position = 'at'
    +payload = '''
    +if blind then
    +    self.in_blind = true
    +end
    +local obj = self.config.blind
    +if not reset and obj.set_blind and type(obj.set_blind) == 'function' then
    +    obj:set_blind()
    +elseif self.name == 'The Eye' and not reset then'''
    +match_indent = true
    +
    +# Blind:disable()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "if self.name == 'The Water' then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +if obj.disable and type(obj.disable) == 'function' then
    +    obj:disable()
    +end'''
    +
    +# Blind:defeat()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "if self.name == 'The Manacle' and not self.disabled then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +if obj.defeat and type(obj.defeat) == 'function' then
    +    obj:defeat()
    +end'''
    +
    +# Blind:debuff_card()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "if self.debuff and not self.disabled and card.area ~= G.jokers then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +if not self.disabled and obj.recalc_debuff and type(obj.recalc_debuff) == 'function' then
    +    if obj:recalc_debuff(card, from_blind) then
    +        card:set_debuff(true)
    +        if card.debuff then card.debuffed_by_blind = true end
    +    else
    +        card:set_debuff(false)
    +    end
    +    return
    +elseif not self.disabled and obj.debuff_card and type(obj.debuff_card) == 'function' then
    +    sendWarnMessage(("Blind object %s has debuff_card function, recalc_debuff is preferred"):format(obj.key), obj.set)
    +    if obj:debuff_card(card, from_blind) then
    +        card:set_debuff(true)
    +        if card.debuff then card.debuffed_by_blind = true end
    +    else
    +        card:set_debuff(false)
    +    end
    +    return
    +end'''
    +
    +# Blind:stay_flipped()
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "function Blind:stay_flipped(area, card)"
    +position = 'at'
    +match_indent = true
    +payload = '''function Blind:stay_flipped(area, card, from_area)'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "if area == G.hand then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +if obj.stay_flipped and type(obj.stay_flipped) == 'function' then
    +    return obj:stay_flipped(area, card, from_area)
    +end'''
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'cardarea.lua'
    +pattern = "local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(self, card)"
    +position = 'at'
    +match_indent = true
    +payload = '''local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(self, card, area)
    +if SMODS.to_area then to = SMODS.to_area; SMODS.to_area = nil end'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = "local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(to, card)"
    +position = 'at'
    +match_indent = true
    +payload = '''local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(to, card, from)
    +if SMODS.to_area then to = SMODS.to_area; SMODS.to_area = nil end
    +'''
    +
    +# Blind:drawn_to_hand()
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "(?[\t ]*)if self.name == 'Cerulean Bell' then\n"
    +position = 'before'
    +line_prepend = '$indent'
    +payload = '''
    +local obj = self.config.blind
    +if obj.drawn_to_hand and type(obj.drawn_to_hand) == 'function' then
    +    obj:drawn_to_hand()
    +end
    +'''
    +
    +# Blind:debuff_hand()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "if self.debuff then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +if obj.debuff_hand and type(obj.debuff_hand) == 'function' then
    +    return obj:debuff_hand(cards, hand, handname, check)
    +end'''
    +
    +# Blind:modify_hand()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "if self.disabled then return mult, hand_chips, false end"
    +position = 'after'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +if obj.modify_hand and type(obj.modify_hand) == 'function' then
    +    return obj:modify_hand(cards, poker_hands, text, mult, hand_chips)
    +end'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "function Blind:modify_hand(cards, poker_hands, text, mult, hand_chips)"
    +position = 'at'
    +match_indent = true
    +payload = '''function Blind:modify_hand(cards, poker_hands, text, mult, hand_chips, scoring_hand)'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/state_events.lua'
    +pattern = "mult, hand_chips, modded = G.GAME.blind:modify_hand(G.play.cards, poker_hands, text, mult, hand_chips)"
    +position = 'at'
    +match_indent = true
    +payload = '''mult, hand_chips, modded = G.GAME.blind:modify_hand(G.play.cards, poker_hands, text, mult, hand_chips, scoring_hand)'''
    +
    +# Blind:press_play()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = 'if self.name == "The Hook" then'
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.blind
    +if obj.press_play and type(obj.press_play) == 'function' then
    +    return obj:press_play()
    +end'''
    +
    +# Blind:get_loc_debuff_text()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = 'function Blind:get_loc_debuff_text()'
    +position = 'after'
    +match_indent = true
    +payload = '''
    +    local obj = self.config.blind
    +    if obj.get_loc_debuff_text and type(obj.get_loc_debuff_text) == 'function' then
    +        return obj:get_loc_debuff_text()
    +    end'''
    +
    +# Blind:set_text()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = "local loc_target = localize{type = 'raw_descriptions', key = self.config.blind.key, set = 'Blind', vars = loc_vars or self.config.blind.vars}"
    +position = 'at'
    +match_indent = true
    +payload = '''
    +local target = {type = 'raw_descriptions', key = self.config.blind.key, set = 'Blind', vars = loc_vars or self.config.blind.vars}
    +local obj = self.config.blind
    +if obj.loc_vars and type(obj.loc_vars) == 'function' then
    +    local res = obj:loc_vars() or {}
    +    target.vars = res.vars or target.vars
    +    target.key = res.key or target.key
    +    target.set = res.set or target.set
    +    target.scale = res.scale
    +    target.text_colour = res.text_colour
    +end
    +local loc_target = localize(target)'''
    +
    +# Blind:load()
    +[[patches]]
    +[patches.pattern]
    +target = 'blind.lua'
    +pattern = 'if G.P_BLINDS[blindTable.config_blind] then'
    +position = 'after'
    +match_indent = true
    +payload = '''
    +if self.config.blind.atlas then
    +    self.children.animatedSprite.atlas = SMODS.get_atlas(self.config.blind.atlas)
    +end'''
    +
    +# create_UIBox_blind_choice()
    +# create_UIBox_round_scores_row()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = "(?[\t ]*)blind_choice.animation = AnimatedSprite\\(0,0, 1.4, 1.4, (?G.ANIMATION_ATLAS\\['blind_chips'\\]),  blind_choice.config.pos\\)"
    +position = 'at'
    +root_capture = 'atlas'
    +payload = "SMODS.get_atlas(blind_choice.config.atlas) or G.ANIMATION_ATLAS['blind_chips']"
    +
    +# create_UIBox_your_collection_blinds()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = "(?[\t ]*)local temp_blind = AnimatedSprite\\(0,0,1.3,1.3, G.ANIMATION_ATLAS\\['blind_chips'\\], discovered and v.pos or G.b_undiscovered.pos\\)"
    +position = 'at'
    +payload = '''
    +
    +local s = 1.3
    +if math.ceil(#blind_tab/6) > 6 then
    +    s = s * 6/math.ceil(#blind_tab/6)
    +end
    +local temp_blind = AnimatedSprite(0,0,s,s, G.ANIMATION_ATLAS[discovered and v.atlas or 'blind_chips'], discovered and v.pos or G.b_undiscovered.pos)'''
    +line_prepend = '$indent'
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = 'blind_matrix[math.ceil((k-1)/5+0.001)][1+((k-1)%5)] = {n=G.UIT.C, config={align = "cm", padding = 0.1}, nodes={'
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local blinds_per_row = math.ceil(#blind_tab / 6)
    +local row = math.ceil((k - 1) / blinds_per_row + 0.001)
    +table.insert(blind_matrix[row], {
    +    n = G.UIT.C,
    +    config = { align = "cm", padding = 0.1 },
    +    nodes = {
    +        ((k - blinds_per_row) % (2 * blinds_per_row) == 1) and { n = G.UIT.B, config = { h = 0.2, w = 0.5 } } or nil,
    +        { n = G.UIT.O, config = { object = temp_blind, focus_with_object = true } },
    +        ((k - blinds_per_row) % (2 * blinds_per_row) == 0) and { n = G.UIT.B, config = { h = 0.2, w = 0.5 } } or nil,
    +    }
    +})'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = '[\t ]*\(k==6 or k ==16 or k == 26\) and \{n=G.UIT.B, config=\{h=0.2,w=0.5\}\} or nil,\n[\t ]*\{n=G.UIT.O, config=\{object = temp_blind, focus_with_object = true\}\},\n[\t ]*\(k==5 or k ==15 or k == 25\) and \{n=G.UIT.B, config=\{h=0.2,w=0.5\}\} or nil,\n[\t ]*\}\}'
    +position = 'at'
    +payload = ''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = 'table.sort(blind_tab, function (a, b) return a.order < b.order end)'
    +match_indent = true
    +position = 'at'
    +payload = '''
    +table.sort(blind_tab, function(a, b) return a.order + (a.boss and a.boss.showdown and 1000 or 0) < b.order + (b.boss and b.boss.showdown and 1000 or 0) end)'''
    +
    +# add_round_eval_row()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = "local blind_sprite = AnimatedSprite(0, 0, 1.2,1.2, G.ANIMATION_ATLAS['blind_chips'], copy_table(G.GAME.blind.pos))"
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local obj = G.GAME.blind.config.blind
    +local blind_sprite = AnimatedSprite(0, 0, 1.2, 1.2, G.ANIMATION_ATLAS[obj.atlas] or G.ANIMATION_ATLAS['blind_chips'], copy_table(G.GAME.blind.pos))'''
    +
    +# display blind descriptions in info_queue
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "elseif _c.set == 'Tarot' then"
    +position = "before"
    +match_indent = true
    +payload = '''elseif _c.set == 'Blind' then
    +    local coll_loc_vars = (_c.collection_loc_vars and type(_c.collection_loc_vars) == 'function' and _c:collection_loc_vars()) or {}
    +    loc_vars = coll_loc_vars.vars or _c.vars
    +    localize{type = 'descriptions', key = coll_loc_vars.key or _c.key, set = _c.set, nodes = desc_nodes, vars = loc_vars}
    +'''
    +
    +# create_UIBox_blind_choice()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = "local loc_target = localize{type = 'raw_descriptions', key = blind_choice.config.key, set = 'Blind', vars = {localize(G.GAME.current_round.most_played_poker_hand, 'poker_hands')}}"
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local target = {type = 'raw_descriptions', key = blind_choice.config.key, set = 'Blind', vars = {}}
    +if blind_choice.config.name == 'The Ox' then
    +       target.vars = {localize(G.GAME.current_round.most_played_poker_hand, 'poker_hands')}
    +end
    +local obj = blind_choice.config
    +if obj.loc_vars and _G['type'](obj.loc_vars) == 'function' then
    +    local res = obj:loc_vars() or {}
    +    target.vars = res.vars or target.vars
    +    target.key = res.key or target.key
    +    target.set = res.set or target.set
    +    target.scale = res.scale
    +    target.text_colour = res.text_colour
    +end
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = "local text_table = loc_target"
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local text_table = G.localization.descriptions[target.set][target.key].text_parsed
    +'''
    +
    +# create_UIBox_blind_popup()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''local loc_target = localize{type = 'raw_descriptions', key = blind.key, set = 'Blind', vars = vars or blind.vars}'''
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local target = {type = 'raw_descriptions', key = blind.key, set = 'Blind', vars = vars or blind.vars}
    +if blind.collection_loc_vars and type(blind.collection_loc_vars) == 'function' then
    +    local res = blind:collection_loc_vars() or {}
    +    target.vars = res.vars or target.vars
    +    target.key = res.key or target.key
    +    target.set = res.set or target.set
    +    target.scale = res.scale
    +    target.text_colour = res.text_colour
    +end
    +local loc_target = G.localization.descriptions[target.set][target.key].text_parsed'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''
    +ability_text[#ability_text + 1] = {n=G.UIT.R, config={align = "cm"}, nodes={{n=G.UIT.T, config={text = (k ==1 and blind.name == 'The Wheel' and '1' or '')..v, scale = 0.35, shadow = true, colour = G.C.WHITE}}}}
    +'''
    +match_indent = true
    +position = 'at'
    +payload = '''
    +ability_text[#ability_text + 1] = {n=G.UIT.R, config={align = "cm"}, nodes=SMODS.localize_box(v, {default_col = target.text_colour or G.C.WHITE, shadow = true, vars = target.vars, scale = target.scale})}
    +'''
    +
    +# get_new_boss()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = '''
    +if not v.boss then
    +
    +elseif not v.boss.showdown and (v.boss.min <= math.max(1, G.GAME.round_resets.ante) and ((math.max(1, G.GAME.round_resets.ante))%G.GAME.win_ante ~= 0 or G.GAME.round_resets.ante < 2)) then
    +    eligible_bosses[k] = true
    +elseif v.boss.showdown and (G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= 2 then
    +    eligible_bosses[k] = true
    +end
    +'''
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local res, options = SMODS.add_to_pool(v)
    +options = options or {}
    +if not v.boss then
    +
    +elseif options.ignore_showdown_check then
    +    eligible_bosses[k] = res and true or nil
    +elseif v.in_pool and type(v.in_pool) == 'function' then
    +    if
    +        (
    +            ((G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= 2) ==
    +            (v.boss.showdown or false)
    +        )
    +    then
    +        eligible_bosses[k] = res and true or nil
    +    end
    +elseif not v.boss.showdown and (v.boss.min <= math.max(1, G.GAME.round_resets.ante) and ((math.max(1, G.GAME.round_resets.ante))%G.GAME.win_ante ~= 0 or G.GAME.round_resets.ante < 2)) then
    +    eligible_bosses[k] = res and true or nil
    +elseif v.boss.showdown and (G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= 2 then
    +    eligible_bosses[k] = res and true or nil
    +end
    +'''
    +
    +# G.UIDEF.challenge_description_tab
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = "local temp_blind = AnimatedSprite(0,0,1,1, G.ANIMATION_ATLAS['blind_chips'], v.pos)"
    +position = 'at'
    +match_indent = true
    +payload = "local temp_blind = AnimatedSprite(0,0,1,1, G.ANIMATION_ATLAS[v.atlas or ''] or G.ANIMATION_ATLAS['blind_chips'], v.pos)"
    +
    +## elseif-ify some if chains
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "end\n(?[\t ]*)if self.name == ['\"](?.*?)['\"]"
    +position = 'at'
    +payload = "elseif self.name == '$ability'"
    +
    +# revert the change for The Pillar
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "(?[\t ]*)elseif self.name == 'The Pillar' and card.ability.played_this_ante then"
    +position = 'at'
    +line_prepend = '$indent'
    +payload = '''end
    +if self.name == 'The Pillar' and card.ability.played_this_ante then'''
    +
    +# revert the change for The Eye in debuff_hand
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "(?[\t ]*)elseif self.name == 'The Eye' then"
    +position = 'at'
    +line_prepend = '$indent'
    +payload = '''end
    +if self.name == 'The Eye' then'''
    +
    +# revert the change for The Arm in debuff_hand
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = "(?[\t ]*)elseif self.name == 'The Arm' then"
    +position = 'at'
    +line_prepend = '$indent'
    +payload = '''end
    +if self.name == 'The Arm' then'''
    +
    +# revert the change for Crimson Heart in debuff_card
    +[[patches]]
    +[patches.regex]
    +target = 'blind.lua'
    +pattern = '''(?[\t ]*)elseif self\.name == 'Crimson Heart' and not self\.disabled'''
    +position = 'at'
    +line_prepend = '$indent'
    +payload = '''end
    +if self.name == 'Crimson Heart' and not self.disabled'''
    +
    +### Add blind.effect
    +# Blind.set_blind
    +[[patches]]
    +[patches.pattern]
    +target = "blind.lua"
    +match_indent = true
    +pattern = "self.config.blind = blind or {}"
    +position = "after"
    +payload = '''
    +self.effect = type(self.config.blind.config) == "table" and copy_table(self.config.blind.config) or {}
    +'''
    +# Blind.load
    +[[patches]]
    +[patches.pattern]
    +target = "blind.lua"
    +match_indent = true
    +pattern = "function Blind:load(blindTable)"
    +position = "after"
    +payload = '''
    +self.effect = blindTable.effect
    +'''
    +# Blind.save
    +[[patches]]
    +[patches.pattern]
    +target = "blind.lua"
    +match_indent = true
    +pattern = "local blindTable = {"
    +position = "after"
    +payload = '''
    +effect = self.effect,
    +'''
    \ No newline at end of file
    diff --git a/lovely/objects/vanilla_collection/centers/back.toml b/lovely/objects/vanilla_collection/centers/back.toml
    new file mode 100644
    index 000000000..b6af41f9d
    --- /dev/null
    +++ b/lovely/objects/vanilla_collection/centers/back.toml
    @@ -0,0 +1,207 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Back API
    +
    +# Back:init()
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = "if not selected_back then selected_back = G.P_CENTERS.b_red end"
    +position = 'after'
    +match_indent = true
    +payload = "self.atlas = selected_back.unlocked and selected_back.atlas or nil"
    +
    +# Back:change_to()
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = "if not new_back then new_back = G.P_CENTERS.b_red end"
    +position = 'after'
    +match_indent = true
    +payload = "self.atlas = new_back.unlocked and new_back.atlas or nil"
    +
    +# G.FUNCS.change_viewed_back
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = "G.PROFILES[G.SETTINGS.profile].MEMORY.deck = args.to_val"
    +position = 'after'
    +match_indent = true
    +payload = '''
    +for key, val in pairs(G.sticker_card.area.cards) do
    +	val.children.back = false
    +	val:set_ability(val.config.center, true)
    +end'''
    +
    +# Back:apply_to_run()
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = "function Back:apply_to_run()"
    +position = 'after'
    +match_indent = true
    +payload = '''
    +	local obj = self.effect.center
    +	if obj.apply and type(obj.apply) == 'function' then
    +		obj:apply(self)
    +	end'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = "if self.effect.config.randomize_rank_suit then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +G.E_MANAGER:add_event(Event({
    +	func = function()
    +		G.E_MANAGER:add_event(Event({
    +			func = function()
    +				save_run()
    +				return true
    +			end
    +		}))
    +		return true
    +	end
    +}))
    +'''
    +
    +# Back:trigger_effect(args)
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = "if not args then return end"
    +position = 'after'
    +match_indent = true
    +payload = '''
    +	local obj = self.effect.center
    +	if type(obj.calculate) == 'function' then
    +		local o = {obj:calculate(self, args)}
    +		if next(o) ~= nil then return unpack(o) end
    +	elseif type(obj.trigger_effect) == 'function' then
    +		-- kept for compatibility
    +		local o = {obj:trigger_effect(args)}
    +		if next(o) ~= nil then
    +			sendWarnMessage(('Found `trigger_effect` function on SMODS.Back object "%s". This field is deprecated; please use `calculate` instead.'):format(obj.key), 'Back')
    +			return unpack(o)
    +		end
    +	end'''
    +
    +## Back:generate_UI
    +
    +# Localization with `unlock` field in loc_txt, same as for Jokers
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = 'if not back_config.unlock_condition then'
    +position = 'at'
    +payload = '''
    +local localized_by_smods
    +local key_override
    +if back_config.locked_loc_vars and type(back_config.locked_loc_vars) == 'function' then
    +	local res = back_config:locked_loc_vars() or {}
    +	loc_args = res.vars or {}
    +	key_override = res.key
    +end
    +if G.localization.descriptions.Back[key_override or back_config.key].unlock_parsed then
    +	localize{type = 'unlocks', key = key_override or back_config.key, set = 'Back', nodes = loc_nodes, vars = loc_args}
    +	localized_by_smods = true
    +end
    +if not back_config.unlock_condition then'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = '''localize{type = 'descriptions', key = 'demo_locked', set = "Other", nodes = loc_nodes, vars = loc_args}'''
    +position = 'at'
    +payload = '''
    +if not localized_by_smods then
    +	localize{type = 'descriptions', key = 'demo_locked', set = "Other", nodes = loc_nodes, vars = loc_args}
    +end'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = 'loc_args = {other_name}'
    +position = 'at'
    +payload = 'loc_args = loc_args or {other_name}'
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = 'loc_args = {tostring(back_config.unlock_condition.amount)}'
    +position = 'at'
    +payload = 'loc_args = loc_args or {tostring(back_config.unlock_condition.amount)}'
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = 'loc_args = {other_name, colours = {get_stake_col(back_config.unlock_condition.stake)}}'
    +position = 'at'
    +payload = 'loc_args = loc_args or {other_name, colours = {get_stake_col(back_config.unlock_condition.stake)}}'
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +pattern = "if name_to_check == 'Blue Deck'*"
    +position = 'at'
    +match_indent = true
    +payload = '''
    +local key_override
    +if back_config.loc_vars and type(back_config.loc_vars) == 'function' then
    +	local res = back_config:loc_vars() or {}
    +	loc_args = res.vars or {}
    +	key_override = res.key
    +elseif name_to_check == 'Blue Deck' then loc_args = {effect_config.hands}'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'back.lua'
    +pattern = "key = back_config\\.key"
    +position = 'at'
    +payload = "key = key_override or back_config.key"
    +
    +# Back:apply_to_run() - add jokers support to config
    +[[patches]]
    +[patches.pattern]
    +target = 'back.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +if self.effect.config.voucher then
    +'''
    +payload = '''
    +if self.effect.config.jokers then
    +        delay(0.4)
    +        G.E_MANAGER:add_event(Event({
    +            func = function()
    +                for k, v in ipairs(self.effect.config.jokers) do
    +                    local card = create_card('Joker', G.jokers, nil, nil, nil, nil, v, 'deck')
    +                    card:add_to_deck()
    +                    G.jokers:emplace(card)
    +					card:start_materialize()
    +                end
    +            return true
    +            end
    +        }))
    +    end
    +'''
    +
    +# Load deck when continuing the run
    +# Game.start_run
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = "self.GAME.selected_back = Back(selected_back)"
    +position = 'after'
    +match_indent = true
    +payload = '''
    +if saveTable then
    +    self.GAME.selected_back:load(saveTable.BACK)
    +end
    +'''
    \ No newline at end of file
    diff --git a/lovely/objects/vanilla_collection/centers/booster.toml b/lovely/objects/vanilla_collection/centers/booster.toml
    new file mode 100644
    index 000000000..fa9c00ba6
    --- /dev/null
    +++ b/lovely/objects/vanilla_collection/centers/booster.toml
    @@ -0,0 +1,367 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +## Booster Pack API
    +
    +# Card:open
    +[[patches]]
    +[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
    +
    +        G.GAME.pack_choices = self.config.center.config.choose or 1'''
    +match_indent = true
    +position = "at"
    +payload = """
    +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
    +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)
    +"""
    +
    +# Card:open
    +# Adds modifier for size of booster
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +local _size = self.ability.extra
    +'''
    +payload = '''
    +local _size = math.max(1, self.ability.extra + (G.GAME.modifiers.booster_size_mod or 0))
    +'''
    +
    +
    +# Card:open
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''(?[\t ]*)if self\.ability\.name:find\('Arcana'\) then[\t\n ]*if G\.GAME\.used_vouchers\.v_omen_globe and pseudorandom\('omen_globe'\) > 0\.8 then''' # Possibly try to target something else
    +position = "at"
    +payload = '''if booster_obj.create_card and type(booster_obj.create_card) == "function" then
    +    local _card_to_spawn = booster_obj:create_card(self, i)
    +    if type((_card_to_spawn or {}).is) == 'function' and _card_to_spawn:is(Card) then
    +        card = _card_to_spawn
    +    else
    +        card = SMODS.create_card(_card_to_spawn)
    +    end
    +elseif self.ability.name:find('Arcana') then
    +    if G.GAME.used_vouchers.v_omen_globe and pseudorandom('omen_globe') > 0.8 then'''
    +line_prepend = '$indent'
    +
    +# 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
    +# 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'''
    +
    +# CardArea:draw()
    +[[patches]]
    +[patches.pattern]
    +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'''
    +match_indent = true
    +
    +# G.FUNCS.use_card
    +[[patches]]
    +[patches.pattern]
    +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'''
    +match_indent = true
    +
    +# CardArea:align_cards()
    +[[patches]]
    +[patches.pattern]
    +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"
    +match_indent = true
    +
    +# CardArea:align_cards()
    +[[patches]]
    +[patches.pattern]
    +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"
    +match_indent = true
    +
    +# Card:can_use_consumable()
    +[[patches]]
    +[patches.pattern]
    +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"
    +match_indent = true
    +
    +# G.FUNCS.use_card()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/button_callbacks.lua"
    +pattern = "if G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SPECTRAL_PACK then"
    +position = "at"
    +payload = """
    +if nc then
    +    if area then area:remove_from_highlighted(card) end
    +    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"""
    +match_indent = true
    +
    +# G.FUNC.use_card()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/button_callbacks.lua"
    +pattern = 'if area == G.consumeables then'
    +position = 'before'
    +match_indent = true
    +payload = '''
    +if nc and area == G.pack_cards and not select_to then G.pack_cards:remove_card(card); G.consumeables:emplace(card) end'''
    +
    +# G.FUNC.use_card()
    +[[patches]]
    +[patches.pattern]
    +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'''
    +
    +# G.FUNC.use_card()
    +[[patches]]
    +[patches.pattern]
    +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'''
    +
    +# G.FUNC.use_card()
    +[[patches]]
    +[patches.pattern]
    +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"
    +match_indent = true
    +
    +# G.FUNC.use_card()
    +[[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"
    +match_indent = true
    +
    +# Card:use_consumeable()
    +[[patches]]
    +[patches.regex]
    +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},'''
    +line_prepend = '$indent'
    +
    +# G.FUNCS.use_card()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/button_callbacks.lua"
    +pattern = "e.config.ref_table:redeem()"
    +position = "before"
    +payload = "if area == G.pack_cards then e.config.ref_table.cost = 0 end"
    +match_indent = true
    +
    +## Stopping ease_dollars anim from playing when voucher is free
    +# Card:redeem()
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = '''(?[\t ]*)ease_dollars\(-self\.cost\)\n[\s\S]{8}inc_career_stat\('c_shop_dollars_spent', self\.cost\)'''
    +position = "at"
    +payload = '''
    +if self.cost ~= 0 then
    +    ease_dollars(-self.cost)
    +    inc_career_stat('c_shop_dollars_spent', self.cost)
    +end'''
    +line_prepend = '$indent'
    +
    +# Add support for saving consumables
    +# G.UIDEF.use_and_sell_buttons()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +if card.ability.consumeable then
    +    if (card.area == G.pack_cards and G.pack_cards) then
    +'''
    +payload = '''
    +if card.ability.consumeable and card.area == G.pack_cards and G.pack_cards and booster_obj and SMODS.card_select_area(card, booster_obj) and card:selectable_from_pack(booster_obj) then
    +    if (card.area == G.pack_cards and G.pack_cards) then
    +        local select_button_text = SMODS.get_select_text(card, booster_obj) or localize('b_select')
    +        return {n=G.UIT.ROOT, config = {padding = 0, colour = G.C.CLEAR}, nodes={
    +                {n=G.UIT.R, config={ref_table = card, r = 0.08, padding = 0.1, align = "bm", minw = 0.5*card.T.w - 0.15, maxw = 0.9*card.T.w - 0.15, minh = 0.3*card.T.h, hover = true, shadow = true, colour = G.C.UI.BACKGROUND_INACTIVE, one_press = true, button = 'use_card', func = 'can_select_from_booster'}, nodes={
    +                {n=G.UIT.T, config={text = select_button_text, colour = G.C.UI.TEXT_LIGHT, scale = 0.45, shadow = true}}
    +                }},
    +            }}
    +    end
    +end
    +'''
    +
    +# G.FUNCS.use_card()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if card.ability.consumeable then
    +      if nc then
    +'''
    +payload = '''
    +if select_to then
    +    card:add_to_deck()
    +    G[select_to]:emplace(card)
    +    if card.config.center.on_select and type(card.config.center.on_select) == 'function' then
    +        card.config.center:on_select(card)
    +    end
    +    play_sound('card1', 0.8, 0.6)
    +    play_sound('generic1')
    +    dont_dissolve = true
    +    delay_fac = 0.2
    +elseif card.ability.consumeable then
    +      if nc then
    +'''
    +# G.FUNCS.end_consumeable()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +for i = 1, #G.GAME.tags do
    +    if G.GAME.tags[i]:apply_to_run({type = 'new_blind_choice'}) then break end
    +end
    +
    +G.E_MANAGER:add_event(Event({trigger = 'after',delay = 0.2*delayfac,
    +          func = function()
    +            save_run()
    +            return true
    +      end}))
    +'''
    +payload = '''
    +booster_obj = nil
    +'''
    +# G.FUNCS.skip_booster()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +G.FUNCS.end_consumeable(e)
    +'''
    +payload = '''
    +booster_obj = nil
    +'''
    +
    +# G.FUNCS.can_select_card
    +# Support negative-ish on Jokers
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = "if e.config.ref_table.ability.set ~= 'Joker' or (e.config.ref_table.edition and e.config.ref_table.edition.negative) or #G.jokers.cards < G.jokers.config.card_limit then"
    +payload = '''local card = e.config.ref_table
    +local card_limit = card.ability.card_limit - card.ability.extra_slots_used
    +if card.ability.set ~= 'Joker' or #G.jokers.cards < G.jokers.config.card_limit + card_limit then'''
    +
    +# Card:redeem()
    +# Specific G.STATE for when a Voucher is redeemed
    +[[patches]]
    +[patches.pattern]
    +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'''
    +
    +# G.FUNCS.use_card()
    +# Consumables in areas other than the consumable don't count 
    +# as picking an item in boosters when used
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if area == G.consumeables then
    +'''
    +payload = '''
    +if area ~= G.pack_cards then
    +'''
    diff --git a/lovely/objects/vanilla_collection/centers/center.toml b/lovely/objects/vanilla_collection/centers/center.toml
    new file mode 100644
    index 000000000..9625c41e8
    --- /dev/null
    +++ b/lovely/objects/vanilla_collection/centers/center.toml
    @@ -0,0 +1,639 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Center API
    +
    +# Card:set_ability()
    +[[patches]]
    +[patches.regex]
    +target = "card.lua"
    +pattern = "(?[\t ]*)if not G\\.OVERLAY_MENU then \n"
    +position = 'before'
    +payload = '''
    +local obj = self.config.center
    +if obj.set_ability and type(obj.set_ability) == 'function' then
    +    obj:set_ability(self, initial, delay_sprites)
    +end
    +
    +'''
    +line_prepend = '$indent'
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = "self.ability.bonus = (self.ability.bonus or 0) + (center.config.bonus or 0)"
    +position = "after"
    +payload = """
    +for k, v in pairs(center.config) do
    +    if k ~= 'bonus' then
    +        if type(v) == 'table' then
    +            self.ability[k] = copy_table(v)
    +        else
    +            self.ability[k] = v
    +        end
    +    end
    +end"""
    +match_indent = true
    +
    +# Card:calculate_joker()
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''function Card:calculate_joker(context)
    +    if self.debuff then return nil end
    +'''
    +position = 'after'
    +payload = '''
    +    local obj = self.config.center
    +    if self.ability.set ~= "Enhanced" and obj.calculate and type(obj.calculate) == 'function' then
    +        local o, t = obj:calculate(self, context)
    +        if o or t then return o, t end
    +    end'''
    +match_indent = true
    +
    +# Card:update()
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = 'if G.STAGE == G.STAGES.RUN then'
    +position = 'before'
    +match_indent = true
    +payload = '''
    +local obj = self.config.center
    +if obj.update and type(obj.update) == 'function' then
    +    obj:update(self, dt)
    +end'''
    +
    +# Card:generate_UIBox_ability_table()
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +pattern = "(?else)\n[\t ]*if self.ability.name == 'Loyalty Card' then\n[\t ]*self.ability.loyalty_remaining"
    +root_capture = 'else'
    +position = 'at'
    +payload = 'elseif context.joker_main then'
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'card.lua'
    +pattern = 'return generate_card_ui(self.config.center, nil, loc_vars, card_type, badges, hide_desc, main_start, main_end)'
    +position = 'at'
    +match_indent = true
    +payload = 'return generate_card_ui(self.config.center, nil, loc_vars, card_type, badges, hide_desc, main_start, main_end, self)'
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = "full_UI_table.name = localize{type = 'name', set = _c.set, key = _c.key, nodes = full_UI_table.name}"
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if not _c.generate_ui or type(_c.generate_ui) ~= 'function' then
    +    full_UI_table.name = localize{type = 'name', set = _c.set, key = _c.key, nodes = full_UI_table.name}
    +end'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = "elseif specific_vars and specific_vars.debuffed then"
    +position = 'before'
    +match_indent = true
    +payload = '''
    +elseif _c.generate_ui and type(_c.generate_ui) == 'function' then
    +    local specific_vars = specific_vars or {}
    +    _c:generate_ui(info_queue, card, desc_nodes, specific_vars, full_UI_table)
    +    if desc_nodes ~= full_UI_table.main and not desc_nodes.name then
    +        -- TODO should be moved into generate_ui;
    +        -- also the multiple generate_ui cases should be refactored
    +        -- to work off a base implementation
    +        desc_nodes.name_styled = {}
    +        local set = name_override and "Other" or _c.set
    +        local key = name_override or _c.key
    +        if set == "Seal" then
    +            if G.localization.descriptions["Other"][_c.key.."_seal"] then set = "Other"; key = key.."_seal" end
    +        else
    +            if not G.localization.descriptions[set] or not G.localization.descriptions[set][_c.key] then set = "Other" end
    +        end
    +
    +        localize{type = 'name', key = key, set = set, nodes = desc_nodes.name_styled, fixed_scale = 0.63, no_pop_in = true, no_shadow = true, y_offset = 0, no_spacing = true, no_bump = true, vars = (_c.create_fake_card and _c.loc_vars and (_c:loc_vars({}, _c:create_fake_card()) or {}).vars) or {colours = {}}} 
    +        desc_nodes.name_styled = SMODS.info_queue_desc_from_rows(desc_nodes.name_styled, true)
    +        desc_nodes.name_styled.config.align = "cm"
    +    end
    +    if specific_vars and specific_vars.pinned then info_queue[#info_queue+1] = {key = 'pinned_left', set = 'Other'} end
    +    if specific_vars and specific_vars.sticker then info_queue[#info_queue+1] = {key = string.lower(specific_vars.sticker)..'_sticker', set = 'Other'} end'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/common_events.lua'
    +pattern = "(?[\t ]+)if (?_c.name == 'Golden Ticket' then)"
    +line_prepend = '$indent'
    +position = 'at'
    +payload = '''
    +local res = {}
    +if _c.locked_loc_vars and type(_c.locked_loc_vars) == 'function' then
    +    local _card = _c.create_fake_card and _c:create_fake_card()
    +    res = _c:locked_loc_vars(info_queue, _card) or {}
    +    loc_vars = res.vars or {}
    +    specific_vars = specific_vars or {}
    +    specific_vars.not_hidden = res.not_hidden or specific_vars.not_hidden
    +    if res.main_start then desc_nodes[#desc_nodes+1] = res.main_start end
    +    main_end = res.main_end or main_end
    +elseif $rest'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +position = 'at'
    +match_indent = true
    +pattern = "localize{type = 'unlocks', key = 'joker_locked_legendary', set = 'Other', nodes = desc_nodes, vars = loc_vars}"
    +payload = "localize{type = 'unlocks', key = res.key or 'joker_locked_legendary', set = res.set or 'Other', nodes = desc_nodes, vars = loc_vars, text_colour = res.text_colour, scale = res.scale}"
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +position = 'at'
    +match_indent = true
    +pattern = "localize{type = 'unlocks', key = _c.key, set = _c.set, nodes = desc_nodes, vars = loc_vars}"
    +payload = "localize{type = 'unlocks', key = res.key or _c.key, set = res.set or _c.set, nodes = desc_nodes, vars = loc_vars, text_colour = res.text_colour, scale = res.scale}"
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +position = 'at'
    +match_indent = true
    +pattern = 'elseif desc_nodes ~= full_UI_table.main then'
    +payload = 'elseif desc_nodes ~= full_UI_table.main and not desc_nodes.name then'
    +
    +
    +# check_for_unlock()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/common_events.lua'
    +pattern = "(?[\t ]*)if not card.unlocked and card.unlock_condition and args.type == 'career_stat' then"
    +line_prepend = '$indent'
    +position = 'before'
    +payload = '''
    +
    +local custom_check
    +if not card.unlocked and card.check_for_unlock and type(card.check_for_unlock) == 'function' then
    +    ret = card:check_for_unlock(args)
    +    if ret then unlock_card(card) end
    +    custom_check = true
    +end'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/common_events.lua'
    +pattern = "(?[\t ]*)if(? )not card.unlocked and card.unlock_condition and args.type == 'career_stat' then"
    +position = 'at'
    +root_capture = 'a'
    +payload = ' not custom_check and '
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/common_events.lua'
    +pattern = "(?[\t ]*)if(? )not card.unlocked and card.unlock_condition and card.unlock_condition.type == args.type then"
    +position = 'at'
    +root_capture = 'a'
    +payload = ' not custom_check and '
    +
    +#Card:use_consumable()
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +pattern = "(?[\t ]*)if self.ability.consumeable.mod_conv or self.ability.consumeable.suit_conv then"
    +line_prepend = '$indent'
    +position = 'before'
    +payload = '''
    +local obj = self.config.center
    +if obj.use and type(obj.use) == 'function' then
    +    obj:use(self, area, copier)
    +    return
    +end'''
    +
    +# Card:can_use_consumable()
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +pattern = "(?[\t ]*)if self.ability.name == 'The Hermit' or self.ability.consumeable.hand_type"
    +line_prepend = '$indent'
    +position = 'before'
    +payload = '''
    +local obj = self.config.center
    +if obj.can_use and type(obj.can_use) == 'function' then
    +    return obj:can_use(self)
    +end'''
    +
    +# G.UIDEF.card_h_popup()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = "(?[\t ]*)(?if AUT.badges.card_type or AUT.badges.force_rarity then)\n[\t ]*(?.*)\n[\t ]*end"
    +line_prepend = '$indent'
    +position = 'at'
    +payload = '''
    +local obj = card.config.center
    +$if
    +    if obj and (obj.set_card_type_badge or obj.type and obj.type.set_card_type_badge) then
    +        if obj.type and type(obj.type.set_card_type_badge) == 'function' then
    +            obj.type:set_card_type_badge(obj, card, badges)
    +        end
    +        if type(obj.set_card_type_badge) == 'function' then
    +            obj:set_card_type_badge(card, badges)
    +        end
    +    else
    +        $rest
    +    end
    +end
    +if obj and obj.set_badges and type(obj.set_badges) == 'function' then
    +    obj:set_badges(card, badges)
    +end'''
    +
    +# mod badges
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +if AUT.info then
    +'''
    +payload = '''
    +if AUT.card_type ~= 'Locked' and AUT.card_type ~= 'Undiscovered' then
    +    SMODS.create_mod_badges(card.config.center, badges)
    +    if card.base then
    +        SMODS.create_mod_badges(SMODS.Ranks[card.base.value], badges)
    +        SMODS.create_mod_badges(SMODS.Suits[card.base.suit], badges)
    +    end
    +    if card.config and card.config.tag then
    +        SMODS.create_mod_badges(SMODS.Tags[card.config.tag.key], badges)
    +    end
    +    badges.mod_set = nil
    +end
    +'''
    +
    +# set_discover_tallies()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +pattern = "(?[\t ]*)if v.set == 'Planet' then(\n[\t ]*.*){15}"
    +line_prepend = '$indent'
    +position = 'at'
    +payload = '''
    +local tally = G.DISCOVER_TALLIES[v.set:lower()..'s']
    +if tally then
    +    tally.of = tally.of + 1
    +    if v.discovered then
    +        tally.tally = tally.tally + 1
    +    end
    +end'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +pattern = "[\t ]*tarots = \\{tally = 0, of = 0\\},\n(.*\n){2}"
    +line_prepend = '$indent'
    +position = 'at'
    +payload = ''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +pattern = "(?[\t ]*)for _, v in pairs\\(G.DISCOVER_TALLIES\\) do"
    +line_prepend = '$indent'
    +position = 'before'
    +payload = '''
    +for _, v in ipairs(SMODS.ConsumableType.obj_buffer) do
    +    G.DISCOVER_TALLIES[v:lower()..'s'] = {tally = 0, of = 0}
    +end'''
    +
    +# create_UIBox_your_collection()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = "(?[\t ]*)local t = create_UIBox_generic_options\\(\\{ back_func = G.STAGE"
    +line_prepend = '$indent'
    +position = 'before'
    +payload = '''
    +local consumable_nodes = {}
    +if #SMODS.ConsumableType.visible_buffer <= 3 then
    +    for _, key in ipairs(SMODS.ConsumableType.visible_buffer) do
    +        local id = 'your_collection_'..key:lower()..'s'
    +        consumable_nodes[#consumable_nodes+1] = UIBox_button({button = id, label = {localize('b_'..key:lower()..'_cards')}, count = G.DISCOVER_TALLIES[key:lower()..'s'], minw = 4, id = id, colour = G.C.SECONDARY_SET[key]})
    +    end
    +else
    +    consumable_nodes[#consumable_nodes+1] = UIBox_button({ button = 'your_collection_consumables', label = {localize('b_stat_consumables'), localize{ type = 'variable', key = 'c_types', vars = {#SMODS.ConsumableType.visible_buffer} } }, count = G.DISCOVER_TALLIES['consumeables'], minw = 4, minh = 4, id = 'your_collection_consumables', colour = G.C.FILTER })
    +end
    +'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = "(?[\t ]*)nodes=\\{\n[\t ]*UIBox_button\\(\\{button = 'your_collection_tarots'(.*\n){3}[\t ]*}"
    +line_prepend = '$indent'
    +position = 'at'
    +payload = 'nodes = consumable_nodes'
    +
    +# Card:apply_to_run()
    +[[patches]]
    +[patches.regex]
    +target = 'card.lua'
    +pattern = "(?[\t ]*)if center_table.name == 'Overstock'"
    +line_prepend = '$indent'
    +position = 'before'
    +payload = '''
    +local obj = center or self.config.center
    +if obj.redeem and type(obj.redeem) == 'function' then
    +    obj:redeem(card_to_save)
    +    return
    +end'''
    +
    +# create_card_for_shop()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = "local total_rate = G.GAME.joker_rate + G.GAME.tarot_rate + G.GAME.planet_rate + G.GAME.playing_card_rate + G.GAME.spectral_rate"
    +match_indent = true
    +position = 'at'
    +payload = '''
    +local total_rate = G.GAME.joker_rate + G.GAME.playing_card_rate
    +for _,v in ipairs(SMODS.ConsumableType.obj_buffer) do
    +    total_rate = total_rate + G.GAME[v:lower()..'_rate']
    +end'''
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = '(?[\t ]*)for _, v in ipairs\((?
  • \{\n(.*\n){5}[\t ]*\})\) do' +line_prepend = '$indent' +position = 'at' +payload = ''' +-- need to preserve order to leave RNG unchanged +local rates = $li +for _, v in ipairs(SMODS.ConsumableType.obj_buffer) do + if not (v == 'Tarot' or v == 'Planet' or v == 'Spectral') then + table.insert(rates, { type = v, val = G.GAME[v:lower()..'_rate'] }) + end +end +for _, v in ipairs(rates) do''' + +# create_card() +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "if not forced_key and soulable and (not G.GAME.banned_keys['c_soul']) then" +match_indent = true +position = 'after' +payload = ''' + local soul_total_rate = 0 + local non_soul_rate = 1 + local modded_souls = {} + for _, v in ipairs(SMODS.Consumable.legendaries) do + if (_type == v.type.key or _type == v.soul_set) and not (G.GAME.used_jokers[v.key] and not SMODS.showman(v.key) and not v.can_repeat_soul) and SMODS.add_to_pool(v) then + soul_total_rate = soul_total_rate + v.soul_rate + non_soul_rate = non_soul_rate * (1 - v.soul_rate) + non_soul_rate = math.max(non_soul_rate, 0) + table.insert(modded_souls, v) + end + end + local roll = pseudorandom('soul_smods_'.._type..G.GAME.round_resets.ante) + local threshold = 1 + for _, v in ipairs(modded_souls) do + threshold = threshold - v.soul_rate/soul_total_rate * (1-non_soul_rate) + if roll > threshold then + forced_key = v.key + break + end + end''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "local card = Card(area.T.x + area.T.w/2, area.T.y, G.CARD_W, G.CARD_H, front, center," +match_indent = true +position = 'at' +payload = ''' +local card = Card(area.T.x + area.T.w/2, area.T.y, G.CARD_W, G.CARD_H, SMODS.set_create_card_front or front, center,''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "{bypass_discovery_center = area==G.shop_jokers or area == G.pack_cards or area == G.shop_vouchers or (G.shop_demo and area==G.shop_demo) or area==G.jokers or area==G.consumeables," +match_indent = true +position = 'at' +payload = ''' +{bypass_discovery_center = SMODS.bypass_create_card_discovery_center or area==G.shop_jokers or area == G.pack_cards or area == G.shop_vouchers or (G.shop_demo and area==G.shop_demo) or area==G.jokers or area==G.consumeables,''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "bypass_discovery_ui = area==G.shop_jokers or area == G.pack_cards or area==G.shop_vouchers or (G.shop_demo and area==G.shop_demo)," +match_indent = true +position = 'at' +payload = ''' +bypass_discovery_ui = SMODS.bypass_create_card_discovery_center or area==G.shop_jokers or area == G.pack_cards or area==G.shop_vouchers or (G.shop_demo and area==G.shop_demo),''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "discover = area==G.jokers or area==G.consumeables, " +match_indent = true +position = 'at' +payload = ''' +discover = SMODS.bypass_create_card_discover or area==G.jokers or area==G.consumeables, ''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = '''not (G.GAME.used_jokers['c_soul'] and not next(find_joker("Showman"))) then''' +match_indent = true +position = 'at' +payload = ''' +not (G.GAME.used_jokers['c_soul'] and not SMODS.showman('c_soul')) then''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = '''not (G.GAME.used_jokers['c_black_hole'] and not next(find_joker("Showman"))) then''' +match_indent = true +position = 'at' +payload = ''' +not (G.GAME.used_jokers['c_black_hole'] and not SMODS.showman('c_black_hole')) then''' + +# Fix vanilla copy_card back bug +# copy_card() +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = ''' +local new_card = new_card or Card(other.T.x, other.T.y, G.CARD_W*(card_scale or 1), G.CARD_H*(card_scale or 1), G.P_CARDS.empty, G.P_CENTERS.c_base, {playing_card = playing_card}) +''' +position = "at" +payload = ''' +local new_card = new_card or Card(other.T.x, other.T.y, G.CARD_W*(card_scale or 1), G.CARD_H*(card_scale or 1), G.P_CARDS.empty, G.P_CENTERS.c_base, {playing_card = playing_card, bypass_back = G.GAME.selected_back.pos}) +''' +match_indent = true + +# Card:add_to_deck() +[[patches]] +[patches.regex] +target = 'card.lua' +pattern = '(?[\t ]*)if self.ability.h_size ~= 0 then\n[\t ]*G\.hand:change_size\(self.ability.h_size\)' +line_prepend = '$indent' +position = 'before' +payload = ''' +local obj = self.config.center +if obj and obj.add_to_deck and type(obj.add_to_deck) == 'function' then + obj:add_to_deck(self, from_debuff) +end''' + +# Card:remove_from_deck() +[[patches]] +[patches.regex] +target = 'card.lua' +pattern = '(?[\t ]*)if self.ability.h_size ~= 0 then\n[\t ]*G\.hand:change_size\(-self.ability.h_size\)' +line_prepend = '$indent' +position = 'before' +payload = ''' +local obj = self.config.center +if obj and obj.remove_from_deck and type(obj.remove_from_deck) == 'function' then + obj:remove_from_deck(self, from_debuff) +end''' + +# G.FUNCS.use_card() +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = ''' +if G.booster_pack and not G.booster_pack.alignment.offset.py and (card.ability.consumeable or not (G.GAME.pack_choices and G.GAME.pack_choices > 1)) then +''' +payload = ''' +local nc +local select_to = card.area == G.pack_cards and G.pack_cards and booster_obj and SMODS.card_select_area(card, booster_obj) and card:selectable_from_pack(booster_obj) +if card.ability.consumeable and not select_to then + local obj = card.config.center + if obj.keep_on_use and type(obj.keep_on_use) == 'function' then + nc = obj:keep_on_use(card) + end +end +if G.booster_pack and not G.booster_pack.alignment.offset.py and ((not select_to and card.ability.consumeable) or not (G.GAME.pack_choices and G.GAME.pack_choices > 1)) then + +''' + + +# G.FUNCS.use_card() +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +pattern = "if card.area then card.area:remove_card(card) end" +match_indent = true +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' +match_indent = true +position = 'before' +pattern = ''' +if area and area.cards[1] then +''' +payload = ''' +if nc and not area then G.consumeables:emplace(card) end +''' +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +pattern = "else draw_card(G.hand, G.play, 1, 'up', true, card, nil, mute) end" +match_indent = true +position = 'at' +payload = '''elseif not nc then draw_card(G.hand, G.play, 1, 'up', true, card, nil, mute) end''' + + +# Card:load() +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = 'if self.config.center.name == "Half Joker" then' +match_indent = true +position = 'at' +payload = ''' +local obj = self.config.center +if obj.load and type(obj.load) == 'function' then + obj:load(self, cardTable, other_card) +elseif self.config.center.name == "Half Joker" then''' + +# Card:calculate_dollar_bonus() +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = """function Card:calculate_dollar_bonus() + if self.debuff then return end""" +position = "after" +match_indent = true +payload = ''' + local obj = self.config.center + if obj.calc_dollar_bonus and type(obj.calc_dollar_bonus) == 'function' then + return obj:calc_dollar_bonus(self) + end +''' + +# extract joker loc_vars +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'at' +pattern = 'function Card:generate_UIBox_ability_table()' +payload = 'function Card:generate_UIBox_ability_table(vars_only)' + +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'before' +pattern = 'local badges = {}' +payload = 'if vars_only then return loc_vars, main_start, main_end end' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +match_indent = true +position = 'after' +pattern = "elseif _c.set == 'Joker' then" +payload = ''' + if not card then + local ability = copy_table(_c.config) + ability.set = 'Joker' + ability.name = _c.name + -- temporary stopgap. fake cards should be implemented better + ability.x_mult = _c['config'].Xmult or _c['config'].x_mult + if ability.name == 'To Do List' then + ability.to_do_poker_hand = "High Card" -- fallback + end + local ret = {Card.generate_UIBox_ability_table({ ability = ability, config = { center = _c }, bypass_lock = true}, true)} + specific_vars = ret[1] + if ret[2] then desc_nodes[#desc_nodes+1] = ret[2] end + main_end = ret[3] + end + ''' + +## Handle tracking cusotm consumable usage +[[patches]] +[patches.pattern] +target = 'functions/misc_functions.lua' +pattern = ''' +elseif card.config.center.set == 'Spectral' then G.GAME.consumeable_usage_total.spectral = G.GAME.consumeable_usage_total.spectral + 1 +''' +position = 'after' +payload = ''' +else + G.GAME.consumeable_usage_total[card.config.center.set:lower()] = G.GAME.consumeable_usage_total[card.config.center.set:lower()] or 0 + G.GAME.consumeable_usage_total[card.config.center.set:lower()] = G.GAME.consumeable_usage_total[card.config.center.set:lower()] + 1 +''' +match_indent = true \ No newline at end of file diff --git a/lovely/objects/vanilla_collection/centers/edition.toml b/lovely/objects/vanilla_collection/centers/edition.toml new file mode 100644 index 000000000..c64a50c3c --- /dev/null +++ b/lovely/objects/vanilla_collection/centers/edition.toml @@ -0,0 +1,219 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +# Fix debug mode edition cycling +[[patches]] +[patches.regex] +target = "engine/controller.lua" +pattern = ''' +(?[\t ]*)local _edition = \{ +[\t ]*foil = not _card\.edition, +[\t ]*holo = _card\.edition and _card\.edition\.foil, +[\t ]*polychrome = _card\.edition and _card\.edition\.holo, +[\t ]*negative = _card\.edition and _card\.edition\.polychrome, +[\t ]*\}''' +position = "at" +payload = ''' +local found_index = 1 +if _card.edition then + for i, v in ipairs(G.P_CENTER_POOLS.Edition) do + if v.key == _card.edition.key then + found_index = i + break + end + end +end +found_index = found_index + 1 +if found_index > #G.P_CENTER_POOLS.Edition then found_index = found_index - #G.P_CENTER_POOLS.Edition end +local _edition = G.P_CENTER_POOLS.Edition[found_index].key''' +line_prepend = "$indent" + + +# Sort P_CENTER_POOLS["Editions"] +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = 'table.sort(self.P_CENTER_POOLS["Enhanced"], function (a, b) return a.order < b.order end)' +position = 'after' +payload = 'table.sort(self.P_CENTER_POOLS["Edition"], function (a, b) return a.order < b.order end)' +match_indent = true + + +# generate_card_ui() +# Adds tooltips for all editions +[[patches]] +[patches.regex] +target = 'functions/common_events.lua' +pattern = ''' +(?[\t ]*)if v == 'foil' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_foil'\] end +[\t ]*if v == 'holographic' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_holo'\] end +[\t ]*if v == 'polychrome' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_polychrome'\] end +[\t ]*if v == 'negative' then info_queue\[#info_queue\+1\] = G\.P_CENTERS\['e_negative'\] end +[\t ]*if v == 'negative_consumable' then info_queue\[#info_queue\+1\] = \{key = 'e_negative_consumable', set = 'Edition', config = \{extra = 1\}\} end''' +position = 'at' +payload = ''' +v = (v == 'holographic' and 'holo' or v) +local ed_key = v +if v:sub(v:len()-14) == '_SMODS_INTERNAL' then + if v:sub(1, 9) == 'negative_' then ed_key = 'negative' else ed_key = v:sub(1, v:find('_', v:find('_')+1)-1) end + v = v:sub(1, v:len()-15) +end + +if G.P_CENTERS[ed_key] and G.P_CENTERS[ed_key].set == 'Edition' then + info_queue[#info_queue + 1] = G.P_CENTERS[ed_key] +end +if G.P_CENTERS['e_'..ed_key] and G.P_CENTERS['e_'..ed_key].set == 'Edition' then + local t = {key = 'e_'..v, set = 'Edition', config = {}} + if localize(SMODS.merge_defaults(t, {type = 'name_text'})) == 'ERROR' then t.key = 'e_'..ed_key end + info_queue[#info_queue + 1] = t + if G.P_CENTERS['e_'..ed_key].loc_vars and type(G.P_CENTERS['e_'..ed_key].loc_vars) == 'function' then + local res = G.P_CENTERS['e_'..ed_key]:loc_vars(info_queue, card) or {} + t.vars = res.vars + t.key = res.key or t.key + t.set = res.set or t.set + end +end''' +line_prepend = "$indent" +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = '''localize{type = 'descriptions', key = _c.key, set = _c.set, nodes = desc_nodes, vars = loc_vars}''' +position = 'at' +match_indent = true +payload = '''localize{type = 'descriptions', key = _c.key, set = _c.set, nodes = desc_nodes, vars = _c.vars or loc_vars}''' + + +# get_badge_colour() +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}' +position = 'before' +match_indent = true +payload = ''' +for _, v in ipairs(G.P_CENTER_POOLS.Edition) do + G.BADGE_COL[v.key:sub(3)] = v.badge_colour +end''' + +# Remove prefix from shader key when calling send() +[[patches]] +[patches.pattern] +target = "engine/sprite.lua" +pattern = "if _send then G.SHADERS[_shader or 'dissolve']:send(_shader,_send) end" +position = "at" +payload = ''' +if _send then + G.SHADERS[_shader or 'dissolve']:send((SMODS.Shaders[_shader or 'dissolve'] and SMODS.Shaders[_shader or 'dissolve'].original_key) or _shader,_send) +end''' +match_indent = true + +# Inject change to edition cost in shop +[[patches]] +[patches.regex] +target = "card.lua" +pattern = '(?[\t ]*)self.ex([a-z._\s=+(0-9)]*)\n([\t ]*)([a-z._\s=+(0-9)]*)or 0\)' +position = "at" +payload = ''' +for k, v in pairs(G.P_CENTER_POOLS.Edition) do + if self.edition[v.key:sub(3)] then + if v.extra_cost then + self.extra_cost = self.extra_cost + v.extra_cost + end + end +end''' +line_prepend = "$indent" + +# Card:save() +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "added_to_deck = self.added_to_deck," +position = "after" +payload = "joker_added_to_deck_but_debuffed = self.joker_added_to_deck_but_debuffed," +match_indent = true + +## Buying card_limit cards fix +# G.FUNCS.check_for_buy_space +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +pattern = " not (card.ability.consumeable and #G.consumeables.cards < G.consumeables.config.card_limit + ((card.edition and card.edition.negative) and 1 or 0)) then" +position = "at" +match_indent = true +payload = ''' + not (card.ability.consumeable and #G.consumeables.cards + (1 + card.ability.extra_slots_used) <= G.consumeables.config.card_limit + card.ability.card_limit) then +''' +times = 1 + +# G.FUNCS.check_for_buy_space +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +pattern = "not (card.ability.set == 'Joker' and #G.jokers.cards < G.jokers.config.card_limit + ((card.edition and card.edition.negative) and 1 or 0)) and" +position = "at" +match_indent = true +payload = ''' + not (card.ability.set == 'Joker' and #G.jokers.cards + (1 + card.ability.extra_slots_used) <= G.jokers.config.card_limit + card.ability.card_limit) and +''' +times = 1 + +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = ''' +if self.edition.type == 'negative' and self.ability.consumeable then + badges[#badges + 1] = 'negative_consumable' +''' +position = "at" +payload = """ +if self.edition.card_limit then + badges[#badges + 1] = SMODS.Edition.get_card_limit_key(self) +""" +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/sprite.lua" +pattern = "love.graphics.setShader( G.SHADERS[_shader or 'dissolve'], G.SHADERS[_shader or 'dissolve'])" +position = "before" +payload = ''' +local p_shader = SMODS.Shader.obj_table[_shader or 'dissolve'] +if p_shader and type(p_shader.send_vars) == "function" then + local sh = G.SHADERS[_shader or 'dissolve'] + local parent_card = self.role.major and self.role.major:is(Card) and self.role.major + local send_vars = p_shader.send_vars(self, parent_card) + + if type(send_vars) == "table" then + for key, value in pairs(send_vars) do + sh:send(key, value) + end + end +end +''' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "if v == 'negative_consumable' then v = 'negative' end" +position = "at" +payload = ''' +if v:sub(v:len()-14) == '_SMODS_INTERNAL' then + if v:sub(1, 9) == 'negative_' then v = 'negative' else v = v:sub(1, v:find('_', v:find('_')+1)-1) end +end +''' +match_indent = true + +# +[[patches]] +[patches.regex] +target = 'functions/common_events.lua' +pattern = '''(?[\t ]*)(?local edition = poll_edition\('edi'\.\.\(key_append or ''\)\.\.G\.GAME\.round_resets\.ante\)(\n.*){2})''' +position = 'at' +line_prepend = '$indent' +payload = ''' +if not SMODS.bypass_create_card_edition and not card.edition then + $edi +end''' diff --git a/lovely/objects/vanilla_collection/centers/enhancement.toml b/lovely/objects/vanilla_collection/centers/enhancement.toml new file mode 100644 index 000000000..de71a9c8f --- /dev/null +++ b/lovely/objects/vanilla_collection/centers/enhancement.toml @@ -0,0 +1,364 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +## no_rank, no_suit, all_suits + +# Card:get_id() +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if self.ability.effect == 'Stone Card' and not self.vampired then" +match_indent = true +position = "at" +payload = '''if SMODS.has_no_rank(self) and not self.vampired then''' + +# Card:get_chip_bonus() +[[patches]] +[patches.regex] +target = "card.lua" +pattern = ''' +(?[\t ]*)if self\.ability\.effect == 'Stone Card' then +[\t ]* return self\.ability\.bonus \+ \(self\.ability\.perma_bonus or 0\) +[\t ]*end''' +position = "at" +payload = '''if self.ability.effect == 'Stone Card' or self.config.center.replace_base_card then + return self.ability.bonus + (self.ability.perma_bonus or 0) +end''' +line_prepend = '$indent' + +# Card:calculate_joker() +# Raised Fist +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if temp_ID >= G.hand.cards[i].base.id and G.hand.cards[i].ability.effect ~= 'Stone Card' then temp_Mult = G.hand.cards[i].base.nominal; temp_ID = G.hand.cards[i].base.id; raised_card = G.hand.cards[i] end" +match_indent = true +position = "at" +payload = """if temp_ID >= G.hand.cards[i].base.id and not SMODS.has_no_rank(G.hand.cards[i]) then + temp_Mult = G.hand.cards[i].base.nominal + temp_ID = G.hand.cards[i].base.id + raised_card = G.hand.cards[i] +end""" +# Flower Pot, Seeing Double +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if context.scoring_hand[i].ability.name ~= 'Wild Card' then" +match_indent = true +position = "at" +payload = '''if not SMODS.has_any_suit(context.scoring_hand[i]) then''' + +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if context.scoring_hand[i].ability.name == 'Wild Card' then" +match_indent = true +position = "at" +payload = '''if SMODS.has_any_suit(context.scoring_hand[i]) then''' + +# Card:get_suit() +[[patches]] +[patches.regex] +target = "card.lua" +pattern = '''(?[\t ]*)if self\.ability\.effect == 'Stone Card' then''' +line_prepend = '$indent' +position = "at" +payload = '''if SMODS.has_no_suit(self) then''' +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = 'if self.ability.name == "Wild Card" then' +match_indent = true +position = "at" +payload = '''if SMODS.has_any_suit(self) then''' +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = 'if self.ability.name == "Wild Card" and not self.debuff then' +match_indent = true +position = "at" +payload = '''if SMODS.has_any_suit(self) and self:can_calculate() then''' + +# check_for_unlock +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if v.ability.name ~= 'Stone Card' and v.base.suit == 'Hearts' then" +match_indent = true +position = "at" +payload = "if not SMODS.has_no_suit(v) and v.base.suit == 'Hearts' then" + +# reset_idol_card() +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "valid_idol_cards[#valid_idol_cards+1] = v" +match_indent = true +position = "at" +payload = """if not SMODS.has_no_suit(v) and not SMODS.has_no_rank(v) then + valid_idol_cards[#valid_idol_cards+1] = v +end""" + +# reset_mail_rank() +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "valid_mail_cards[#valid_mail_cards+1] = v" +match_indent = true +position = "at" +payload = """if not SMODS.has_no_rank(v) then + valid_mail_cards[#valid_mail_cards+1] = v +end""" + +# reset_castle_card() +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "valid_castle_cards[#valid_castle_cards+1] = v" +match_indent = true +position = "at" +payload = """if not SMODS.has_no_suit(v) then + valid_castle_cards[#valid_castle_cards+1] = v +end""" + +# G.FUNCS.evaluate_play() +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "if scoring_hand[i].ability.effect ~= 'Stone Card' then" +match_indent = true +position = "at" +payload = '''if not SMODS.has_no_rank(scoring_hand[i]) then''' +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "G.GAME.cards_played[scoring_hand[i].base.value].suits[scoring_hand[i].base.suit] = true" +match_indent = true +position = "at" +payload = """if not SMODS.has_no_suit(scoring_hand[i]) then + G.GAME.cards_played[scoring_hand[i].base.value].suits[scoring_hand[i].base.suit] = true +end""" + +# Add the delayed property to sprites that are delayed +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'at' +pattern = ''' +if delay_sprites then + G.E_MANAGER:add_event(Event({ + func = function() + if not self.REMOVED then + self:set_sprites(center) + end + return true + end + })) +''' +payload = ''' +if delay_sprites == 'quantum' or delay_sprites == 'manual' then +elseif delay_sprites then + self.ability.delayed = true + G.E_MANAGER:add_event(Event({ + func = function() + if not self.REMOVED then + self:set_sprites(center) + self.ability.delayed = false + end + return true + end + })) +''' + + + +# Card:generate_UIBox_ability_table() +# replaces two consecutive lines +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if (_c.name == 'Stone Card') then full_UI_table.name = true end" +match_indent = true +position = "at" +payload = "if _c.name == 'Stone Card' or _c.replace_base_card then full_UI_table.name = true" +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if (specific_vars.playing_card and (_c.name ~= 'Stone Card')) then" +match_indent = true +position = "at" +payload = "elseif specific_vars.playing_card then" + + +## Allow cards to function as multiple enhancements (e.g. from jokers) +# Calculate extra enhancements when held in hand at end of round +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "local effects = {G.hand.cards[i]:get_end_of_round_effect()}" +position = "at" +payload = ''' +local effects = {[1] = {playing_card = G.hand.cards[i]:get_end_of_round_effect()}} +local extra_enhancements = SMODS.get_enhancements(G.hand.cards[i], true) +local old_ability = copy_table(G.hand.cards[i].ability) +local old_center = G.hand.cards[i].config.center +local old_center_key = G.hand.cards[i].config.center_key +for k, _ in pairs(extra_enhancements) do + if G.P_CENTERS[k] then + G.hand.cards[i]:set_ability(G.P_CENTERS[k]) + G.hand.cards[i].ability.extra_enhancement = k + effects[#effects+1] = {[1] = {playing_card = G.hand.cards[i]:get_end_of_round_effect()}} + end +end +G.hand.cards[i].ability = old_ability +G.hand.cards[i].config.center = old_center +G.hand.cards[i].config.center_key = old_center_key +G.hand.cards[i]:set_sprites(old_center) +''' +match_indent = true + +# Prevent blue seal effect on extra enhancements at end of round +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if self.seal == 'Blue' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then" +position = "before" +payload = ''' +if self.extra_enhancement then return ret end +''' +match_indent = true + +# Use the has enhancement function for enhancement gates +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if vv.config.center.key == v.enhancement_gate then" +position = "at" +payload = "if SMODS.has_enhancement(vv, v.enhancement_gate) then" +match_indent = true + +# Glass Card shattering +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if card.ability.name == 'Glass Card' then" +position = "at" +payload = "if SMODS.shatters(card) then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "if G.hand.highlighted[i].ability.name == 'Glass Card' then" +position = "at" +payload = "if SMODS.shatters(G.hand.highlighted[i]) then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "if scoring_hand[i].ability.name == 'Glass Card' then" +position = "at" +payload = "if SMODS.shatters(scoring_hand[i]) then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "if cards_destroyed[i].ability.name == 'Glass Card' then" +position = "at" +payload = "if cards_destroyed[i].shattered then" +match_indent = true + +# Prevent blue seals from applying on quantum enhancement calc +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if self.seal == 'Blue' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then" +position = "at" +payload = "if self.seal == 'Blue' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit and not self.ability.extra_enhancement then" +match_indent = true + +# Reset enh cache on game update +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "modulate_sound(dt)" +position = "after" +payload = "SMODS.enh_cache:clear()" +match_indent = true + +# Invalidate enhancement cache when card changes / replace_base_card fix pt1 +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "function Card:set_ability(center, initial, delay_sprites)" +position = "after" +payload = ''' + SMODS.enh_cache:write(self, nil) + + if self.ability and not initial then + self.ability.card_limit = self.ability.card_limit - (self.config.center.config.card_limit or 0) + self.ability.extra_slots_used = self.ability.extra_slots_used - (self.config.center.config.extra_slots_used or 0) + end + + if self.ability and not initial then + self.front_hidden = self:should_hide_front() + end +''' +match_indent = true + +# replace_base_card Fix Part 2 +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "self:set_sprites(center)" +position = "after" +payload = '''if self.ability and not initial then + self.front_hidden = self:should_hide_front() +end''' +match_indent = true + +# replace_base_card Fix Part 3 +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if not G.OVERLAY_MENU then " +position = "before" +payload = '''if self.ability and not initial then + self.front_hidden = self:should_hide_front() +end''' +match_indent = true +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "function Card:set_base(card, initial)" +position = "after" +payload = "SMODS.enh_cache:write(self, nil)" +match_indent = true + +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "function Card:set_seal(_seal, silent, immediate)" +position = "after" +payload = ''' + SMODS.enh_cache:write(self, nil) + if self.seal then + self.ability.card_limit = self.ability.card_limit - (self.ability.seal.card_limit or 0) + self.ability.extra_slots_used = self.ability.extra_slots_used - (self.ability.seal.extra_slots_used or 0) + end +''' +match_indent = true + +# safeguards an infloop with debuff context +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = 'if not initial then G.GAME.blind:debuff_card(self) end' +position = 'at' +payload = 'if not initial and delay_sprites ~= "quantum" and G.GAME.blind then G.GAME.blind:debuff_card(self) end' +match_indent = true \ No newline at end of file diff --git a/lovely/objects/vanilla_collection/seal.toml b/lovely/objects/vanilla_collection/seal.toml new file mode 100644 index 000000000..1e7f73d60 --- /dev/null +++ b/lovely/objects/vanilla_collection/seal.toml @@ -0,0 +1,241 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +### Seal API +# Card:open() +[[patches]] +[patches.regex] +target = 'card.lua' +pattern = ''' +(?[\t ]*)local seal_rate = 10 +[\n\t ]*local seal_poll = pseudorandom\(pseudoseed\('stdseal'..G.GAME.round_resets.ante\)\) +[\n\t ]*if seal_poll > 1 - 0.02\*seal_rate then +[\n\t ]*local seal_type = pseudorandom\(pseudoseed\('stdsealtype'..G.GAME.round_resets.ante\)\) +[\n\t ]*if seal_type > 0.75 then card:set_seal\('Red'\) +[\n\t ]*elseif seal_type > 0.5 then card:set_seal\('Blue'\) +[\n\t ]*elseif seal_type > 0.25 then card:set_seal\('Gold'\) +[\n\t ]*else card:set_seal\('Purple'\) +[\n\t ]*end +[\n\t ]*end''' +position = 'at' +line_prepend = '$indent' +payload = ''' +card:set_seal(SMODS.poll_seal({mod = 10}), true, true)''' + +# get_badge_colour() +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}' +position = 'before' +match_indent = true +payload = ''' +for k, v in pairs(SMODS.Seals) do + G.BADGE_COL[k:lower()..'_seal'] = v.badge_colour +end''' + +# Card:calculate_seal() +[[patches]] +[patches.regex] +target = "card.lua" +pattern = 'function Card:calculate_seal\(context\)\n(?[\t ]*)if self.debuff then return nil end' +position = 'after' +line_prepend = '$indent' +payload = ''' + + +local obj = G.P_SEALS[self.seal] or {} +if obj.calculate and type(obj.calculate) == 'function' then + local o = obj:calculate(self, context) + if o then + if not o.card then o.card = self end + return o + end +end''' + +# Card:update() +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = 'if G.STAGE == G.STAGES.RUN then' +position = 'before' +match_indent = true +payload = ''' +local obj = G.P_SEALS[self.seal] or {} +if obj.update and type(obj.update) == 'function' then + obj:update(self, dt) +end''' + +# Card:get_p_dollars() +# Also, Gold Seal respects quantum enhancements +# Patch is here to avoid conflict +[[patches]] +[patches.regex] +target = "card.lua" +pattern = '''(?[\t ]*)if self\.seal == 'Gold' then''' +position = 'at' +line_prepend = '$indent' +payload = ''' +local obj = G.P_SEALS[self.seal] or {} +if obj.get_p_dollars and type(obj.get_p_dollars) == 'function' then + ret = ret + obj:get_p_dollars(self) +elseif self.seal == 'Gold' and not self.ability.extra_enhancement then''' +# note for later: the Card:get_xxx functions sometimes take context and sometimes don't, +# which is annoying for enhancements +# this should probably be changed to be consistent in better calc + +# generate_card_ui() +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "if v == 'gold_seal'*" +match_indent = true +position = 'before' +payload = ''' +local seal = G.P_SEALS[v] or G.P_SEALS[SMODS.Seal.badge_to_key[v] or ''] +if seal then + local t = {key = v, set = 'Other', config = {}} + info_queue[#info_queue + 1] = t + if seal.loc_vars and type(seal.loc_vars) == 'function' then + local res = seal:loc_vars(info_queue, card) or {} + t.vars = res.vars + t.key = res.key or t.key + t.set = res.set or t.set + end +else''' +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "if v == 'purple_seal'*" +match_indent = true +position = 'after' +payload = 'end' + +[[patches]] +[patches.regex] +target = 'functions/common_events.lua' +position = 'at' +pattern = '''\{key = (?'.*?_seal'), set = 'Other'\}''' +payload = '''G.P_SEALS[$badge] or G.P_SEALS[SMODS.Seal.badge_to_key[$badge] or '']''' + +# Card:update_alert() +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = "function Card:update_alert()" +match_indent = true +position = 'after' +payload = ''' + if self.ability.set == 'Default' and self.config.center and self.config.center.key == 'c_base' and self.seal then + if G.P_SEALS[self.seal].alerted and self.children.alert then + self.children.alert:remove() + self.children.alert = nil + elseif not G.P_SEALS[self.seal].alerted and not self.children.alert and G.P_SEALS[self.seal].discovered then + self.children.alert = UIBox{ + definition = create_UIBox_card_alert(), + config = {align="tli", + offset = {x = 0.1, y = 0.1}, + parent = self} + } + end + end''' + +# Card:hover() +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = "G:save_progress()" +match_indent = false +position = "after" +payload = ''' + elseif self.children.alert and self.seal and not G.P_SEALS[self.seal].alerted then + G.P_SEALS[self.seal].alerted = true + G:save_progress()''' + +# Game:init_item_prototypes() +[[patches]] +[patches.regex] +target = 'game.lua' +pattern = '''(?[\t ]*)Gold =[ {A-z=1-4,"}\n]*},[\n\t ]*}''' +position = 'at' +line_prepend = '$indent' +payload = ''' +Red = {order = 1, discovered = false, set = "Seal"}, +Blue = {order = 2, discovered = false, set = "Seal"}, +Gold = {order = 3, discovered = false, set = "Seal"}, +Purple = {order = 4, discovered = false, set = "Seal"}, +} +''' + +# Card:set_seal() +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = '''G.CONTROLLER.locks.seal = true''' +position = 'after' +match_indent = true +payload = '''local sound = G.P_SEALS[_seal].sound or {sound = 'gold_seal', per = 1.2, vol = 0.4}''' +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = '''play_sound('gold_seal', 1.2, 0.4)''' +position = 'at' +match_indent = true +payload = ''' +self.ability.delay_seal = false +play_sound(sound.sound, sound.per, sound.vol)''' +## Populate Seal Ability Table +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = '''self.seal = _seal''' +position = 'after' +match_indent = true +payload = ''' +self.ability.seal = {} +for k, v in pairs(G.P_SEALS[_seal].config or {}) do + if type(v) == 'table' then + self.ability.seal[k] = copy_table(v) + else + self.ability.seal[k] = v + end +end + +self.ability.delay_seal = not silent +''' + +# card_limit support +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'before' +pattern = ''' +end + if self.ability.name == 'Gold Card' and self.seal == 'Gold' and self.playing_card then +''' +payload = ''' + self.ability.card_limit = self.ability.card_limit + (self.ability.seal.card_limit or 0) + self.ability.extra_slots_used = self.ability.extra_slots_used + (self.ability.seal.extra_slots_used or 0) +''' + + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = '''new_card:set_seal(other.seal, true)''' +position = 'after' +match_indent = true +payload = ''' +if other.seal then + for k, v in pairs(other.ability.seal or {}) do + if type(v) == 'table' then + new_card.ability.seal[k] = copy_table(v) + else + new_card.ability.seal[k] = v + end + end +end +''' diff --git a/lovely/objects/vanilla_collection/stake.toml b/lovely/objects/vanilla_collection/stake.toml new file mode 100644 index 000000000..e3016925b --- /dev/null +++ b/lovely/objects/vanilla_collection/stake.toml @@ -0,0 +1,281 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +# Fix areas where highest stake is hardcoded as Gold Stake +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = 8 end" +position = "at" +payload = "if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = #G.P_CENTER_POOLS['Stake'] end" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "for i = 1, math.min(max_stake+1, 8) do" +position = "at" +payload = "for i = 1, math.min(max_stake+1, #G.P_CENTER_POOLS['Stake']) do" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "if G.GAME.stake >= 8 then" +position = "at" +payload = "if G.GAME.stake >= #G.P_CENTER_POOLS['Stake'] then" +match_indent = true + +# Stake modifier API +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "if self.GAME.stake >= 2 then" +position = "before" +payload = "if false then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "if self.GAME.stake >= 8 then self.GAME.modifiers.enable_rentals_in_shop = true end" +position = "after" +payload = "end SMODS.setup_stake(self.GAME.stake)" +match_indent = true + +# Stake shininess API +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "if _stake == 8 then" +position = "at" +payload = "if G.P_CENTER_POOLS['Stake'][_stake].shiny then" +match_indent = true + +# Override stake listing to make room for our recursive version +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "for i = G.GAME.stake-1, 2, -1 do" +position = "before" +payload = "if false then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = 'other_col = {n=G.UIT.R, config={align = "cm", padding = 0.05, r = 0.1, colour = G.C.L_BLACK}, nodes=stake_desc_rows}' +position = "before" +payload = "end SMODS.applied_stakes_UI(G.GAME.stake, stake_desc_rows)" +match_indent = true + +# Set win stake to that specified in unlocked stake +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'for i = 1, G.GAME.stake do' +position = "at" +payload = '''for i = 1, +(G.P_CENTER_POOLS["Stake"][G.GAME.stake].unlocked_stake) and +(G.P_STAKES[G.P_CENTER_POOLS["Stake"][G.GAME.stake].unlocked_stake].stake_level-1) or (G.GAME.stake-1) +do''' +match_indent = true + +# Stake Sprites +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'local stake_sprite = Sprite(0,0,_scale*1,_scale*1,G.ASSET_ATLAS["chips"], G.P_CENTER_POOLS.Stake[_stake].pos)' +position = "at" +payload = 'local stake_sprite = Sprite(0,0,_scale*1,_scale*1,G.ASSET_ATLAS[G.P_CENTER_POOLS.Stake[_stake].atlas], G.P_CENTER_POOLS.Stake[_stake].pos)' +match_indent = true + +# Achievements and unlocks +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = 'if highest_win >= 2 then' +position = "at" +payload = 'if highest_win >= G.P_STAKES["stake_red"].stake_level then' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = 'if highest_win >= 4 then' +position = "at" +payload = 'if highest_win >= G.P_STAKES["stake_black"].stake_level then' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = 'if highest_win >= 8 then' +position = "at" +payload = 'if highest_win >= G.P_STAKES["stake_gold"].stake_level then' +match_indent = true + +# get_blind_amount +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'function get_blind_amount(ante)' +position = "after" +payload = '''if G.GAME.modifiers.scaling and (G.GAME.modifiers.scaling ~= 1 and G.GAME.modifiers.scaling ~= 2 and G.GAME.modifiers.scaling ~= 3) then return SMODS.get_blind_amount(ante) end''' +match_indent = true + +# set_joker_usage +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = {count = 1, order = v.config.center.order, wins = {}, losses = {}}' +position = "at" +payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = {count = 1, order = v.config.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}' +match_indent = true + +# set_joker_win +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] or {count = 1, order = v.config.center.order, wins = {}, losses = {}}' +position = "at" +payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] or {count = 1, order = v.config.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}' +match_indent = true + +#set_joker_win +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins[G.GAME.stake] or 0) + 1' +position = "after" +payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins_by_key[SMODS.stake_from_index(G.GAME.stake)] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins_by_key[SMODS.stake_from_index(G.GAME.stake)] or 0) + 1' +match_indent = true + +#set_joker_loss +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses[G.GAME.stake] or 0) + 1' +position = "after" +payload = 'G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses_by_key[SMODS.stake_from_index(G.GAME.stake)] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses_by_key[SMODS.stake_from_index(G.GAME.stake)] or 0) + 1' +match_indent = true + +# set_deck_usage +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}}' +position = "at" +payload = 'G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}' +match_indent = true + +# set_deck_loss +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = 'if not G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}} end' +position = "at" +payload = 'if not G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}} end' +match_indent = true + +# G.UIDEF.viewed_stake_option +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = 'G.viewed_stake = math.min(max_stake+1, G.viewed_stake)' +position = "after" +payload = '''if G.viewed_stake > #G.P_CENTER_POOLS.Stake then G.viewed_stake = #G.P_CENTER_POOLS.Stake end''' +match_indent = true + +# Fix stake select in run start +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +position = 'at' +pattern = ''' +local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key) or 0 +G.viewed_stake = math.min(G.viewed_stake, max_stake + 1) +''' +payload = ''' +-- local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key) or 0 +-- G.viewed_stake = math.min(G.viewed_stake, max_stake + 1) +''' + +# pt 2 +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +position = 'at' +pattern = ''' +local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key) +if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = #G.P_CENTER_POOLS['Stake'] end + +G.viewed_stake = math.min(max_stake+1, G.viewed_stake) +''' +payload = ''' +--[[ +local max_stake = get_deck_win_stake(G.GAME.viewed_back.effect.center.key) +if G.PROFILES[G.SETTINGS.profile].all_unlocked then max_stake = #G.P_CENTER_POOLS['Stake'] end + +G.viewed_stake = math.min(max_stake+1, G.viewed_stake) +]]-- +''' + +# Stake loc_vars +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +position = 'at' +pattern = ''' +if _stake_center then localize{type = 'descriptions', key = _stake_center.key, set = _stake_center.set, nodes = ret_nodes} end +''' +payload = ''' +local t, res = {}, {} +if _stake_center then + if _stake_center.loc_vars and type(_stake_center.loc_vars) == 'function' then + res = _stake_center:loc_vars() or {} + end + t.vars = res.vars or {} + t.key = res.key or _stake_center.key + t.set = res.set or _stake_center.set + localize{type = 'descriptions', key = t.key, set = t.set, nodes = ret_nodes, vars = t.vars} +end +''' + +# Stake loc_vars pt 2 +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +position = 'at' +pattern = ''' +{n=G.UIT.T, config={text = localize{type = 'name_text', key = _stake_center.key, set = _stake_center.set}, scale = 0.35, colour = G.C.WHITE}} +''' +payload = ''' +{n=G.UIT.T, config={text = localize{type = 'name_text', key = t.key, set = t.set}, scale = 0.35, colour = G.C.WHITE}} +''' + +# Stake loc_vars pt 3 +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +position = 'at' +pattern = ''' +localize{type = 'descriptions', key = _stake_center.key, set = _stake_center.set, nodes = _stake_desc} +''' +payload = ''' +local t, res = {}, {} +if _stake_center.loc_vars and type(_stake_center.loc_vars) == 'function' then + res = _stake_center:loc_vars() or {} +end +t.vars = res.vars or {} +t.key = res.key or _stake_center.key +t.set = res.set or _stake_center.set +localize{type = 'descriptions', key = t.key, set = t.set, nodes = _stake_desc, vars = t.vars} +''' diff --git a/lovely/objects/vanilla_collection/sticker.toml b/lovely/objects/vanilla_collection/sticker.toml new file mode 100644 index 000000000..f251f2cf6 --- /dev/null +++ b/lovely/objects/vanilla_collection/sticker.toml @@ -0,0 +1,111 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +### Sticker API + +# generate_UIBox_ability_table() +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if self.sticker or ((self.sticker_run and self.sticker_run~='NONE') and G.SETTINGS.run_stake_stickers) then loc_vars = loc_vars or {}; loc_vars.sticker=(self.sticker or self.sticker_run) end" +position = "before" +match_indent = true +payload = ''' +for k, v in ipairs(SMODS.Sticker.obj_buffer) do + if self.ability[v] and not SMODS.Stickers[v].hide_badge then + badges[#badges+1] = v + end +end''' + +# generate_card_ui() +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if v == 'eternal' then*" +match_indent = true +position = "before" +payload = ''' +local sticker = SMODS.Stickers[v] +if sticker then + local t = { key = v, set = 'Other' } + local res = {} + if sticker.loc_vars and type(sticker.loc_vars) == 'function' then + res = sticker:loc_vars(info_queue, card) or {} + t.vars = res.vars or {} + t.key = res.key or t.key + t.set = res.set or t.set + end + info_queue[#info_queue+1] = t +else''' + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if v == 'rental' then*" +match_indent = true +position = "after" +payload = '''end''' + +# create_card() +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if card.ability.consumeable and not skip_materialize then card:start_materialize() end" +position = "after" +match_indent = true +payload = ''' +for k, v in ipairs(SMODS.Sticker.obj_buffer) do + local sticker = SMODS.Stickers[v] + if sticker.should_apply and type(sticker.should_apply) == 'function' and sticker:should_apply(card, center, area) then + sticker:apply(card, true) + end +end''' + +## Remove base game sticker rolls if one is added +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if G.GAME.modifiers.enable_eternals_in_shop and eternal_perishable_poll > 0.7 then" +position = "at" +match_indent = true +payload = '''if G.GAME.modifiers.enable_eternals_in_shop and eternal_perishable_poll > 0.7 and not SMODS.Stickers["eternal"].should_apply then''' + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "elseif G.GAME.modifiers.enable_perishables_in_shop and ((eternal_perishable_poll > 0.4) and (eternal_perishable_poll <= 0.7)) then" +position = "at" +match_indent = true +payload = '''elseif G.GAME.modifiers.enable_perishables_in_shop and ((eternal_perishable_poll > 0.4) and (eternal_perishable_poll <= 0.7)) and not SMODS.Stickers["perishable"].should_apply then''' + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "if G.GAME.modifiers.enable_rentals_in_shop and pseudorandom((area == G.pack_cards and 'packssjr' or 'ssjr')..G.GAME.round_resets.ante) > 0.7 then" +position = "at" +match_indent = true +payload = '''if G.GAME.modifiers.enable_rentals_in_shop and pseudorandom((area == G.pack_cards and 'packssjr' or 'ssjr')..G.GAME.round_resets.ante) > 0.7 and not SMODS.Stickers["rental"].should_apply then''' + +# get_badge_colour() +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}' +position = 'before' +match_indent = true +payload = ''' +for k, v in pairs(SMODS.Stickers) do + G.BADGE_COL[k] = v.badge_colour +end''' + +## Remove Pinned effect when in Sticker collections +# CardArea:aling_cards +[[patches]] +[patches.pattern] +target = 'cardarea.lua' +pattern = '''table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*(a.pinned and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*(b.pinned and b.sort_id or 0) end)''' +position = 'at' +match_indent = true +payload = '''table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*((a.pinned and not a.ignore_pinned) and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*((b.pinned and not b.ignore_pinned) and b.sort_id or 0) end)''' diff --git a/lovely/objects/vanilla_collection/tag.toml b/lovely/objects/vanilla_collection/tag.toml new file mode 100644 index 000000000..f2523a02f --- /dev/null +++ b/lovely/objects/vanilla_collection/tag.toml @@ -0,0 +1,166 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +### Tag API +# Tag:apply_to_run() +# Adds prevent_tag_trigger context +[[patches]] +[patches.pattern] +target = "tag.lua" +pattern = "function Tag:apply_to_run(_context)" +position = 'after' +match_indent = true +payload = ''' + if self.triggered then return end + local flags = SMODS.calculate_context({prevent_tag_trigger = self, other_context = _context}) + if flags.prevent_trigger then return end + local obj = SMODS.Tags[self.key] + local res + if obj and obj.apply and type(obj.apply) == 'function' then + res = obj:apply(self, _context) + end + if res then return res end +''' + +# Tag:set_ability() +[[patches]] +[patches.pattern] +target = "tag.lua" +pattern = "function Tag:set_ability()" +position = 'after' +match_indent = true +payload = ''' + local obj = SMODS.Tags[self.key] + local res + if obj and obj.set_ability and type(obj.set_ability) == 'function' then + obj:set_ability(self) + end +''' + +# Orbital Tag's set_ability +# Initialize fallback Poker Hand +[[patches]] +[patches.pattern] +target = "tag.lua" +pattern = ''' +elseif self.ability.blind_type then + if G.GAME.orbital_choices and G.GAME.orbital_choices[G.GAME.round_resets.ante][self.ability.blind_type] then + self.ability.orbital_hand = G.GAME.orbital_choices[G.GAME.round_resets.ante][self.ability.blind_type] + end +end +''' +position = 'after' +match_indent = true +payload = ''' +if self.ability.orbital_hand == '['..localize('k_poker_hand')..']' then + local _poker_hands = {} + for k, v in pairs(G.GAME.hands) do + if SMODS.is_poker_hand_visible(k) then _poker_hands[#_poker_hands+1] = k end + end + self.ability.orbital_hand = pseudorandom_element(_poker_hands, pseudoseed('orbital')) +end +''' + +# create_UIBox_your_collection_tags() +[[patches]] +[patches.regex] +target = "functions/UI_definitions.lua" +pattern = "(?[\t ]*)local tag_matrix = \\{(\n.*){6}" +position = 'at' +line_prepend = '$indent' +payload = ''' +local tag_matrix = {} +local counter = 0 +local tag_tab = {} +local tag_pool = {} +if G.ACTIVE_MOD_UI then + for k, v in pairs(G.P_TAGS) do + if v.mod and G.ACTIVE_MOD_UI.id == v.mod.id then tag_pool[k] = v end + end +else + tag_pool = G.P_TAGS +end +for k, v in pairs(tag_pool) do + counter = counter + 1 + tag_tab[#tag_tab+1] = v +end +for i = 1, math.ceil(counter / 6) do + table.insert(tag_matrix, {}) +end''' + +[[patches]] +[patches.regex] +target = "functions/UI_definitions.lua" +pattern = '''(?[\t ]*)v\.children\.alert\.states\.collide\.can = false\n[\s\S]{8}end\n[\s\S]{8}return true\n[\s\S]{4}end\)\n[\s\S]{2}\}\)\)\n{3}''' +position = 'after' +line_prepend = '$indent' +payload = ''' +local table_nodes = {} +for i = 1, math.ceil(counter / 6) do + table.insert(table_nodes, {n=G.UIT.R, config={align = "cm"}, nodes=tag_matrix[i]}) +end''' + +[[patches]] +[patches.regex] +target = "functions/UI_definitions.lua" +pattern = '''(?[\t ]*)\{\n[\s\S]{10}\{n=G\.UIT\.R, config=\{align = "cm"\}, nodes=tag_matrix\[1\]},[\s\S]*tag_matrix\[4\]\},\n[\s\S]{8}\}''' +position = 'at' +line_prepend = '$indent' +payload = '''table_nodes''' + +# Tag:generate_UI() +[[patches]] +[patches.regex] +target = "tag.lua" +pattern = 'G.ASSET_ATLAS\["tags"\]' +position = 'at' +payload = 'G.ASSET_ATLAS[(not self.hide_ability) and G.P_TAGS[self.key].atlas or "tags"]' + +# Tag:get_uibox_table() +[[patches]] +[patches.pattern] +target = "tag.lua" +pattern = '''function Tag:get_uibox_table(tag_sprite)''' +position = 'at' +match_indent = true +payload = '''function Tag:get_uibox_table(tag_sprite, vars_only)''' +[[patches]] +[patches.pattern] +target = "tag.lua" +pattern = '''tag_sprite.ability_UIBox_table = generate_card_ui(G.P_TAGS[self.key], nil, loc_vars, (self.hide_ability) and 'Undiscovered' or 'Tag', nil, (self.hide_ability))''' +position = 'at' +match_indent = true +payload = '''if vars_only then return loc_vars end +tag_sprite.ability_UIBox_table = generate_card_ui(G.P_TAGS[self.key], nil, loc_vars, (self.hide_ability) and 'Undiscovered' or 'Tag', nil, (self.hide_ability), nil, nil, self)''' + +# generate_card_ui() + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "elseif _c.set == 'Tag' then" +position = "after" +match_indent = true +payload = '''specific_vars = specific_vars or Tag.get_uibox_table({ name = _c.name, config = _c.config, ability = { orbital_hand = '['..localize('k_poker_hand')..']' }}, nil, true) +''' + +# Prevent Boss Tag from crashing when triggered at a bad time +# by quietly rerolling it if blind select UI is not there +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +pattern = "G.FUNCS.reroll_boss = function(e)" +position = "after" +match_indent = true +payload = '''if not G.blind_select_opts then + G.GAME.round_resets.boss_rerolled = true + if not G.from_boss_tag then ease_dollars(-10) end + G.from_boss_tag = nil + G.GAME.round_resets.blind_choices.Boss = get_new_boss() + 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''' \ No newline at end of file diff --git a/lovely/objects/vanilla_other/achievement.toml b/lovely/objects/vanilla_other/achievement.toml new file mode 100644 index 000000000..6c98ee42b --- /dev/null +++ b/lovely/objects/vanilla_other/achievement.toml @@ -0,0 +1,109 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +## Achievement API + +# fetch_achievements() +[[patches]] +[patches.regex] +target = "functions/common_events.lua" +pattern = '''(?[\t ]*)if G\.F_NO_ACHIEVEMENTS then return end[\n\s]*?--\|FROM LOCAL SETTINGS FILE''' +position = 'before' +# match_indent = true +line_prepend = '$indent' +payload = ''' +G.SETTINGS.ACHIEVEMENTS_EARNED = G.SETTINGS.ACHIEVEMENTS_EARNED or {} +for k, v in pairs(G.ACHIEVEMENTS) do + if not v.key then v.key = k end + for kk, vv in pairs(G.SETTINGS.ACHIEVEMENTS_EARNED) do + if G.ACHIEVEMENTS[kk] and G.ACHIEVEMENTS[kk].mod then + G.ACHIEVEMENTS[kk].earned = true + end + end +end +''' + +# check_for_unlock +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = '''if G.GAME.challenge then return end''' +position = "after" +payload = ''' +fetch_achievements() -- Refreshes achievements +for k, v in pairs(G.ACHIEVEMENTS) do + if (not v.earned) and (v.unlock_condition and type(v.unlock_condition) == 'function') and v:unlock_condition(args) then + unlock_achievement(k) + end +end''' +match_indent = true + +# unlock_achievement() +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = '''if G.PROFILES[G.SETTINGS.profile].all_unlocked then return end''' +position = "at" +payload = '''if G.PROFILES[G.SETTINGS.profile].all_unlocked and (G.ACHIEVEMENTS and G.ACHIEVEMENTS[achievement_name] and not G.ACHIEVEMENTS[achievement_name].bypass_all_unlocked and SMODS.config.achievements < 3) or (SMODS.config.achievements < 3 and (G.GAME.seeded or G.GAME.challenge)) then return true end''' +match_indent = true + +# unlock_achievement() - fix event queue leaking +# fixed smods achievements not unlocking due to above comment's memory leak fix +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = '''local achievement_set = false +if G.F_NO_ACHIEVEMENTS then return end''' +position = "at" +payload = '''local achievement_set = false +if not G.ACHIEVEMENTS then fetch_achievements() end +G.SETTINGS.ACHIEVEMENTS_EARNED[achievement_name] = true +G:save_progress() + +if G.ACHIEVEMENTS[achievement_name] and G.ACHIEVEMENTS[achievement_name].mod then + if not G.ACHIEVEMENTS[achievement_name].earned then + --|THIS IS THE FIRST TIME THIS ACHIEVEMENT HAS BEEN EARNED + achievement_set = true + G.FILE_HANDLER.force = true + end + G.ACHIEVEMENTS[achievement_name].earned = true +end + +if achievement_set then + notify_alert(achievement_name) + return true +end +if G.F_NO_ACHIEVEMENTS and not (G.ACHIEVEMENTS[achievement_name] or {}).mod then return true end''' +match_indent = true + +# option to allow unlocks and discoveries in seeded runs +[[patches]] +[patches.regex] +target = 'functions/common_events.lua' +pattern = 'if G\.GAME\.seeded or G\.GAME\.challenge then return end' +position = 'at' +payload = 'if not SMODS.config.seeded_unlocks and (G.GAME.seeded or G.GAME.challenge) then return end' + +[[patches]] +[patches.pattern] +target = 'functions/state_events.lua' +match_indent = true +position = 'at' +pattern = 'if not G.GAME.seeded and not G.GAME.challenge then' +payload = 'if (not G.GAME.seeded and not G.GAME.challenge) or SMODS.config.seeded_unlocks then' + +[[patches]] +[patches.regex] +target = 'functions/common_events.lua' +pattern = 'if G\.GAME\.seeded then' +position = 'at' +payload = 'if false then' + +[[patches]] +[patches.regex] +target = 'functions/common_events.lua' +pattern = 'if G\.GAME\.challenge then' +position = 'at' +payload = 'if false then' diff --git a/lovely/objects/vanilla_other/cardarea.toml b/lovely/objects/vanilla_other/cardarea.toml new file mode 100644 index 000000000..790836945 --- /dev/null +++ b/lovely/objects/vanilla_other/cardarea.toml @@ -0,0 +1,79 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +# Change CardArea's background colour +# CardArea.draw +[[patches]] +[patches.pattern] +target = "cardarea.lua" +pattern = "{n=G.UIT.R, config={minw = self.T.w,minh = self.T.h,align = \"cm\", padding = 0.1, mid = true, r = 0.1, colour = self ~= G.shop_vouchers and {0,0,0,0.1} or nil, ref_table = self}, nodes={" +position = 'at' +match_indent = true +payload = ''' +{n=G.UIT.R, config={minw = self.T.w,minh = self.T.h,align = "cm", padding = 0.1, mid = true, r = 0.1, colour = self.config.bg_colour or self ~= G.shop_vouchers and {0,0,0,0.1} or nil, ref_table = self}, nodes={ +''' + +# Remove CardArea count +# CardArea.draw +[[patches]] +[patches.pattern] +target = "cardarea.lua" +pattern = ''' +local card_count = self ~= G.shop_vouchers and {n=G.UIT.R, config={align = self == G.jokers and 'cl' or self == G.hand and 'cm' or 'cr', padding = 0.03, no_fill = true}, nodes={ +''' +position = 'at' +match_indent = true +payload = ''' +local card_count = not self.config.no_card_count and self ~= G.shop_vouchers and {n=G.UIT.R, config={align = self == G.jokers and 'cl' or self == G.hand and 'cm' or 'cr', padding = 0.03, no_fill = true}, nodes={ +''' + +## cardareas.toml +# Create custom card areas at appropriate timing via helper function +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'after' +pattern = ''' +G.playing_cards = {} + +set_screen_positions() +''' +payload = ''' +-- Add align config to existing areas that should use it +self.jokers.config.align_buttons = true +self.consumeables.config.align_buttons = true + +for _, mod in ipairs(SMODS.mod_list) do + if mod.can_load and mod.custom_card_areas and type(mod.custom_card_areas) == "function" then + mod.custom_card_areas(self) + end +end +''' + +# Check area.config.align_buttons if an area should align buttons like normal +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'at' +pattern = ''' +((self.area == G.jokers) or (self.area == G.consumeables)) and "cr" or +''' +payload = ''' +self.area.config.align_buttons and "cr" or +''' +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'at' +pattern = ''' +((self.area == G.jokers) or (self.area == G.consumeables)) and {x=x_off - 0.4,y=0} or +''' +payload = ''' +self.area.config.align_buttons and {x=x_off - 0.4,y=0} or +''' +## end cardareas.toml \ No newline at end of file diff --git a/lovely/objects/vanilla_other/challenge.toml b/lovely/objects/vanilla_other/challenge.toml new file mode 100644 index 000000000..033c9a36f --- /dev/null +++ b/lovely/objects/vanilla_other/challenge.toml @@ -0,0 +1,93 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +# function G.UIDEF.challenge_list_page() +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "local challenge_unlocked = G.PROFILES[G.SETTINGS.profile].challenges_unlocked and (G.PROFILES[G.SETTINGS.profile].challenges_unlocked >= k)" +position = 'at' +payload = "local challenge_unlocked = SMODS.challenge_is_unlocked(v, k)" +match_indent = true + +# function G.UIDEF.challenges() - fix challenge unlock count +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = """ +local _ch_tab = {comp = _ch_comp, unlocked = G.PROFILES[G.SETTINGS.profile].challenges_unlocked} +""" +position = 'at' +payload = """ +local unlock_count = 0 +for k, v in ipairs(G.CHALLENGES) do + if SMODS.challenge_is_unlocked(v, k) then + unlock_count = unlock_count + 1 + end +end +local _ch_tab = {comp = _ch_comp, unlocked = unlock_count} +""" +match_indent = true + +# Add button colour +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "UIBox_button({id = k, col = true, label = {challenge_unlocked and localize(v.id, 'challenge_names') or localize('k_locked'),}, button = challenge_unlocked and 'change_challenge_description' or 'nil', colour = challenge_unlocked and G.C.RED or G.C.GREY, minw = 4, scale = 0.4, minh = 0.6, focus_args = {snap_to = not snapped}})," +position = 'at' +match_indent = true +payload = ''' +UIBox_button({id = k, col = true, label = {challenge_unlocked and localize(v.id, 'challenge_names') or localize('k_locked'),}, button = challenge_unlocked and 'change_challenge_description' or 'nil', text_colour = v.text_colour, colour = challenge_unlocked and (v.button_colour or G.C.RED) or G.C.GREY, minw = 4, scale = 0.4, minh = 0.6, focus_args = {snap_to = not snapped}}), +''' + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = '''if _ch.restrictions.banned_cards then''' +position = "after" +payload = ''' if type(_ch.restrictions.banned_cards) == 'function' then + _ch.restrictions.banned_cards = _ch.restrictions.banned_cards() + end''' +match_indent = true +times = 1 + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = '''if _ch.restrictions.banned_tags then''' +position = "after" +payload = ''' + if type(_ch.restrictions.banned_tags) == 'function' then + _ch.restrictions.banned_tags = _ch.restrictions.banned_tags() + end''' +match_indent = true +times = 1 + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = '''if _ch.restrictions.banned_other then''' +position = "after" +payload = ''' + if type(_ch.restrictions.banned_other) == 'function' then + _ch.restrictions.banned_other = _ch.restrictions.banned_other() + end''' +match_indent = true +times = 1 + +# apply +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = ''' +local _ch = args.challenge +''' +position = "after" +payload = ''' +if _ch.apply and type(_ch.apply) == "function" then + _ch:apply() +end +''' +match_indent = true \ No newline at end of file diff --git a/lovely/objects/vanilla_other/playing_card.toml b/lovely/objects/vanilla_other/playing_card.toml new file mode 100644 index 000000000..1c7c22435 --- /dev/null +++ b/lovely/objects/vanilla_other/playing_card.toml @@ -0,0 +1,378 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +### Playing Card API + +# Game:init_game_object() +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = 'function Game:init_game_object()' +position = 'after' +match_indent = true +payload = ''' + local cards_played = {} + for _,v in ipairs(SMODS.Rank.obj_buffer) do + cards_played[v] = { suits = {}, total = 0 } + end''' + +[[patches]] +[patches.regex] +target = "game.lua" +pattern = '(?[\t ]*)cards_played = \{\n(.*\n){13}[\t ]*\},' +position = 'at' +line_prepend = '$indent' +payload = ''' + cards_played = cards_played, + disabled_suits = {}, + disabled_ranks = {},''' + +# Game:start_run() +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = 'local _ = nil' +position = 'before' +match_indent = true +payload = ''' +if not SMODS.add_to_pool(SMODS.Ranks[v.value], {initial_deck = true, suit = v.suit}) +or not SMODS.add_to_pool(SMODS.Suits[v.suit], {initial_deck = true, rank = v.value}) then + goto continue +end''' + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = 'local _ = nil' +position = 'before' +match_indent = true +payload = ''' +if self.GAME.selected_back_key.initial_deck then + local _id = self.GAME.selected_back_key.initial_deck + local _exclude = _id.exclude + for i,_tab in ipairs({_id.Ranks or _id.exclude and {} or SMODS.Rank.obj_buffer, _id.Suits}) do + if _tab ~= nil then + local _create = not not _exclude + for _,_v in ipairs(_tab) do + if (i==1 and v.value or v.suit) == _v then _create = not _exclude; break end + end + if not _create then + goto continue + end + end + end +end''' + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "if self.GAME.starting_params.erratic_suits_and_ranks then _, k = pseudorandom_element(G.P_CARDS, pseudoseed('erratic')) end" +position = 'at' +match_indent = true +payload = '''if self.GAME.starting_params.erratic_suits_and_ranks then + v, k = pseudorandom_element(G.P_CARDS, pseudoseed('erratic'), {starting_deck = true}) +end''' + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = 'local _r, _s = string.sub(k, 3, 3), string.sub(k, 1, 1)' +position = 'at' +match_indent = true +payload = 'local _r, _s = SMODS.Ranks[v.value].card_key, SMODS.Suits[v.suit].card_key' + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "if self.GAME.starting_params.no_faces and (_r == 'K' or _r == 'Q' or _r == 'J') then keep = false end" +position = 'at' +match_indent = true +payload = ''' +if self.GAME.starting_params.no_faces and SMODS.Ranks[v.value].face then keep = false end''' + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "if keep then card_protos[#card_protos+1] = {s=_s,r=_r,e=_e,d=_d,g=_g} end" +position = "after" +payload = "::continue::" +match_indent = true + +# loc_colour() +[[patches]] +[patches.pattern] +target = 'functions/misc_functions.lua' +pattern = 'return G.ARGS.LOC_COLOURS[_c] or _default or G.C.UI.TEXT_DARK' +position = 'before' +match_indent = true +payload = ''' + for _, v in ipairs(SMODS.Rarity.obj_buffer) do + G.ARGS.LOC_COLOURS[v:lower()] = G.C.RARITY[v] + end + for _, v in ipairs(SMODS.Gradient.obj_buffer) do + G.ARGS.LOC_COLOURS[v:lower()] = SMODS.Gradients[v] + end + for _, v in ipairs(SMODS.ConsumableType.obj_buffer) do + G.ARGS.LOC_COLOURS[v:lower()] = G.C.SECONDARY_SET[v] + end + for _, v in ipairs(SMODS.Suit.obj_buffer) do + G.ARGS.LOC_COLOURS[v:lower()] = G.C.SUITS[v] + end''' + +# get_flush() +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = ''' +local suits = { + "Spades", + "Hearts", + "Clubs", + "Diamonds" + } +''' +position = 'at' +payload = 'local suits = SMODS.Suit.obj_buffer' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "local four_fingers = next(find_joker('Four Fingers'))" +position = 'at' +payload = "local four_fingers = SMODS.four_fingers('flush')" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "if #hand > 5 or #hand < (5 - (four_fingers and 1 or 0)) then return ret else" +position = 'at' +payload = "if #hand < four_fingers then return ret else" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "if flush_count >= (5 - (four_fingers and 1 or 0)) then" +position = 'at' +payload = "if flush_count >= four_fingers then" +match_indent = true + +# get_X_same() +[[patches]] +[patches.pattern] +target = 'functions/misc_functions.lua' +pattern = 'local vals = {{},{},{},{},{},{},{},{},{},{},{},{},{},{}}' +position = 'at' +match_indent = true +payload = ''' +local vals = {} +for i = 1, SMODS.Rank.max_id.value do + vals[i] = {} +end''' + +[[patches]] +[patches.pattern] +target = 'functions/misc_functions.lua' +pattern = 'function get_X_same(num, hand)' +position = 'at' +match_indent = true +payload = '''function get_X_same(num, hand, or_more)''' + +[[patches]] +[patches.pattern] +target = 'functions/misc_functions.lua' +pattern = 'if #curr == num then' +position = 'at' +match_indent = true +payload = '''if or_more and (#curr >= num) or (#curr == num) then''' + +# Card:get_nominal() +[[patches]] +[patches.regex] +target = "card.lua" +pattern = 'function Card:get_nominal\(mod\)\n([\t ]+.*\n)*end' +position = 'at' +payload = ''' +function Card:get_nominal(mod) + local mult = 1 + local rank_mult = 1 + if mod == 'suit' then mult = 10000 end + if self.ability.effect == 'Stone Card' or (self.config.center.no_suit and self.config.center.no_rank) then + mult = -10000 + elseif self.config.center.no_suit then + mult = 0 + elseif self.config.center.no_rank then + rank_mult = 0 + end + return 10*self.base.nominal*rank_mult + self.base.suit_nominal*mult + (self.base.suit_nominal_original or 0)*0.0001*mult + 10*self.base.face_nominal*rank_mult + 0.000001*self.unique_val +end''' + +# Card:set_base() +[[patches]] +[patches.regex] +target = 'card.lua' +pattern = "(?[\t ]*)if self.base.value == '2' then self.base.nominal = 2; self.base.id = 2(\n[\t ]+elseif .*)*" +position = 'at' +line_prepend = '$indent' +payload = ''' +local rank = SMODS.Ranks[self.base.value] or {} +self.base.nominal = rank.nominal or 0 +self.base.face_nominal = rank.face_nominal or 0 +self.base.id = rank.id''' + +[[patches]] +[patches.regex] +target = 'card.lua' +pattern = "(?[\t ]*)if self.base.suit == 'Diamonds' then self.base.suit_nominal = 0.01; self.base.suit_nominal_original = suit_base_nominal_original or 0.001 (\n[\t ]+elseif .*)*" +position = 'at' +line_prepend = '$indent' +payload = ''' +local suit = SMODS.Suits[self.base.suit] or {} +self.base.suit_nominal = suit.suit_nominal or 0 +self.base.suit_nominal_original = suit_base_nominal_original or suit.suit_nominal or 0''' + +# Card:change_suit() +[[patches]] +[patches.regex] +target = 'card.lua' +pattern = "(?[\t ]*)local new_code = [\\s\\S]*?local new_val = [\\s\\S]*?local new_card = G.P_CARDS\\[new_code..new_val\\]" +position = 'at' +line_prepend = '$indent' +payload = ''' +local new_code = SMODS.Suits[new_suit].card_key +local new_val = SMODS.Ranks[self.base.value].card_key +local new_card = G.P_CARDS[new_code..'_'..new_val]''' + +# Card:is_face() +[[patches]] +[patches.regex] +target = 'card.lua' +pattern = "(?[\t ]*)if id == 11 or id == 12 or id == 13 or next\\(find_joker\\(\"Pareidolia\"\\)\\) then" +position = 'at' +line_prepend = '$indent' +payload = ''' +local rank = SMODS.Ranks[self.base.value] +if not id then return end +if (id > 0 and rank and rank.face) or next(find_joker("Pareidolia")) then''' + +# Card:load() +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = '''self.VT.h = self.T.H''' +position = "at" +payload = '''self.VT.h = self.T.h''' +match_indent = true + +# tally_sprite() +[[patches]] +[patches.regex] +target = 'functions/UI_definitions.lua' +pattern = '(?[\t ]*local t_s = Sprite\(0,0,0.5,0.5,)G.ASSET_ATLAS\[.*?\](?.*?\))' +position = 'at' +payload = '$start G.ASSET_ATLAS[suit and SMODS.Suits[suit][G.SETTINGS.colourblind_option and "hc_ui_atlas" or "lc_ui_atlas"]] or G.ASSET_ATLAS[("ui_"..(G.SETTINGS.colourblind_option and "2" or "1"))]$rest' + +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +pattern = 'function tally_sprite(pos, value, tooltip)' +position = 'at' +match_indent = true +payload = 'function tally_sprite(pos, value, tooltip, suit)' + +# G.UIDEF.challenge_description_tab() +[[patches]] +[patches.regex] +target = 'functions/UI_definitions.lua' +pattern = "(?[\t ]*)local SUITS = \\{(\n.*){5}\n[\t ]*local suit_map = \\{'S', 'H', 'C', 'D'\\}" +position = 'at' +line_prepend = '$indent' +payload = ''' +local SUITS = {} +local suit_map = {} +for i = #SMODS.Suit.obj_buffer, 1, -1 do + local suit = SMODS.Suits[SMODS.Suit.obj_buffer[i]] + SUITS[suit.card_key] = {} + suit_map[#suit_map+1] = suit.card_key +end''' + +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = 'local _r, _s = string.sub(k, 3, 3), string.sub(k, 1, 1)' +position = 'at' +match_indent = true +payload = 'local _r, _s = SMODS.Ranks[v.value].card_key, SMODS.Suits[v.suit].card_key' + +# TODO there may need to be a way to let in_pool know what challenge is being displayed +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = 'local keep, _e, _d, _g = true, nil, nil, nil' +position = 'after' +match_indent = true +payload = ''' +if not SMODS.add_to_pool(SMODS.Ranks[v.value], {initial_deck = true, suit = v.suit}) then + keep = false +end +if not SMODS.add_to_pool(SMODS.Suits[v.suit], {initial_deck = true, rank = v.value}) then + keep = false +end''' + +[[patches]] +[patches.regex] +target = 'functions/UI_definitions.lua' +pattern = '(?[\t ]*)for j = 1, 4 do\n[\t ]*(?if SUITS\[suit_map\[j\]\]\[1\] then\n[\t ]*table.sort.*(\n.*)*?)\n[\t ]*0\.42\*G.CARD_H,' +position = 'at' +line_prepend = '$indent' +payload = ''' +local num_suits = 0 +for j = 1, #suit_map do + if SUITS[suit_map[j]][1] then num_suits = num_suits + 1 end +end +for j = 1, #suit_map do + $mid + (0.42 - (num_suits <= 4 and 0 or num_suits >= 8 and 0.28 or 0.07 * (num_suits - 4))) * G.CARD_H,''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = '--Fill all remaining info if this is the main desc' +position = 'before' +match_indent = true +payload = '''if card_type == 'Default' or card_type == 'Enhanced' and not _c.replace_base_card and card and card.base then + if not _c.no_suit then + local suit = SMODS.Suits[card.base.suit] or {} + if suit.loc_vars and type(suit.loc_vars) == 'function' then + suit:loc_vars(info_queue, card) + end + end + if not _c.no_rank then + local rank = SMODS.Ranks[card.base.value] or {} + if rank.loc_vars and type(rank.loc_vars) == 'function' then + rank:loc_vars(info_queue, card) + end + end +end + +''' + +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = '''function Card:set_base(card, initial)''' +position = 'at' +match_indent = true +payload = '''function Card:set_base(card, initial, manual_sprites)''' + +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = '''if next(card) then''' +position = 'at' +match_indent = true +payload = '''if next(card) and not manual_sprites then''' diff --git a/lovely/objects/vanilla_other/poker_hand.toml b/lovely/objects/vanilla_other/poker_hand.toml new file mode 100644 index 000000000..8bb29183a --- /dev/null +++ b/lovely/objects/vanilla_other/poker_hand.toml @@ -0,0 +1,127 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +### Poker Hand API + +# evaluate_poker_hand() +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "local parts = {" +position = 'before' +payload = ''' +for _,v in ipairs(SMODS.PokerHand.obj_buffer) do + results[v] = {} +end''' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "if next(parts._5) and next(parts._flush) then" +position = 'before' +payload = ''' +for _,_hand in pairs(SMODS.PokerHands) do + if _hand.atomic_part and type(_hand.atomic_part) == 'function' then + parts[_hand.key] = _hand.atomic_part(hand) + end +end''' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "return results" +position = 'before' +payload = ''' +for _,_hand in pairs(SMODS.PokerHands) do + if _hand.composite and type(_hand.composite) == 'function' then + local other_hands + results[_hand.key], other_hands = _hand.composite(parts) + results[_hand.key] = results[_hand.key] or {} + if other_hands and type(other_hands) == 'table' then + for k, v in pairs(other_hands) do + results[k] = v + end + end + else + results[_hand.key] = parts[_hand.key] + end +end +results.top = nil +for _, v in ipairs(G.handlist) do + if not results.top and results[v] then + results.top = results[v] + break + end +end''' +match_indent = true + +## is_visible + +# create_UIBox_blind_choice +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = '''if v.visible then _poker_hands[#_poker_hands+1] = k end''' +position = "at" +payload = ''' +if SMODS.is_poker_hand_visible(k) then _poker_hands[#_poker_hands+1] = k end +''' +match_indent = true + +# card:calculate_joker +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = '''if k ~= context.scoring_name and v.played >= play_more_than and v.visible then''' +position = "at" +payload = ''' +if k ~= context.scoring_name and v.played >= play_more_than and SMODS.is_poker_hand_visible(k) then +''' +match_indent = true + +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = '''if v.visible and k ~= self.ability.to_do_poker_hand then _poker_hands[#_poker_hands+1] = k end''' +position = "at" +payload = ''' +if SMODS.is_poker_hand_visible(k) and k ~= self.ability.to_do_poker_hand then _poker_hands[#_poker_hands+1] = k end +''' +match_indent = true + +# card:set_ability +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = '''if v.visible then _poker_hands[#_poker_hands+1] = k end''' +position = "at" +payload = ''' +if SMODS.is_poker_hand_visible(k) then _poker_hands[#_poker_hands+1] = k end +''' +match_indent = true + +# G.FUNCS.evaluate_play() +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = '''G.GAME.hands[text].played_this_round = G.GAME.hands[text].played_this_round + 1''' +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/lovely/objects/vanilla_other/rarity.toml b/lovely/objects/vanilla_other/rarity.toml new file mode 100644 index 000000000..6b426ee32 --- /dev/null +++ b/lovely/objects/vanilla_other/rarity.toml @@ -0,0 +1,61 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +### Rarity API + +# get_badge_colour +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +pattern = 'return G.BADGE_COL[key] or {1, 0, 0, 1}' +position = 'before' +match_indent = true +payload = ''' +for k, v in pairs(SMODS.Rarity.obj_buffer) do + G.BADGE_COL[k] = G.C.RARITY[v] +end''' + +# G.UIDEF.card_h_popup +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "if AUT.card_type == 'Joker' or (AUT.badges and AUT.badges.force_rarity) then card_type = ({localize('k_common'), localize('k_uncommon'), localize('k_rare'), localize('k_legendary')})[card.config.center.rarity] end" +position = "at" +payload = "if AUT.card_type == 'Joker' or (AUT.badges and AUT.badges.force_rarity) then card_type = SMODS.Rarity:get_rarity_badge(card.config.center.rarity) end" +match_indent = true + +# Game:update +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "self.C.EDITION[2] = 0.7+0.2*(1+math.sin(self.TIMERS.REAL*1.5 + 6))" +position = "after" +payload = ''' +for k, v in pairs(SMODS.Rarities) do + if v.gradient and type(v.gradient) == "function" then v:gradient(dt) end +end''' +match_indent = true + +# get_current_pool +[[patches]] +[patches.regex] +target = "functions/common_events.lua" +pattern = '''(?[\t ]*)local rarity = _rarity or pseudorandom\('rarity'\.\.G\.GAME\.round_resets\.ante\.\.\(_append or ''\)\) \n[\s\S]{12}rarity = \(_legendary and 4\) or \(rarity > 0\.95 and 3\) or \(rarity > 0\.7 and 2\) or 1''' +position = "at" +payload = ''' +_rarity = (_legendary and 4) or (type(_rarity) == "number" and ((_rarity > 0.95 and 3) or (_rarity > 0.7 and 2) or 1)) or _rarity +_rarity = ({Common = 1, Uncommon = 2, Rare = 3, Legendary = 4})[_rarity] or _rarity +local rarity = _rarity or SMODS.poll_rarity("Joker", 'rarity'..G.GAME.round_resets.ante..(_append or '')) +''' + +## Ensure that other cards set to string rarity work the same as set for int rarity +# Card:calculate_joker +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "if self.ability.name == 'Baseball Card' and context.other_joker.config.center.rarity == 2 and self ~= context.other_joker then" +position = "at" +payload = '''if self.ability.name == 'Baseball Card' and self ~= context.other_joker and context.other_joker:is_rarity("Uncommon") then''' +match_indent = true diff --git a/lovely/objects/visual/atlas.toml b/lovely/objects/visual/atlas.toml new file mode 100644 index 000000000..df4f7fd68 --- /dev/null +++ b/lovely/objects/visual/atlas.toml @@ -0,0 +1,55 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +### Sprite API + +# Game:set_render_settings() +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +pattern = "G:set_render_settings()" +position = 'at' +match_indent = true +payload = "SMODS.injectObjects(SMODS.Atlas)" + + +## Set ability resizing fix +[[patches]] +[patches.pattern] +target = 'engine/moveable.lua' +pattern = '''Node.init(self, args)''' +position = 'after' +match_indent = true +payload = '''self.original_T = copy_table(self.T)''' +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = '''self.T.scale = 0.95''' +position = 'after' +match_indent = true +payload = '''self.original_T.scale = 0.95''' +[[patches]] +[patches.pattern] +target = 'card.lua' +pattern = '''self:set_ability(center, true)''' +position = 'before' +match_indent = true +payload = '''self.original_T = copy_table(self.T)''' +[[patches]] +[patches.pattern] +target = 'card.lua' +match_indent = true +position = 'before' +pattern = ''' +local X, Y, W, H = self.T.x, self.T.y, self.T.w, self.T.h +''' +payload = ''' +if delay_sprites ~= 'quantum' then + for key, _ in pairs(self.T) do + self.T[key] = self.original_T[key] + end +end +''' + diff --git a/lovely/objects/visual/deck_skin.toml b/lovely/objects/visual/deck_skin.toml new file mode 100644 index 000000000..1ad2fcae8 --- /dev/null +++ b/lovely/objects/visual/deck_skin.toml @@ -0,0 +1,110 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +#========================================================# +# Choose any rank for custom deck and use provided atlas # +#========================================================# + +[[patches]] +[patches.regex] +target = "functions/UI_definitions.lua" +pattern = '''function create_UIBox_customize_deck()([\s\S]*?)end''' +position = "at" +payload = ''' +function create_UIBox_customize_deck() + local suitTabs = {} + + local index = 1 + for i, suit in ipairs(SMODS.Suit:obj_list(true)) do + if G.COLLABS.options[suit.key] then + suitTabs[index] = { + label = localize(suit.key, 'suits_plural'), + tab_definition_function = G.UIDEF.custom_deck_tab, + tab_definition_function_args = suit.key + } + index = index + 1 + end + end + + if suitTabs[1] then + suitTabs[1].chosen = true + end + + local t = create_UIBox_generic_options({ back_func = 'options', snap_back = nil, contents = { + {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ + create_tabs( + {tabs = suitTabs, snap_to_nav = true, no_shoulders = true} + )}}} + }) + + return t +end +''' + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = ''' +if specific_vars.nominal_chips then + localize{type = 'other', key = 'card_chips', nodes = desc_nodes, vars = {specific_vars.nominal_chips}} +end +if specific_vars.bonus_chips then + localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {specific_vars.bonus_chips}} +end +''' +position = "at" +payload = ''' +if card.area == G.cdds_cards and card.generate_ds_card_ui and type(card.generate_ds_card_ui) == 'function' and card.deckskin and card.palette then + card.generate_ds_card_ui(card, card.deckskin, card.palette, info_queue, desc_nodes, specific_vars, full_UI_table) +else + if specific_vars.nominal_chips then + localize{type = 'other', key = 'card_chips', nodes = desc_nodes, vars = {specific_vars.nominal_chips}} + end + if specific_vars.bonus_chips then + localize{type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = {specific_vars.bonus_chips}} + end +end +''' +match_indent = true + +#=======================# +# DeckSkin Crediting UI # +#=======================# +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = ''' +label = "Production", +chosen = true, +''' +position = "at" +payload = ''' +label = "Production", +chosen = not SMODS.init_collab_credits, +''' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = '''label = "Collabs",''' +position = "after" +payload = '''chosen = SMODS.init_collab_credits,''' +match_indent = true + +# Add note for overriden function +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +position = 'before' +pattern = ''' +function G.UIDEF.custom_deck_tab(_suit) +''' +payload = ''' +-- WARNING +-- This function is overriden by SMODS and can be found in src/ui.lua +-- WARNING +''' diff --git a/lovely/objects/visual/gradient.toml b/lovely/objects/visual/gradient.toml new file mode 100644 index 000000000..458165d77 --- /dev/null +++ b/lovely/objects/visual/gradient.toml @@ -0,0 +1,56 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +# Game:update +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "self.C.EDITION[2] = 0.7+0.2*(1+math.sin(self.TIMERS.REAL*1.5 + 6))" +position = "after" +payload = ''' +for _,v in pairs(SMODS.Gradients) do + v:update(dt) +end''' +match_indent = true + +# Fix for effect messages +# attention_text +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "args.colour = copy_table(args.colour or G.C.WHITE)" +position = "at" +payload = ''' +args.colour = SMODS.shallow_copy(args.colour or G.C.WHITE) +''' +match_indent = true + +# attention_text +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = ''' +args.cover_colour = copy_table(args.cover_colour or G.C.RED) +args.cover_colour_l = copy_table(lighten(args.cover_colour, 0.2)) +args.cover_colour_d = copy_table(darken(args.cover_colour, 0.2)) +''' +position = "at" +payload = ''' +args.cover_colour = SMODS.shallow_copy(args.cover_colour or G.C.RED) +args.cover_colour_l = SMODS.shallow_copy(lighten(args.cover_colour, 0.2)) +args.cover_colour_d = SMODS.shallow_copy(darken(args.cover_colour, 0.2)) +''' +match_indent = true + +# attention_text +[[patches]] +[patches.pattern] +target = "functions/UI_definitions.lua" +pattern = "args.backdrop_colour = copy_table(args.backdrop_colour)" +position = "at" +payload = ''' +args.backdrop_colour = SMODS.shallow_copy(args.backdrop_colour) +''' +match_indent = true diff --git a/lovely/pool.toml b/lovely/pool.toml new file mode 100644 index 000000000..c21ea79ea --- /dev/null +++ b/lovely/pool.toml @@ -0,0 +1,208 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +### Functions that affect random selection from pools + +# pseudorandom_element() +# TODO special cases for now +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "function pseudorandom_element(_t, seed)" +position = "at" +payload = """function pseudorandom_element(_t, seed, args) + if seed and type(seed) == "string" then seed = pseudoseed(seed) end + -- TODO special cases for now + -- Preserves reverse nominal order for Suits, nominal+face_nominal order for Ranks + -- for vanilla RNG + if _t == SMODS.Suits then + _t = SMODS.Suit:obj_list(true) + end + if _t == SMODS.Ranks then + _t = SMODS.Rank:obj_list() + end +""" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "keys[#keys+1] = {k = k,v = v}" +position = "at" +payload = """ +local keep = true +local in_pool_func = + args and args.in_pool + or type(v) == 'table' and type(v.in_pool) == 'function' and v.in_pool + or _t == G.P_CARDS and function(c) + --Handles special case for Erratic Deck + local initial_deck = args and args.starting_deck or false + + return not ( + not SMODS.add_to_pool(SMODS.Ranks[c.value], {initial_deck = initial_deck, suit = c.suit}) + or not SMODS.add_to_pool(SMODS.Suits[c.suit], {initial_deck = initial_deck, rank = c.value}) + ) + end +if in_pool_func then + keep = in_pool_func(v, args) +end +if keep then + keys[#keys+1] = {k = k,v = v} +end""" +match_indent = true + +# fixes pseudorandom_element on an empty list +# nil, nil is returned in that case +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = "local key = keys[math.random(#keys)].k" +position = "before" +payload = "if #keys == 0 then return nil, nil end" +match_indent = true + +## get_current_pool() + +# Centers + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "else _starting_pool, _pool_key = G.P_CENTER_POOLS[_type], _type..(_append or '')" +match_indent = true +position = 'before' +payload = ''' +elseif SMODS.ObjectTypes[_type] and SMODS.ObjectTypes[_type].rarities then + local rarities = SMODS.ObjectTypes[_type].rarities + local rarity + if _legendary and rarities.legendary then + rarity = rarities.legendary.key + else + rarity = _rarity or SMODS.poll_rarity(_type, 'rarity_'.._type..G.GAME.round_resets.ante..(_append or '')) + end + _starting_pool, _pool_key = SMODS.ObjectTypes[_type].rarity_pools[rarity], _type..rarity..(_append or '')''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "if _type == 'Tarot' or _type == 'Tarot_Planet' then _pool[#_pool + 1] = \"c_strength\"" +match_indent = true +position = 'at' +payload = ''' +if _rarity and SMODS.Rarities[_rarity] and SMODS.Rarities[_rarity].disable_if_empty then _pool[#_pool + 1] = "empty_rarity" +elseif SMODS.ObjectTypes[_type] and SMODS.ObjectTypes[_type].default and G.P_CENTERS[SMODS.ObjectTypes[_type].default] then + _pool[#_pool+1] = SMODS.ObjectTypes[_type].default +elseif _type == 'Tarot' or _type == 'Tarot_Planet' then _pool[#_pool + 1] = "c_strength"''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "if v.name == 'Black Hole' or v.name == 'The Soul' then" +match_indent = true +position = 'at' +payload = "if v.name == 'Black Hole' or v.name == 'The Soul' or v.hidden then" + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "if _type == 'Enhanced' then" +match_indent = true +position = 'before' +payload = ''' +local in_pool, pool_opts = SMODS.add_to_pool(v, { source = _append }) +pool_opts = pool_opts or {} +''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = ''' +if _type == 'Enhanced' then + add = true +''' +match_indent = true +position = 'after' +payload = ''' +elseif _type == 'Edition' then + if v.in_shop then add = true end +''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = 'elseif not (G.GAME.used_jokers[v.key] and not next(find_joker("Showman"))) and' +match_indent = true +position = 'at' +payload = '''elseif not (G.GAME.used_jokers[v.key] and not pool_opts.allow_duplicates and not SMODS.showman(v.key)) and''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = "if add and not G.GAME.banned_keys[v.key] then" +match_indent = true +position = 'before' +payload = ''' +add = in_pool and (add or pool_opts.override_base_checks) +''' + +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = ''' +elseif _type == 'Tag' then _pool[#_pool + 1] = "tag_handy" +''' +match_indent = true +position = 'after' +payload = ''' +elseif _type == 'Edition' then _pool[#_pool + 1] = "e_foil" +''' + +## G.GAME.used_jokers now checks keys, not names +# Card:set_ability() +# Remove the old center from `used_jokers` if set_ability overrides +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "self.config.center = center" +position = 'after' +payload = ''' +if not G.OVERLAY_MENU and old_center and not next(SMODS.find_card(old_center.key, true)) then + G.GAME.used_jokers[old_center.key] = nil +end''' +match_indent = true +[[patches]] +[patches.regex] +target = "card.lua" +pattern = ''' +(?[\t ]*)for k, v in pairs\(G\.P_CENTERS\) do +[\t ]*if v\.name == self\.ability\.name then +[\t ]*G\.GAME\.used_jokers\[k\] = true +[\t ]*end +[\t ]*end''' +position = "at" +payload = ''' +if self.config.center.key then + G.GAME.used_jokers[self.config.center.key] = true +end +''' +line_prepend = "$indent" +# Card:remove() +[[patches]] +[patches.regex] +target = "card.lua" +pattern = ''' +(?[\t ]*)for k, v in pairs\(G\.P_CENTERS\) do +[\t ]*if v\.name == self\.ability\.name then +[\t ]*if not next\(find_joker\(self\.ability\.name, true\)\) then +[\t ]*G\.GAME\.used_jokers\[k\] = nil +[\t ]*end +[\t ]*end +[\t ]*end''' +position = "at" +payload = ''' +if not next(SMODS.find_card(self.config.center.key, true)) then + G.GAME.used_jokers[self.config.center.key] = nil +end''' +line_prepend = "$indent" diff --git a/lovely/shaders/screen_shader_stack.toml b/lovely/shaders/screen_shader_stack.toml new file mode 100644 index 000000000..ee3fd793e --- /dev/null +++ b/lovely/shaders/screen_shader_stack.toml @@ -0,0 +1,74 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +# i think prio 0 is correct. idk if this patch builds off of any other patches + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = ''' +if G.AA_CANVAS then + love.graphics.push() + love.graphics.scale(1/G.CANV_SCALE) + love.graphics.draw(G.AA_CANVAS, 0, 0) + love.graphics.pop() +end +''' +position = "before" +payload = ''' +local last_canvas = self.CANVAS +G.SHADER_CANVAS_A = G.SHADER_CANVAS_A or SMODS.create_canvas() +G.SHADER_CANVAS_B = G.SHADER_CANVAS_B or SMODS.create_canvas() +for index, key in ipairs(SMODS.ScreenShader.obj_buffer) do + local shader = SMODS.ScreenShaders[key] + if (shader.should_apply and shader:should_apply()) or (not shader.should_apply) then + --hypothetically less table accesses + local shader_object = G.SHADERS[shader.shader] + assert(shader_object, "Shader " .. shader.key .. " not found in G.SHADERS") + + -- effectively swaps between A and B + local current_canvas = (last_canvas == G.SHADER_CANVAS_A) and G.SHADER_CANVAS_B or G.SHADER_CANVAS_A + + if shader.send_vars then + local vars = shader:send_vars() + for k,v in pairs(vars) do + shader_object:send(k, v) + end + + end + + love.graphics.setCanvas(current_canvas) + love.graphics.setShader(shader_object) + love.graphics.draw(last_canvas, 0, 0) + + last_canvas = current_canvas + end +end +-- I don't like having this out here but id rather not run it when i don't have to +-- also less setcanvas and setshader means it should run a smidge faster +love.graphics.setCanvas() +love.graphics.setShader() +if last_canvas then + love.graphics.draw(last_canvas, 0, 0) + -- Draw everything we just did to the main canvas (what the player actually sees) +end +''' +match_indent = true + + + +[[patches]] +[patches.pattern] +target = "main.lua" +pattern = ''' +G.CANVAS = love.graphics.newCanvas(w*G.CANV_SCALE, h*G.CANV_SCALE, {type = '2d', readable = true}) +G.CANVAS:setFilter('linear', 'linear') +''' +position = "after" +payload = ''' +G.SHADER_CANVAS_A = SMODS.create_canvas() +G.SHADER_CANVAS_B = SMODS.create_canvas() +''' +match_indent = true \ No newline at end of file diff --git a/lovely/shaders/shaders.toml b/lovely/shaders/shaders.toml new file mode 100644 index 000000000..a7ebeb361 --- /dev/null +++ b/lovely/shaders/shaders.toml @@ -0,0 +1,18 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +# Shader patching +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = '''self.SHADERS[shader_name] = love.graphics.newShader("resources/shaders/"..filename)''' +position = "at" +payload = ''' +local shader = "resources/shaders/"..filename +local lovely = require "lovely" +shader = assert(lovely.apply_patches(filename, love.filesystem.read(shader))) +self.SHADERS[shader_name] = love.graphics.newShader(shader) +''' +match_indent = true diff --git a/lovely/smods_core/compat_0_9_8.toml b/lovely/smods_core/compat_0_9_8.toml new file mode 100644 index 000000000..a61c552ef --- /dev/null +++ b/lovely/smods_core/compat_0_9_8.toml @@ -0,0 +1,38 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +# fallback for card.ability.name +# Card:set_ability() +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "self.ability.bonus = (self.ability.bonus or 0) + (center.config.bonus or 0)" +position = 'after' +payload = "if not self.ability.name then self.ability.name = center.key end" +match_indent = true + +# generate_card_ui() +# `card_type` is used to check whether card should be nil; non-recursive calls +# to generate_card_ui always have that arg set +[[patches]] +[patches.regex] +target = "functions/common_events.lua" +pattern = '(?[\t ]*)function generate_card_ui\([^)]*\)\n' +position = "after" +line_prepend = '$indent' +payload = """ + if card == nil and card_type then + card = SMODS.compat_0_9_8.generate_UIBox_ability_table_card + end + +""" +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "for _, v in ipairs(info_queue) do" +position = 'before' +payload = "SMODS.compat_0_9_8.generate_UIBox_ability_table_card = nil" +match_indent = true + diff --git a/lovely/smods_core/core.toml b/lovely/smods_core/core.toml new file mode 100644 index 000000000..38932c104 --- /dev/null +++ b/lovely/smods_core/core.toml @@ -0,0 +1,28 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "self.SPEEDFACTOR = 1" +position = "after" +payload = 'require "SMODS.preflight.loader".initSteamodded()' +match_indent = true + +[[patches]] +[patches.copy] +target = "main.lua" +position = "append" +sources = ["src/core.lua"] + +[[patches]] +[patches.module] +source = "version.lua" +name = "SMODS.version" + +[[patches]] +[patches.module] +source = "release.lua" +name = "SMODS.release" diff --git a/lovely/smods_core/crash_handler.toml b/lovely/smods_core/crash_handler.toml new file mode 100644 index 000000000..3f9dca29d --- /dev/null +++ b/lovely/smods_core/crash_handler.toml @@ -0,0 +1,32 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + +[[patches]] +[patches.pattern] +target = "main.lua" +pattern = "function love.errhand(msg)" +position = "at" +payload = "if false then" +match_indent = true + +[[patches]] +[patches.copy] +target = "main.lua" +position = "prepend" +sources = [ + "src/crash_handler.lua", +] + +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'after' +pattern = ''' +local saveTable = args.savetext or nil +''' +payload = ''' +if G.SAVED_GAME then SMODS.save_game = G.SAVED_GAME.GAME.smods_version else SMODS.save_game = nil end +''' diff --git a/lovely/smods_core/libs.toml b/lovely/smods_core/libs.toml new file mode 100644 index 000000000..04cbc7bee --- /dev/null +++ b/lovely/smods_core/libs.toml @@ -0,0 +1,32 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +[[patches]] +[patches.module] +source = "libs/json/json.lua" +name = "json" + +[[patches]] +[patches.module] +source = "libs/nativefs/nativefs.lua" +name = "nativefs" + +# We use our own module because we modifed it +# and other mods may vendor their own which doesn't +# have our patches +[[patches]] +[patches.module] +source = "libs/nativefs/nativefs.lua" +name = "SMODS.nativefs" + +[[patches]] +[patches.module] +source = "libs/https/luajit-curl.lua" +name = "luajit-curl" + +[[patches]] +[patches.module] +source = "libs/https/smods-https.lua" +name = "SMODS.https" diff --git a/lovely/smods_core/loader.toml b/lovely/smods_core/loader.toml new file mode 100644 index 000000000..dfe4bd96a --- /dev/null +++ b/lovely/smods_core/loader.toml @@ -0,0 +1,27 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +### Supporting code for loader.lua + +## Save discovered, unlocked states +# Game:init_item_prototypes() +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = "meta.alerted = meta.alerted or {}" +position = 'after' +payload = ''' +for _, t in ipairs{ + G.P_CENTERS, + G.P_BLINDS, + G.P_TAGS, + G.P_SEALS, +} do + for k, v in pairs(t) do + SMODS._save_d_u(v) + v._discovered_unlocked_overwritten = true + end +end''' +match_indent = true diff --git a/lovely/smods_core/preflight.toml b/lovely/smods_core/preflight.toml new file mode 100644 index 000000000..41371ede6 --- /dev/null +++ b/lovely/smods_core/preflight.toml @@ -0,0 +1,47 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -11 + +[[patches]] +[patches.module] +source = "src/preflight/sharedUtil.lua" +name = "SMODS.preflight.sharedUtil" + +[[patches]] +[patches.module] +source = "src/preflight/logging.lua" +name = "SMODS.preflight.logging" + +[[patches]] +[patches.module] +source = "src/preflight/loader.lua" +name = "SMODS.preflight.loader" + +[[patches]] +[patches.module] +source = "src/preflight/sharedUI.lua" +name = "SMODS.preflight.sharedUI" + +[[patches]] +[patches.module] +source = "src/preflight/core.lua" +before = "main.lua" +load_now = true +name = "SMODS.preflight.core" + +[[patches]] +[patches.pattern] +target = '=[lovely SMODS.preflight.core "src/preflight/core.lua"]' +pattern = "local lovely_path = false -- This line is patched, don't edit it" +position = "at" +payload = """local lovely_path = [[{{lovely_hack:patch_dir}}/]]""" +match_indent = true + +[[patches]] +[patches.pattern] +target = "main.lua" +pattern = "if (love.system.getOS() == 'OS X' ) and (jit.arch == 'arm64' or jit.arch == 'arm') then jit.off() end" +position = "before" +payload = """if SMODS and SMODS.preflight_force_quit then if SMODS.preflight_quit_before then SMODS.preflight_quit_before() end return end""" +match_indent = true diff --git a/lovely/sprites/create_sprite.toml b/lovely/sprites/create_sprite.toml new file mode 100644 index 000000000..fb427dbca --- /dev/null +++ b/lovely/sprites/create_sprite.toml @@ -0,0 +1,97 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + + +### Sprite/AnimatedSprite or G.ASSET_ATLAS/ANIMATION_ATLAS -> SMODS.create_sprite() + +# Game:set_render_settings() +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = ''' + self.ANIMATION_ATLAS[self.animation_atli[i].name].frames = self.animation_atli[i].frames +''' +position = 'after' +payload = ''' +self.ANIMATION_ATLAS[self.animation_atli[i].name].atlas_table = "ANIMATION_ATLAS" +''' +match_indent = true + +# Add custom FPS support +[[patches]] +[patches.pattern] +target = 'engine/animatedsprite.lua' +match_indent = true +position = 'at' +pattern = '''local new_frame = math.floor(G.ANIMATION_FPS*(G.TIMERS.REAL - self.offset_seconds))%self.current_animation.frames''' +payload = '''local new_frame = math.floor(self.current_animation.FPS*(G.TIMERS.REAL - self.offset_seconds))%self.current_animation.frames''' +[[patches]] +[patches.pattern] +target = 'engine/animatedsprite.lua' +match_indent = true +position = 'after' +pattern = '''frames = self.animation.frames,''' +payload = '''FPS = self.atlas.fps or G.ANIMATION_FPS,''' + + + + +# Case: ..ATLAS[] +# Blind:init() +[[patches]] +[patches.pattern] +target = 'blind.lua' +pattern = ''' + self.children.animatedSprite = AnimatedSprite(self.T.x, self.T.y, self.T.w, self.T.h, G.ANIMATION_ATLAS['blind_chips'], G.P_BLINDS.bl_small.pos) +''' +position = 'at' +payload = ''' +self.children.animatedSprite = SMODS.create_sprite(self.T.x, self.T.y, self.T.w, self.T.h, 'blind_chips', G.P_BLINDS.bl_small.pos) +''' +match_indent = true +# Tag:generate_UI() +[[patches]] +[patches.pattern] +target = 'tag.lua' +pattern = ''' + local tag_sprite = Sprite(0,0,_size*1,_size*1,G.ASSET_ATLAS[(not self.hide_ability) and G.P_TAGS[self.key].atlas or "tags"], (self.hide_ability) and G.tag_undiscovered.pos or self.pos) +''' +position = 'at' +payload = ''' +local tag_sprite = SMODS.create_sprite(0, 0, _size*1, _size*1, SMODS.get_atlas((not self.hide_ability) and G.P_TAGS[self.key].atlas or "tags"), (self.hide_ability) and G.tag_undiscovered.pos or self.pos) +''' +match_indent = true +# functions/common_events.lua +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = ''' + local blind_sprite = AnimatedSprite(0, 0, 1.2, 1.2, G.ANIMATION_ATLAS[obj.atlas] or G.ANIMATION_ATLAS['blind_chips'], copy_table(G.GAME.blind.pos)) +''' +position = 'at' +payload = ''' +local blind_sprite = SMODS.create_sprite(0, 0, 1.2, 1.2, obj.atlas or 'blind_chips', copy_table(G.GAME.blind.pos)) +''' +match_indent = true +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = ''' + local blind_sprite = Sprite(0, 0, 0.7,0.7, G.ASSET_ATLAS['tags'], copy_table(config.pos)) +''' +position = 'at' +payload = ''' +local blind_sprite = SMODS.create_sprite(0, 0, 0.7,0.7, 'tags', copy_table(config.pos)) +''' +match_indent = true +# functions/UI_definitions.lua +[[patches]] +[patches.regex] +target = 'functions/UI_definitions.lua' +pattern = '(?[\t ].*)(?:Animated)Sprite\((?
    .*)G\.(?:ASSET|ANIMATION)_ATLAS\[(?.*)\],(?.*)'
    +position = 'at'
    +payload = '''
    +$start SMODS.create_sprite($pre $key, $rest 
    +'''
    \ No newline at end of file
    diff --git a/lovely/sprites/easings.toml b/lovely/sprites/easings.toml
    new file mode 100644
    index 000000000..9da752454
    --- /dev/null
    +++ b/lovely/sprites/easings.toml
    @@ -0,0 +1,52 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +## Adds extra easings to the "ease" event type
    +[[patches]]
    +[patches.pattern]
    +target = "engine/event.lua"
    +pattern = "type = config.ease or 'lerp',"
    +position = "at"
    +payload = "type = config.type and string.lower(config.type) or config.ease and string.lower(config.ease) or 'lerp',"
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "engine/event.lua"
    +pattern = '''if self.ease.type == 'lerp' then
    +                    self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
    +                end
    +                if self.ease.type == 'elastic' then
    +                    percent_done = -math.pow(2, 10 * percent_done - 10) * math.sin((percent_done * 10 - 10.75) * 2*math.pi/3);
    +                    self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
    +                end
    +                if self.ease.type == 'quad' then
    +                    percent_done = percent_done * percent_done;
    +                    self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
    +                end'''
    +position = "at"
    +payload = '''
    +local c1 = 1.70158
    +local c2 = c1 * 1.525
    +local c3 = c1 + 1
    +
    +assert(SMODS.ease_types[self.ease.type], "Event created with invalid ease type: "..self.ease.type..(self.func and SMODS.log_crash_info(debug.getinfo(self.func), true)))
    +percent_done = SMODS.ease_types[self.ease.type](percent_done, c1, c2, c3)
    +
    +self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)'''
    +match_indent = true
    +
    +# Fix easing type of splash screen cards (vanilla implementation is broken and always uses 'lerp')
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +ease_value(card.T, 'scale', -card.T.scale, nil, nil, nil, 1.*speed, 'elastic')
    +'''
    +payload = '''
    +ease_value(card.T, 'scale', -card.T.scale, nil, nil, nil, 1.*speed)
    +'''
    \ No newline at end of file
    diff --git a/lovely/sprites/joker_size.toml b/lovely/sprites/joker_size.toml
    new file mode 100644
    index 000000000..aa78d15ca
    --- /dev/null
    +++ b/lovely/sprites/joker_size.toml
    @@ -0,0 +1,73 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +if _center.name == 'Square Joker' and (_center.discovered or self.bypass_discovery_center) then 
    +    self.children.center.scale.y = self.children.center.scale.x
    +end
    +'''
    +position = "after"
    +payload = '''
    +if _center.pixel_size and _center.pixel_size.h and (_center.discovered or self.bypass_discovery_center) then
    +    self.children.center.scale.y = self.children.center.scale.y*(_center.pixel_size.h/95)
    +end
    +if _center.pixel_size and _center.pixel_size.w and (_center.discovered or self.bypass_discovery_center) then
    +    self.children.center.scale.x = self.children.center.scale.x*(_center.pixel_size.w/71)
    +end
    +'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +if center.name == "Wee Joker" and (center.discovered or self.bypass_discovery_center) then 
    +    H = H*0.7
    +    W = W*0.7
    +    self.T.h = H
    +    self.T.w = W
    +end
    +'''
    +position = "after"
    +payload = '''
    +if center.display_size and center.display_size.h and (center.discovered or self.bypass_discovery_center) then
    +    H = H*(center.display_size.h/95)
    +    self.T.h = H
    +elseif center.pixel_size and center.pixel_size.h and (center.discovered or self.bypass_discovery_center) then
    +    H = H*(center.pixel_size.h/95)
    +    self.T.h = H
    +end
    +if center.display_size and center.display_size.w and (center.discovered or self.bypass_discovery_center) then
    +    W = W*(center.display_size.w/71)
    +    self.T.w = W
    +elseif center.pixel_size and center.pixel_size.w and (center.discovered or self.bypass_discovery_center) then
    +    W = W*(center.pixel_size.w/71)
    +    self.T.w = W
    +end
    +'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = "card.lua"
    +pattern = '''
    +self.VT.h = self.T.h
    +self.VT.w = self.T.w
    +'''
    +position = "before"
    +payload = '''
    +if self.config.center.display_size and self.config.center.display_size.h then
    +    self.T.h = H*(self.config.center.display_size.h/95)
    +elseif self.config.center.pixel_size and self.config.center.pixel_size.h then
    +    self.T.h = H*(self.config.center.pixel_size.h/95)
    +end
    +if self.config.center.display_size and self.config.center.display_size.w then
    +    self.T.w = W*(self.config.center.display_size.w/71)
    +elseif self.config.center.pixel_size and self.config.center.pixel_size.w then
    +    self.T.w = W*(self.config.center.pixel_size.w/71)
    +end
    +'''
    +match_indent = true
    diff --git a/lovely/ui/compact_cashout.toml b/lovely/ui/compact_cashout.toml
    new file mode 100644
    index 000000000..98676f316
    --- /dev/null
    +++ b/lovely/ui/compact_cashout.toml
    @@ -0,0 +1,65 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +
    +#
    +# End of round money
    +#
    +
    +# Hide off screen rows
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "if config.name ~= 'bottom' then"
    +position = "after"
    +payload = '''
    +    total_cashout_rows = (total_cashout_rows or 0) + 1
    +    if total_cashout_rows > 7 then
    +        return
    +    end'''
    +match_indent = true
    +
    +# Reset rows amount
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/state_events.lua"
    +pattern = 'G\.FUNCS\.evaluate_round = function\(\)'
    +position = "after"
    +payload = '''
    +
    +    total_cashout_rows = 0'''
    +
    +# Add UI row with total rows hidden
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/state_events.lua"
    +pattern = "add_round_eval_row({name = 'bottom', dollars = dollars})"
    +position = "before"
    +payload = '''
    +if total_cashout_rows > 7 then
    +    local total_hidden = total_cashout_rows - 7
    +
    +    G.E_MANAGER:add_event(Event({
    +        trigger = 'before',delay = 0.38,
    +        func = function()
    +            local hidden = {n=G.UIT.R, config={align = "cm"}, nodes={
    +                {n=G.UIT.O, config={object = DynaText({
    +                    string = {localize{type = 'variable', key = 'cashout_hidden', vars = {total_hidden}}}, 
    +                    colours = {G.C.WHITE}, shadow = true, float = false, 
    +                    scale = 0.45,
    +                    font = G.LANGUAGES['en-us'].font, pop_in = 0
    +                })}}
    +            }}
    +
    +            G.round_eval:add_child(hidden, G.round_eval:get_UIE_by_ID('bonus_round_eval'))
    +            return true
    +        end
    +    }))
    +end'''
    +match_indent = true
    +
    diff --git a/lovely/ui/dollar_row.toml b/lovely/ui/dollar_row.toml
    new file mode 100644
    index 000000000..0a02b1c73
    --- /dev/null
    +++ b/lovely/ui/dollar_row.toml
    @@ -0,0 +1,77 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Dollar row patches (API removed)
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "if num_dollars > 60 then"
    +position = "at"
    +payload = '''
    +if num_dollars > 60 or num_dollars < -60 then'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "local dollar_string = localize('$')..num_dollars"
    +position = "at"
    +payload = '''
    +if num_dollars < 0 then --if negative
    +    G.E_MANAGER:add_event(Event({
    +        trigger = 'before',delay = 0.38,
    +        func = function()
    +            G.round_eval:add_child(
    +                {n=G.UIT.R, config={align = "cm", id = 'dollar_row_'..(dollar_row+1)..'_'..config.name}, nodes={
    +                    {n=G.UIT.O, config={object = DynaText({string = {localize('$')..format_ui_value(num_dollars)}, colours = {G.C.RED}, shadow = true, pop_in = 0, scale = 0.65, float = true})}}
    +                }},
    +                G.round_eval:get_UIE_by_ID('dollar_'..config.name))
    +            play_sound('coin3', 0.9+0.2*math.random(), 0.7)
    +            play_sound('coin6', 1.3, 0.8)
    +            return true
    +        end
    +    }))
    +else --if positive
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "for i = 1, num_dollars or 1 do"
    +position = "at"
    +payload = '''
    +local dollars_to_loop
    +if num_dollars < 0 then dollars_to_loop = (num_dollars*-1)+1 else dollars_to_loop = num_dollars end
    +for i = 1, dollars_to_loop do'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/common_events.lua"
    +pattern = '''(?[\t ]*)else\n[\t ]*local dollars_to_loop'''
    +position = "before"
    +line_prepend = "$indent"
    +payload = '''
    +--asdf
    +end'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = "local r = {n=G.UIT.T, config={text = localize('$'), colour = G.C.MONEY, scale = ((num_dollars > 20 and 0.28) or (num_dollars > 9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}}"
    +position = "at"
    +payload = '''
    +local r
    +if i == 1 and num_dollars < 0 then
    +    r = {n=G.UIT.T, config={text = '-', colour = G.C.RED, scale = ((num_dollars < -20 and 0.28) or (num_dollars < -9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}}
    +    play_sound('coin3', 0.9+0.2*math.random(), 0.7 - (num_dollars < -20 and 0.2 or 0))
    +else
    +    if num_dollars < 0 then r = {n=G.UIT.T, config={text = localize('$'), colour = G.C.RED, scale = ((num_dollars > 20 and 0.28) or (num_dollars > 9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}}
    +    else r = {n=G.UIT.T, config={text = localize('$'), colour = G.C.MONEY, scale = ((num_dollars > 20 and 0.28) or (num_dollars > 9 and 0.43) or 0.58), shadow = true, hover = true, can_collide = false, juice = true}} end
    +end'''
    +match_indent = true
    diff --git a/lovely/ui/menu.toml b/lovely/ui/menu.toml
    new file mode 100644
    index 000000000..0e3e9fb57
    --- /dev/null
    +++ b/lovely/ui/menu.toml
    @@ -0,0 +1,60 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''local main_menu = nil'''
    +position = "after"
    +payload = '''local mods = nil'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''main_menu = UIBox_button{ label = {localize('b_main_menu')}, button = "go_to_menu", minw = 5}'''
    +position = "after"
    +payload = '''mods = UIBox_button{ id = "mods_button", label = {localize('b_mods')}, button = "mods_button", minw = 5}'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''G.ARGS.set_alerts_alertables[11].should_alert = alert_booster'''
    +position = "after"
    +payload = '''table.insert(G.ARGS.set_alerts_alertables, {id = 'mods_button', alert_uibox_name = 'mods_button_alert', should_alert = SMODS.mod_button_alert})'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''main_menu,'''
    +position = "after"
    +payload = '''mods,'''
    +match_indent = true
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = '''self.ASSET_ATLAS[self.asset_atli[i].name].image = love.graphics.newImage(self.asset_atli[i].path, {mipmaps = true, dpiscale = self.SETTINGS.GRAPHICS.texture_scaling})'''
    +position = 'after'
    +payload = '''
    +local mipmap_level = SMODS.config.graphics_mipmap_level_options[SMODS.config.graphics_mipmap_level]
    +if mipmap_level and mipmap_level > 0 then
    +    self.ASSET_ATLAS[self.asset_atli[i].name].image:setMipmapFilter('linear', mipmap_level)
    +end'''
    +match_indent = true
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''create_option_cycle({w = 4,scale = 0.8, label = localize("b_set_CRT_bloom"),options = localize('ml_bloom_opt'), opt_callback = 'change_crt_bloom', current_option = G.SETTINGS.GRAPHICS.bloom}),'''
    +position = 'after'
    +payload = '''
    +create_option_cycle({label = localize('b_graphics_mipmap_level'),scale = 0.8, options = SMODS.config.graphics_mipmap_level_options, opt_callback = 'SMODS_change_mipmap', current_option = SMODS.config.graphics_mipmap_level}),'''
    +match_indent = true
    +
    diff --git a/lovely/ui/object_ui/blind_ui.toml b/lovely/ui/object_ui/blind_ui.toml
    new file mode 100644
    index 000000000..c2a739a5a
    --- /dev/null
    +++ b/lovely/ui/object_ui/blind_ui.toml
    @@ -0,0 +1,176 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Allow blinds to have more than 2 lines
    +
    +# create_UIBox_blind_choice()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = "if blind_state == 'Select' then blind_state = 'Current' end"
    +position = 'after'
    +payload = '''
    +local blind_desc_nodes = {}
    +for k, v in ipairs(text_table) do
    +  blind_desc_nodes[#blind_desc_nodes+1] = {n=G.UIT.R, config={align = "cm", maxw = 2.8}, nodes=SMODS.localize_box(v, {default_col = disabled and G.C.UI.TEXT_INACTIVE or target.text_colour or G.C.WHITE, shadow = not disabled, vars = target.vars, scale = target.scale})}
    +end'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/UI_definitions.lua"
    +pattern = '''
    +(?[\t ]*)text_table\[1\] and \{n=G\.UIT\.R, config=\{align = "cm", minh = 0\.7, padding = 0\.05, minw = 2\.9}, nodes=\{
    +[\t ]*  text_table\[1\] and \{n=G\.UIT\.R, config=\{align = "cm", maxw = 2\.8\}, nodes=\{
    +[\t ]*    \{n=G\.UIT\.T, config=\{id = blind_choice\.config\.key, ref_table = \{val = ''\}, ref_value = 'val', scale = 0\.32, colour = disabled and G\.C\.UI\.TEXT_INACTIVE or G\.C\.WHITE, shadow = not disabled, func = 'HUD_blind_debuff_prefix'\}\},
    +[\t ]*    \{n=G\.UIT\.T, config=\{text = text_table\[1\] or '\-', scale = 0\.32, colour = disabled and G\.C\.UI\.TEXT_INACTIVE or G\.C\.WHITE, shadow = not disabled\}\}
    +[\t ]*  \}\} or nil,
    +[\t ]*  text_table\[2\] and \{n=G\.UIT\.R, config=\{align = "cm", maxw = 2\.8\}, nodes=\{
    +[\t ]*    \{n=G\.UIT\.T, config=\{text = text_table\[2\] or '\-', scale = 0\.32, colour = disabled and G\.C\.UI\.TEXT_INACTIVE or G\.C\.WHITE, shadow = not disabled\}\}
    +[\t ]*  \}\} or nil,
    +[\t ]*\}\} or nil,'''
    +position = "at"
    +payload = '''
    +text_table[1] and {n=G.UIT.R, config={align = "cm", minh = 0.7, padding = 0.05, minw = 2.9}, nodes = blind_desc_nodes} or nil,'''
    +line_prepend = '$indent'
    +
    +# create_UIBox_HUD_blind()
    +# Padding and contained nodes are set in G.FUNCS.HUD_blind_debuff (overrides.lua)
    +[[patches]]
    +[patches.regex]
    +target = "functions/UI_definitions.lua"
    +pattern = '''
    +(?[\t ]*)\{n=G\.UIT\.R, config=\{align = "cm", padding = 0\.05\}, nodes=\{
    +[\t ]*  \{n=G\.UIT\.R, config=\{align = "cm", minh = 0\.3, maxw = 4\.2\}, nodes=\{
    +[\t ]*    \{n=G\.UIT\.T, config=\{ref_table = \{val = ''\}, ref_value = 'val', scale = scale\*0\.9, colour = G\.C\.UI\.TEXT_LIGHT, func = 'HUD_blind_debuff_prefix'\}\},
    +[\t ]*    \{n=G\.UIT\.T, config=\{ref_table = G\.GAME\.blind\.loc_debuff_lines, ref_value = 1, scale = scale\*0\.9, colour = G\.C\.UI\.TEXT_LIGHT, id = 'HUD_blind_debuff_1', func = 'HUD_blind_debuff'\}\}
    +[\t ]*  \}\},
    +[\t ]*  \{n=G\.UIT\.R, config=\{align = "cm", minh = 0\.3, maxw = 4\.2\}, nodes=\{
    +[\t ]*    \{n=G\.UIT\.T, config=\{ref_table = G\.GAME\.blind\.loc_debuff_lines, ref_value = 2, scale = scale\*0\.9, colour = G\.C\.UI\.TEXT_LIGHT, id = 'HUD_blind_debuff_2', func = 'HUD_blind_debuff'\}\}
    +[\t ]*  \}\},
    +[\t ]*\}\},'''
    +position = "at"
    +payload = '''
    +{n=G.UIT.R, config={align = "cm", id = 'HUD_blind_debuff', func = 'HUD_blind_debuff'}, nodes={}},'''
    +line_prepend = '$indent'
    +
    +# Blind:set_text
    +[[patches]]
    +[patches.regex]
    +target = "blind.lua"
    +pattern = """
    +(?[\t ]*)self\\.loc_debuff_lines\\[1\\] = ''
    +[\t ]*self\\.loc_debuff_lines\\[2\\] = ''"""
    +position = 'at'
    +payload = 'EMPTY(self.loc_debuff_lines)'
    +line_prepend = '$indent'
    +
    +[[patches]]
    +[patches.pattern]
    +target = "blind.lua"
    +pattern = "for k, v in ipairs(loc_target) do"
    +position = 'before'
    +payload = '''
    +EMPTY(self.loc_debuff_lines)
    +if G.localization.descriptions[target.set][target.key] then
    +    for k, v in ipairs(G.localization.descriptions[target.set][target.key].text_parsed) do
    +        self.loc_debuff_lines[k] = v
    +    end
    +    self.loc_debuff_lines.vars = target.vars
    +    self.loc_debuff_lines.scale = target.scale
    +    self.loc_debuff_lines.text_colour = target.text_colour
    +else
    +    for k, v in ipairs(loc_target) do
    +        self.loc_debuff_lines[k] = v
    +    end
    +end
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.regex]
    +target = "blind.lua"
    +pattern = """
    +(?[\t ]*)self\\.loc_debuff_lines\\[1\\] = loc_target\\[1\\] or ''
    +[\t ]*self\\.loc_debuff_lines\\[2\\] = loc_target\\[2\\] or ''
    +"""
    +position = 'at'
    +payload = ''
    +
    +## Add a box with h=3.64 (magic number equal to the height of HUD_blind)
    +## centered inside 'row_blind'
    +# create_UIBox_HUD
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = """{n=G.UIT.R, config={align = "cm", id = 'row_blind', minw = 1, minh = 3.75}, nodes={}},"""
    +position = 'at'
    +payload = """{n=G.UIT.R, config={align = "cm", id = 'row_blind', minw = 1, minh = 3.75}, nodes={
    +    {n=G.UIT.B, config={w=0, h=3.64, id = 'row_blind_bottom'}, nodes={}}
    +}},"""
    +match_indent = true
    +
    +## Blind UI's bottom edge is aligned to it
    +[[patches]]
    +[patches.pattern]
    +target = "game.lua"
    +pattern = "config = {major = G.HUD:get_UIE_by_ID('row_blind'), align = 'cm', offset = {x=0,y=-10}, bond = 'Weak'}"
    +position = 'at'
    +payload = "config = {major = G.HUD:get_UIE_by_ID('row_blind_bottom'), align = 'bmi', offset = {x=0,y=-10}, bond = 'Weak'}"
    +match_indent = true
    +
    +## Patch G.GAME.blind:juice_up() across all files
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/common_events.lua"
    +pattern = '''
    +(?[\t ]*)G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_1'\):juice_up\(0\.3, 0\)
    +[\t ]*G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_2'\):juice_up\(0\.3, 0\)
    +[\t ]*G\.GAME\.blind:juice_up\(\)'''
    +position = 'at'
    +payload = 'SMODS.juice_up_blind()'
    +line_prepend = '$indent'
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/state_events.lua"
    +pattern = '''
    +(?[\t ]*)G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_1'\):juice_up\(0\.3, 0\)
    +[\t ]*G\.HUD_blind:get_UIE_by_ID\('HUD_blind_debuff_2'\):juice_up\(0\.3, 0\)
    +[\t ]*G\.GAME\.blind:juice_up\(\)'''
    +position = 'at'
    +payload = 'SMODS.juice_up_blind()'
    +line_prepend = '$indent'
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/state_events.lua"
    +pattern = '''hand_chips = mod_chips\(0\)(\n.*)*?\n[\t ]*(?SMODS.juice_up_blind\(\))'''
    +position = 'at'
    +root_capture = 'juice'
    +payload = 'if SMODS.hand_debuff_source then SMODS.hand_debuff_source:juice_up(0.3,0) else SMODS.juice_up_blind() end'
    +times = 1
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +pattern = 'G.GAME.blind.children.animatedSprite:juice_up(0.05, 0.02)'
    +position = 'at'
    +match_indent = true
    +payload = '''if SMODS.hand_debuff_source then
    +    SMODS.hand_debuff_source:juice_up(0.05, 0.1)
    +else
    +    G.GAME.blind.children.animatedSprite:juice_up(0.05, 0.02)
    +end
    +'''
    +
    +# remove statically added 1 from The Wheel's collection description
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''\(k ==1 and blind\.name == 'The Wheel' and '1' or ''\)\.\.'''
    +position = 'at'
    +payload = ''
    diff --git a/lovely/ui/object_ui/poker_hand_screen.toml b/lovely/ui/object_ui/poker_hand_screen.toml
    new file mode 100644
    index 000000000..e37d667b9
    --- /dev/null
    +++ b/lovely/ui/object_ui/poker_hand_screen.toml
    @@ -0,0 +1,54 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Add enhancements to poker hand screen
    +# create_UIBox_hand_tip
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''local card = Card(0,0, 0.5*G.CARD_W, 0.5*G.CARD_H, G.P_CARDS[v[1]], G.P_CENTERS.c_base)'''
    +position = "at"
    +payload = '''
    +local card = Card(0,0, 0.5*G.CARD_W, 0.5*G.CARD_H, G.P_CARDS[v[1]], G.P_CENTERS[v.enhancement or 'c_base'])
    +if v.edition then card:set_edition(v.edition, true, true) end
    +if v.seal then card:set_seal(v.seal, true, true) end
    +'''
    +match_indent = true
    +
    +# Add text styling to poker hand screen
    +# create_popup_UIBox_tooltip
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''local r = {n=G.UIT.R, config={align = "cm", padding = 0.03}, nodes={
    +                {n=G.UIT.T, config={text = text[i],colour = G.C.UI.TEXT_DARK, scale = 0.4}}}}'''
    +position = "at"
    +payload = '''
    +local r = {n=G.UIT.R, config={align = "cm", padding = 0.03}, nodes=SMODS.localize_box(loc_parse_string(text[i]), {scale = 1.25})}
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''
    +function create_UIBox_current_hand_row(handname, simple)
    +    return (G.GAME.hands[handname].visible) and
    +'''
    +position = "at"
    +payload = '''
    +function create_UIBox_current_hand_row(handname, simple, in_collection)
    +    return (in_collection or SMODS.is_poker_hand_visible(handname)) and
    +'''
    +match_indent = true
    +
    +# Display total played count in collection
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''{n=G.UIT.T, config={text = G.GAME.hands[handname].played, scale = 0.45, colour = G.C.FILTER, shadow = true}},'''
    +position = "at"
    +payload = '''{n=G.UIT.O, config={object = DynaText({string = {tostring(in_collection and G.PROFILES[G.SETTINGS.profile].hand_usage[string.gsub(handname, " ", "")] and G.PROFILES[G.SETTINGS.profile].hand_usage[string.gsub(handname, " ", "")].count or G.GAME.hands[handname].played)}, maxw = 0.9, scale = 0.45, colours = {G.C.FILTER}, shadow = true})}},'''
    +match_indent = true
    \ No newline at end of file
    diff --git a/lovely/ui/object_ui/poker_hand_text.toml b/lovely/ui/object_ui/poker_hand_text.toml
    new file mode 100644
    index 000000000..7ea7ed81e
    --- /dev/null
    +++ b/lovely/ui/object_ui/poker_hand_text.toml
    @@ -0,0 +1,42 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -5
    +
    +# statustext parameter in level_up_hand
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = 'function level_up_hand(card, hand, instant, amount)'
    +position = 'at'
    +match_indent = true
    +payload = 'function level_up_hand(card, hand, instant, amount, statustext)'
    +
    +# Allow custom StatusText values in update_hand_text
    +[[patches]]
    +[patches.regex]
    +target = 'functions/common_events.lua'
    +position = 'at'
    +line_prepend = '$indent'
    +pattern = '''(?[\t ]*)if vals\.StatusText[A-z0-9\n\t .~=()'\-+,<;>:{}]+config\.align\n[\t ]+\}\)\n[\t ]+end'''
    +payload = '''
    +if vals.StatusText then
    +    local StatusText = {
    +        text = delta,
    +        scale = 0.8, 
    +        hold = 1,
    +        cover = G.hand_text_area[name].parent,
    +        cover_colour = mix_colours(parameter.colour, col, 0.1),
    +        emboss = 0.05,
    +        align = 'cm',
    +        cover_align = G.hand_text_area[name].parent.config.align
    +    }
    +    if type(vals.StatusText) == 'string' then StatusText.text = vals.StatusText
    +    elseif type(vals.StatusText) == 'table' then
    +        for k,v in pairs(vals.StatusText) do
    +            if v ~= nil then StatusText[k] = v end
    +        end
    +    end
    +    attention_text(StatusText)
    +end
    +'''
    \ No newline at end of file
    diff --git a/lovely/ui/text/multi_box_descriptions.toml b/lovely/ui/text/multi_box_descriptions.toml
    new file mode 100644
    index 000000000..b60194f0e
    --- /dev/null
    +++ b/lovely/ui/text/multi_box_descriptions.toml
    @@ -0,0 +1,158 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +# Handle multi boxes in localize
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +for _, lines in ipairs(args.type == 'unlocks' and loc_target.unlock_parsed or args.type == 'name' and loc_target.name_parsed or (args.type == 'text' or args.type == 'tutorial' or args.type == 'quips') and loc_target or loc_target.text_parsed) do
    +'''
    +payload = '''
    +args.AUT = args.AUT or {}
    +args.AUT.box_colours = {}
    +if (args.type == 'descriptions' or args.type == 'other') and type(loc_target.text) == 'table' and type(loc_target.text[1]) == 'table' then
    +    args.AUT.multi_box = args.AUT.multi_box or {} 
    +    for i, box in ipairs(loc_target.text_parsed) do
    +        for j, line in ipairs(box) do
    +            local final_line = SMODS.localize_box(line, args)
    +            if i == 1 or next(args.AUT.info) then
    +                args.nodes[#args.nodes+1] = final_line -- Sends main box to AUT.main
    +                if not next(args.AUT.info) then args.nodes.main_box_flag = true end
    +            elseif not next(args.AUT.info) then 
    +                args.AUT.multi_box[i-1] = args.AUT.multi_box[i-1] or {}
    +                args.AUT.multi_box[i-1][#args.AUT.multi_box[i-1]+1] = final_line
    +            end
    +            if not next(args.AUT.info) then args.AUT.box_colours[i] = args.vars.box_colours and args.vars.box_colours[i] or G.C.UI.BACKGROUND_WHITE end
    +        end
    +    end
    +    return
    +end
    +'''
    +
    +# Patch importing localizations
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +for _, line in ipairs(center.text) do
    +    center.text_parsed[#center.text_parsed+1] = loc_parse_string(line)
    +end
    +'''
    +payload = '''
    +for _, line in ipairs(center.text) do
    +    if type(line) == 'table' then
    +        center.text_parsed[#center.text_parsed+1] = {}
    +        for _, new_line in ipairs(line) do
    +             center.text_parsed[#center.text_parsed][#center.text_parsed[#center.text_parsed]+1] = loc_parse_string(new_line)
    +        end
    +    else
    +        center.text_parsed[#center.text_parsed+1] = loc_parse_string(line)
    +    end
    +end
    +'''
    +
    +# Create extra boxes
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +if AUT.info then
    +'''
    +payload = '''
    +AUT.main.background_colour = AUT.main.background_colour or AUT.box_colours and AUT.box_colours[1] or nil
    +local multi_boxes = {}
    +if AUT.multi_box then
    +    for i, box in ipairs(AUT.multi_box) do
    +        box.background_colour = box.background_colour or AUT.box_colours and AUT.box_colours[i+1] or nil
    +        multi_boxes[#multi_boxes+1] = desc_from_rows(box)
    +    end
    +end
    +'''
    +
    +# Change return so it can be modified
    +# Includes some info_boxes patch that got munched
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +return {n=G.UIT.ROOT, config = {align = 'cm', colour = G.C.CLEAR}, nodes={
    +    {n=G.UIT.C, config={align = "cm", func = 'show_infotip',object = Moveable(),ref_table = next(info_boxes) and info_boxes or nil}, nodes={
    +'''
    +payload = '''
    +local cols
    +if #info_boxes <= 3 then
    +    cols = 1
    +elseif #info_boxes <= 10 then
    +    cols = 2
    +elseif #info_boxes <= 24 then
    +    cols = 3
    +else
    +    cols = 4
    +end
    +local nodes_per_col = math.ceil(#info_boxes/cols)
    +local info_cols = {}
    +for i = 0, cols-1 do
    +    local col = {}
    +    for j = 1, nodes_per_col do
    +        local info_box = info_boxes[i*nodes_per_col+j]
    +        if info_box then
    +            table.insert(col, info_box)
    +        else break end
    +    end
    +    table.insert(info_cols, {n=G.UIT.C, config = {align="cm"}, nodes = col})
    +end
    +info_boxes = {{n=G.UIT.R, config = {align="cm", padding = 0.05, card_pos = card.T.x }, nodes = info_cols}}
    +local ret_val = {n=G.UIT.ROOT, config = {align = 'cm', colour = G.C.CLEAR}, nodes={
    +    {n=G.UIT.C, config={align = "cm", func = 'show_infotip',object = Moveable(),ref_table = next(info_boxes) and info_boxes or nil}, nodes={
    +'''
    +
    +# Add multi boxes to return table
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +            badges[1] and {n=G.UIT.R, config={align = "cm", padding = 0.03}, nodes=badges} or nil,
    +            }}
    +        }}
    +    }},
    +}}
    +'''
    +payload = '''
    +if multi_boxes[1] then
    +    for i=1, #ret_val.nodes[1].nodes[1].nodes[1].nodes do -- find the main box
    +        if ret_val.nodes[1].nodes[1].nodes[1].nodes[i] and ret_val.nodes[1].nodes[1].nodes[1].nodes[i].config and ret_val.nodes[1].nodes[1].nodes[1].nodes[i].config.main_box_flag then
    +            for j=#multi_boxes, 1, -1 do -- add the extra boxes
    +                table.insert(ret_val.nodes[1].nodes[1].nodes[1].nodes, i+1, multi_boxes[j])
    +            end
    +            break
    +        end
    +    end
    +    
    +end
    +
    +return ret_val
    +'''
    +
    +# Add main_box_flag to the main box
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +return {n=G.UIT.R, config={align = "cm", colour = empty and G.C.CLEAR or G.C.UI.BACKGROUND_WHITE, r = 0.1, padding = 0.04, minw = 2, minh = 0.8, emboss = not empty and 0.05 or nil, filler = true}, nodes={'''
    +payload = '''
    +return {n=G.UIT.R, config={align = "cm", colour = empty and G.C.CLEAR or G.C.UI.BACKGROUND_WHITE, r = 0.1, padding = 0.04, minw = 2, minh = 0.8, emboss = not empty and 0.05 or nil, filler = true, main_box_flag = desc_nodes.main_box_flag and true or nil}, nodes={'''
    diff --git a/lovely/ui/text/number_formatting.toml b/lovely/ui/text/number_formatting.toml
    new file mode 100644
    index 000000000..0d57d7a49
    --- /dev/null
    +++ b/lovely/ui/text/number_formatting.toml
    @@ -0,0 +1,177 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +#
    +# Use number_format for...
    +#
    +
    +# DynaText
    +
    +[[patches]] 
    +[patches.regex]
    +target = "engine/text.lua"
    +pattern = 'tostring\((?v\.ref_table and v\.ref_table\[v\.ref_value\] or v\.string)\)'
    +position = "at"
    +payload = "format_ui_value($param)"
    +
    +# Cash Out
    +
    +[[patches]] 
    +[patches.regex]
    +target = "functions/common_events.lua"
    +pattern = '''
    +localize\('\$'\)\.\.config\.dollars'''
    +position = "at"
    +payload = "localize('$')..format_ui_value(config.dollars)"
    +
    +# End of round money
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/common_events.lua"
    +pattern = '''
    +localize\('\$'\)\.\.num_dollars\}'''
    +position = "at"
    +payload = "localize('$')..format_ui_value(num_dollars)}"
    +
    +# Tooltip numbers
    +
    +[[patches]] 
    +[patches.regex]
    +target = "functions/misc_functions.lua"
    +pattern = '(?args\.vars\[tonumber\(subpart\[1\]\)\])'
    +position = "at"
    +payload = 'format_ui_value($param)'
    +
    +# Poker Hand chips
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = "{n=G.UIT.T, config={text = G.GAME.hands[handname].chips, scale = 0.45, colour = G.C.UI.TEXT_LIGHT}},"
    +position = "at"
    +payload = "{n=G.UIT.T, config={text = number_format(G.GAME.hands[handname].chips, 1000000), scale = 0.45, colour = G.C.UI.TEXT_LIGHT}},"
    +match_indent = true
    +
    +# Poker Hand mult
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = "{n=G.UIT.T, config={text = G.GAME.hands[handname].mult, scale = 0.45, colour = G.C.UI.TEXT_LIGHT}}"
    +position = "at"
    +payload = "{n=G.UIT.T, config={text = number_format(G.GAME.hands[handname].mult, 1000000), scale = 0.45, colour = G.C.UI.TEXT_LIGHT}}"
    +match_indent = true
    +
    +# Continue Run - Money
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/UI_definitions.lua"
    +pattern = 'tostring\(saved_game\.GAME\.dollars\)'
    +position = "at"
    +payload = "format_ui_value(saved_game.GAME.dollars)"
    +
    +# Continue Run - Best Hand - bigger size
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/UI_definitions.lua"
    +pattern = 'scale_number\(saved_game\.GAME\.round_scores\.hand\.amt\, 0\.8\*scale\)'
    +position = "at"
    +payload = "scale_number(saved_game.GAME.round_scores.hand.amt, 0.8*scale, 100000000000)"
    +
    +
    +#
    +# Custom sci notation switch point
    +#
    +
    +## number_format
    +[[patches]] 
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = 'function number_format(num)'
    +position = "at"
    +payload = '''
    +function number_format(num, e_switch_point)
    +    if type(num) ~= 'number' then return num end
    +
    +    local sign = (num >= 0 and "") or "-"
    +    num = math.abs(num)'''
    +match_indent = true
    +
    +[[patches]] 
    +[patches.regex]
    +target = "functions/misc_functions.lua"
    +pattern = 'num >= G\.E_SWITCH_POINT'
    +position = "at"
    +payload = "num >= (e_switch_point or G.E_SWITCH_POINT)"
    +
    +# 1. Fix floating point error (1.000e92 instead of 10.000e91)
    +# 2. Lower precision with higher numbers
    +[[patches]] 
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''
    +return string.format("%.3f",x/(10^fac))..'e'..fac'''
    +position = "at"
    +payload = '''
    +if num == math.huge then
    +    return sign.."naneinf"
    +end
    +
    +local mantissa = round_number(x/(10^fac), 3)
    +if mantissa >= 10 then
    +    mantissa = mantissa / 10
    +    fac = fac + 1
    +end
    +return sign..(string.format(fac >= 100 and "%.1fe%i" or fac >= 10 and "%.2fe%i" or "%.3fe%i", mantissa, fac))'''
    +match_indent = true
    +
    +# Remove trailing zeroes
    +# E.g. X1.5 being displayed as X1.50
    +[[patches]] 
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''
    +return string.format(num ~= math.floor(num) and (num >= 100 and "%.0f" or num >= 10 and "%.1f" or "%.2f") or "%.0f", num):reverse():gsub("(%d%d%d)", "%1,"):gsub(",$", ""):reverse()'''
    +position = "at"
    +payload = '''
    +local formatted
    +if num ~= math.floor(num) and num < 100 then
    +    formatted = string.format(num >= 10 and "%.1f" or "%.2f", num)
    +    if formatted:sub(-1) == "0" then
    +        formatted = formatted:gsub("%.?0+$", "")
    +    end
    +    -- Return already to avoid comas being added
    +    if num < 0.01 then return tostring(num) end
    +else 
    +    formatted = string.format("%.0f", num)
    +end
    +return sign..(formatted:reverse():gsub("(%d%d%d)", "%1,"):gsub(",$", ""):reverse())'''
    +match_indent = true
    +
    +## scale_number
    +[[patches]] 
    +[patches.pattern]
    +target = "functions/button_callbacks.lua"
    +pattern = 'function scale_number(number, scale, max)'
    +position = "at"
    +payload = 'function scale_number(number, scale, max, e_switch_point)'
    +match_indent = true
    +
    +[[patches]] 
    +[patches.regex]
    +target = "functions/button_callbacks.lua"
    +pattern = 'number >='
    +position = "at"
    +payload = "math.abs(number) >="
    +
    +[[patches]]
    +[patches.regex]
    +target = "functions/button_callbacks.lua"
    +pattern = 'math\.log\(number\*10, 10\)'
    +position = "at"
    +payload = "math.log(math.abs(number)*10, 10)"
    diff --git a/lovely/ui/text/text_effect.toml b/lovely/ui/text/text_effect.toml
    new file mode 100644
    index 000000000..d5a651409
    --- /dev/null
    +++ b/lovely/ui/text/text_effect.toml
    @@ -0,0 +1,126 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +pattern = "if self.config.bump then letter.offset.y = (G.SETTINGS.reduced_motion and 0 or 1)*self.bump_amount*math.sqrt(self.scale)*7*math.max(0, (5+self.bump_rate)*math.sin(self.bump_rate*G.TIMERS.REAL+200*k) - 3 - self.bump_rate) end"
    +position = "after"
    +payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].func) == "function" then
    +    SMODS.DynaTextEffects[self.config.text_effect].func(self, k, letter) -- k is index
    +end'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +pattern = "          local _float, _silent, _pop_in, _bump, _spacing = nil, true, nil, nil, nil"
    +position = "after"
    +payload = '''local text_effects'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +pattern = "            _bump = true; _spacing = 1"
    +position = "after"
    +payload = '''elseif SMODS.DynaTextEffects[part.control.E] then
    +    text_effects = part.control.E'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +pattern = "            spacing = _spacing,"
    +position = "after"
    +payload = '''text_effect = text_effects,'''
    +match_indent = true
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +pattern = """        love.graphics.draw(
    +            letter.letter,
    +            0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.x,
    +            0.5*(letter.dims.y - letter.offset.y)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.y, 
    +            letter.r or 0,
    +            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +            0.5*letter.dims.x/(self.scale),
    +            0.5*letter.dims.y/(self.scale)
    +    )"""
    +position = "before"
    +payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].draw_letter) == "function" then
    +    SMODS.DynaTextEffects[self.config.text_effect].draw_letter(self, k, letter, false) -- actual text
    +else'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +pattern = """        love.graphics.draw(
    +            letter.letter,
    +            0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.x,
    +            0.5*(letter.dims.y - letter.offset.y)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.y, 
    +            letter.r or 0,
    +            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +            real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +            0.5*letter.dims.x/(self.scale),
    +            0.5*letter.dims.y/(self.scale)
    +    )"""
    +position = "after"
    +payload = '''end'''
    +match_indent = true
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +pattern = """            love.graphics.draw(
    +                letter.letter,
    +                0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.x*self.scale/(G.TILESIZE),
    +                0.5*(letter.dims.y)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.y*self.scale/(G.TILESIZE), 
    +                letter.r or 0,
    +                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +                0.5*letter.dims.x/self.scale,
    +                0.5*letter.dims.y/self.scale
    +            )
    +            love.graphics.translate(letter.dims.x*self.font.FONTSCALE/G.TILESIZE, 0)"""
    +position = "before"
    +payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].draw_shadow) == "function" then
    +    SMODS.DynaTextEffects[self.config.text_effect].draw_shadow(self, k, letter) -- shadow
    +else'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +pattern = """            love.graphics.draw(
    +                letter.letter,
    +                0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.x*self.scale/(G.TILESIZE),
    +                0.5*(letter.dims.y)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.y*self.scale/(G.TILESIZE), 
    +                letter.r or 0,
    +                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +                real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
    +                0.5*letter.dims.x/self.scale,
    +                0.5*letter.dims.y/self.scale
    +            )
    +            love.graphics.translate(letter.dims.x*self.font.FONTSCALE/G.TILESIZE, 0)"""
    +position = "after"
    +payload = '''end'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +pattern = """    if self.children.particle_effect then self.children.particle_effect:draw() end"""
    +position = "before"
    +payload = '''if self.config.text_effect and SMODS.DynaTextEffects[self.config.text_effect] and type(SMODS.DynaTextEffects[self.config.text_effect].draw_override) == "function" then
    +    SMODS.DynaTextEffects[self.config.text_effect].draw_override(self)
    +    return
    +end'''
    +match_indent = true
    diff --git a/lovely/ui/text/ui_additional_text_props.toml b/lovely/ui/text/ui_additional_text_props.toml
    new file mode 100644
    index 000000000..a67727523
    --- /dev/null
    +++ b/lovely/ui/text/ui_additional_text_props.toml
    @@ -0,0 +1,386 @@
    +[manifest]
    +version = "1.2"
    +dump_lua = true
    +priority = -10
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = """spacing = math.max(0, 0.32*(17 - #assembled_string)),"""
    +position = "after"
    +payload = """font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],"""
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = """spacing = _spacing,"""
    +position = "after"
    +payload = """font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],"""
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = """text = assembled_string,"""
    +position = "after"
    +payload = """font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],"""
    +match_indent = true
    +
    +# descsacle
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +local desc_scale = G.LANG.font.DESCSCALE
    +'''
    +payload = '''
    +local desc_scale = (SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)] or G.LANG.font).DESCSCALE
    +'''
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = """loc_target = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key]"""
    +position = "at"
    +payload = """
    +loc_target = loc_target or {}
    +if pcall(function() loc_target.name_parsed = {loc_parse_string(G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name)} end) then
    +else loc_target.name_parsed = {} end"""
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = """if ret_string then return ret_string end"""
    +position = "before"
    +payload = """if ret_string and type(ret_string) == 'string' then ret_string = string.gsub(ret_string, "{.-}", "") end"""
    +match_indent = true
    +
    +# Replace all instances of the language font
    +[[patches]]
    +[patches.regex]
    +target = "engine/ui.lua"
    +pattern = '''
    +self.config.lang.font'''
    +position = "at"
    +payload = '''(self.config.font or self.config.lang.font)'''
    +line_prepend = "$indent"
    +
    +[[patches]]
    +[patches.regex]
    +target = "engine/ui.lua"
    +pattern = '''
    +node.config.lang.font'''
    +position = "at"
    +payload = '''(node.config.font or node.config.lang.font)'''
    +line_prepend = "$indent"
    +
    +# Fix name scale when using formatting
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''
    +local final_line = {}
    +'''
    +position = 'after'
    +match_indent = true
    +payload = '''
    +local final_name_assembled_string = ''
    +if args.type == 'name' and loc_target.name_parsed then
    +    for _, part in ipairs(lines) do
    +        local assembled_string_part = ''
    +        for _, subpart in ipairs(part.strings) do
    +            assembled_string_part = assembled_string_part..(type(subpart) == 'string' and subpart or format_ui_value(args.vars[tonumber(subpart[1])]) or 'ERROR')
    +        end
    +        final_name_assembled_string = final_name_assembled_string..assembled_string_part
    +    end
    +end
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''
    +spacing = math.max(0, 0.32*(17 - #assembled_string)),
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +spacing = (not args.no_spacing and math.max(0, 0.32*(17 - #(final_name_assembled_string or assembled_string)))) or nil,
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +position = 'at'
    +match_indent = true
    +pattern = 'scale =  (0.55 - 0.004*#assembled_string)*(part.control.s and tonumber(part.control.s) or 1)'
    +payload = 'scale = (0.55 - 0.004*#(final_name_assembled_string or assembled_string))*(part.control.s and tonumber(part.control.s) or 1)*(args.fixed_scale or 1)'
    +
    +# Add support for multi line name
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''
    +if pcall(function() loc_target.name_parsed = {loc_parse_string(G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name)} end) then
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if pcall(function()
    +local name = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key]
    +loc_target.name_parsed = name.name_parsed or {loc_parse_string(name.name)}
    +end) then
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''
    +if pcall(function() ret_string = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name end) then
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +if pcall(function()
    +    local name_text = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name
    +    if type(name_text) == "table" then
    +        ret_string = ""
    +        for i, line in ipairs(name_text) do
    +            ret_string = ret_string.. (i ~= 1 and " " or "")..line
    +        end
    +    else
    +        ret_string = name_text
    +    end
    +end) then
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = '''
    +    if args.type == 'name' or args.type == 'text' then return final_line end
    +    args.nodes[#args.nodes+1] = final_line
    +end
    +'''
    +position = 'at'
    +match_indent = true
    +payload = '''
    +    if args.type == 'text' then return final_line end
    +    if not args.nodes and args.type == 'name' then args.nodes = {} end
    +    args.nodes[#args.nodes+1] = final_line
    +end
    +if args.type == 'name' then
    +    local final_name = {}
    +
    +    for _, line in ipairs(args.nodes or {}) do
    +        final_name[#final_name+1] = {n=G.UIT.R, config={align = "m"}, nodes=line}
    +    end
    +
    +    return final_name
    +end
    +'''
    +
    +# Marquee text in DynaText
    +# Adjust width when marquee is enabled
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self.strings[k].W > self.config.W then self.config.W = self.strings[k].W; self.strings[k].W_offset = 0 end
    +'''
    +payload = '''
    +if self.strings[k].W > self.config.W then
    +    self.config.W = self.strings[k].W
    +    self.strings[k].W_offset = 0
    +    if self.config.marquee and self.config.maxw then
    +        if self.config.W > self.config.maxw then
    +            self.config.marquee_width = self.config.W/self.config.maxw
    +            self.config.W = self.config.maxw
    +        else
    +            self.config.marquee = 'no'
    +        end
    +    end
    +end
    +'''
    +# Adjust width when marquee is enabled
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +if self.config.maxw and self.config.W > self.config.maxw then
    +'''
    +payload = '''
    +if self.config.maxw and self.config.W > self.config.maxw and not self.config.marquee then
    +'''
    +# Adjust width when marquee is enabled
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +v.W_offset = 0.5*(self.config.W - v.W)
    +'''
    +payload = '''
    +v.W_offset = 0.5*(self.config.W - (self.config.marquee and self.config.maxw and self.config.maxw < v.W and self.config.maxw or v.W))
    +'''
    +
    +# Add start/end index calculation
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +if self.children.particle_effect then self.children.particle_effect:draw() end
    +'''
    +payload = '''
    +local start_index = 1
    +local end_index = #self.strings[self.focused_string].letters
    +if self.config.marquee and self.config.marquee ~= 'no' then
    +    local padding = math.floor(#self.strings[self.focused_string].letters / (self.config.marquee_width or 1)) - 1
    +    if self.dt and (self.dt - self.config.hold) / self.config.scroll_speed > (#self.strings[self.focused_string].letters + math.ceil(padding/4)) then self.dt = 0 end
    +    if self.dt and self.dt > self.config.hold then
    +        start_index = 1 + (math.floor((self.dt - self.config.hold) / self.config.scroll_speed) % (#self.strings[self.focused_string].letters + math.ceil(padding/4)))
    +    end
    +    end_index = math.min(start_index + padding, #self.strings[self.focused_string].letters)
    +end
    +'''
    +
    +# Use start/end index
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +for k, letter in ipairs(self.strings[self.focused_string].letters) do
    +    local real_pop_in = self.config.min_cycle_time == 0 and 1 or letter.pop_in
    +'''
    +payload = '''
    +for k=start_index, end_index do
    +    local letter = self.strings[self.focused_string].letters[k]
    +    local real_pop_in = self.config.min_cycle_time == 0 and 1 or letter.pop_in
    +'''
    +
    +# Update dt
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +function DynaText:update(dt)
    +'''
    +payload = '''
    +function DynaText:update(dt, real_dt)
    +if self.config.marquee then
    +    self.dt = (self.dt or 0) + real_dt
    +end
    +'''
    +
    +# Ensure hold is present
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/text.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +self.silent = (config.silent)
    +'''
    +payload = '''
    +self.config.marquee = self.config.marquee and not G.SETTINGS.reduced_motion
    +self.config.hold = self.config.hold or self.config.marquee and 1.5 or nil
    +self.config.scroll_speed = self.config.scroll_speed or self.config.marquee and 0.1 or nil
    +'''
    +
    +# Pass real_dt to update function
    +[[patches]]
    +[patches.pattern]
    +target = 'game.lua'
    +match_indent = true
    +position = 'at'
    +pattern = '''
    +v:update(dt*self.SPEEDFACTOR)
    +'''
    +payload = '''
    +v:update(dt*self.SPEEDFACTOR, self.real_dt)
    +'''
    +
    +
    +# Add underline property for text
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/ui.lua'
    +match_indent = true
    +position = 'before'
    +pattern = '''
    +--Draw the 'chosen triangle'
    +'''
    +payload = '''
    +if self.config.underline and self.config.underline[4] > 0.01 then 
    +    prep_draw(self, 1)
    +    love.graphics.scale(1/(G.TILESIZE))
    +    love.graphics.setLineWidth(1)
    +    love.graphics.setColor(self.config.underline)
    +    self:draw_pixellated_under('line', parallax_dist)
    +    love.graphics.pop()
    +end
    +'''
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],
    +'''
    +payload = '''
    +underline = part.control.u and loc_colour(part.control.u),
    +'''
    +# DynaText compat
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +final_line[#final_line].nodes[1] = {n=G.UIT.O, config={
    +'''
    +payload = '''
    +underline = part.control.u and loc_colour(part.control.u),
    +'''
    +
    +# Add button support for text formatting
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +font = SMODS.Fonts[part.control.f] or G.FONTS[tonumber(part.control.f)],
    +'''
    +payload = '''
    +button = part.control.button,
    +'''
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'after'
    +pattern = '''
    +final_line[#final_line].nodes[1] = {n=G.UIT.O, config={
    +'''
    +payload = '''
    +button = part.control.button,
    +'''
    \ No newline at end of file
    diff --git a/lovely/ui/ui.toml b/lovely/ui/ui.toml
    new file mode 100644
    index 000000000..17427bb55
    --- /dev/null
    +++ b/lovely/ui/ui.toml
    @@ -0,0 +1,405 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +### Addition Tab
    +
    +## Decks tab
    +# create_UIBox_your_collection_decks()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''G.GAME.viewed_back = Back(G.P_CENTERS.b_red)'''
    +position = "at"
    +payload = '''
    +local deck_pool = SMODS.collection_pool(G.P_CENTER_POOLS.Back)
    +G.GAME.viewed_back = Back(G.ACTIVE_MOD_UI and deck_pool[1] or G.P_CENTERS.b_red)'''
    +match_indent = true
    +
    +# create_UIBox_your_collection_decks()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''(?[\t ]*)for k, v in ipairs\(G\.P_CENTER_POOLS\.Back\) do\n[\s\S]{4}ordered_names\[#ordered_names\+1\] = v\.name\n[\s\S]{2}end'''
    +position = 'at'
    +payload = '''
    +for k, v in ipairs(deck_pool) do
    +    ordered_names[#ordered_names+1] = v.key
    +end'''
    +line_prepend = '$indent'
    +
    +# create_UIBox_your_collection_decks()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''local t = create_UIBox_generic_options({ back_func = 'your_collection', contents = {'''
    +position = "at"
    +payload = '''local t = create_UIBox_generic_options({ 
    +colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_colour or
    +    (G.ACTIVE_MOD_UI.ui_config or {}).colour),
    +bg_colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_bg_colour or
    +    (G.ACTIVE_MOD_UI.ui_config or {}).bg_colour),
    +back_colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_back_colour or
    +    (G.ACTIVE_MOD_UI.ui_config or {}).back_colour),
    +outline_colour = G.ACTIVE_MOD_UI and ((G.ACTIVE_MOD_UI.ui_config or {}).collection_outline_colour or
    +    (G.ACTIVE_MOD_UI.ui_config or {}).outline_colour),
    +back_func = G.ACTIVE_MOD_UI and "openModUI_"..G.ACTIVE_MOD_UI.id or 'your_collection', contents = {'''
    +match_indent = true
    +
    +# create_UIBox_your_collection_decks()
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''create_option_cycle({options = ordered_names, opt_callback = 'change_viewed_back', current_option = 1, colour = G.C.RED, w = 4.5, focus_args = {snap_to = true}, mid = '''
    +position = "at"
    +payload = '''create_option_cycle({options = ordered_names, opt_callback = 'change_viewed_back', current_option = 1, colour = G.ACTIVE_MOD_UI and (G.ACTIVE_MOD_UI.ui_config or {}).collection_option_cycle_colour or G.C.RED, w = 4.5, focus_args = {snap_to = true}, mid = '''
    +match_indent = true
    +
    +# G.FUNCS.your_collection_deck_page
    +[[patches]]
    +[patches.pattern]
    +target = "functions/button_callbacks.lua"
    +pattern = '''G.GAME.viewed_back:change_to(G.P_CENTER_POOLS.Back[args.to_key])'''
    +position = "at"
    +payload = '''
    +local deck_pool = SMODS.collection_pool(G.P_CENTER_POOLS.Back)
    +G.GAME.viewed_back:change_to(deck_pool[args.to_key])'''
    +match_indent = true
    +
    +# create_UIBox_your_collection()
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''(?[\t ]*)UIBox_button\(\{button = 'your_collection_blinds', label = \{localize\('b_blinds'\)\}, count = G\.DISCOVER_TALLIES\.blinds, minw = 5, minh = 2.0, id = 'your_collection_blinds', focus_args = \{snap_to = true\}\}\),'''
    +position = 'after'
    +payload = '''UIBox_button({button = 'your_collection_other_gameobjects', label = {localize('k_other')}, minw = 5, id = 'your_collection_other_gameobjects', focus_args = {snap_to = true}}),'''
    +
    +# Fix UIElement.config.chosen being overriden if choice=true is set
    +# UIElement:click()
    +[[patches]]
    +[patches.pattern]
    +target = "engine/ui.lua"
    +match_indent = true
    +position = "after"
    +pattern = "if self.config.choice then"
    +payload = "    local chosen_temp = self.config.chosen"
    +
    +[[patches]]
    +[patches.pattern]
    +target = "engine/ui.lua"
    +match_indent = true
    +position = "at"
    +pattern = "self.config.chosen = true"
    +payload = "self.config.chosen = chosen_temp or true"
    +
    +# Escape from mod menu saves config
    +# Needs to be before all checks
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/controller.lua'
    +pattern = "function Controller:key_press_update(key, dt)"
    +position = "after"
    +payload = '''
    +    if key == "escape" and (G.ACTIVE_MOD_UI or SMODS.IN_MODS_TAB) then
    +        G.FUNCS.exit_mods()
    +    end
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +match_indent = true
    +position = 'at'
    +pattern = "config = {offset = {x=-0.03,y=0}, align = 'cl', parent = e}"
    +payload = """config = (not e.config.ref_table or not e.config.ref_table[1].config.card_pos or e.config.ref_table[1].config.card_pos > G.ROOM.T.w*0.4) and
    +    {offset = {x=-0.03,y=0}, align = 'cl', parent = e} or
    +    {offset = {x=0.03,y=0}, align = 'cr', parent = e}"""
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'tag.lua'
    +match_indent = true
    +position = 'at'
    +pattern = "_self.config.h_popup_config ={align = 'cl', offset = {x=-0.1,y=0},parent = _self}"
    +payload = """_self.config.h_popup_config = (_self.T.x > G.ROOM.T.w*0.4) and
    +    {align =  'cl', offset = {x=-0.1,y=0},parent = _self} or
    +    {align =  'cr', offset = {x=0.1,y=0},parent = _self}"""
    +    
    +# desc_from_rows
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +position = 'at'
    +pattern = 'colour = empty and G\.C\.CLEAR or G\.C\.UI\.BACKGROUND_WHITE'
    +payload = 'colour = desc_nodes.background_colour or empty and G.C.CLEAR or G.C.UI.BACKGROUND_WHITE'
    +
    +# info_tip_from_rows
    +[[patches]]
    +[patches.regex]
    +target = 'functions/UI_definitions.lua'
    +position = 'at'
    +pattern = 'padding = 0\.05, colour = G\.C\.WHITE\}'
    +payload = 'padding = 0.05, colour = desc_nodes.background_colour or G.C.WHITE}'
    +
    +# localize
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +position = 'after'
    +pattern = '\(part\.control\.C and loc_colour\(part\.control\.C\)\)'
    +payload = ' or args.text_colour'
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +position = 'at'
    +pattern = 'loc_colour\(part\.control\.C or nil, args\.default_col\)'
    +payload = 'not part.control.C and args.text_colour or loc_colour(part.control.C or nil, args.default_col)'
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +position = 'after'
    +pattern = 'part\.control\.s and tonumber\(part\.control\.s\)'
    +payload = ' or args.scale '
    +
    +# set_discover_tallies()
    +# exclude no_collection objects
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = "if not v.omit then"
    +payload = "if not v.omit and not v.no_collection then"
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/misc_functions.lua"
    +pattern = "if v.set == 'Joker' then"
    +position = "at"
    +payload = "if v.set == 'Joker' and not v.no_collection and not v.omit then "
    +match_indent = true
    +times = 1
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +line_prepend = '$indent'
    +position = 'at'
    +pattern = '(?[\t ]*)(?for _, v in pairs\(G\.P_[BT].*)(?(\n.*){7})'
    +payload = '''$start
    +    if not v.no_collection then
    +    $rest
    +end
    +'''
    +
    +#set_alerts()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +match_indent = true
    +position = 'at'
    +pattern = "if v.discovered and not v.alerted then"
    +payload = "if v.discovered and not v.alerted and not v.no_collection then"
    +
    +## Description controls
    +# localize
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = 'final_line[#final_line+1] = {n=G.UIT.O, config={'
    +payload = '''
    +final_line[#final_line+1] = {n=G.UIT.C, config={align = "m", colour = part.control.B and args.vars.colours[tonumber(part.control.B)] or part.control.X and loc_colour(part.control.X) or nil, r = 0.05, padding = 0.03, res = 0.15}, nodes={}}
    +final_line[#final_line].nodes[1] = {n=G.UIT.O, config={
    +'''
    +
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = 'elseif part.control.X then'
    +payload = 'elseif part.control.X or part.control.B then' 
    +
    +[[patches]]
    +[patches.regex]
    +target = 'functions/misc_functions.lua'
    +position = 'at'
    +pattern = 'colour = loc_colour\(part.control.X\)'
    +payload = 'colour = part.control.B and args.vars.colours[tonumber(part.control.B)] or loc_colour(part.control.X)' 
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +match_indent = true
    +position = 'at'
    +pattern = 'colour = loc_colour(part.control.C or nil),'
    +payload = 'colour = part.control.V and args.vars.colours[tonumber(part.control.V)] or loc_colour(part.control.C or nil),'
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/common_events.lua"
    +pattern = '''
    +G.E_MANAGER:add_event(Event({ --Add bonus chips from this card
    +                    trigger = 'before',
    +                    delay = delay,
    +'''
    +position = "at"
    +payload = '''
    +G.E_MANAGER:add_event(Event({ --Add bonus chips from this card
    +                    trigger = trigger,
    +                    delay = delay,
    +                    blocking = blocking,
    +                    blockable = blockable,
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = "functions/UI_definitions.lua"
    +pattern = '''   if args.info then 
    +     t = {n=args.col and G.UIT.C or G.UIT.R, config={align = "cm"}, nodes={'''
    +position = "before"
    +payload = """if args.hide_label then 
    +    local t2 = {}
    +    for i = 1, #t.nodes do
    +        if i ~= 1 then table.insert(t2, t.nodes[i]) end
    +    end
    +    t.nodes = t2
    +end"""
    +match_indent = true
    +
    +# UIBox_button():
    +# the counters on collection buttons use text_colour instead of being hardcoded to white
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = "{n=G.UIT.T, config={scale = 0.35,text = args.count.tally..' / '..args.count.of, colour = {1,1,1,0.9}}}"
    +position = "at"
    +match_indent = true
    +payload = "{n=G.UIT.T, config={scale = 0.35,text = args.count.tally..' / '..args.count.of, colour = args.text_colour}}"
    +
    +# G.UIDEF.card_h_popup():
    +# add a "card_type_text_colour" variable
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = 'local card_type_colour = get_type_colour(card.config.center or card.config, card)'
    +position = 'after'
    +match_indent = true
    +payload = 'local card_type_text_colour = (AUT.card_type and SMODS.ConsumableTypes[AUT.card_type] and SMODS.ConsumableTypes[AUT.card_type].text_colour) or G.C.UI.TEXT_LIGHT'
    +
    +# G.UIDEF.card_h_popup():
    +# pass "card_type_text_colour" variable to create_badge() when creating the badge for a card type
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = "badges[#badges + 1] = create_badge(((card.ability.name == 'Pluto' or card.ability.name == 'Ceres' or card.ability.name == 'Eris') and localize('k_dwarf_planet')) or (card.ability.name == 'Planet X' and localize('k_planet_q') or card_type),card_type_colour, nil, 1.2)"
    +position = 'at'
    +match_indent = true
    +payload = "badges[#badges + 1] = create_badge(((card.ability.name == 'Pluto' or card.ability.name == 'Ceres' or card.ability.name == 'Eris') and localize('k_dwarf_planet')) or (card.ability.name == 'Planet X' and localize('k_planet_q') or card_type), card_type_colour, card_type_text_colour, 1.2)"
    +
    +# Fixing description error when info_queue has multi-box descriptions.
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/misc_functions.lua'
    +pattern = '''
    +bump = true,
    +silent = true,
    +pop_in = 0,
    +pop_in_rate = 4,
    +maxw = 5,
    +shadow = true,
    +y_offset = -0.6,
    +'''
    +position = "at"
    +payload = '''
    +bump = not args.no_bump,
    +text_effect = SMODS.DynaTextEffects[part.control.E] and part.control.E,
    +silent = not args.no_silent,
    +pop_in = (not args.no_pop_in and (args.pop_in or 0)) or nil,
    +pop_in_rate = (not args.no_pop_in and (args.pop_in_rate or 4)) or nil,
    +maxw = args.maxw or 5,
    +shadow = not args.no_shadow,
    +y_offset = args.y_offset or -0.6,'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/common_events.lua'
    +pattern = '''
    +desc_nodes.name = localize{type = 'name_text', key = name_override or _c.key, set = name_override and 'Other' or _c.set} 
    +'''
    +position = "after"
    +payload = '''
    +-- If statement guards against setting `name_styled` twice. This apparently happens
    +-- when generating ui for Lucky Cards: smods takes ownership of vanilla, so
    +-- this code is reached in both places it appears
    +if not desc_nodes.name_styled then
    +  local set = name_override and "Other" or _c.set
    +  local key = name_override or _c.key
    +  if set == "Seal" then
    +    if G.localization.descriptions["Other"][_c.key.."_seal"] then set = "Other"; key = key.."_seal" end
    +  else
    +    if not G.localization.descriptions[set][_c.key] then set = "Other" end
    +  end
    +  desc_nodes.name_styled = {}
    +  localize{type = 'name', key = key, set = set, nodes = desc_nodes.name_styled, fixed_scale = 0.63, no_pop_in = true, no_shadow = true, y_offset = 0, no_spacing = true, no_bump = true, vars = (_c.create_fake_card and _c.loc_vars and (_c:loc_vars({}, _c:create_fake_card()) or {}).vars) or {colours = {}}} 
    +  desc_nodes.name_styled = SMODS.info_queue_desc_from_rows(desc_nodes.name_styled, true)
    +  desc_nodes.name_styled.config.align = "cm"
    +end
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''
    +function info_tip_from_rows(desc_nodes, name)
    +'''
    +position = "after"
    +payload = '''
    +  local name_nodes
    +  if not desc_nodes.name_styled then
    +    name_nodes = {{n=G.UIT.T, config={text = name, scale = 0.32, colour = G.C.UI.TEXT_LIGHT}}}
    +  else
    +    name_nodes = {desc_nodes.name_styled}
    +  end
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/node.lua'
    +pattern = '''
    +self.children.h_popup.states.drag.can = true
    +'''
    +position = "after"
    +payload = '''
    +-- Fixes styled info_queue names
    +-- This ensures show_infotip runs just after the main hover box is created instead of being able to fall one frame after
    +if ((self.children.h_popup.UIRoot.children[1] or {}).config or {}).func == "show_infotip" then
    +  G.FUNCS.show_infotip(self.children.h_popup.UIRoot.children[1])
    +end
    +'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''
    +{n=G.UIT.R, config={align = "tm", minh = 0.36, padding = 0.03}, nodes={{n=G.UIT.T, config={text = name, scale = 0.32, colour = G.C.UI.TEXT_LIGHT}}}},
    +'''
    +position = "at"
    +payload = '''
    +{n=G.UIT.R, config={align = "tm", minh = 0.36, padding = 0.03}, nodes=name_nodes},
    +'''
    +match_indent = true
    diff --git a/lovely/ui/ui_elements.toml b/lovely/ui/ui_elements.toml
    new file mode 100644
    index 000000000..820a48a4b
    --- /dev/null
    +++ b/lovely/ui/ui_elements.toml
    @@ -0,0 +1,152 @@
    +[manifest]
    +version = "1.0.0"
    +dump_lua = true
    +priority = -10
    +
    +## colour argument fix
    +# create_tabs()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''tab_buttons[#tab_buttons+1] = UIBox_button({id = 'tab_but_'..(v.label or ''), ref_table = v, button = 'change_tab', label = {v.label}, minh = 0.8*args.scale, minw = 2.5*args.scale, col = true, choice = true, scale = args.text_scale, chosen = v.chosen, func = v.func, focus_args = {type = 'none'}})'''
    +position = 'at'
    +payload = '''tab_buttons[#tab_buttons+1] = UIBox_button({id = 'tab_but_'..(v.label or ''), ref_table = v, button = 'change_tab', label = {v.label}, minh = 0.8*args.scale, minw = 2.5*args.scale, col = true, choice = true, scale = args.text_scale, chosen = v.chosen, func = v.func, colour = args.colour, focus_args = {type = 'none'}})'''
    +match_indent = true
    +
    +# UIElement:draw_self()
    +[[patches]]
    +[patches.pattern]
    +target = 'engine/ui.lua'
    +pattern = '''love.graphics.polygon("fill", get_chosen_triangle_from_rect(self.layered_parallax.x, self.layered_parallax.y, self.VT.w*G.TILESIZE, self.VT.h*G.TILESIZE, self.config.chosen == 'vert'))'''
    +position = 'before'
    +payload = '''love.graphics.setColor(self.config.colour)'''
    +match_indent = true
    +
    +
    +## multiple text input fix
    +# create_text_input()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''args.current_prompt_text = '''''
    +position = 'after'
    +payload = '''args.id = args.id or "text_input"'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''ui_letters[#ui_letters+1] = {n=G.UIT.T, config={ref_table = args, ref_value = 'current_prompt_text', scale = args.text_scale, colour = lighten(copy_table(args.colour), 0.4), id = 'prompt'}}'''
    +position = 'at'
    +payload = '''ui_letters[#ui_letters+1] = {n=G.UIT.T, config={ref_table = args, ref_value = 'current_prompt_text', scale = args.text_scale, colour = lighten(copy_table(args.colour), 0.4), id = args.id..'_prompt'}}'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''ui_letters[i] = {n=G.UIT.T, config={ref_table = text.letters, ref_value = i, scale = args.text_scale, colour = G.C.UI.TEXT_LIGHT, id = 'letter_'..i}}'''
    +position = 'at'
    +payload = '''ui_letters[i] = {n=G.UIT.T, config={ref_table = text.letters, ref_value = i, scale = args.text_scale, colour = G.C.UI.TEXT_LIGHT, id = args.id..'_letter_'..i}}'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''ui_letters[#ui_letters+1] = {n=G.UIT.B, config={r = 0.03,w=0.1, h=0.4, colour = position_text_colour, id = 'position', func = 'flash'}}'''
    +position = 'at'
    +payload = '''ui_letters[#ui_letters+1] = {n=G.UIT.B, config={r = 0.03,w=0, h=0.4, colour = position_text_colour, id = args.id..'_position', func = 'flash'}}'''
    +match_indent = true
    +
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''{n=G.UIT.C, config={align = "cm", draw_layer = 1, colour = G.C.CLEAR}, nodes = {'''
    +position = 'at'
    +payload = '''{n=G.UIT.C, config={align = "cm", colour = G.C.CLEAR}, nodes = {'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = '''{n=G.UIT.C, config={id = 'text_input', align = "cm", padding = 0.05, r = 0.1, draw_layer = 2, hover = true, colour = args.colour,minw = args.w, min_h = args.h, button = 'select_text_input', shadow = true}, nodes={'''
    +position = 'at'
    +payload = '''{n=G.UIT.C, config={id = args.id, align = "cm", padding = 0.05, r = 0.1, hover = true, colour = args.colour,minw = args.w, min_h = args.h, button = 'select_text_input', shadow = true}, nodes={'''
    +match_indent = true
    +
    +# G.FUNCS.select_text_input
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''G.CONTROLLER.text_input_hook = e.children[1].children[1]'''
    +position = 'after'
    +payload = '''G.CONTROLLER.text_input_id = e.config.id'''
    +match_indent = true
    +
    +# G.FUNCS.paste_seed
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''G.CONTROLLER.text_input_hook = e.UIBox:get_UIE_by_ID('text_input').children[1].children[1]'''
    +position = 'after'
    +payload = """G.CONTROLLER.text_input_id = 'text_input'"""
    +match_indent = true
    +
    +# G.FUNCS.flash
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''if G.CONTROLLER.text_input_hook then'''
    +position = 'at'
    +payload = '''if G.CONTROLLER.text_input_hook and G.CONTROLLER.text_input_id == e.config.id:sub(1,string.len(G.CONTROLLER.text_input_id)) then'''
    +match_indent = true
    +
    +# TRANSPOSE_TEXT_INPUT()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''if hook.children[i].config.id == 'position' then'''
    +position = 'at'
    +payload = '''if hook.children[i].config.id == G.CONTROLLER.text_input_id..'_position' then'''
    +match_indent = true
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''local real_letter = hook.children[position_child+dir].config.id:sub(1, 7) == 'letter_' and hook.children[position_child+dir].config.text ~= '''''
    +position = 'at'
    +payload = '''local real_letter = hook.children[position_child+dir].config.id:sub(1, 8+string.len(G.CONTROLLER.text_input_id)) == G.CONTROLLER.text_input_id..'_letter_' and hook.children[position_child+dir].config.text ~= '''''
    +match_indent = true
    +
    +# GET_TEXT_FROM_INPUT()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/button_callbacks.lua'
    +pattern = '''if hook.children[i].config and hook.children[i].config.id:sub(1, 7) == 'letter_' and hook.children[i].config.text ~= '' then'''
    +position = 'at'
    +payload = '''if hook.children[i].config and hook.children[i].config.id:sub(1, 8+string.len(G.CONTROLLER.text_input_id)) == G.CONTROLLER.text_input_id..'_letter_' and hook.children[i].config.text ~= '' then'''
    +match_indent = true
    +
    +# remove hardcoded shop selection limit
    +[[patches]]
    +[patches.pattern]
    +target = "cardarea.lua"
    +pattern = "if self.highlighted[1] then"
    +position = "at"
    +payload = "if #self.highlighted >= self.config.highlighted_limit then"
    +match_indent = true
    +
    +# Deck unlock popup shows custom unlock descriptions
    +# create_UIBox_deck_unlock()
    +[[patches]]
    +[patches.pattern]
    +target = 'functions/UI_definitions.lua'
    +pattern = "if deck_center.unlock_condition.type == 'win_deck' then"
    +position = "before"
    +payload = """
    +if deck_center.check_for_unlock and type(deck_center.check_for_unlock) == "function" then
    +    local loc_args = {}
    +    local key_override
    +    if deck_center.locked_loc_vars and type(deck_center.locked_loc_vars) == 'function' then
    +        local res = deck_center:locked_loc_vars() or {}
    +        loc_args = res.vars or {}
    +        key_override = res.key
    +    end
    +    localize{type = 'unlocks', key = key_override or deck_center.key, set = "Back", nodes = deck_criteria, vars = loc_args, default_col = G.C.WHITE, shadow = true}
    +end
    +"""
    +match_indent = true