From 1e11095f5c060086d16edbd19c4150b2d696cc78 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 8 Apr 2026 11:15:50 +0200 Subject: [PATCH 1/7] Moving Blind code from #1307. */+Title, realized this best be a separate PR too. --- src/game_object.lua | 113 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/game_object.lua b/src/game_object.lua index 6c0e25812..9692196ad 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -1806,6 +1806,8 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. debuff = {}, vars = {}, config = {}, + blind_types = nil, -- Map of types, e.g. {Boss = true, Big = true} + -- [type] = {min=..., max=...} -> Per-type constraints for appearing. dollars = 5, mult = 2, atlas = 'blind_chips', @@ -1827,8 +1829,119 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end G.P_BLINDS[self.key] = self if self.modifies_draw then SMODS.Blinds.modifies_draw[self.key] = true end + end, + get_types = function(self) + return self.blind_types end } + + function SMODS.get_blind_types(blind_obj) + local ret + if type(blind_obj.get_types) == "function" then + ret = blind_obj:get_types() + end + if not ret then + if blind_obj.boss then + if not blind_obj.boss.showdown then + return {Boss = true} + else + return {Showdown = true} + end + else + if blind_obj.name == "Small Blind" then + return {Small = true} + else + return {Big = true} + end + end + end + return ret + end + + function get_new_boss() + -- sendWarnMessage("get_new_boss() is deprecated; Call SMODS.get_new_blind() instead.", "utils") + local b_types = SMODS.is_showdown_ante() and {Showdown = true} or {Boss = true} + local boss = SMODS.get_new_blind(b_types) + return boss + end + + function SMODS.get_new_blind(blind_types) + -- Use SMODS object weight system when enabled + if SMODS.optional_features.object_weights then return SMODS.poll_object({type = 'Blind', blind_types = blind_types}) end + if not blind_types or (blind_types.Boss or blind_types.Showdown) then + G.GAME.perscribed_bosses = G.GAME.perscribed_bosses or {} + if G.GAME.perscribed_bosses and G.GAME.perscribed_bosses[G.GAME.round_resets.ante] then + local ret_boss = G.GAME.perscribed_bosses[G.GAME.round_resets.ante] + G.GAME.perscribed_bosses[G.GAME.round_resets.ante] = nil + G.GAME.bosses_used[ret_boss] = G.GAME.bosses_used[ret_boss] + 1 + return ret_boss + end + if G.FORCE_BOSS then return G.FORCE_BOSS end + end + + local eligible_bosses = {} + for k, v in pairs(G.P_BLINDS) do + local res = SMODS.add_to_pool(v) + if res then + local b_types = SMODS.get_blind_types(v) + local blind_config = v.boss or (table_length(b_types) == 1 and next(b_types)) or {} + for b_type, _ in pairs(b_types) do + if blind_types[b_type] then + if v.in_pool or not (blind_config and (blind_config.min and blind_config.min > math.max(1, G.GAME.round_resets.ante) or blind_config.max and blind_config.max <= G.GAME.round_resets.ante)) then + eligible_bosses[k] = true + break + end + end + end + end + end + for k, v in pairs(G.GAME.banned_keys) do + if eligible_bosses[k] then eligible_bosses[k] = nil end + end + + local min_use = 100 + for k, v in pairs(G.GAME.bosses_used) do + if eligible_bosses[k] then + eligible_bosses[k] = v + if eligible_bosses[k] <= min_use then + min_use = eligible_bosses[k] + end + end + end + for k, v in pairs(eligible_bosses) do + if eligible_bosses[k] then + if eligible_bosses[k] > min_use then + eligible_bosses[k] = nil + end + end + end + local _, boss = pseudorandom_element(eligible_bosses, pseudoseed('boss')) + G.GAME.bosses_used[boss] = G.GAME.bosses_used[boss] + 1 + + return boss + end + + function Blind:get_type() + return SMODS.get_blind_types(self.config.blind) + end + + function Blind:is_type(b_type) + return self:is_types({[b_type] = true}, false) + end + + function Blind:is_types(b_types_map, all) + for k, v in pairs(self:get_type()) do + if v and b_types_map[k] or (k == "Showdown" and b_types_map.Boss) then + if not all then + return true + end + elseif all then + return false + end + end + return all + end + SMODS.Blind:take_ownership('eye', { set_blind = function(self, reset, silent) if not reset then From 126930fd8f3b34bc7e4cb1393897aea9cd272221 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 8 Apr 2026 11:17:26 +0200 Subject: [PATCH 2/7] Moved patches *Title --- lovely/better_calc.toml | 4 +- lovely/blind.toml | 81 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/lovely/better_calc.toml b/lovely/better_calc.toml index 1c1fd2665..5ce194858 100644 --- a/lovely/better_calc.toml +++ b/lovely/better_calc.toml @@ -943,7 +943,7 @@ payload = ''' 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 }) +SMODS.calculate_context({end_of_round = true, game_over = game_over, beat_boss = G.GAME.blind:is_type("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 ''' @@ -956,7 +956,7 @@ 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 }) + SMODS.calculate_end_of_round_effects({ cardarea = v, end_of_round = true, beat_boss = G.GAME.blind:is_type("Boss") }) end ''' diff --git a/lovely/blind.toml b/lovely/blind.toml index 9d1771fa1..da762863b 100644 --- a/lovely/blind.toml +++ b/lovely/blind.toml @@ -587,4 +587,83 @@ pattern = "local blindTable = {" position = "after" payload = ''' effect = self.effect, -''' \ No newline at end of file +''' + +### Account for changed Blind:get_type() function +# Blind.set_blind : Use new Blind:is_types() +[[patches]] +[patches.pattern] +target = "blind.lua" +match_indent = true +pattern = "if G.GAME.modifiers.no_blind_reward and G.GAME.modifiers.no_blind_reward[self:get_type()] then self.dollars = 0 end" +position = "at" +payload = '''if G.GAME.modifiers.no_blind_reward and self:is_types(G.GAME.modifiers.no_blind_reward) then self.dollars = 0 end''' +# game.lua : Game:start_run() : account for "Showdown" type with "no_reward" rule +[[patches]] +[patches.pattern] +target = "game.lua" +match_indent = true +pattern = "self.GAME.modifiers.no_blind_reward.Boss = true" +position = "after" +payload = '''self.GAME.modifiers.no_blind_reward.Showdown = true''' +# card.lua : Card:generate_UIBox_ability_table() +[[patches]] +[patches.pattern] +target = "game.lua" +match_indent = true +pattern = "local disableable = G.GAME.blind and ((not G.GAME.blind.disabled) and (G.GAME.blind:get_type() == 'Boss'))" +position = "at" +payload = '''local disableable = G.GAME.blind and ((not G.GAME.blind.disabled) and (G.GAME.blind:is_type("Boss")))''' +# card.lua : Card:calculate_joker() : Luchador !-- could be taken ownership of instead +[[patches]] +[patches.pattern] +target = "game.lua" +match_indent = true +pattern = "if G.GAME.blind and ((not G.GAME.blind.disabled) and (G.GAME.blind:get_type() == 'Boss')) then " +position = "at" +payload = '''if G.GAME.blind and ((not G.GAME.blind.disabled) and (G.GAME.blind:is_type("Boss"))) then ''' +# functions/button_callbacks.lua : G.FUNCS.HUD_blind_reward() +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +match_indent = true +pattern = "if G.GAME.modifiers.no_blind_reward and (G.GAME.blind and G.GAME.modifiers.no_blind_reward[G.GAME.blind:get_type()]) then" +position = "at" +payload = ''' +if G.GAME.modifiers.no_blind_reward and (G.GAME.blind and blind:is_types(G.GAME.modifiers.no_blind_reward)) then +''' +# functions/state_events.lua : end_round() +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +match_indent = true +pattern = '''if G.GAME.blind:get_type() == 'Boss' and not G.GAME.seeded and not G.GAME.challenge then''' +position = "at" +payload = ''' +if G.GAME.blind:is_type("Boss") and not G.GAME.seeded and not G.GAME.challenge then +''' +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +match_indent = true +pattern = '''if G.GAME.blind:get_type() == 'Boss' then''' +position = "at" +payload = ''' +if G.GAME.blind:is_type("Boss") then +''' +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +match_indent = true +pattern = '''if not G.GAME.won and G.GAME.round_resets.ante >= G.GAME.win_ante and G.GAME.blind_on_deck == 'Boss' then''' +position = "at" +payload = ''' +if G.GAME.round_resets.ante == G.GAME.win_ante and G.GAME.blind:is_type("Boss") then +''' +# game.lua : Game:init_game_object() +[[patches]] +[patches.regex] +target = "game.lua" +pattern = '''if v.boss then bosses_used[k] = 0 end''' +position = "at" +payload = '''if SMODS.get_blind_types(blind_obj).Boss then bosses_used[k] = 0 end''' From aea56092bf61e417d9662f9877fb09fffa0521cf Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 8 Apr 2026 11:20:15 +0200 Subject: [PATCH 3/7] Moved updated weights code *Title --- src/utils/weights.lua | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/utils/weights.lua b/src/utils/weights.lua index b40f4cdda..b0a4a912a 100644 --- a/src/utils/weights.lua +++ b/src/utils/weights.lua @@ -145,25 +145,26 @@ function SMODS.get_poll_key(type, infill) return t.str .. (t.block_infill and "" or infill or "") .. (t.ante and G.GAME.round_resets.ante or "") end -function SMODS.create_blind_pool(blind_type, skip_cull) - assert(type(blind_type) == 'string', "SMODS.create_blind_pool called with a non-string type argument."..SMODS.log_crash_info(debug.getinfo(2))) +function SMODS.create_blind_pool(_blind_type, skip_cull) + assert(type(_blind_type) == 'string' or type(_blind_type) == 'table', "SMODS.create_blind_pool called with an invalid argument."..SMODS.log_crash_info(debug.getinfo(2))) + local blind_types + if type(_blind_type) == "string" then + blind_types = {[_blind_type] = true} + else + blind_types = _blind_type + end local eligible_bosses = {} + _blind_type = (table_length(blind_types) == 1 and next(blind_types)) or "" -- If a string was passed, remains unchanged. for k, v in pairs(G.P_BLINDS) do local res, options = SMODS.add_to_pool(v) options = options or {} - if not v[blind_type] then - elseif options.ignore_showdown_check then + local blind_config = v.boss or v[_blind_type] or {} + if v:is_types(blind_types) and ( + v.in_pool or blind_config and ((blind_config.min or G.GAME.round_resets.ante) <= math.max(1, G.GAME.round_resets.ante) and + (blind_config.max or G.GAME.round_resets.ante) >= G.GAME.round_resets.ante) + ) + then eligible_bosses[k] = res and true or nil - elseif blind_type == 'boss' then - if - ((SMODS.is_showdown_ante()) == (v.boss.showdown or false)) and ((v[blind_type].min or G.GAME.round_resets.ante) <= math.max(1, G.GAME.round_resets.ante)) and ((v[blind_type].max or G.GAME.round_resets.ante) >= G.GAME.round_resets.ante) - then - eligible_bosses[k] = res and true or nil - end - else - if (v[blind_type].min or G.GAME.round_resets.ante) <= math.max(1, G.GAME.round_resets.ante) and (v[blind_type].max or G.GAME.round_resets.ante) >= G.GAME.round_resets.ante then - eligible_bosses[k] = res and true or nil - end end end for k, v in pairs(G.GAME.banned_keys) do @@ -286,7 +287,7 @@ function SMODS.create_poll_pool(labels, args) local temp_pool = {} local join_func = (args.attributes and not args.union) and SMODS.intersect_lists or join_lists for i=1, #(args.rarities or {true}) do - local _p = label == 'Blind' and SMODS.create_blind_pool(args.blind_type or 'boss') or SMODS.Attributes[label] and SMODS.get_attribute_pool(label) or get_current_pool(label, args.rarities and args.rarities[i], nil, args.append) + local _p = label == 'Blind' and SMODS.create_blind_pool(args.blind_types or {Boss = true}) or SMODS.Attributes[label] and SMODS.get_attribute_pool(label) or get_current_pool(label, args.rarities and args.rarities[i], nil, args.append) if SMODS.Attributes[label] then _p = SMODS.cull_pool(_p, args) end From 2fa6f0f52e3a471868482ab3050d5da0ded590e2 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 8 Apr 2026 11:29:37 +0200 Subject: [PATCH 4/7] Updated Madness patch *Title, untested. --- lovely/scaling.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lovely/scaling.toml b/lovely/scaling.toml index f0f1d80c5..bf1995aa4 100644 --- a/lovely/scaling.toml +++ b/lovely/scaling.toml @@ -275,7 +275,7 @@ if self.ability.name == 'Madness' and not context.blueprint and not context.blin position = 'at' match_indent = true payload = ''' -if self.ability.name == 'Madness' and not context.blueprint and not context.blind.boss then +if self.ability.name == 'Madness' and not context.blueprint and not (context.blind:get_types() or {}).Boss then ''' # Madness pt2 [[patches]] From 33e20d810373885745df56b2da70a2dd4da926f8 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 8 Apr 2026 11:30:57 +0200 Subject: [PATCH 5/7] Missing `SMODS.Joker:take_ownership()`s +Title --- src/game_object.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/game_object.lua b/src/game_object.lua index 9692196ad..f85d8b0d7 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -1942,6 +1942,17 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return all end + SMODS.Joker:take_ownership("j_matador", { + check_for_unlock = function (self, args) + return G.GAME.current_round.hands_played == 1 and G.GAME.current_round.discards_left == G.GAME.round_resets.discards and G.GAME.blind:is_type("Boss") + end + }) + + SMODS.Joker:take_ownership("j_hanging_chad", { + check_for_unlock = function (self, args) + return G.GAME.last_hand_played == self.unlock_condition.extra and G.GAME.blind:is_type("Boss") + end + }) SMODS.Blind:take_ownership('eye', { set_blind = function(self, reset, silent) if not reset then From c7f181367eb7c95eec33b0825af8f0ecd00de47c Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 8 Apr 2026 11:47:39 +0200 Subject: [PATCH 6/7] Removed unnecessary patches -Title --- lovely/blind.toml | 38 -------------------------------------- lovely/weights.toml | 14 -------------- 2 files changed, 52 deletions(-) diff --git a/lovely/blind.toml b/lovely/blind.toml index da762863b..a190e6053 100644 --- a/lovely/blind.toml +++ b/lovely/blind.toml @@ -462,44 +462,6 @@ payload = ''' {n=G.UIT.O, config={object = DynaText({string = localize(target), colours = {G.C.WHITE},shadow = true, float = true,maxw = 2.2, scale = 0.45})}} ''' -# 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] diff --git a/lovely/weights.toml b/lovely/weights.toml index f3f843893..edcf93b53 100644 --- a/lovely/weights.toml +++ b/lovely/weights.toml @@ -3,20 +3,6 @@ version = "1.0.0" dump_lua = true priority = -10 -# Bypass get_new_boss() -[[patches]] -[patches.pattern] -target = 'functions/common_events.lua' -match_indent = true -position = 'after' -pattern = ''' -function get_new_boss() -''' -payload = ''' --- Use SMODS object weight system when enabled -if SMODS.optional_features.object_weights then return SMODS.poll_object({type = 'Blind'}) end -''' - # Bypass create_card_for_shop() [[patches]] [patches.pattern] From ef4f4441b2e1741e2b0154da6bf35fb9a891cf1f Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Mon, 20 Apr 2026 10:55:58 +0200 Subject: [PATCH 7/7] Updated `SMODS.get_new_blind()` *Title, now includes boss counter fix. --- src/game_object.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/game_object.lua b/src/game_object.lua index 19444b33a..6546e1daa 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -1870,7 +1870,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.get_new_blind(blind_types) -- Use SMODS object weight system when enabled - if SMODS.optional_features.object_weights then return SMODS.poll_object({type = 'Blind', blind_types = blind_types}) end + if SMODS.optional_features.object_weights then + local ret = SMODS.poll_object({type = 'Blind', blind_types = blind_types}) + G.GAME.bosses_used[ret] = G.GAME.bosses_used[ret] + 1 + return ret + end if not blind_types or (blind_types.Boss or blind_types.Showdown) then G.GAME.perscribed_bosses = G.GAME.perscribed_bosses or {} if G.GAME.perscribed_bosses and G.GAME.perscribed_bosses[G.GAME.round_resets.ante] then