balatro-mods/Steamodded/lovely/fixes.toml
2025-05-20 03:22:28 +08:00

697 lines
20 KiB
TOML

[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 = "(?<indent>[\t ]*)if not \\(st.init and st:init\\(\\)\\) then\n[\t ]*(?<quit>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 = '''
(?<indent>[\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 = '''
(?<indent>[\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'
## Prevent softlock if booster pack is empty
## Crashes the game when you skip too fast on this PR, along with being the culprit for allowing you to skip boosters early
# G.FUNCS.can_skip_booster()
# [[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'
# 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
# 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.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
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
if self.added_to_deck and old_center and not self.debuff then
self:remove_from_deck()
self.added_to_deck = true
end
if type(center) == 'string' then
assert(G.P_CENTERS[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 self.added_to_deck and old_center and not self.debuff then
self.added_to_deck = false
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 = '''
(?<indent>[\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
self.ability.extra_value = self.ability.extra_value or 0
for k, v in pairs(new_ability) do
self.ability[k] = v
end
-- reset keys do not persist an 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
### 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 = '''
(?<indent>[\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+
((?<indent>[\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 (v.in_pool and type(v.in_pool) == 'function' and not v:in_pool()) 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
# 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 = NFS.getWorkingDirectory()
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 = '''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 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 next(find_joker('Smeared Joker')) and 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