From d5315cc6e1796b2acf8f0b812437ed1ad4090b37 Mon Sep 17 00:00:00 2001 From: icyethics <97385843+icyethics@users.noreply.github.com> Date: Sun, 8 Mar 2026 13:59:46 +0100 Subject: [PATCH] Blind Slots API added pooling, reroll and customization functionality to blind slots --- lovely/better_calc.toml | 4 +- lovely/blind.toml | 279 ++++++++++++++++++++++++++++++++++++- lovely/fixes.toml | 2 +- lovely/shop.toml | 24 ++-- lsp_def/utils.lua | 84 ++++++++++- src/game_object.lua | 78 ++++++++++- src/overrides.lua | 49 +++++++ src/utils.lua | 299 +++++++++++++++++++++++++++++++++++++++- 8 files changed, 800 insertions(+), 19 deletions(-) diff --git a/lovely/better_calc.toml b/lovely/better_calc.toml index 57de48a98..514d52864 100644 --- a/lovely/better_calc.toml +++ b/lovely/better_calc.toml @@ -839,7 +839,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 = SMODS.blind_is_boss(), beat_boss_exception = SMODS.blind_is_boss(true), ends_ante = SMODS.blind_ends_ante(), 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 ''' @@ -852,7 +852,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 = SMODS.blind_is_boss(), beat_boss_exception = SMODS.blind_is_boss(true), ends_ante = SMODS.blind_ends_ante()}) end ''' diff --git a/lovely/blind.toml b/lovely/blind.toml index ada510652..ec97fd432 100644 --- a/lovely/blind.toml +++ b/lovely/blind.toml @@ -549,4 +549,281 @@ pattern = "local blindTable = {" position = "after" payload = ''' effect = self.effect, -''' \ No newline at end of file +''' + +# G.FUNCS.select_blind +[[patches]] +[patches.pattern] +target = "functions/button_callbacks.lua" +match_indent = true +pattern = "G.GAME.round_resets.blind = e.config.ref_table" +position = "after" +payload = ''' +G.GAME.round_resets.current_blind_info = G.GAME.round_resets.current_blind_info or {} +G.GAME.round_resets.current_blind_info[G.GAME.blind_on_deck] = G.GAME.round_resets.blind_info[G.GAME.blind_on_deck] +''' + +[[patches]] +[patches.pattern] +target = "game.lua" +match_indent = true +pattern = "self.GAME.round_resets.blind_choices.Boss = get_new_boss()" +position = "at" +payload = ''' +SMODS.set_new_blind("Small", G.GAME.round_resets.blind_info.Small.pools) +SMODS.set_new_blind("Big", G.GAME.round_resets.blind_info.Big.pools) +SMODS.set_new_blind("Boss", G.GAME.round_resets.blind_info.Boss.pools) +''' + +# end_round() +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +match_indent = true +pattern = ''' +if G.GAME.round_resets.blind == G.P_BLINDS.bl_small then + G.GAME.round_resets.blind_states.Small = 'Defeated' +elseif G.GAME.round_resets.blind == G.P_BLINDS.bl_big then + G.GAME.round_resets.blind_states.Big = 'Defeated' +else + G.GAME.current_round.voucher = get_next_voucher_key() + G.GAME.round_resets.blind_states.Boss = 'Defeated' + for k, v in ipairs(G.playing_cards) do + v.ability.played_this_ante = nil + end +end +''' +position = "at" +payload = ''' +G.GAME.round_resets.blind_states[G.GAME.blind.blind_info.default_pool_key] = "Defeated" +if SMODS.blind_ends_ante() then + G.GAME.current_round.voucher = SMODS.get_next_vouchers() + for k, v in ipairs(G.playing_cards) do + v.ability.played_this_ante = nil + end +end +''' + +# end_round() +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +match_indent = true +pattern = ''' +if G.GAME.blind:get_type() == 'Boss' then +''' +position = "at" +payload = ''' +if SMODS.blind_ends_ante() then +''' + +# Card:calculate_joker() +[[patches]] +[patches.pattern] +target = "card.lua" +match_indent = true +pattern = ''' +if self.ability.name == 'Campfire' and G.GAME.blind.boss and self.ability.x_mult > 1 then +''' +position = "at" +payload = ''' +if self.ability.name == 'Campfire' and SMODS.blind_is_boss() and self.ability.x_mult > 1 then +''' + +# Card:calculate_joker() +[[patches]] +[patches.pattern] +target = "card.lua" +match_indent = true +pattern = ''' +if self.ability.name == 'Rocket' and G.GAME.blind.boss then +''' +position = "at" +payload = ''' +if self.ability.name == 'Rocket' and SMODS.blind_is_boss() then +''' + +# Card:calculate_joker() +# Chicot +[[patches]] +[patches.pattern] +target = "card.lua" +match_indent = true +pattern = ''' +if self.ability.name == 'Chicot' and G.GAME.blind and G.GAME.blind.boss and not G.GAME.blind.disabled then +''' +position = "at" +payload = ''' +if self.ability.name == 'Chicot' and SMODS.blind_can_disable() then +''' + +# Card:calculate_joker() +# Matador +[[patches]] +[patches.pattern] +target = "card.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 SMODS.blind_can_disable() then +''' + +# Tag:apply_to_run(_context) +[[patches]] +[patches.pattern] +target = "tag.lua" +match_indent = true +pattern = ''' +G.GAME.last_blind and G.GAME.last_blind.blind_info and G.GAME.last_blind.blind_info.is_boss then +''' +position = "at" +payload = ''' +G.GAME.last_blind and G.GAME.last_blind.boss then +''' + +# Tag:apply_to_run(_context) +[[patches]] +[patches.pattern] +target = "back.lua" +match_indent = true +pattern = ''' +if self.name == 'Anaglyph Deck' and args.context == 'eval' and G.GAME.last_blind and G.GAME.last_blind.boss then +''' +position = "at" +payload = ''' +if self.name == 'Anaglyph Deck' and args.context == 'eval' and G.GAME.last_blind and G.GAME.last_blind.blind_info and G.GAME.last_blind.blind_info.is_boss then +''' + + +# check_for_unlock +# hanging chad +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +match_indent = true +pattern = ''' +if G.GAME.last_hand_played == card.unlock_condition.extra and G.GAME.blind:get_type() == 'Boss' then +''' +position = "at" +payload = ''' +if G.GAME.last_hand_played == card.unlock_condition.extra and SMODS.blind_is_boss() then +''' + +# check_for_unlock +# matador +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +match_indent = true +pattern = ''' +G.GAME.blind:get_type() == 'Boss' then +''' +position = "at" +payload = ''' +SMODS.blind_is_boss() then +''' + +# card:generate_uiability_box +[[patches]] +[patches.pattern] +target = 'functions/common_events.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 = SMODS.blind_can_disable() +''' + +# Disable Skipping +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +match_indent = true +pattern = ''' +if _tag and _tag_container then + _tag_container.children[2].config.draw_after = false + _tag_container.children[2].config.colour = G.C.BLACK + _tag.children[2].config.button = 'skip_blind' + _tag.config.outline_colour = adjust_alpha(G.C.BLUE, 0.5) + _tag.children[2].config.hover = true + _tag.children[2].config.colour = G.C.RED + _tag.children[2].children[1].config.colour = G.C.UI.TEXT_LIGHT + local _sprite = _tag.config.ref_table + _sprite.config.force_focus = nil +end +''' +position = "at" +payload = ''' +if _tag and _tag_container then + if G.GAME.round_resets.blind_info[e.config.id].can_skip and not G.GAME.disable_skips then + _tag_container.children[2].config.draw_after = false + _tag_container.children[2].config.colour = G.C.BLACK + _tag.children[2].config.button = 'skip_blind' + _tag.config.outline_colour = adjust_alpha(G.C.BLUE, 0.5) + _tag.children[2].config.hover = true + _tag.children[2].config.colour = G.C.RED + _tag.children[2].children[1].config.colour = G.C.UI.TEXT_LIGHT + local _sprite = _tag.config.ref_table + _sprite.config.force_focus = nil + else + _tag.children[2].config.button = nil + _tag.config.outline_colour = G.C.UI.BACKGROUND_INACTIVE + _tag.children[2].config.hover = false + _tag.children[2].config.colour = G.C.UI.BACKGROUND_INACTIVE + _tag.children[2].children[1].config.colour = G.C.UI.TEXT_INACTIVE + end +end +''' + +# Disable Skipping +[[patches]] +[patches.pattern] +target = 'blind.lua' +match_indent = true +pattern = ''' +self.chips = get_blind_amount(G.GAME.round_resets.ante)*self.mult*G.GAME.starting_params.ante_scaling +''' +position = "at" +payload = ''' +self.chips = get_blind_amount(G.GAME.round_resets.ante)*self.mult*G.GAME.starting_params.ante_scaling + +if G.GAME.blind.blind_info then + if G.GAME.blind.blind_info and G.GAME.blind.blind_info.blind_multiplier then + self.chips = self.chips * G.GAME.blind.blind_info.blind_multiplier + end + + if G.GAME.blind.blind_info and G.GAME.blind.blind_info.forced_blind_num then + self.chips = G.GAME.blind.blind_info.forced_blind_num + end +end +''' + +# Disable Skipping +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +pattern = ''' +local blind_amt = get_blind_amount(G.GAME.round_resets.blind_ante)*blind_choice.config.mult*G.GAME.starting_params.ante_scaling +''' +position = "at" +payload = ''' +local blind_amt = get_blind_amount(G.GAME.round_resets.blind_ante)*blind_choice.config.mult*G.GAME.starting_params.ante_scaling + +if G.GAME.round_resets.blind_info[type] and G.GAME.round_resets.blind_info[type].blind_multiplier then + blind_amt = blind_amt * G.GAME.round_resets.blind_info[type].blind_multiplier +end + +if G.GAME.round_resets.blind_info[type] and G.GAME.round_resets.blind_info[type].forced_blind_num then + blind_amt = G.GAME.round_resets.blind_info[type].forced_blind_num +end +''' + +# NOTE: Should boss music be a separate consideration? +# (G.GAME.blind and G.GAME.blind.boss and 'music5') or +# does check the boss blind state \ No newline at end of file diff --git a/lovely/fixes.toml b/lovely/fixes.toml index 74896c1d4..d87ecc1d8 100644 --- a/lovely/fixes.toml +++ b/lovely/fixes.toml @@ -437,7 +437,7 @@ match_indent = true 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" +payload = "if not G.GAME.won and G.GAME.round_resets.ante >= G.GAME.win_ante and SMODS.blind_ends_ante() then" match_indent = true diff --git a/lovely/shop.toml b/lovely/shop.toml index 89ec1f4e4..19f076758 100644 --- a/lovely/shop.toml +++ b/lovely/shop.toml @@ -74,18 +74,18 @@ for _, key in ipairs(G.GAME.current_round.voucher or {}) do 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() -''' +# Modify generating vouchers (Incorporated in a larger blind related patch) +# [[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' diff --git a/lsp_def/utils.lua b/lsp_def/utils.lua index e2aa01d12..095a483f9 100644 --- a/lsp_def/utils.lua +++ b/lsp_def/utils.lua @@ -792,4 +792,86 @@ function SMODS.get_card_type_text_colour(type, center, card) end ---Returns the text colour for the badge of an object with this key or nil if none ---@param key string ---@return table? -function SMODS.get_badge_text_colour(key) end \ No newline at end of file +function SMODS.get_badge_text_colour(key) end + + +---@param number number Value by which requirement will be increased +---@param is_percentage boolean Defaults to False. Increase will be a percentage if set to true +---@param silent boolean Set to True to skip easing animation +---@param force boolean Set to True to ignore blind slots alteration immunity +---@return number +---Changes the blind requirement by the given number or percentage. +function SMODS.alter_blind_requirement(number, is_percentage, silent, force) end + +---@param new_value number Value to ease to +---Eases blind requirement UI to new value +function SMODS.ease_blind_requirement(mod) end + + +---@param blind_slot string Targetted blind slot +---@param blind_types table Table of keys for blind pools. Defaults to blind slot pool list if nil +---@param all_qualities boolean set to True to require blind type matches all pools +---Sets new blind for targetted blind slot +function SMODS.set_new_blind(blind_slot, blind_types, all_qualities) end + +---@param blind_slot string Targetted blind slot +---@param blind_key string Key of blind to be forced +---Forces next blind for given slot +function SMODS.force_blind(blind_slot, blind_key) end + +---@param blind_pools table Table of pools to parse +---@param all_qualities boolean Set to True if blinds must meet all requirements rather than meet one or more +---@param blind_slot string? Blind slot pool is checked from +---@return table +---Constructs a blind pool based on spawn conditions and blind pool types of blinds +function SMODS.get_blind_pool(blind_pools, all_qualities, blind_slot) end + +---@param blind_pools table Table of pools to parse +---@param all_qualities boolean Set to True if blinds must meet all requirements rather than meet one or more +---@param blind_slot string? Blind slot pool is checked from +---@return Blind +---Randomly selects a blind from the desired blind pools +function SMODS.get_blind_from_pool(blind_pools, all_qualities) end + +---@param key string Key of blind to be added to pool +---@param blind_pool string Key of blind pool to add blind to +---Adds blind to a pool for the remainder of the run +function SMODS.add_blind_to_pool(key, blind_pool) end + +---@param blind_slot string Targetted blind slot +---@param pool_key string Pool to add to available pools for blind slot +---Adds blind pool to types of blinds that can spawn in the given blind slot +function SMODS.add_pool_to_blind_slot(blind_slot, pool_key) end + +---@param blind_slot string Targetted blind slot +---@param pool_key string Pool to remove from available pools for blind slot +---@param full_reset boolean Removes all pools if True, and restores default pool +---Removes blind pool from types of blinds that can spawn. If all pools are removed, will resort to default for slot +function SMODS.remove_pool_from_blind_slot(blind_slot, pool_key) end + +---@param blind_slot string Targetted blind slot +---Rerolls the targetted blind slot if possible +function SMODS.reroll_blind(blind_slot) end + +---@param blind_slots table? Table of strings for targetted blind slots +---Rerolls all blind slots or specified slots +function SMODS.reload_blinds(blind_slots) end + +---@return boolean +---Checks if the current blind slot will end and transition the ante +function SMODS.blind_ends_ante() end + +---@param ignore_exceptions boolean Set to true to check the state of the blind rather than the blind slot regardless of slot configuration +---@return boolean +---Checks if the current blind slot counts as a boss +function SMODS.blind_is_boss(ignore_exceptions) end + +---@return boolean +---Checks if the current blind slot allows blind effects to be disabled +function SMODS.blind_can_disable() end + +---@param source string Key to identify source +---@param remove boolean True if skip-disabling effect from this source is removed +---@param hide boolean True if skip UI should be hidden instead of greyed out by this source +---Manages ability to skip by either hiding option fully or greying it out +function SMODS.disable_skip(source, remove, hide) end \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index 4862f97ea..68bd2635d 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -1767,6 +1767,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. 'key', }, set = 'Blind', + blind_pools = {Boss = true}, get_obj = function(self, key) return G.P_BLINDS[key] end, register = function(self) self.name = self.name or self.key @@ -1781,7 +1782,14 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. if self.modifies_draw then SMODS.Blinds.modifies_draw[self.key] = true end end } + SMODS.Blind:take_ownership('small', { + blind_pools = {Small = true}, + }) + SMODS.Blind:take_ownership('big', { + blind_pools = {Big = true}, + }) SMODS.Blind:take_ownership('eye', { + blind_pools = {Boss = true}, set_blind = function(self, reset, silent) if not reset then G.GAME.blind.hands = {} @@ -1792,6 +1800,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end }) SMODS.Blind:take_ownership('wheel', { + blind_pools = {Boss = true}, loc_vars = function(self) return { vars = { SMODS.get_probability_vars(self, 1, 7, 'wheel') } } end, @@ -1813,7 +1822,74 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.Blinds.modifies_draw = { bl_serpent = true } - + local game_init_game_object_ref = Game.init_game_object + function Game:init_game_object() + local t = game_init_game_object_ref(self) + t.round_resets.blind_info = { + Small = { + default_pool_key = "Small", -- Default to fall back to + pools = {"Small"}, -- Currently added pools + default_key = "bl_small", -- Default boss blind in case pool is empty + can_skip = true, -- Whether skip tag is clickable + remove_skip = false, -- Whether skip tag is visible at all + can_reroll = false, -- Whether blind is rerolled upon using a boss reroll + match_boss_status_to_blind = true, -- Whether blind counting as a boss is + --determined by the blind object itself, or the blind info + is_boss = false, -- Whether blind counts as a Boss Blind (only checked if match_boss_status_to_blind is false) + can_be_showdown = false, -- Whether blinds marked as showdown can spawn in this slot + ante_ender = false, -- Whether ante increase and related effects happens upon defeat + cannot_be_disabled = false, -- Disallow blind disabling (automatically relevant if blind cannot be disabled normally) + unalterable = true, -- Whether effects can alter the requirement + blind_multiplier = 1, -- Additional non-blind object specific multiplier + forced_blind_num = nil, -- Always sets blind requirement to this, regardless of other prompts + -- Potential additional features: + + transitions_into = "Big" -- transitions into string to allow for adding of additional blind slots + }, + Big = { + default_pool_key = "Big", + pools = {"Big"}, + default_key = "bl_big", + can_skip = true, + remove_skip = false, + can_reroll = false, -- Unsure yet on how to handle this + is_boss = false, + can_be_showdown = false, + match_boss_status_to_blind = true, + ante_ender = false, + cannot_be_disabled = false, -- Disallow blind disabling (automatically relevant if blind cannot be disabled normally) + unalterable = true, -- Whether effects can alter the requirement + blind_multiplier = 1, -- Additional non-blind object specific multiplier + forced_blind_num = nil, + + transitions_into = "Boss" -- transitions into string to allow for adding of additional blind slots + }, + Boss = { + default_pool_key = "Boss", + pools = {"Boss"}, + default_key = "bl_manacle", + can_skip = false, -- Can never be true for a Boss blind + remove_skip = false, -- Can never be true for a Boss blind + can_reroll = true, + is_boss = true, + can_be_showdown = true, + match_boss_status_to_blind = false, -- Final blinds are always counted as boss (can be changed) + ante_ender = true, + cannot_be_disabled = false, -- Disallow blind disabling (automatically relevant if blind cannot be disabled normally) + unalterable = true, -- Whether effects can alter the requirement + blind_multiplier = 1, -- Additional non-blind object specific multiplier + forced_blind_num = nil, + + transitions_into = "Small" -- transitions into string to allow for adding of additional blind slots + }, + } + t.forced_blind = {} + t.final_ante_showdown = true + t.minimum_showdown_ante = 2 -- 2 matches default check + t.showdown_antes = {} + return t + end + ------------------------------------------------------------------------------------------------- ----- API CODE GameObject.Seal ------------------------------------------------------------------------------------------------- diff --git a/src/overrides.lua b/src/overrides.lua index 296293b45..b6c3e918c 100644 --- a/src/overrides.lua +++ b/src/overrides.lua @@ -2677,3 +2677,52 @@ function add_tag(_tag) end add_tag_ref(_tag) end + +-- Blinds API +function reset_blinds() + G.GAME.round_resets.blind_states = G.GAME.round_resets.blind_states or {Small = 'Select', Big = 'Upcoming', Boss = 'Upcoming'} + + if G.GAME.round_resets.blind_states.Boss == 'Defeated' then + G.GAME.round_resets.blind_states.Small = 'Upcoming' + G.GAME.round_resets.blind_states.Big = 'Upcoming' + G.GAME.round_resets.blind_states.Boss = 'Upcoming' + G.GAME.blind_on_deck = 'Small' + SMODS.set_new_blind("Small", G.GAME.round_resets.blind_info.Small.pools) + SMODS.set_new_blind("Big", G.GAME.round_resets.blind_info.Big.pools) + SMODS.set_new_blind("Boss", G.GAME.round_resets.blind_info.Boss.pools) + + -- Boss rerolled is kept for safety, but no should no longer be used + G.GAME.round_resets.boss_rerolled = false + end +end + +-- Override vanilla reroll function to allow all blinds to be rerolled by tag and button +G.FUNCS.reroll_boss = function(e) + stop_use() + if not G.from_boss_tag then ease_dollars(-10) end + G.from_boss_tag = nil + G.CONTROLLER.locks.boss_reroll = true + + + local blind_slots = {} + for k, v in pairs(G.GAME.round_resets.blind_info) do + if v.can_reroll then + blind_slots[#blind_slots + 1] = k + end + end + + for i, v in ipairs(blind_slots) do + SMODS.reroll_blind(v) + end + + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0, + func = (function() + 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) + })) +end \ No newline at end of file diff --git a/src/utils.lua b/src/utils.lua index 2590d963e..678215301 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -3455,4 +3455,301 @@ function SMODS.get_badge_text_colour(key) for k, v in pairs(SMODS.Seals) do if k:lower()..'_seal' == key and v.text_colour then return v.text_colour end end -end \ No newline at end of file +end + +-- Boss Blind API functions + +function SMODS.alter_blind_requirement(number, is_percentage, silent, force) + if not force and (G.GAME.blind.blind_info and G.GAME.blind.blind_info.unalterable) then + print("Cannot alter this blind requirement") + return nil + end + local change + if is_percentage then + change = G.GAME.blind.chips * (number / 100) + else + change = number + end + + G.GAME.blind.chips = math.ceil(math.max(G.GAME.blind.chips + change, 1)) + if silent then + G.GAME.blind.chips_text = number_format(G.GAME.blind.chips) + else + SMODS.ease_blind_requirement(G.GAME.blind.chips) + end + + + return change +end + +function SMODS.ease_blind_requirement(new_value) + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + new_value = new_value or 0 + + G.E_MANAGER:add_event(Event({ + trigger = 'ease', + blockable = false, + ref_table = G.GAME.blind, + ref_value = 'chip_text', + ease_to = new_value, + delay = 0.3, + func = (function(t) + return number_format(math.max(math.floor(t),0)) + end) + })) + + return true + end + })) +end + +function SMODS.set_new_blind(blind_slot, blind_types, all_qualities) + local result = "bl_small" + blind_types = blind_types or G.GAME.round_resets.blind_info[blind_slot].pools + + if G.GAME.forced_blind[blind_slot] then + result = G.GAME.forced_blind[blind_slot] + G.GAME.forced_blind[blind_slot] = nil + else + result = SMODS.get_blind_from_pool(blind_types, all_qualities, blind_slot) or G.GAME.round_resets.blind_info[blind_slot].default_key + end + + G.GAME.blinds_used = G.GAME.blinds_used or {} + G.GAME.blinds_used[blind_slot] = G.GAME.blinds_used[blind_slot] or {} + G.GAME.blinds_used[blind_slot][result] = G.GAME.blinds_used[blind_slot][result] and G.GAME.blinds_used[blind_slot][result] + 1 or 0 + + G.GAME.round_resets.blind_choices[blind_slot] = result +end + +function SMODS.force_blind(blind_slot, blind_key) + G.GAME.forced_blind[blind_slot] = blind_key +end + +function SMODS.get_blind_pool(blind_pools, all_qualities, blind_slot) + local eligible_bosses = {} + local can_be_showdown_blind = blind_slot and G.GAME.round_resets.blind_info[blind_slot] and G.GAME.round_resets.blind_info[blind_slot].can_be_showdown or false + if ((G.GAME.final_ante_showdown and (G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= G.GAME.minimum_showdown_ante) or + G.GAME.showdown_antes[G.GAME.round_resets.ante] == true) == false then + can_be_showdown_blind = false + end + + for k, v in pairs(G.P_BLINDS) do + local continue = false + if v.blind_pools then + for i, blind_pool in ipairs(blind_pools) do + if v.blind_pools[blind_pool] and not all_qualities then + continue = true + elseif not v.blind_pools[blind_pool] and all_qualities then + continue = false + end + end + + else + -- Basically adds any blind that isn't a boss + v.blind_pools = {} + v.blind_pools.Boss = true + if #blind_pools == 1 and blind_pools[1] == "Boss" then + continue = true + end + end + if continue then + local res, options = SMODS.add_to_pool(v) + options = options or {} + + if v.ignore_showdown_check then + eligible_bosses[k] = res and true or nil + elseif v.boss then + if v.boss.showdown and can_be_showdown_blind then + eligible_bosses[k] = res and true or nil + elseif not can_be_showdown_blind and ((v.boss.min <= math.max(1, G.GAME.round_resets.ante) and + v.boss.max >= 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))) + and not v.boss.showdown then + eligible_bosses[k] = res and true or nil + end + else + eligible_bosses[k] = res and true or nil + end + end + end + for k, v in pairs(G.GAME.banned_keys) do + if eligible_bosses[k] then eligible_bosses[k] = nil end + end + return eligible_bosses +end + +function SMODS.get_blind_from_pool(blind_pools, all_qualities, blind_slot) + if type(blind_pools) == 'string' then + blind_pools = {blind_pools} + end + if #blind_pools == 1 and blind_pools[1] == "Boss" then + G.GAME.prescribed_bosses = G.GAME.prescribed_bosses or { + } + if G.GAME.prescribed_bosses and G.GAME.prescribed_bosses[G.GAME.round_resets.ante] then + local ret_boss = G.GAME.prescribed_bosses[G.GAME.round_resets.ante] + G.GAME.prescribed_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 = SMODS.get_blind_pool(blind_pools, all_qualities, blind_slot) + local _, boss = pseudorandom_element(eligible_bosses, pseudoseed('boss')) + return boss +end + +function SMODS.add_blind_to_pool(key, blind_pool) + G.P_BLINDS[key].blind_pools = G.P_BLINDS[key].blind_pools or {} + G.P_BLINDS[key].blind_pools[blind_pool] = true +end + +function SMODS.add_pool_to_blind_slot(blind_slot, pool_key) + G.GAME.round_resets.blind_info[blind_slot].pools[#G.GAME.round_resets.blind_info[blind_slot].pools + 1] = pool_key +end + +function SMODS.remove_pool_from_blind_slot(blind_slot, pool_key, reset) + if reset or #G.GAME.round_resets.blind_info[blind_slot].pools <= 1 then + G.GAME.round_resets.blind_info[blind_slot].pools = {G.GAME.round_resets.blind_info[blind_slot].default_pool_key} + else + local new_pools = {} + for i, v in ipairs(G.GAME.round_resets.blind_info[blind_slot].pools) do + if v ~= pool_key then + new_pools[#new_pools + 1] = v + end + end + G.GAME.round_resets.blind_info[blind_slot].pools = new_pools + end +end + +function SMODS.add_showdown_ante(ante_num, remove) + G.GAME.showdown_antes[ante_num] = not remove +end + +function SMODS.reroll_blind(blind_slot) + local blind_lc = blind_slot:lower() + G.GAME.round_resets.blind_rerolled = G.GAME.round_resets.blind_rerolled or {} + G.GAME.round_resets.blind_rerolled[blind_slot] = true + + G.CONTROLLER.locks.boss_reroll = true + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + play_sound('other1') + G.blind_select_opts[blind_lc]:set_role({xy_bond = 'Weak'}) + G.blind_select_opts[blind_lc].alignment.offset.y = 20 + return true + end + })) + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.3, + func = (function() + local par = G.blind_select_opts[blind_lc].parent + SMODS.set_new_blind(blind_slot, G.GAME.round_resets.blind_info[blind_slot].pools) + + G.blind_select_opts[blind_lc]:remove() + G.blind_select_opts[blind_lc] = UIBox{ + T = {par.T.x, 0, 0, 0, }, + definition = + {n=G.UIT.ROOT, config={align = "cm", colour = G.C.CLEAR}, nodes={ + UIBox_dyn_container({create_UIBox_blind_choice(blind_slot)},false,get_blind_main_colour(blind_slot), mix_colours(G.C.BLACK, get_blind_main_colour(blind_slot), 0.8)) + }}, + config = {align="bmi", + offset = {x=0,y=G.ROOM.T.y + 9}, + major = par, + xy_bond = 'Weak' + } + } + par.config.object = G.blind_select_opts[blind_lc] + par.config.object:recalculate() + G.blind_select_opts[blind_lc].parent = par + G.blind_select_opts[blind_lc].alignment.offset.y = 0 + + G.E_MANAGER:add_event(Event({blocking = false, trigger = 'after', delay = 0.5,func = function() + G.CONTROLLER.locks.boss_reroll = nil + return true + end + })) + + save_run() + return true + end) + })) +end + +function SMODS.reload_blinds(blind_slots) + if not blind_slots then + blind_slots = {} + for k, v in pairs(G.GAME.round_resets.blind_info) do + blind_slots[#blind_slots + 1] = k + end + elseif type(blind_slots) == "string" then + blind_slots = {blind_slots} + end + + for i, v in ipairs(blind_slots) do + SMODS.reroll_blind(v) + end +end + +local smods_hook_set_blind = Blind.set_blind +function Blind:set_blind(blind, reset, silent) + smods_hook_set_blind(self, blind, reset, silent) + G.GAME.last_blind = G.GAME.last_blind or {} + G.GAME.last_blind.blind_info = (G.GAME.blind and G.GAME.blind.blind_info) and G.GAME.blind.blind_info or {} + G.GAME.blind.blind_info = G.GAME.round_resets.blind_info[G.GAME.blind_on_deck] +end + +function SMODS.blind_ends_ante() + return G.GAME.blind.blind_info and G.GAME.blind.blind_info.ante_ender or false +end + +function SMODS.blind_is_boss(ignore_exceptions) + if ignore_exceptions or (G.GAME.blind.blind_info and G.GAME.blind.blind_info.match_boss_status_to_blind) then + return (G.GAME.blind and G.GAME.blind.boss) and true or false + else + return G.GAME.blind.blind_info and G.GAME.blind.blind_info.is_boss or false + end +end + +function SMODS.blind_can_disable() + return G.GAME.blind and ((not G.GAME.blind.disabled) and (SMODS.blind_is_boss(true) and G.GAME.blind.blind_info and (not G.GAME.blind.blind_info.cannot_be_disabled))) +end + +function SMODS.disable_skip(source, remove, hide) + if not hide then + G.GAME.disable_skip_sources = G.GAME.disable_skip_sources or {} + G.GAME.disable_skip_sources[source] = remove and nil or true + local _count = 0 + for k, v in pairs(G.GAME.disable_skip_sources) do + _count = _count + 1 + end + G.GAME.disable_skip_sources_count = _count + else + G.GAME.hide_skip_sources = G.GAME.hide_skip_sources or {} + G.GAME.hide_skip_sources[source] = remove and nil or true + local _count = 0 + for k, v in pairs(G.GAME.hide_skip_sources) do + _count = _count + 1 + end + G.GAME.hide_skip_sources_count = _count + end + + if G.GAME.disable_skip_sources_count >= 1 then + G.GAME.disable_skips = true + end + if G.GAME.hide_skip_sources_count >= 1 then + G.GAME.hide_skips = true + end +end + +local smods_hook_create_uibox_blind_tag = create_UIBox_blind_tag +function create_UIBox_blind_tag(blind_choice, run_info) + if not G.GAME.round_resets.blind_info[blind_choice].remove_skip and not G.GAME.hide_skips then + return smods_hook_create_uibox_blind_tag(blind_choice, run_info) + end +end