diff --git a/lovely/blind.toml b/lovely/blind.toml index 9d1771fa1..f8fcb60d6 100644 --- a/lovely/blind.toml +++ b/lovely/blind.toml @@ -587,4 +587,164 @@ pattern = "local blindTable = {" position = "after" payload = ''' effect = self.effect, -''' \ No newline at end of file +''' + +## Custom Small/Big Blinds Support + +# Add custom small/big blinds to `G.GAME.bosses_used` +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' +local bosses_used = {} +''' +payload = ''' +local bosses_used = setmetatable({boss = {}, small = {}, big = {}},{ + __index = function(t, key) + return t.boss[key] or t.big[key] or t.small[key] + end, + __newindex = function(t, key, value) + rawset(t.boss, key, value) + end +}) +''' + +# Prepare bosses_used +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' +if v.boss then bosses_used[k] = 0 end +''' +payload = ''' +if v.boss then bosses_used.boss[k] = 0 end +if v.small then bosses_used.small[k] = 0 end +if v.big then bosses_used.big[k] = 0 end +''' + + +## Custom Small/Big Blinds +[[patches]] +[patches.pattern] +target = 'functions/common_events.lua' +pattern = '''G.GAME.round_resets.blind_choices.Boss = get_new_boss()''' +position = 'at' +match_indent = true +payload = ''' +SMODS.reset_blind_choices(G.GAME.round_resets.blind_choices) +''' + +# Add Custom Small/Big Blinds +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' +self.GAME.round_resets.blind_choices.Boss = get_new_boss() +''' +payload = ''' +SMODS.reset_blind_choices(self.GAME.round_resets.blind_choices) +''' + +# Handle setting new blinds +[[patches]] +[patches.pattern] +target = 'blind.lua' +match_indent = true +position = 'at' +pattern = ''' +self.boss = blind and not not blind.boss +''' +payload = ''' +self[self.current_slot or ''] = nil +self[string.lower(G.GAME.blind_on_deck or '')] = blind and true or nil +self.current_slot = string.lower(G.GAME.blind_on_deck or '') +''' +# Handle 'Defeated' tagging +[[patches]] +[patches.pattern] +target = 'functions/state_events.lua' +match_indent = true +position = 'at' +pattern = ''' +if G.GAME.round_resets.blind == G.P_BLINDS.bl_small then +''' +payload = ''' +if G.GAME.round_resets.blind_states.Small == 'Current' then +''' +[[patches]] +[patches.pattern] +target = 'functions/state_events.lua' +match_indent = true +position = 'at' +pattern = ''' +elseif G.GAME.round_resets.blind == G.P_BLINDS.bl_big then +''' +payload = ''' +elseif G.GAME.round_resets.blind_states.Big == 'Current' then +''' + +# Ensure boss table exists +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = '''if v.boss.showdown then ''' +position = "at" +payload = '''if v.boss and v.boss.showdown then ''' +match_indent = true + +# # Fix boss colour not resetting properly +# [[patches]] +# [patches.pattern] +# target = 'blind.lua' +# match_indent = true +# position = 'at' +# pattern = ''' +# if not self.boss and self.name then +# ''' +# payload = ''' +# if not self.boss and not self.in_blind then +# ''' + + + +# These patches prepare vanilla blinds for polling with weights and differing slots +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'before' +pattern = ''' +self.b_undiscovered = {name = 'Undiscovered', debuff_text = 'Defeat this blind to discover', pos = {x=0,y=30}} +''' +payload = ''' +for key, blind in pairs(self.P_BLINDS) do + if blind.boss and blind.boss.max then + blind.boss.max = nil + if blind.boss.showdown then + blind.boss.min = nil + end + blind.blind_types = {'boss'} + end + if key == 'bl_small' then blind.small = {min = 1, allow_duplicates = true}; blind.blind_types = {'small'} end + if key == 'bl_big' then blind.big = {min = 1, allow_duplicates = true}; blind.blind_types = {'big'} end +end +''' + +# Allow small and big blinds to block skipping +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +position = 'at' +pattern = ''' +extras = create_UIBox_blind_tag(type, run_info) +''' +payload = ''' +if not blind_choice.config.unskippable then extras = create_UIBox_blind_tag(type, run_info) end +''' diff --git a/lovely/ui.toml b/lovely/ui.toml index 5da6bd5b6..9e156f439 100644 --- a/lovely/ui.toml +++ b/lovely/ui.toml @@ -490,3 +490,48 @@ self.draw_steps[#self.draw_steps+1] = { payload = ''' tilt_shadow = v.tilt_shadow, ''' + +# Blind mod badge display during run +[[patches]] +[patches.pattern] +target = 'functions/UI_definitions.lua' +match_indent = true +position = 'after' +pattern = ''' +{n=G.UIT.R, config={align = "cm",padding = 0.15}, nodes={ + {n=G.UIT.O, config={object = G.GAME.blind, draw_layer = 1}}, + {n=G.UIT.C, config={align = "cm",r = 0.1, padding = 0.05, emboss = 0.05, minw = 2.9, colour = G.C.BLACK}, nodes={ + {n=G.UIT.R, config={align = "cm", maxw = 2.8}, nodes={ + {n=G.UIT.T, config={text = localize('ph_blind_score_at_least'), scale = 0.3, colour = G.C.WHITE, shadow = true}} + }}, + {n=G.UIT.R, config={align = "cm", minh = 0.6}, nodes={ + {n=G.UIT.O, config={w=0.5,h=0.5, colour = G.C.BLUE, object = stake_sprite, hover = true, can_collide = false}}, + {n=G.UIT.B, config={h=0.1,w=0.1}}, + {n=G.UIT.T, config={ref_table = G.GAME.blind, ref_value = 'chip_text', scale = 0.001, colour = G.C.RED, shadow = true, id = 'HUD_blind_count', func = 'blind_chip_UI_scale'}} + }}, + {n=G.UIT.R, config={align = "cm", minh = 0.45, maxw = 2.8, func = 'HUD_blind_reward'}, nodes={ + {n=G.UIT.T, config={text = localize('ph_blind_reward'), scale = 0.3, colour = G.C.WHITE}}, + {n=G.UIT.O, config={object = DynaText({string = {{ref_table = G.GAME.current_round, ref_value = 'dollars_to_be_earned'}}, colours = {G.C.MONEY},shadow = true, rotate = true, bump = true, silent = true, scale = 0.45}),id = 'dollars_to_be_earned'}}, + }}, + }}, + }}, +}}, +''' +payload = ''' +{n=G.UIT.R, config = {align = "cm", r = 0.1, id = 'HUD_blind_badge', func = 'HUD_blind_badge'}, nodes = { + SMODS.create_blind_mod_badge() +}}, +''' + +# adjust placement of G.HUD +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' +config = {align=('cli'), offset = {x=-0.7,y=0},major = G.ROOM_ATTACH} +''' +payload = ''' +config = {align=('cli'), offset = {x=-0.7,y=0.4},major = G.ROOM_ATTACH} +''' diff --git a/lovely/weights.toml b/lovely/weights.toml index c2c113a72..ebc01064d 100644 --- a/lovely/weights.toml +++ b/lovely/weights.toml @@ -61,42 +61,4 @@ if G.FORCE_TAG then return G.FORCE_TAG end ''' payload = ''' if SMODS.optional_features.object_weights then return SMODS.poll_object({type = 'Tag', append = append}) end -''' - - -# These patches add functionality for polling blinds, but are a precursor for full support of modded small and big blinds -# TODO: move to blind related toml -# Adjust vanilla blinds max ante property -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'before' -pattern = ''' -self.b_undiscovered = {name = 'Undiscovered', debuff_text = 'Defeat this blind to discover', pos = {x=0,y=30}} -''' -payload = ''' -for key, blind in pairs(self.P_BLINDS) do - if blind.boss and blind.boss.max then - blind.boss.max = nil - if blind.boss.showdown then - blind.boss.min = nil - end - end - if key == 'bl_small' then blind.small = {min = 1} end - if key == 'bl_big' then blind.big = {min = 1} end -end -''' - -# Add custom small/big blinds to `G.GAME.bosses_used` -[[patches]] -[patches.pattern] -target = 'game.lua' -match_indent = true -position = 'after' -pattern = ''' -if v.boss then bosses_used[k] = 0 end -''' -payload = ''' -if v.small or v.big then bosses_used[k] = 0 end ''' \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index 9f7242eb4..4c7d90894 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -1809,69 +1809,7 @@ SMODS.UndiscoveredCompat = { ----- API CODE GameObject.Blind ------------------------------------------------------------------------------------------------- - SMODS.Blinds = {} - SMODS.Blind = SMODS.GameObject:extend { - obj_table = SMODS.Blinds, - obj_buffer = {}, - class_prefix = 'bl', - debuff = {}, - vars = {}, - config = {}, - dollars = 5, - mult = 2, - atlas = 'blind_chips', - discovered = false, - pos = { x = 0, y = 0 }, - required_params = { - 'key', - }, - set = 'Blind', - get_obj = function(self, key) return G.P_BLINDS[key] end, - register = function(self) - self.name = self.name or self.key - SMODS.Blind.super.register(self) - end, - inject = function(self, i) - -- no pools to query length of, so we assign order manually - if not self.taken_ownership then - self.order = 30 + i - end - G.P_BLINDS[self.key] = self - if self.modifies_draw then SMODS.Blinds.modifies_draw[self.key] = true end - end - } - SMODS.Blind:take_ownership('eye', { - set_blind = function(self, reset, silent) - if not reset then - G.GAME.blind.hands = {} - for _, v in ipairs(G.handlist) do - G.GAME.blind.hands[v] = false - end - end - end - }) - SMODS.Blind:take_ownership('wheel', { - loc_vars = function(self) - return { vars = { SMODS.get_probability_vars(self, 1, 7, 'wheel') } } - end, - collection_loc_vars = function(self) - return { vars = { '1', '7' }} - end, - process_loc_text = function(self) - local text = G.localization.descriptions.Blind[self.key].text[1] - if string.sub(text, 1, 3) ~= '#1#' then - G.localization.descriptions.Blind[self.key].text[1] = "#1#"..text - end - -- Is this too much hacky? - G.localization.descriptions.Blind[self.key].text[1] = string.gsub(G.localization.descriptions.Blind[self.key].text[1], "7", "#2#") - SMODS.Blind.process_loc_text(self) - end, - get_loc_debuff_text = function() return G.GAME.blind.loc_debuff_text end, - }) - - SMODS.Blinds.modifies_draw = { - bl_serpent = true - } + assert(load(SMODS.NFS.read(SMODS.path..'src/game_objects/blind.lua'), ('=[SMODS _ "src/game_objects/blind.lua"]')))() ------------------------------------------------------------------------------------------------- ----- API CODE GameObject.Seal diff --git a/src/game_objects/blind.lua b/src/game_objects/blind.lua new file mode 100644 index 000000000..1790df71d --- /dev/null +++ b/src/game_objects/blind.lua @@ -0,0 +1,115 @@ +SMODS.Blinds = {} +SMODS.Blind = SMODS.GameObject:extend { + obj_table = SMODS.Blinds, + obj_buffer = {}, + class_prefix = 'bl', + debuff = {}, + vars = {}, + config = {}, + dollars = 5, + mult = 2, + atlas = 'blind_chips', + discovered = false, + pos = { x = 0, y = 0 }, + required_params = { + 'key', + }, + set = 'Blind', + get_obj = function(self, key) return G.P_BLINDS[key] end, + register = function(self) + self.name = self.name or self.key + SMODS.Blind.super.register(self) + end, + inject = function(self, i) + -- no pools to query length of, so we assign order manually + if not self.taken_ownership then + self.order = 30 + i + end + self.blind_types = { + self.boss and 'boss', + self.big and 'big', + self.small and 'small' + } + + G.P_BLINDS[self.key] = self + if self.modifies_draw then SMODS.Blinds.modifies_draw[self.key] = true end + end +} +SMODS.Blind:take_ownership('eye', { + set_blind = function(self, reset, silent) + if not reset then + G.GAME.blind.hands = {} + for _, v in ipairs(G.handlist) do + G.GAME.blind.hands[v] = false + end + end + end +}) +SMODS.Blind:take_ownership('wheel', { + loc_vars = function(self) + return { vars = { SMODS.get_probability_vars(self, 1, 7, 'wheel') } } + end, + collection_loc_vars = function(self) + return { vars = { '1', '7' }} + end, + process_loc_text = function(self) + local text = G.localization.descriptions.Blind[self.key].text[1] + if string.sub(text, 1, 3) ~= '#1#' then + G.localization.descriptions.Blind[self.key].text[1] = "#1#"..text + end + -- Is this too much hacky? + G.localization.descriptions.Blind[self.key].text[1] = string.gsub(G.localization.descriptions.Blind[self.key].text[1], "7", "#2#") + SMODS.Blind.process_loc_text(self) + end, + get_loc_debuff_text = function() return G.GAME.blind.loc_debuff_text end, +}) + +SMODS.Blinds.modifies_draw = { + bl_serpent = true +} + +function SMODS.add_boss_to_used_table(boss_key, type) + if G.P_BLINDS[boss_key][type].allow_others then + G.GAME.bosses_used[type][boss_key] = G.GAME.bosses_used[type][boss_key] + 1 + return + end + for _, _type in pairs(G.P_BLINDS[boss_key].blind_types) do + G.GAME.bosses_used[_type][boss_key] = G.GAME.bosses_used[_type][boss_key] + 1 + end +end + +function SMODS.get_new_blind(blind_type) + local ret_boss + if SMODS.optional_features.object_weights then + ret_boss = SMODS.poll_object({type = 'Blind', blind_type = blind_type, seed = blind_type or 'boss'}) + else + ret_boss = pseudorandom_element(SMODS.create_blind_pool(blind_type), pseudoseed(blind_type or 'boss')) + end + SMODS.add_boss_to_used_table(ret_boss, blind_type or 'boss') + return ret_boss +end + +local blind_get_type = Blind.get_type +function Blind:get_type() + if G.GAME.blind and self == G.GAME.blind then + return G.GAME.blind_on_deck + end + if self.boss then return 'Boss' + elseif self.big then return 'Big' + elseif self.small then return 'Small' + else return '' end +end + +function Blind:is_type(blind_type) + return self:get_type() == blind_type +end + +function SMODS.reset_blind_choices(choices) + G.GAME.round_resets.blind_order = {'Small', 'Big', 'Boss'} -- prepared for custom antes + for _, k in ipairs(G.GAME.round_resets.blind_order) do + choices[k] = nil + end + for _, k in ipairs(G.GAME.round_resets.blind_order) do + choices[k] = SMODS.get_new_blind(string.lower(k)) + end +end \ No newline at end of file diff --git a/src/ui.lua b/src/ui.lua index 4f5943b56..668e827b8 100644 --- a/src/ui.lua +++ b/src/ui.lua @@ -2540,6 +2540,9 @@ local igo = Game.init_game_object function Game:init_game_object() local t = igo(self) t.smods_version = SMODS.version + t.blind_badge = { + name = 'temp' + } return t end @@ -3203,3 +3206,108 @@ function SMODS.GUI.create_UIBox_dropdown_menu(args, parent_width, parent) } } end + +-- #region blind tooltips + +local old_blind_popup = create_UIBox_blind_popup +function create_UIBox_blind_popup(blind, discovered, vars) + local popup = old_blind_popup(blind, discovered, vars) + popup.config.colour = darken(G.C.BLACK, 0.1) + popup.config.align = 'cm' + if blind.mod then + local badges = {} + SMODS.create_mod_badges(blind, badges) + for i=1, #badges do + table.insert(popup.nodes, badges[i]) + end + end + popup = {n=G.UIT.R, config={colour=lighten(G.C.JOKER_GREY, 0.5), align='cm', padding=0.05, emboss=0.07, r=0.12}, nodes = { + {n=G.UIT.R, config={align = "cm", padding = 0.07, r = 0.1, colour = adjust_alpha(darken(G.C.BLACK, 0.1), 0.8), id = 'blind_popup_container'}, nodes= + popup.nodes + }} + } + return popup + end + +local old_blind_choice_UI = create_UIBox_blind_choice +function create_UIBox_blind_choice(type, run_info) + local box = old_blind_choice_UI(type, run_info) + local blind = G.P_BLINDS[G.GAME.round_resets.blind_choices[type]] + if blind.mod then + local badges = {} + SMODS.create_mod_badges(blind, badges) + for i=1, #badges do + badges[i].nodes[1].config.minw = 2.7 + local text = SMODS.deepfind(badges[i], 'smods_mod_badge_text') + if next(text) and text[1].table.object then text[1].table.object.scroll_args.overflow.definition.config.maxw = text[1].table.object.scroll_args.overflow.definition.config.maxw * 2.7/2 end + table.insert(box.nodes[1].nodes[2].nodes, badges[i]) + end + box.nodes[1].nodes[3].config.padding = 0.1 + end + return box +end + +function SMODS.create_blind_mod_badge() + if G.GAME.blind and G.GAME.blind.config.blind and G.GAME.blind.config.blind.mod then + local mod = G.GAME.blind.config.blind.mod + local text = DynaText({string = {{ref_table = G.GAME.blind_badge, ref_value = 'name'}}, colours = {mod.badge_text_colour or G.C.WHITE}, shadow = true, silent = true, float = true, scale = 0.36}) + local text_scroll = SMODS.UIScrollBox({ + content = text, + container = { + config = { + can_collide = false, + } + }, + overflow = { + node_config = { + no_overflow = not mod.no_marquee and "h" or false, + maxw = not mod.no_marquee and 4.4 or nil, + }, + config = { + can_collide = false, + } + }, + sync_mode = "offset", + scroll_move = function(self, dt) + local dx = self:get_scroll_distance() + if dx == 0 or mod.no_marquee then return end + if not self.scroll_start_pause then + self.scroll_start_pause = 1.5 + end + if self.scroll_start_pause > 0 and self.scroll_offset.x >= 0 then + self.scroll_start_pause = self.scroll_start_pause - G.real_dt + else + self.scroll_offset.x = (self.scroll_offset.x or 0) + G.real_dt / 1.5 + if self.scroll_offset.x > self.content_container.T.w then + self.scroll_start_pause = 1.5 + self.scroll_offset.x = -self.T.w - 0.1 + end + end + end, + }) + return {n=G.UIT.R, config={align = "cm", padding = 0.03*0.9, minh = 0.4}, nodes={ + {n=G.UIT.O, config={object = text_scroll}}, + }} + end +end + +G.FUNCS.HUD_blind_badge = function(e) + if G.GAME.blind.in_blind then + if G.GAME.blind.config.blind.mod then + if not e.children[1] then + local mod = G.GAME.blind.config.blind.mod + G.GAME.blind_badge.name = mod.display_name + e.UIBox:add_child(SMODS.create_blind_mod_badge(), e) + e.config.colour = mod.badge_colour or G.C.DYN_UI.MAIN + e.config.emboss = 0.05 + e.states.visible = true + end + elseif e.children[1] then + e.states.visible = false + e.children[1]:remove() + e.children[1] = nil + end + end +end + +-- #endregion \ No newline at end of file diff --git a/src/utils.lua b/src/utils.lua index aaa957bef..99261ca9f 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -547,7 +547,7 @@ function SMODS.create_mod_badges(obj, badges) badges[#badges + 1] = {n=G.UIT.R, config={align = "cm"}, nodes={ {n=G.UIT.R, config={align = "cm", colour = mod.badge_colour or G.C.GREEN, r = 0.1, minw = 2, minh = 0.36, emboss = 0.05, padding = 0.03*size}, nodes={ {n=G.UIT.B, config={h=0.1,w=0.03}}, - {n=G.UIT.O, config={object=badge_scroll}}, + {n=G.UIT.O, config={id = 'smods_mod_badge_text', object=badge_scroll}}, {n=G.UIT.B, config={h=0.1,w=0.03}}, }} }} diff --git a/src/utils/weights.lua b/src/utils/weights.lua index 47f51b614..8cb47f31b 100644 --- a/src/utils/weights.lua +++ b/src/utils/weights.lua @@ -150,10 +150,18 @@ 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))) local eligible_bosses = {} + + local boss_already_chosen = function(key) + for _, k in pairs(G.GAME.round_resets.blind_choices) do + if k == key then return true end + end + end + 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 boss_already_chosen(k) then elseif options.ignore_showdown_check then eligible_bosses[k] = res and true or nil elseif blind_type == 'boss' then @@ -181,7 +189,7 @@ function SMODS.create_blind_pool(blind_type, skip_cull) end local min_use = 100 - for k, v in pairs(G.GAME.bosses_used) do + for k, v in pairs(G.GAME.bosses_used[blind_type] or G.GAME.bosses_used) do if eligible_bosses[k] then eligible_bosses[k] = v if eligible_bosses[k] <= min_use then @@ -192,7 +200,7 @@ function SMODS.create_blind_pool(blind_type, skip_cull) local final_pool = {} for k, v in pairs(eligible_bosses) do if eligible_bosses[k] then - if eligible_bosses[k] > min_use then + if eligible_bosses[k] > min_use and not G.P_BLINDS[k][blind_type].allow_duplicates then eligible_bosses[k] = nil else final_pool[#final_pool + 1] = k