From 393e6ba8559218d8f4d146dc48fb9355ee831cc0 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 15 Oct 2025 22:40:32 +0200 Subject: [PATCH 01/14] First classes and override target functions defined +Title, lots to do. --- src/game_object.lua | 158 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/src/game_object.lua b/src/game_object.lua index cc9a41ec2..52c75d9af 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3802,6 +3802,164 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. } + ------------------------------------------------------------------------------------------------- + ------- API CODE SMODS.BlindTree AND RELATED + ------------------------------------------------------------------------------------------------- + + ------- API CODE SMODS.BlindTree + SMODS.BlindTrees = {} + SMODS.BlindTree = SMODS.GameObject:extend { + set = 'BlindTree', + obj_table = SMODS.BlindTrees, + obj_buffer = {}, + required_parameters = { + 'key', + }, + inject = function(self) + + end, + create_data = function () + -- Vanilla Blind structure + -- "Small" -> "Big" -> "Boss" + end, + create_ui = function () + -- Vanilla Blind select UI + end, + create_run_info_ui = function () + -- Vanilla Run Info Blind Tab UI + end, + } + + SMODS.BlindTree { + key = "vanilla", + } + + function SMODS.get_blind_tree() + local tree_key = SMODS.get_active_blind_tree() + tree_key = type(tree_key) == "table" and tree_key.key or tree_key + return SMODS.BlindTrees[tree_key] or SMODS.BlindTrees["vanilla"] + end + + function SMODS.get_active_blind_tree() + return "vanilla" + end + + ------- API CODE Object.BTNode + SMODS.BTNode = Object:extend() + function SMODS.BTNode:init(...) + local args = {...} + + self.callbacks = args.callbacks or {} + self.blinds = args.blinds or {} + self.tags = args.tags or {} + + self.resolved_callback_types = {} + end + + function SMODS.BTNode:trigger_callbacks(type, leave_unresolved) + if not type or self.resolved_callback_types[type] then return end + local hold = false + for _, callback in ipairs(self.callbacks) do + if not callback.called and (not hold or callback.ignore_hold) and callback.type == type then + hold = hold or callback:on_callback(self) + end + end + if not (hold or leave_unresolved) then + self.resolved_callback_types[type] = true + end + end + + ------- API CODE GameObject.BTNodeCallback + SMODS.BTNodeCallbacks = {} + SMODS.BTNodeCallback = SMODS.GameObject:extend { + set = 'BTNodeCallback', + obj_table = SMODS.BTNodeCallbacks, + obj_buffer = {}, + required_parameters = { + 'key', + 'on_callback', + }, + ignore_hold = false, + inject = function(self) + if type(self.on_callback) ~= "function" then + sendWarnMessage(("BTNodeCallback injected with invalid function '%s'"):format(self.on_callback)) + end + self.type = self.type or "selected" + self.called = false + end, + } + + SMODS.BTNodeCallback { + key = "enter_blind", + type = "selected", + on_callback = function (self, bt_node) + -- Change game state to bt_node:get_blind() + return true + end + } + + SMODS.BTNodeCallback { + key = "enter_shop", + type = "cashout", + on_callback = function (self, bt_node) + -- Change game state to shop + return true + end + } + + SMODS.BTNodeCallback { + key = "ante_up", + type = "defeated", + ignore_hold = true, + on_callback = function (self, bt_node) + -- Ante up + return false + end + } + + SMODS.BTNodeCallback { + key = "create_tag", + type = "skipped", + ignore_hold = true, + on_callback = function (self, bt_node) + -- Create tag(s) from bt_node:get_tag() + return false + end + } + + ------- API CODE OVERRIDES + + -- Create Blind Select UI + function create_UIBox_blind_select() + return SMODS.get_blind_tree().create_ui() + end + + -- Toggle Shop + function G.FUNCS.toggle_shop(e) + + end + + -- Run Info Tab + function G.UIDEF.current_blinds() + return SMODS.get_blind_tree().create_run_info_ui() + end + + -- Select Blind + function G.FUNCS.select_blind(e) + + end + + -- Skip Blind + function G.FUNCS.skip_blind(e) + + end + + -- Reroll Boss + function G.FUNCS.reroll_boss(e) + + end + + ------------------------------------------------------------------------------------------------- ----- API IMPORT GameObject.DrawStep ------------------------------------------------------------------------------------------------- From 7349f9fc2e3d87832fe2608496c1089ef745592e Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 15 Oct 2025 23:18:30 +0200 Subject: [PATCH 02/14] Further patch/override targets identified +Title --- src/game_object.lua | 79 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index 52c75d9af..3dcbb836d 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3852,21 +3852,28 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. self.callbacks = args.callbacks or {} self.blinds = args.blinds or {} self.tags = args.tags or {} - - self.resolved_callback_types = {} end - function SMODS.BTNode:trigger_callbacks(type, leave_unresolved) - if not type or self.resolved_callback_types[type] then return end + function SMODS.BTNode:trigger_callbacks(type) + if not type then return end local hold = false for _, callback in ipairs(self.callbacks) do if not callback.called and (not hold or callback.ignore_hold) and callback.type == type then hold = hold or callback:on_callback(self) end end - if not (hold or leave_unresolved) then - self.resolved_callback_types[type] = true - end + end + + function SMODS.BTNode:get_blind(keep) + local blind = self.blinds[1] + if not keep then table.remove(self.blinds, 1) end + return blind + end + + function SMODS.BTNode:get_tag(keep) + local tag = self.tags[1] + if not keep then table.remove(self.tags, 1) end + return tag end ------- API CODE GameObject.BTNodeCallback @@ -3900,7 +3907,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.BTNodeCallback { key = "enter_shop", - type = "cashout", + type = "cashed_out", on_callback = function (self, bt_node) -- Change game state to shop return true @@ -3928,33 +3935,71 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. } ------- API CODE OVERRIDES - + + + --[[ + Game:init_game_object() + Game:update(dt) + G.GAME.round_resets.blind_tags + ]] + -- Create Blind Select UI function create_UIBox_blind_select() return SMODS.get_blind_tree().create_ui() end - -- Toggle Shop - function G.FUNCS.toggle_shop(e) - - end - -- Run Info Tab function G.UIDEF.current_blinds() return SMODS.get_blind_tree().create_run_info_ui() end - -- Select Blind + -- Blind Choice Handler + function G.FUNCS.blind_choice_handler(e) + + end + + -- Cash Out (probably should be a lovely patch) + -- AND/OR: Consider separating the cashout into its own BTNodeCallback "enter_cashout" + G.FUNCS.cash_out = function(e) + + end + + -- reset_blinds() + function reset_blinds() + + end + + -- new_round() (also lovely patch maybe) + function new_round() + + end + + -- end_round() (also lovely patch maybe) + function end_round() + + end + + -- get_blind_main_colour() + function get_blind_main_colour(blind) + + end + + -- Toggle Shop + function G.FUNCS.toggle_shop(e) + + end + + -- Select Blind (-> use own func in UI defs) function G.FUNCS.select_blind(e) end - -- Skip Blind + -- Skip Blind (-> use own func in UI defs) function G.FUNCS.skip_blind(e) end - -- Reroll Boss + -- Reroll Boss (-> use own func in UI defs) function G.FUNCS.reroll_boss(e) end From c39f930d9d930783e270e1e9ac9e279146879130 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 15 Oct 2025 23:41:29 +0200 Subject: [PATCH 03/14] Fixed incorrect logic *Title --- src/game_object.lua | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index 3dcbb836d..2e7f4a269 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3849,7 +3849,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.BTNode:init(...) local args = {...} - self.callbacks = args.callbacks or {} + self.callbacks = args.callbacks or {} -- {key: String, called: Boolean} self.blinds = args.blinds or {} self.tags = args.tags or {} end @@ -3857,9 +3857,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.BTNode:trigger_callbacks(type) if not type then return end local hold = false - for _, callback in ipairs(self.callbacks) do - if not callback.called and (not hold or callback.ignore_hold) and callback.type == type then - hold = hold or callback:on_callback(self) + for _, cb in ipairs(self.callbacks) do + local callback = SMODS.BTNodeCallbacks[cb.key] + if not cb.called and (not hold or callback.ignore_hold) and callback.type == type then + cb.called = true + hold = hold or callback:on_callback(self, cb) end end end @@ -3876,6 +3878,21 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return tag end + function SMODS.BTNode:save() + local node_table = { + callbacks = self.callbacks, + blinds = self.blinds, + tags = self.tags + } + return node_table + end + + function SMODS.BTNode:load(node_table) + self.callbacks = node_table.callbacks or {} + self.blinds = node_table.blinds or {} + self.tags = node_table.tags or {} + end + ------- API CODE GameObject.BTNodeCallback SMODS.BTNodeCallbacks = {} SMODS.BTNodeCallback = SMODS.GameObject:extend { @@ -3892,14 +3909,13 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. sendWarnMessage(("BTNodeCallback injected with invalid function '%s'"):format(self.on_callback)) end self.type = self.type or "selected" - self.called = false end, } SMODS.BTNodeCallback { key = "enter_blind", type = "selected", - on_callback = function (self, bt_node) + on_callback = function (self, bt_node, cb) -- Change game state to bt_node:get_blind() return true end @@ -3908,7 +3924,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.BTNodeCallback { key = "enter_shop", type = "cashed_out", - on_callback = function (self, bt_node) + on_callback = function (self, bt_node, cb) -- Change game state to shop return true end @@ -3918,7 +3934,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "ante_up", type = "defeated", ignore_hold = true, - on_callback = function (self, bt_node) + on_callback = function (self, bt_node, cb) -- Ante up return false end @@ -3928,7 +3944,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "create_tag", type = "skipped", ignore_hold = true, - on_callback = function (self, bt_node) + on_callback = function (self, bt_node, cb) -- Create tag(s) from bt_node:get_tag() return false end From 3066b165048bfffdd8b1ffb74bfc008a923f23eb Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Thu, 16 Oct 2025 21:49:33 +0200 Subject: [PATCH 04/14] `BlindTree.create_data` first iteration and some refactoring + more +/*Title --- src/game_object.lua | 102 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index 2e7f4a269..07d4c837d 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3821,6 +3821,44 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. create_data = function () -- Vanilla Blind structure -- "Small" -> "Big" -> "Boss" + local tree_data = { + key = "vanilla", + active_node = nil, + nodes = { + SMODS.BTNode { + index = 1, + callbacks = { + {key = "enter_blind", triggers = {selected = true}}, + {key = "enter_shop", triggers = {cashed_out = true}}, + {key = "create_tag", triggers = {skipped = true}}, + }, + blinds = {"bl_small"}, + tags = {get_next_tag_key()}, + next_nodes_indices = {[2] = true} + }, + SMODS.BTNode { + index = 2, + callbacks = { + {key = "enter_blind", triggers = {selected = true}}, + {key = "enter_shop", triggers = {cashed_out = true}}, + {key = "create_tag", triggers = {skipped = true}}, + }, + blinds = {"bl_big"}, + tags = {get_next_tag_key()}, + next_nodes_indices = {[3] = true} + }, + SMODS.BTNode { + index = 3, + callbacks = { + {key = "enter_blind", triggers = {selected = true}}, + {key = "enter_shop", triggers = {cashed_out = true}}, + {key = "ante_up", triggers = {defeated = true}}, + }, + blinds = {get_new_boss()}, -- TODO: Replace with own function + } + } + } + return tree_data end, create_ui = function () -- Vanilla Blind select UI @@ -3835,13 +3873,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. } function SMODS.get_blind_tree() - local tree_key = SMODS.get_active_blind_tree() - tree_key = type(tree_key) == "table" and tree_key.key or tree_key - return SMODS.BlindTrees[tree_key] or SMODS.BlindTrees["vanilla"] + return SMODS.BLIND_TREE and SMODS.BlindTrees[SMODS.BLIND_TREE.key] or SMODS.BlindTrees["vanilla"] end - function SMODS.get_active_blind_tree() - return "vanilla" + function SMODS.get_bt_node(index) + return SMODS.BLIND_TREE and SMODS.BLIND_TREE.nodes and SMODS.BLIND_TREE.nodes[index] or {} end ------- API CODE Object.BTNode @@ -3849,19 +3885,36 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.BTNode:init(...) local args = {...} - self.callbacks = args.callbacks or {} -- {key: String, called: Boolean} + self.index = args.index + + self:set_callbacks(args.callbacks or {}) self.blinds = args.blinds or {} self.tags = args.tags or {} + + self.selected = args.selected or false + self:set_next_nodes(args.next_nodes_indices or {}) + end + + function SMODS.BTNode:set_callbacks(cbs) + self.callbacks = {} + for _, cb in ipairs(cbs) do + table.insert(self.callbacks, { + key = cb.key, + triggers = cb.triggers, + called = cb.called or false, + ignore_hold = cb.ignore_hold or false + }) + end end - function SMODS.BTNode:trigger_callbacks(type) + function SMODS.BTNode:trigger_callbacks(trigger_type) if not type then return end local hold = false for _, cb in ipairs(self.callbacks) do local callback = SMODS.BTNodeCallbacks[cb.key] - if not cb.called and (not hold or callback.ignore_hold) and callback.type == type then + if not cb.called and (not hold or cb.ignore_hold) and cb.triggers[trigger_type] then cb.called = true - hold = hold or callback:on_callback(self, cb) + hold = hold or callback:on_callback(self, cb, trigger_type) end end end @@ -3878,11 +3931,17 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return tag end + function SMODS.BTNode:set_next_nodes(nni) + self.next_nodes_indices = nni or {} + end + function SMODS.BTNode:save() local node_table = { callbacks = self.callbacks, blinds = self.blinds, - tags = self.tags + tags = self.tags, + selected = self.selected, + next_nodes_indices = self.next_nodes_indices, } return node_table end @@ -3891,6 +3950,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. self.callbacks = node_table.callbacks or {} self.blinds = node_table.blinds or {} self.tags = node_table.tags or {} + self.selected = node_table.selected or false + self.next_nodes_indices = node_table.next_nodes_indices or {} + self:load_next_nodes() end ------- API CODE GameObject.BTNodeCallback @@ -3903,19 +3965,16 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. 'key', 'on_callback', }, - ignore_hold = false, inject = function(self) if type(self.on_callback) ~= "function" then sendWarnMessage(("BTNodeCallback injected with invalid function '%s'"):format(self.on_callback)) end - self.type = self.type or "selected" - end, + end } SMODS.BTNodeCallback { key = "enter_blind", - type = "selected", - on_callback = function (self, bt_node, cb) + on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to bt_node:get_blind() return true end @@ -3923,8 +3982,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.BTNodeCallback { key = "enter_shop", - type = "cashed_out", - on_callback = function (self, bt_node, cb) + on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to shop return true end @@ -3932,9 +3990,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.BTNodeCallback { key = "ante_up", - type = "defeated", - ignore_hold = true, - on_callback = function (self, bt_node, cb) + on_callback = function (self, bt_node, cb, trigger_type) -- Ante up return false end @@ -3942,9 +3998,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.BTNodeCallback { key = "create_tag", - type = "skipped", - ignore_hold = true, - on_callback = function (self, bt_node, cb) + on_callback = function (self, bt_node, cb, trigger_type) -- Create tag(s) from bt_node:get_tag() return false end @@ -3956,7 +4010,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. --[[ Game:init_game_object() Game:update(dt) + Game:start_run() G.GAME.round_resets.blind_tags + get_new_boss() ]] -- Create Blind Select UI From 0687907178ab70e033b59fb17b3d971b1082e56c Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Thu, 16 Oct 2025 23:56:56 +0200 Subject: [PATCH 05/14] Added `evaluate_round` callback because I'll separate that from defeating a Blind +Title --- src/game_object.lua | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index 07d4c837d..5f261542d 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3829,6 +3829,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. index = 1, callbacks = { {key = "enter_blind", triggers = {selected = true}}, + {key = "evaluate_round", triggers = {defeated = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, {key = "create_tag", triggers = {skipped = true}}, }, @@ -3840,6 +3841,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. index = 2, callbacks = { {key = "enter_blind", triggers = {selected = true}}, + {key = "evaluate_round", triggers = {defeated = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, {key = "create_tag", triggers = {skipped = true}}, }, @@ -3851,8 +3853,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. index = 3, callbacks = { {key = "enter_blind", triggers = {selected = true}}, - {key = "enter_shop", triggers = {cashed_out = true}}, {key = "ante_up", triggers = {defeated = true}}, + {key = "evaluate_round", triggers = {defeated = true}}, + {key = "enter_shop", triggers = {cashed_out = true}}, }, blinds = {get_new_boss()}, -- TODO: Replace with own function } @@ -3980,6 +3983,14 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end } + SMODS.BTNodeCallback { + key = "evaluate_round", + on_callback = function (self, bt_node, cb, trigger_type) + -- Create cashout and evaluate round + return true + end + } + SMODS.BTNodeCallback { key = "enter_shop", on_callback = function (self, bt_node, cb, trigger_type) @@ -4013,6 +4024,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. Game:start_run() G.GAME.round_resets.blind_tags get_new_boss() + function Node:collides_with_point(point) -- Needs to be fixed so that clipping parents are accounted for ]] -- Create Blind Select UI @@ -4031,8 +4043,13 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end -- Cash Out (probably should be a lovely patch) - -- AND/OR: Consider separating the cashout into its own BTNodeCallback "enter_cashout" - G.FUNCS.cash_out = function(e) + -- AND/OR: Consider separating the Round eval/cashout into its own BTNodeCallback "evaluate_round" + function G.FUNCS.cash_out(e) + + end + + -- If I separate the cash out from necessarily following a Blind, override this as well + function G.FUNCS.evaluate_round() end From 6be47b04c1df1dbec682a2c758cc9e25909d9aa5 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Mon, 20 Oct 2025 20:48:36 +0200 Subject: [PATCH 06/14] WIP `SMODS.CanvasContainer` and `SMODS.CanvasContainerUIE` +These are needed to override alignment of children (indoctrination) :)))) --- src/ui.lua | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/ui.lua b/src/ui.lua index 18c010249..b63755369 100644 --- a/src/ui.lua +++ b/src/ui.lua @@ -2175,3 +2175,112 @@ G.FUNCS.hand_type_UI_set = function(e) if not G.TAROT_INTERRUPT_PULSE then G.FUNCS.text_super_juice(e, math.max(0,math.floor(math.log10(type(G.GAME.current_round.current_hand[e.config.type]) == 'number' and G.GAME.current_round.current_hand[e.config.type] or 1)))) end end end + + +SMODS.CanvasContainer = UIBox:extend() + +local function inverted_collides_with_point(self, point) + return not self.parent:collides_with_point(point) +end +function SMODS.CanvasContainer:init(args) + UIBox.init(self, args) + self.canvas = love.graphics.newCanvas(self.T.w, self.T.h, {type = '2d'}) + self.canvas:setFilter('linear', 'linear') + self.culler = Node() + self.culler.parent = self + self.culler.states.visible = false + self.culler.states.collide.can = true + self.culler.states.drag.can = false + self.culler.collides_with_point = inverted_collides_with_point +end + +function SMODS.CanvasContainer:draw() + if self.FRAME.DRAW >= G.FRAMES.DRAW and not G.OVERLAY_TUTORIAL then return end + self.FRAME.DRAW = G.FRAMES.DRAW + + love.graphics.setCanvas(self.canvas) + for k, v in pairs(self.children) do + if k ~= 'h_popup' and k ~= 'alert' then v:draw() end + end + + if self.states.visible then + add_to_drawhash(self) + love.graphics.setCanvas(G.CANVAS) + self.UIRoot:draw_self() + love.graphics.setCanvas(self.canvas) + self.UIRoot:draw_children() + for k, v in ipairs(self.draw_layers) do + love.graphics.setCanvas(G.CANVAS) + if v.draw_self then v:draw_self() else v:draw() end + love.graphics.setCanvas(self.canvas) + if v.draw_children then v:draw_children() end + end + end + + add_to_drawhash(self.culler) + + love.graphics.setCanvas(G.CANVAS) + self:draw_canvas() + + if self.children.alert then self.children.alert:draw() end + + self:draw_boundingrect() +end + +function SMODS.CanvasContainer:draw_canvas() + if not self.states.visible then return end + love.graphics.draw( + self.canvas, + self.sprite, + 0 ,0, + 0, + self.VT.w/(self.T.w), + self.VT.h/(self.T.h) + ) + love.graphics.pop() +end + +function SMODS.CanvasContainer:set_parent_child(node, parent) + local UIE = (not parent and UIElement or SMODS.CanvasContainerUIE)(parent, self, node.n, node.config) + + --set the group of the element + if parent and parent.config and parent.config.group then if UIE.config then UIE.config.group = parent.config.group else UIE.config = {group = parent.config.group} end end + + --set the button for the element + if parent and parent.config and parent.config.button then if UIE.config then UIE.config.button_UIE = parent else UIE.config = {button_UIE = parent} end end + if parent and parent.config and parent.config.button_UIE then if UIE.config then UIE.config.button_UIE = parent.config.button_UIE else UIE.config = {button = parent.config.button} end end + + if node.n and node.n == G.UIT.O and UIE.config.button then + UIE.config.object.states.click.can = false + end + + --current node is a container + if (node.n and node.n == G.UIT.C or node.n == G.UIT.R or node.n == G.UIT.ROOT) and node.nodes then + for k, v in pairs(node.nodes) do + self:set_parent_child(v, UIE) + end + end + + if not parent then + self.UIRoot = UIE + self.UIRoot.parent = self + else + table.insert(parent.children, UIE) + end + if node.config and node.config.mid then + self.Mid = UIE + end +end + +function SMODS.CanvasContainer:calculate_xywh(node, _T, recalculate, _scale) + +end + +SMODS.CanvasContainerUIE = UIElement:extend() +function SMODS.CanvasContainerUIE:set_wh() + +end + +function SMODS.CanvasContainerUIE:set_alignments() + +end \ No newline at end of file From 611f754e52f1bc61a71cbb59fc68a2f2538c9a5a Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Wed, 22 Oct 2025 21:25:31 +0200 Subject: [PATCH 07/14] `SMODS.GameState` wip stuff and more +/*Title --- lovely/compact_cashout.toml | 44 +------ lovely/game_state.toml | 22 ++++ src/game_object.lua | 228 ++++++++++++++++++++++++++++++++++-- src/ui.lua | 7 +- 4 files changed, 243 insertions(+), 58 deletions(-) create mode 100644 lovely/game_state.toml diff --git a/lovely/compact_cashout.toml b/lovely/compact_cashout.toml index 98676f316..699c237e1 100644 --- a/lovely/compact_cashout.toml +++ b/lovely/compact_cashout.toml @@ -20,46 +20,4 @@ payload = ''' 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 - +match_indent = true \ No newline at end of file diff --git a/lovely/game_state.toml b/lovely/game_state.toml new file mode 100644 index 000000000..ccb211c43 --- /dev/null +++ b/lovely/game_state.toml @@ -0,0 +1,22 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -10 + + +# game:update() inject SMODS.GameStates +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = ''' + if self.STATE == self.STATES.MENU then + self:update_menu(dt) + end +''' +position = 'after' +match_indent = true +payload = ''' +if self.STATE and SMODS.GameStates[self.STATE] then + SMODS.GameStates[self.STATE]:update(dt) +end +''' diff --git a/src/game_object.lua b/src/game_object.lua index 5f261542d..231331f3d 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3806,6 +3806,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. ------- API CODE SMODS.BlindTree AND RELATED ------------------------------------------------------------------------------------------------- + ------- UI DEFS + function SMODS.create_CanvasContainer_BlindSelect(run_info) + + end + ------- API CODE SMODS.BlindTree SMODS.BlindTrees = {} SMODS.BlindTree = SMODS.GameObject:extend { @@ -3818,7 +3823,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. inject = function(self) end, - create_data = function () + create_data = function (self) -- Vanilla Blind structure -- "Small" -> "Big" -> "Boss" local tree_data = { @@ -3855,20 +3860,33 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. {key = "enter_blind", triggers = {selected = true}}, {key = "ante_up", triggers = {defeated = true}}, {key = "evaluate_round", triggers = {defeated = true}}, + {key = "next_blind_tree", triggers = {cashed_out = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, }, - blinds = {get_new_boss()}, -- TODO: Replace with own function + blinds = {get_new_boss()}, } } } return tree_data end, - create_ui = function () + create_ui = function (self) -- Vanilla Blind select UI + local canvas_container = SMODS.CanvasContainer { + definition = SMODS.create_CanvasContainer_BlindSelect(), + config = {align="cm", offset = {x=0,y=0}, major = G.ROOM_ATTACH, bond = 'Weak'} + } end, - create_run_info_ui = function () + create_run_info_ui = function (self) -- Vanilla Run Info Blind Tab UI + local canvas_container = SMODS.CanvasContainer { + definition = SMODS.create_CanvasContainer_BlindSelect(true), + config = {align="cm", offset = {x=0,y=0}, major = G.ROOM_ATTACH, bond = 'Weak'} + } end, + create_callback_ui = function (self, cb_key, bt_node, bt_node_UIE) + if not cb_key or not SMODS.BTNodeCallbacks[cb_key] then return end + return SMODS.BTNodeCallbacks[cb_key]:create_ui(bt_node, bt_node_UIE) + end } SMODS.BlindTree { @@ -3880,7 +3898,15 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end function SMODS.get_bt_node(index) - return SMODS.BLIND_TREE and SMODS.BLIND_TREE.nodes and SMODS.BLIND_TREE.nodes[index] or {} + return SMODS.BLIND_TREE and SMODS.BLIND_TREE.nodes and SMODS.BLIND_TREE.nodes[index] + end + + function SMODS.next_blind_tree() + SMODS.set_blind_tree(SMODS.get_blind_tree():create_data()) + end + + function SMODS.set_blind_tree(data) + SMODS.BLIND_TREE = data end ------- API CODE Object.BTNode @@ -3911,7 +3937,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end function SMODS.BTNode:trigger_callbacks(trigger_type) - if not type then return end + if not trigger_type then return end local hold = false for _, cb in ipairs(self.callbacks) do local callback = SMODS.BTNodeCallbacks[cb.key] @@ -3924,13 +3950,13 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.BTNode:get_blind(keep) local blind = self.blinds[1] - if not keep then table.remove(self.blinds, 1) end + if blind and not keep then table.remove(self.blinds, 1) end return blind end function SMODS.BTNode:get_tag(keep) local tag = self.tags[1] - if not keep then table.remove(self.tags, 1) end + if tag and not keep then table.remove(self.tags, 1) end return tag end @@ -3972,6 +3998,8 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. if type(self.on_callback) ~= "function" then sendWarnMessage(("BTNodeCallback injected with invalid function '%s'"):format(self.on_callback)) end + end, + create_ui = function (self, bt_node, bt_node_UIE) end } @@ -3980,6 +4008,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to bt_node:get_blind() return true + end, + create_ui = function (self, bt_node, bt_node_UIE) + return SMODS.GUI.bt_callback_enter_blind(bt_node, bt_node_UIE) end } @@ -3988,6 +4019,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_callback = function (self, bt_node, cb, trigger_type) -- Create cashout and evaluate round return true + end, + create_ui = function (self, bt_node, bt_node_UIE) + return SMODS.GUI.bt_callback_evaluate_round(bt_node, bt_node_UIE) end } @@ -3996,6 +4030,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to shop return true + end, + create_ui = function (self, bt_node, bt_node_UIE) + return SMODS.GUI.bt_callback_enter_shop(bt_node, bt_node_UIE) end } @@ -4004,6 +4041,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_callback = function (self, bt_node, cb, trigger_type) -- Ante up return false + end, + create_ui = function (self, bt_node, bt_node_UIE) + return SMODS.GUI.bt_callback_ante_up(bt_node, bt_node_UIE) end } @@ -4012,6 +4052,26 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_callback = function (self, bt_node, cb, trigger_type) -- Create tag(s) from bt_node:get_tag() return false + end, + create_ui = function (self, bt_node, bt_node_UIE) + return SMODS.GUI.bt_callback_create_tag(bt_node, bt_node_UIE) + end + } + + SMODS.BTNodeCallback { + key = "next_blind_tree", + on_callback = function (self, bt_node, cb, trigger_type) + G.E_MANAGER:add_event(Event{ + trigger = "immediate", + func = function () + SMODS.next_blind_tree() + return true + end + }) + return false + end, + create_ui = function (self, bt_node, bt_node_UIE) + return SMODS.GUI.bt_callback_next_blind_tree(bt_node, bt_node_UIE) end } @@ -4024,17 +4084,18 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. Game:start_run() G.GAME.round_resets.blind_tags get_new_boss() - function Node:collides_with_point(point) -- Needs to be fixed so that clipping parents are accounted for + add_round_eval_row() + Blind:get_type() ]] -- Create Blind Select UI function create_UIBox_blind_select() - return SMODS.get_blind_tree().create_ui() + return SMODS.get_blind_tree():create_ui() end -- Run Info Tab function G.UIDEF.current_blinds() - return SMODS.get_blind_tree().create_run_info_ui() + return SMODS.get_blind_tree():create_run_info_ui() end -- Blind Choice Handler @@ -4048,9 +4109,113 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- If I separate the cash out from necessarily following a Blind, override this as well function G.FUNCS.evaluate_round() + total_cashout_rows = 0 + local pitch = 0.95 + local dollars = 0 + + if not G.GAME.blind then + add_round_eval_row({dollars = G.GAME.default_eval_dollars or 0, name='???', pitch = pitch}) -- TODO: Check name + pitch = pitch + 0.06 + dollars = dollars + (G.GAME.default_eval_dollars or 0) + elseif G.GAME.chips - G.GAME.blind.chips >= 0 then + add_round_eval_row({dollars = G.GAME.blind.dollars, name='blind1', pitch = pitch}) + pitch = pitch + 0.06 + dollars = dollars + G.GAME.blind.dollars + else + add_round_eval_row({dollars = 0, name='blind1', pitch = pitch, saved = true}) + pitch = pitch + 0.06 + end + + -- if G.GAME.blind then + -- G.E_MANAGER:add_event(Event({ + -- trigger = 'before', + -- delay = 1.3*math.min(G.GAME.blind.dollars+2, 7)/2*0.15 + 0.5, + -- func = function() + -- G.GAME.blind:defeat() -- TODO: Replace in end_round() + -- return true + -- end + -- })) + -- end + delay(0.2) + G.E_MANAGER:add_event(Event({ + func = function() + ease_background_colour_blind(G.STATES.ROUND_EVAL, '') + return true + end + })) + SMODS.calculate_context{round_eval = true} + G.GAME.selected_back:trigger_effect({context = 'eval'}) + + if G.GAME.current_round.hands_left > 0 and not G.GAME.modifiers.no_extra_hand_money then + add_round_eval_row({dollars = G.GAME.current_round.hands_left*(G.GAME.modifiers.money_per_hand or 1), disp = G.GAME.current_round.hands_left, bonus = true, name='hands', pitch = pitch}) + pitch = pitch + 0.06 + dollars = dollars + G.GAME.current_round.hands_left*(G.GAME.modifiers.money_per_hand or 1) + end + if G.GAME.current_round.discards_left > 0 and G.GAME.modifiers.money_per_discard then + add_round_eval_row({dollars = G.GAME.current_round.discards_left*(G.GAME.modifiers.money_per_discard), disp = G.GAME.current_round.discards_left, bonus = true, name='discards', pitch = pitch}) + pitch = pitch + 0.06 + dollars = dollars + G.GAME.current_round.discards_left*(G.GAME.modifiers.money_per_discard) + end + 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 + for i = 1, #G.GAME.tags do + local ret = G.GAME.tags[i]:apply_to_run({type = 'eval'}) + if ret then + add_round_eval_row({dollars = ret.dollars, bonus = true, name='tag'..i, pitch = pitch, condition = ret.condition, pos = ret.pos, tag = ret.tag}) + pitch = pitch + 0.06 + dollars = dollars + ret.dollars + end + end + if G.GAME.dollars >= 5 and not G.GAME.modifiers.no_interest then + add_round_eval_row({bonus = true, name='interest', pitch = pitch, dollars = G.GAME.interest_amount*math.min(math.floor(G.GAME.dollars/5), G.GAME.interest_cap/5)}) + pitch = pitch + 0.06 + if (not G.GAME.seeded and not G.GAME.challenge) or SMODS.config.seeded_unlocks then + if G.GAME.interest_amount*math.min(math.floor(G.GAME.dollars/5), G.GAME.interest_cap/5) == G.GAME.interest_amount*G.GAME.interest_cap/5 then + G.PROFILES[G.SETTINGS.profile].career_stats.c_round_interest_cap_streak = G.PROFILES[G.SETTINGS.profile].career_stats.c_round_interest_cap_streak + 1 + else + G.PROFILES[G.SETTINGS.profile].career_stats.c_round_interest_cap_streak = 0 + end + end + check_for_unlock({type = 'interest_streak'}) + dollars = dollars + G.GAME.interest_amount*math.min(math.floor(G.GAME.dollars/5), G.GAME.interest_cap/5) + end + + pitch = pitch + 0.06 + 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 + add_round_eval_row({name = 'bottom', dollars = dollars}) end -- reset_blinds() @@ -4064,6 +4229,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end -- end_round() (also lovely patch maybe) + -- Needs to create an Event to call Blind:defeat() because I removed that from G.FUNCS.evaluate_round() function end_round() end @@ -4093,6 +4259,44 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end + ------------------------------------------------------------------------------------------------- + ----- API CODE SMODS.GameState + ------------------------------------------------------------------------------------------------- + + SMODS.STATES = { + SHOP = "SHOP", + ROUND_EVAL = "ROUND_EVAL", + BLIND = "BLIND", + BLIND_SELECT = "BLIND_SELECT" + } + SMODS.GameStates = {} + SMODS.GameState = SMODS.GameObject:extend{ + set = 'GameState', + obj_table = SMODS.GameStates, + obj_buffer = {}, + required_parameters = { + 'key', + }, + update = function (self, dt) + + end + } + + SMODS.GameState { + key = SMODS.STATES.SHOP + } + + SMODS.GameState { + key = SMODS.STATES.ROUND_EVAL + } + + SMODS.GameState { + key = SMODS.STATES.BLIND + } + + SMODS.GameState { + key = SMODS.STATES.BLIND_SELECT + } ------------------------------------------------------------------------------------------------- ----- API IMPORT GameObject.DrawStep diff --git a/src/ui.lua b/src/ui.lua index b63755369..dd36f7b45 100644 --- a/src/ui.lua +++ b/src/ui.lua @@ -2190,6 +2190,7 @@ function SMODS.CanvasContainer:init(args) self.culler.parent = self self.culler.states.visible = false self.culler.states.collide.can = true + self.culler.states.focus.can = true self.culler.states.drag.can = false self.culler.collides_with_point = inverted_collides_with_point end @@ -2231,8 +2232,8 @@ function SMODS.CanvasContainer:draw_canvas() if not self.states.visible then return end love.graphics.draw( self.canvas, - self.sprite, - 0 ,0, + --self.sprite, -- WRONG + 0, 0, 0, self.VT.w/(self.T.w), self.VT.h/(self.T.h) @@ -2278,7 +2279,7 @@ end SMODS.CanvasContainerUIE = UIElement:extend() function SMODS.CanvasContainerUIE:set_wh() - + end function SMODS.CanvasContainerUIE:set_alignments() From 4b5b96bc6c5031b71e10210f26770a69b936c5a5 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Thu, 30 Oct 2025 22:24:45 +0100 Subject: [PATCH 08/14] `SMODS.BLIND_TREE` save and load stuff + some callback functionality + WIP `SMODS.BTNodeButton` + WIP `SMODS.GameState` stuff +/*Title --- src/game_object.lua | 242 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 222 insertions(+), 20 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index a35ab7c17..723466c8c 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3819,7 +3819,13 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. ------- UI DEFS function SMODS.create_CanvasContainer_BlindSelect(run_info) - + local ui_out + if run_info then + + else + ui_out = {} + end + return ui_out end ------- API CODE SMODS.BlindTree @@ -3839,10 +3845,15 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. -- "Small" -> "Big" -> "Boss" local tree_data = { key = "vanilla", - active_node = nil, + active_node = 1, nodes = { SMODS.BTNode { index = 1, + hidden = true, + selected = true, + }, + SMODS.BTNode { + index = 2, callbacks = { {key = "enter_blind", triggers = {selected = true}}, {key = "evaluate_round", triggers = {defeated = true}}, @@ -3851,10 +3862,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. }, blinds = {"bl_small"}, tags = {get_next_tag_key()}, - next_nodes_indices = {[2] = true} + next_nodes_indices = {[3] = true} }, SMODS.BTNode { - index = 2, + index = 3, callbacks = { {key = "enter_blind", triggers = {selected = true}}, {key = "evaluate_round", triggers = {defeated = true}}, @@ -3863,10 +3874,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. }, blinds = {"bl_big"}, tags = {get_next_tag_key()}, - next_nodes_indices = {[3] = true} + next_nodes_indices = {[4] = true} }, SMODS.BTNode { - index = 3, + index = 4, callbacks = { {key = "enter_blind", triggers = {selected = true}}, {key = "ante_up", triggers = {defeated = true}}, @@ -3884,8 +3895,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. -- Vanilla Blind select UI local canvas_container = SMODS.CanvasContainer { definition = SMODS.create_CanvasContainer_BlindSelect(), - config = {align="cm", offset = {x=0,y=0}, major = G.ROOM_ATTACH, bond = 'Weak'} + config = {align="cm", offset = {x=0,y=G.ROOM.T.y + 29}, major = G.ROOM_ATTACH, bond = 'Weak'} } + return canvas_container end, create_run_info_ui = function (self) -- Vanilla Run Info Blind Tab UI @@ -3893,6 +3905,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. definition = SMODS.create_CanvasContainer_BlindSelect(true), config = {align="cm", offset = {x=0,y=0}, major = G.ROOM_ATTACH, bond = 'Weak'} } + return canvas_container end, create_callback_ui = function (self, cb_key, bt_node, bt_node_UIE) if not cb_key or not SMODS.BTNodeCallbacks[cb_key] then return end @@ -3912,6 +3925,27 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return SMODS.BLIND_TREE and SMODS.BLIND_TREE.nodes and SMODS.BLIND_TREE.nodes[index] end + function SMODS.save_blind_tree() + if not SMODS.BLIND_TREE then return {} end + local nodes_table = {} + for _, bt_node in ipairs(SMODS.BLIND_TREE.nodes) do + table.insert(nodes_table, bt_node:save()) + end + local blind_tree_table = { + key = SMODS.BLIND_TREE.key, + active_node = SMODS.BLIND_TREE.active_node, + nodes = nodes_table, + } + return blind_tree_table + end + + function SMODS.load_blind_tree(blind_tree_table) + for i, bt_node_table in ipairs(blind_tree_table.nodes) do + blind_tree_table.nodes[i] = SMODS.BTNode{node_table = bt_node_table} + end + SMODS.set_blind_tree(blind_tree_table) + end + function SMODS.next_blind_tree() SMODS.set_blind_tree(SMODS.get_blind_tree():create_data()) end @@ -3919,19 +3953,54 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.set_blind_tree(data) SMODS.BLIND_TREE = data end + + function SMODS.set_active_bt_node(bt_node) + if type(bt_node) == "number" then + SMODS.BLIND_TREE.active_node = bt_node + else + SMODS.BLIND_TREE.active_node = bt_node.index + end + end + + local start_run_ref = Game.start_run + function Game:start_run(args) + start_run_ref(self, args) + local saveTable = args.savetext + if saveTable and saveTable.BLIND_TREE then + SMODS.load_blind_tree(saveTable.BLIND_TREE) + end + end + + local save_run_ref = save_run + function save_run() + save_run_ref() + G.ARGS.save_run.BLIND_TREE = recursive_table_cull(SMODS.save_blind_tree()) + end + + local delete_run_ref = Game.delete_run + function Game:delete_run() + delete_run_ref(self) + SMODS.BLIND_TREE = nil + end ------- API CODE Object.BTNode SMODS.BTNode = Object:extend() function SMODS.BTNode:init(...) local args = {...} + if args.node_table then + self:load(args.node_table) + return + end + self.index = args.index + self.hidden = args.hidden + self.selected = args.selected or false self:set_callbacks(args.callbacks or {}) - self.blinds = args.blinds or {} - self.tags = args.tags or {} + self:set_blinds(args.blinds or {}) + self:set_tags(args.tags or {}) - self.selected = args.selected or false self:set_next_nodes(args.next_nodes_indices or {}) end @@ -3947,6 +4016,26 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end + function SMODS.BTNode:set_blinds(blinds) + self.blinds = {} + for _, blind_key in ipairs(blinds) do + table.insert(self.blinds, { + key = blind_key, + used = false, + }) + end + end + + function SMODS.BTNode:set_tags(tags) + self.tags = {} + for _, tag_key in ipairs(tags) do + table.insert(self.tags, { + key = tag_key, + used = false, + }) + end + end + function SMODS.BTNode:trigger_callbacks(trigger_type) if not trigger_type then return end local hold = false @@ -3960,14 +4049,28 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end function SMODS.BTNode:get_blind(keep) - local blind = self.blinds[1] - if blind and not keep then table.remove(self.blinds, 1) end + local blind_tuple + for _, tup in ipairs(self.blinds) do + if not tup.used then + blind_tuple = tup + break + end + end + local blind = blind_tuple.key + if blind and not keep then blind_tuple.used = true end return blind end function SMODS.BTNode:get_tag(keep) - local tag = self.tags[1] - if tag and not keep then table.remove(self.tags, 1) end + local tag_tuple + for _, tup in ipairs(self.tags) do + if not tup.used then + tag_tuple = tup + break + end + end + local tag = tag_tuple.key + if tag and not keep then tag_tuple.used = true end return tag end @@ -3977,6 +4080,8 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.BTNode:save() local node_table = { + index = self.index, + hidden = self.hidden, callbacks = self.callbacks, blinds = self.blinds, tags = self.tags, @@ -3987,12 +4092,13 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end function SMODS.BTNode:load(node_table) + self.index = node_table.index + self.hidden = node_table.hidden self.callbacks = node_table.callbacks or {} self.blinds = node_table.blinds or {} self.tags = node_table.tags or {} self.selected = node_table.selected or false self.next_nodes_indices = node_table.next_nodes_indices or {} - self:load_next_nodes() end ------- API CODE GameObject.BTNodeCallback @@ -4025,6 +4131,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end } + function SMODS.GUI.bt_callback_enter_blind(bt_node, bt_node_UIE) + return {} + end + SMODS.BTNodeCallback { key = "evaluate_round", on_callback = function (self, bt_node, cb, trigger_type) @@ -4051,6 +4161,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "ante_up", on_callback = function (self, bt_node, cb, trigger_type) -- Ante up + SMODS.ante_end = true + ease_ante(1) + SMODS.ante_end = nil return false end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4062,6 +4175,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "create_tag", on_callback = function (self, bt_node, cb, trigger_type) -- Create tag(s) from bt_node:get_tag() + add_tag(bt_node:get_tag()) return false end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4082,10 +4196,57 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return false end, create_ui = function (self, bt_node, bt_node_UIE) - return SMODS.GUI.bt_callback_next_blind_tree(bt_node, bt_node_UIE) + return nil end } + ------- API CODE GameObject.BTNodeButton + SMODS.BTNodeButtons = {} + SMODS.BTNodeButton = SMODS.GameObject:extend { + set = 'BTNodeButton', + obj_table = SMODS.BTNodeButtons, + obj_buffer = {}, + required_parameters = { + 'key', + 'on_click', + }, + inject = function(self) + if type(self.on_click) ~= "function" then + sendWarnMessage(("BTNodeButton injected with invalid function '%s'"):format(self.on_click)) + end + end, + create_ui = function (self, bt_node, bt_node_UIE) + + end, + on_click = function (self, bt_node) + + end + } + + SMODS.BTNodeButton { + key = "select", + on_click = function (self, bt_node) + SMODS.set_active_bt_node(bt_node) + bt_node:trigger_callbacks("selected") + end, + } + + SMODS.BTNodeButton { + key = "skip", + on_click = function (self, bt_node) + SMODS.set_active_bt_node(bt_node) + bt_node:trigger_callbacks("skipped") + end, + } + + SMODS.BTNodeButton { + key = "reroll", + on_click = function (self, bt_node) + -- Reroll Blind + end, + } + + ------- API CODE OVERRIDES @@ -4252,27 +4413,39 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. -- Toggle Shop function G.FUNCS.toggle_shop(e) - end -- Select Blind (-> use own func in UI defs) function G.FUNCS.select_blind(e) - end -- Skip Blind (-> use own func in UI defs) function G.FUNCS.skip_blind(e) - end -- Reroll Boss (-> use own func in UI defs) function G.FUNCS.reroll_boss(e) + end + function G.FUNCS.reroll_boss_button(e) end ------------------------------------------------------------------------------------------------- ----- API CODE SMODS.GameState ------------------------------------------------------------------------------------------------- + + SMODS.StateQueue = {} + + function SMODS.clear_states() + if G.blind_select then G.blind_select:remove(); G.blind_select = nil end + if G.shop then G.shop:remove(); G.shop = nil end + if G.buttons then G.buttons:remove(); G.buttons = nil end + if G.round_eval then G.round_eval:remove(); G.round_eval = nil end + end + + function SMODS.queue_state(state) + + end SMODS.STATES = { SHOP = "SHOP", @@ -4306,7 +4479,36 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. } SMODS.GameState { - key = SMODS.STATES.BLIND_SELECT + key = SMODS.STATES.BLIND_SELECT, + update = function (self, dt) + SMODS.clear_states() + if not G.STATE_COMPLETE then + stop_use() + ease_background_colour_blind(G.STATES.BLIND_SELECT) + G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) + G.STATE_COMPLETE = true + G.CONTROLLER.interrupt.focus = true + G.E_MANAGER:add_event(Event({ func = function() + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + play_sound('cancel') + G.blind_select = SMODS.get_blind_tree():create_ui() + G.blind_select.alignment.offset.y = 0.8-(G.hand.T.y - G.jokers.T.y) + G.blind_select.T.h + G.ROOM.jiggle = G.ROOM.jiggle + 3 + G.blind_select.alignment.offset.x = 0 + G.CONTROLLER.lock_input = false + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'immediate'}) + end + 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 + })) ; return true end})) + end + end } ------------------------------------------------------------------------------------------------- From 32eae555ea601fc83a952eac8437717cc9fa3741 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Fri, 31 Oct 2025 14:49:40 +0100 Subject: [PATCH 09/14] Added `Blind:is_type()` and related + new `SMODS.get_new_blind()` function to replace `get_new_boss()` +/*Title --- lovely/blind.toml | 90 ++++++++------- lovely/blind_tree.toml | 28 +++++ src/game_object.lua | 249 ++++++++++++++++++++++++++++++++--------- 3 files changed, 276 insertions(+), 91 deletions(-) create mode 100644 lovely/blind_tree.toml diff --git a/lovely/blind.toml b/lovely/blind.toml index 3155aa1df..4df6eba3a 100644 --- a/lovely/blind.toml +++ b/lovely/blind.toml @@ -389,44 +389,6 @@ if blind.collection_loc_vars and type(blind.collection_loc_vars) == 'function' t end local loc_target = localize(target)''' -# 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] @@ -514,4 +476,54 @@ 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.regex] +target = "functions/state_events.lua" +pattern = '''G\.GAME\.blind:get_type\(\) == 'Boss' ''' +position = "at" +payload = '''G.GAME.blind:is_type("Boss") ''' \ No newline at end of file diff --git a/lovely/blind_tree.toml b/lovely/blind_tree.toml new file mode 100644 index 000000000..435055c83 --- /dev/null +++ b/lovely/blind_tree.toml @@ -0,0 +1,28 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -5 + +# Game:start_run() +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' + self:prep_stage(G.STAGES.RUN, saveTable and saveTable.STATE or G.STATES.BLIND_SELECT) +''' +payload = ''' +self:prep_stage(G.STAGES.RUN, saveTable and saveTable.STATE or SMODS.STATES.BLIND_SELECT) +''' +[[patches]] +[patches.pattern] +target = 'game.lua' +match_indent = true +position = 'at' +pattern = ''' + self:prep_stage(G.STAGES.RUN, saveTable and saveTable.STATE or G.STATES.BLIND_SELECT) +''' +payload = ''' +self:prep_stage(G.STAGES.RUN, saveTable and saveTable.STATE or SMODS.STATES.BLIND_SELECT) +''' \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index 723466c8c..1b750c0cf 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -1702,6 +1702,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. config = {}, dollars = 5, mult = 2, + blind_types = nil, -- Map of types, used by SMODS.get_new_blind() atlas = 'blind_chips', discovered = false, pos = { x = 0, y = 0 }, @@ -1721,6 +1722,9 @@ 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 } SMODS.Blind:take_ownership('eye', { @@ -3843,6 +3847,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. create_data = function (self) -- Vanilla Blind structure -- "Small" -> "Big" -> "Boss" + local boss_type = (G.GAME.round_resets.ante)%G.GAME.win_ante == 0 and G.GAME.round_resets.ante > 0 and "Showdown" or "Boss" local tree_data = { key = "vanilla", active_node = 1, @@ -3885,7 +3890,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. {key = "next_blind_tree", triggers = {cashed_out = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, }, - blinds = {get_new_boss()}, + blinds = {SMODS.get_new_blind({[boss_type] = true})}, } } } @@ -4124,6 +4129,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "enter_blind", on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to bt_node:get_blind() + SMODS.queue_state(SMODS.STATES.BLIND, {key = bt_node:get_blind()}) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4139,6 +4145,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "evaluate_round", on_callback = function (self, bt_node, cb, trigger_type) -- Create cashout and evaluate round + SMODS.queue_state(SMODS.STATES.ROUND_EVAL) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4150,6 +4157,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "enter_shop", on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to shop + SMODS.queue_state(SMODS.STATES.SHOP) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4228,6 +4236,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_click = function (self, bt_node) SMODS.set_active_bt_node(bt_node) bt_node:trigger_callbacks("selected") + SMODS.next_state() end, } @@ -4236,6 +4245,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_click = function (self, bt_node) SMODS.set_active_bt_node(bt_node) bt_node:trigger_callbacks("skipped") + SMODS.next_state() end, } @@ -4246,7 +4256,6 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end, } - ------- API CODE OVERRIDES @@ -4254,11 +4263,117 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. Game:init_game_object() Game:update(dt) Game:start_run() - G.GAME.round_resets.blind_tags - get_new_boss() - add_round_eval_row() Blind:get_type() ]] + function reset_blinds() + G.GAME.round_resets.boss_rerolled = false + end + + function SMODS.get_blind_types(blind_obj) + if type(blind_obj.get_types) == "function" then + return blind_obj:get_types() + else + if v.boss then + if not v.boss.showdown then + return {Boss = true} + else + return {Showdown = true} + end + else + if v.name == "Small Blind" then + return {Small = true} + else + return {Big = true} + end + end + end + end + + function SMODS.get_new_blind(blind_types) + 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) + for _, b_type in pairs(b_types) do + if blind_types[b_type] then + if v.in_pool or not v.boss or (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 + 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.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 + }) -- Create Blind Select UI function create_UIBox_blind_select() @@ -4275,7 +4390,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- Cash Out (probably should be a lovely patch) + -- Cash Out -- AND/OR: Consider separating the Round eval/cashout into its own BTNodeCallback "evaluate_round" function G.FUNCS.cash_out(e) @@ -4390,11 +4505,6 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. add_round_eval_row({name = 'bottom', dollars = dollars}) end - -- reset_blinds() - function reset_blinds() - - end - -- new_round() (also lovely patch maybe) function new_round() @@ -4411,31 +4521,43 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- Toggle Shop - function G.FUNCS.toggle_shop(e) + -- Toggle Shop + function G.FUNCS.toggle_shop(e) + end -- Select Blind (-> use own func in UI defs) - function G.FUNCS.select_blind(e) - end + function G.FUNCS.select_blind(e) end -- Skip Blind (-> use own func in UI defs) - function G.FUNCS.skip_blind(e) - end + function G.FUNCS.skip_blind(e) end -- Reroll Boss (-> use own func in UI defs) - function G.FUNCS.reroll_boss(e) - end + function G.FUNCS.reroll_boss(e) end + + function G.FUNCS.reroll_boss_button(e) end - function G.FUNCS.reroll_boss_button(e) + ease_bg_col_bl_ref = ease_background_colour_blind + function ease_background_colour_blind(state, blind_override) + if SMODS.GameStates[state] and SMODS.GameStates[state].ease_background_colour then + return SMODS.GameStates[state]:ease_background_colour(blind_override) + end + return ease_bg_col_bl_ref(state, blind_override) end ------------------------------------------------------------------------------------------------- ----- API CODE SMODS.GameState ------------------------------------------------------------------------------------------------- - - SMODS.StateQueue = {} - + + SMODS.STATES = { + SHOP = "SHOP", + ROUND_EVAL = "ROUND_EVAL", + BLIND = "BLIND", + BLIND_SELECT = "BLIND_SELECT" + } + + SMODS.state_queue = {} + function SMODS.clear_states() if G.blind_select then G.blind_select:remove(); G.blind_select = nil end if G.shop then G.shop:remove(); G.shop = nil end @@ -4443,16 +4565,21 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. if G.round_eval then G.round_eval:remove(); G.round_eval = nil end end - function SMODS.queue_state(state) + function SMODS.queue_state(state, args) + table.insert(SMODS.state_queue, {state=state, args=args}) + end + function SMODS.next_state() + if #SMODS.state_queue > 0 then + local next_state = SMODS.state_queue[#SMODS.state_queue] + G.STATE = next_state.state + if SMODS.GameStates[next_state.state] then + SMODS.GameStates[next_state.state]:on_set(next_state.args) + end + table.remove(SMODS.state_queue, #SMODS.state_queue) + end end - SMODS.STATES = { - SHOP = "SHOP", - ROUND_EVAL = "ROUND_EVAL", - BLIND = "BLIND", - BLIND_SELECT = "BLIND_SELECT" - } SMODS.GameStates = {} SMODS.GameState = SMODS.GameObject:extend{ set = 'GameState', @@ -4461,9 +4588,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. required_parameters = { 'key', }, - update = function (self, dt) - - end + on_set = function (self, args) end, + update = function (self, dt) end, + ease_background_colour = nil, -- function } SMODS.GameState { @@ -4475,7 +4602,23 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. } SMODS.GameState { - key = SMODS.STATES.BLIND + key = SMODS.STATES.BLIND, + ease_background_colour = function (self, blind_override) + local blindname = ((blind_override or (G.GAME.blind and G.GAME.blind.name ~= '' and G.GAME.blind.name)) or 'Small Blind') + blindname = (blindname == '' and 'Small Blind' or blindname) + + local boss_col = G.C.BLACK + for k, v in pairs(G.P_BLINDS) do + if v.name == blindname then + if v.boss.showdown then + ease_background_colour{new_colour = G.C.BLUE, special_colour = G.C.RED, tertiary_colour = darken(G.C.BLACK, 0.4), contrast = 3} + return + end + boss_col = v.boss_colour or G.C.BLACK + end + end + ease_background_colour{new_colour = lighten(mix_colours(boss_col, G.C.BLACK, 0.3), 0.1), special_colour = boss_col, contrast = 2} + end } SMODS.GameState { @@ -4488,25 +4631,27 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) G.STATE_COMPLETE = true G.CONTROLLER.interrupt.focus = true - G.E_MANAGER:add_event(Event({ func = function() - G.E_MANAGER:add_event(Event({ - trigger = 'immediate', - func = function() - play_sound('cancel') - G.blind_select = SMODS.get_blind_tree():create_ui() - G.blind_select.alignment.offset.y = 0.8-(G.hand.T.y - G.jokers.T.y) + G.blind_select.T.h - G.ROOM.jiggle = G.ROOM.jiggle + 3 - G.blind_select.alignment.offset.x = 0 - G.CONTROLLER.lock_input = false - for i = 1, #G.GAME.tags do - G.GAME.tags[i]:apply_to_run({type = 'immediate'}) - end - for i = 1, #G.GAME.tags do - if G.GAME.tags[i]:apply_to_run({type = 'new_blind_choice'}) then break end + G.E_MANAGER:add_event(Event({ func = function() + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + play_sound('cancel') + G.blind_select = SMODS.get_blind_tree():create_ui() + G.blind_select.alignment.offset.y = 0.8-(G.hand.T.y - G.jokers.T.y) + G.blind_select.T.h + G.ROOM.jiggle = G.ROOM.jiggle + 3 + G.blind_select.alignment.offset.x = 0 + G.CONTROLLER.lock_input = false + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'immediate'}) + end + 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 - return true - end - })) ; return true end})) + })) + return true + end})) end end } From 7cb79174617e844327e6d8126679673114413011 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Sat, 1 Nov 2025 11:06:46 +0100 Subject: [PATCH 10/14] Changed state functions to use a stack instead of a queue *Title, this is much easier and more intuitive to work with. --- src/game_object.lua | 59 +++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index 1b750c0cf..4ba5b11e3 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -4129,7 +4129,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "enter_blind", on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to bt_node:get_blind() - SMODS.queue_state(SMODS.STATES.BLIND, {key = bt_node:get_blind()}) + SMODS.enter_state(SMODS.STATES.BLIND, {key = bt_node:get_blind()}) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4145,7 +4145,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "evaluate_round", on_callback = function (self, bt_node, cb, trigger_type) -- Create cashout and evaluate round - SMODS.queue_state(SMODS.STATES.ROUND_EVAL) + SMODS.enter_state(SMODS.STATES.ROUND_EVAL) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4157,7 +4157,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "enter_shop", on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to shop - SMODS.queue_state(SMODS.STATES.SHOP) + SMODS.enter_state(SMODS.STATES.SHOP) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4555,8 +4555,20 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. BLIND = "BLIND", BLIND_SELECT = "BLIND_SELECT" } + SMODS.default_state = SMODS.STATES.BLIND_SELECT - SMODS.state_queue = {} + SMODS.state_stack = {} + + function SMODS.push_to_state_stack(state, args) + table.insert(SMODS.state_stack, {state=state, args=args}) + end + + function SMODS.pop_from_state_stack(state) + if #SMODS.state_stack < 1 then return end + if SMODS.state_stack[#SMODS.state_stack].state == state then + table.remove(SMODS.state_stack, #SMODS.state_stack) + end + end function SMODS.clear_states() if G.blind_select then G.blind_select:remove(); G.blind_select = nil end @@ -4565,18 +4577,34 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. if G.round_eval then G.round_eval:remove(); G.round_eval = nil end end - function SMODS.queue_state(state, args) - table.insert(SMODS.state_queue, {state=state, args=args}) + function SMODS.enter_state(state, args, hold_state) + if G.STATE == state then return end + if SMODS.GameStates[G.STATE] and hold_state then + SMODS.GameStates[G.STATE]:on_exit({new_state=state}, true) + end + if not hold_state then + SMODS.pop_from_state_stack(G.STATE) + end + G.STATE = state + if SMODS.GameStates[G.STATE] then + SMODS.GameStates[G.STATE]:on_enter(args) + end + SMODS.push_to_state_stack(state, args) end - function SMODS.next_state() - if #SMODS.state_queue > 0 then - local next_state = SMODS.state_queue[#SMODS.state_queue] - G.STATE = next_state.state - if SMODS.GameStates[next_state.state] then - SMODS.GameStates[next_state.state]:on_set(next_state.args) - end - table.remove(SMODS.state_queue, #SMODS.state_queue) + function SMODS.exit_state(args) + if SMODS.GameStates[G.STATE] then + SMODS.GameStates[G.STATE]:on_exit(args) + end + SMODS.pop_from_state_stack(G.STATE) + if #SMODS.context_stack < 1 then + G.STATE = nil + SMODS.enter_state(SMODS.default_state) + return + end + G.STATE = SMODS.context_stack[#SMODS.context_stack].state + if SMODS.GameStates[G.STATE] then + SMODS.GameStates[G.STATE]:on_enter(args, true) end end @@ -4588,7 +4616,8 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. required_parameters = { 'key', }, - on_set = function (self, args) end, + on_enter = function (self, args, from_hold) end, + on_exit = function (self, args, from_hold) end, update = function (self, dt) end, ease_background_colour = nil, -- function } From e2b6366daa5d4b6494cb9da37d6fc6e94e9448e2 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Sat, 1 Nov 2025 11:54:34 +0100 Subject: [PATCH 11/14] Moved SMODS states `BOOSTER_OPENED` and `REDEEM_VOUCHER` into their own GameStates *Title --- lovely/booster.toml | 56 +++++++++++------------------------------- lovely/fixes.toml | 2 +- lovely/game_state.toml | 2 +- src/game_object.lua | 13 ++++++++++ src/overrides.lua | 2 +- src/utils.lua | 6 ++--- 6 files changed, 34 insertions(+), 47 deletions(-) diff --git a/lovely/booster.toml b/lovely/booster.toml index 9db51b8d9..dc0628465 100644 --- a/lovely/booster.toml +++ b/lovely/booster.toml @@ -33,7 +33,7 @@ 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 + G.STATE = SMODS.STATES.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) @@ -71,32 +71,6 @@ 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]] @@ -104,7 +78,7 @@ line_prepend = '$indent' 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''' +payload = '''(G.STATE == SMODS.STATES.BOOSTER_OPENED or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.STANDARD_PACK''' # CardArea:draw() [[patches]] @@ -113,7 +87,7 @@ 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''' +(self.config.type == 'hand' and state == SMODS.STATES.BOOSTER_OPENED) or''' match_indent = true # G.FUNCS.use_card @@ -123,7 +97,7 @@ 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''' +prev_state == SMODS.STATES.BOOSTER_OPENED or''' match_indent = true # CardArea:align_cards() @@ -132,7 +106,7 @@ match_indent = true 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" +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 == SMODS.STATES.BOOSTER_OPENED) then" match_indent = true # CardArea:align_cards() @@ -141,7 +115,7 @@ match_indent = true 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" +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 == SMODS.STATES.BOOSTER_OPENED) then" match_indent = true # Card:can_use_consumable() @@ -150,7 +124,7 @@ match_indent = true 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" +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 == SMODS.STATES.BOOSTER_OPENED then" match_indent = true # G.FUNCS.use_card() @@ -165,7 +139,7 @@ if nc then 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""" +if (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED) then""" match_indent = true # G.FUNC.use_card() @@ -185,7 +159,7 @@ 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''' +payload = '''if prev_state == SMODS.STATES.BOOSTER_OPENED and booster_obj.name:find('Arcana') then inc_career_stat('c_tarot_reading_used', 1) end''' # G.FUNC.use_card() [[patches]] @@ -194,7 +168,7 @@ 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''' +payload = '''if prev_state == SMODS.STATES.BOOSTER_OPENED and booster_obj.name:find('Celestial') then inc_career_stat('c_planetarium_used', 1) end''' # G.FUNC.use_card() [[patches]] @@ -202,7 +176,7 @@ payload = '''if prev_state == G.STATES.SMODS_BOOSTER_OPENED and booster_obj.name 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" +payload = "(G.STATE == SMODS.STATES.BOOSTER_OPENED and SMODS.STATES.BOOSTER_OPENED) or" match_indent = true # G.FUNC.use_card() @@ -211,7 +185,7 @@ match_indent = true 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" +payload = "if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED) and" match_indent = true # Card:use_consumeable() @@ -221,8 +195,8 @@ 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},''' +align = (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == SMODS.STATES.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 == SMODS.STATES.BOOSTER_OPENED) and -0.2 or 0},''' line_prepend = '$indent' # G.FUNCS.use_card() @@ -349,4 +323,4 @@ 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''' \ No newline at end of file +payload = '''G.STATE = SMODS.STATES.REDEEM_VOUCHER''' \ No newline at end of file diff --git a/lovely/fixes.toml b/lovely/fixes.toml index ab6e8e40c..da2fedfe9 100644 --- a/lovely/fixes.toml +++ b/lovely/fixes.toml @@ -78,7 +78,7 @@ 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""" + or G.STATE == G.STATES.BUFFOON_PACK or G.STATE == G.STATES.STANDARD_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED then return end""" match_indent = true ## Set `G.your_collection.config.collection` to true in all cases diff --git a/lovely/game_state.toml b/lovely/game_state.toml index ccb211c43..b9fc93ea5 100644 --- a/lovely/game_state.toml +++ b/lovely/game_state.toml @@ -16,7 +16,7 @@ pattern = ''' position = 'after' match_indent = true payload = ''' -if self.STATE and SMODS.GameStates[self.STATE] then +if SMODS.GameStates[self.STATE] then SMODS.GameStates[self.STATE]:update(dt) end ''' diff --git a/src/game_object.lua b/src/game_object.lua index 4ba5b11e3..ac17b475e 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -4550,6 +4550,8 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. ------------------------------------------------------------------------------------------------- SMODS.STATES = { + BOOSTER_OPENED = "BOOSTER_OPENED", + REDEEM_VOUCHER = "REDEEM_VOUCHER", SHOP = "SHOP", ROUND_EVAL = "ROUND_EVAL", BLIND = "BLIND", @@ -4622,6 +4624,17 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. ease_background_colour = nil, -- function } + SMODS.GameState { + key = SMODS.STATES.BOOSTER_OPENED, + update = function (self, dt) + SMODS.OPENED_BOOSTER.config.center:update_pack(dt) + end + } + + SMODS.GameState { + key = SMODS.STATES.REDEEM_VOUCHER + } + SMODS.GameState { key = SMODS.STATES.SHOP } diff --git a/src/overrides.lua b/src/overrides.lua index c6d6364ac..90fe4f2c8 100644 --- a/src/overrides.lua +++ b/src/overrides.lua @@ -2406,7 +2406,7 @@ local set_ability = Card.set_ability function Card:set_ability(center, initial, delay_sprites) local old_center = self.config.center set_ability(self, center, initial, delay_sprites) - if not initial and (G.STATE ~= G.STATES.SMODS_BOOSTER_OPENED and G.STATE ~= G.STATES.SHOP and not G.SETTINGS.paused or G.TAROT_INTERRUPT) then + if not initial and (G.STATE ~= SMODS.STATES.BOOSTER_OPENED and G.STATE ~= G.STATES.SHOP and not G.SETTINGS.paused or G.TAROT_INTERRUPT) then SMODS.calculate_context({setting_ability = true, old = old_center.key, new = self.config.center_key, other_card = self, unchanged = old_center.key == self.config.center.key}) end end diff --git a/src/utils.lua b/src/utils.lua index 76a20b3d7..4ac398151 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -1257,7 +1257,7 @@ SMODS.calculate_individual_effect = function(effect, scored_card, key, amount, f SMODS.calculate_context({ money_altered = true, amount = amount, - from_shop = (G.STATE == G.STATES.SHOP or G.STATE == G.STATES.SMODS_BOOSTER_OPENED or G.STATE == G.STATES.SMODS_REDEEM_VOUCHER) or nil, + from_shop = (G.STATE == SMODS.STATES.SHOP or G.STATE == SMODS.STATES.BOOSTER_OPENED or G.STATE == SMODS.STATES.REDEEM_VOUCHER) or nil, from_consumeable = (G.STATE == G.STATES.PLAY_TAROT) or nil, from_scoring = (G.STATE == G.STATES.HAND_PLAYED) or nil, }) @@ -2629,7 +2629,7 @@ function SMODS.update_hand_limit_text(play, discard) end function SMODS.draw_cards(hand_space) - if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) and + if not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == SMODS.STATES.BOOSTER_OPENED) and G.hand.config.card_limit <= 0 and #G.hand.cards == 0 then G.STATE = G.STATES.GAME_OVER; G.STATE_COMPLETE = false return true @@ -3041,7 +3041,7 @@ function ease_dollars(mod, instant) SMODS.calculate_context({ money_altered = true, amount = mod, - from_shop = (G.STATE == G.STATES.SHOP or G.STATE == G.STATES.SMODS_BOOSTER_OPENED or G.STATE == G.STATES.SMODS_REDEEM_VOUCHER) or nil, + from_shop = (G.STATE == SMODS.STATES.SHOP or G.STATE == SMODS.STATES.BOOSTER_OPENED or G.STATE == SMODS.STATES.REDEEM_VOUCHER) or nil, from_consumeable = (G.STATE == G.STATES.PLAY_TAROT) or nil, from_scoring = (G.STATE == G.STATES.HAND_PLAYED) or nil, }) From 99e8f90cabdcb4d54ce16ef6b9a52f236e88c426 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Sat, 1 Nov 2025 21:13:34 +0100 Subject: [PATCH 12/14] Moved a bunch of State stuff from base game / patches to `State:on_enter()` / `State:on_exit()` functions *Title --- lovely/game_state.toml | 11 +- lovely/shop.toml | 44 --- src/game_object.lua | 610 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 562 insertions(+), 103 deletions(-) diff --git a/lovely/game_state.toml b/lovely/game_state.toml index b9fc93ea5..4e667627f 100644 --- a/lovely/game_state.toml +++ b/lovely/game_state.toml @@ -4,7 +4,7 @@ dump_lua = true priority = -10 -# game:update() inject SMODS.GameStates +# game.lua : Game:update() : inject SMODS.GameStates [[patches]] [patches.pattern] target = 'game.lua' @@ -20,3 +20,12 @@ if SMODS.GameStates[self.STATE] then SMODS.GameStates[self.STATE]:update(dt) end ''' + +# functions/button_callbacks.lua : G.FUNCS.use_card() : update logic +[[patches]] +[patches.pattern] +target = 'functions/button_callbacks.lua' +pattern = '''if card.ability.set == 'Booster' and not nosave and G.STATE == G.STATES.SHOP then''' +position = 'at' +match_indent = true +payload = '''if card.ability.set == 'Booster' and not nosave and G.STATE == SMODS.STATES.SHOP then''' \ No newline at end of file diff --git a/lovely/shop.toml b/lovely/shop.toml index 8adf71876..814a1ed2f 100644 --- a/lovely/shop.toml +++ b/lovely/shop.toml @@ -16,21 +16,6 @@ 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] @@ -45,35 +30,6 @@ 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] diff --git a/src/game_object.lua b/src/game_object.lua index ac17b475e..09a83914f 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3827,6 +3827,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. if run_info then else + -- Needs to set G.blind_prompt_box ui_out = {} end return ui_out @@ -3967,6 +3968,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end + function SMODS.get_active_bt_node() + return SMODS.get_bt_node(SMODS.BLIND_TREE.active_node) + end + local start_run_ref = Game.start_run function Game:start_run(args) start_run_ref(self, args) @@ -4117,9 +4122,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. 'on_callback', }, inject = function(self) - if type(self.on_callback) ~= "function" then - sendWarnMessage(("BTNodeCallback injected with invalid function '%s'"):format(self.on_callback)) - end + end, create_ui = function (self, bt_node, bt_node_UIE) end @@ -4153,6 +4156,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end } + function SMODS.GUI.bt_callback_evaluate_round(bt_node, bt_node_UIE) + return {} + end + SMODS.BTNodeCallback { key = "enter_shop", on_callback = function (self, bt_node, cb, trigger_type) @@ -4165,6 +4172,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end } + function SMODS.GUI.bt_callback_enter_shop(bt_node, bt_node_UIE) + return {} + end + SMODS.BTNodeCallback { key = "ante_up", on_callback = function (self, bt_node, cb, trigger_type) @@ -4172,6 +4183,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.ante_end = true ease_ante(1) SMODS.ante_end = nil + -- Moved here from G.FUNCS.cash_out() -> Might have to be moved again for better timing + G.GAME.round_resets.blind_ante = G.GAME.round_resets.ante + ------ return false end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4179,6 +4193,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end } + function SMODS.GUI.bt_callback_ante_up(bt_node, bt_node_UIE) + return {} + end + SMODS.BTNodeCallback { key = "create_tag", on_callback = function (self, bt_node, cb, trigger_type) @@ -4191,6 +4209,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end } + function SMODS.GUI.bt_callback_create_tag(bt_node, bt_node_UIE) + return {} + end + SMODS.BTNodeCallback { key = "next_blind_tree", on_callback = function (self, bt_node, cb, trigger_type) @@ -4236,7 +4258,6 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_click = function (self, bt_node) SMODS.set_active_bt_node(bt_node) bt_node:trigger_callbacks("selected") - SMODS.next_state() end, } @@ -4245,7 +4266,6 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. on_click = function (self, bt_node) SMODS.set_active_bt_node(bt_node) bt_node:trigger_callbacks("skipped") - SMODS.next_state() end, } @@ -4263,7 +4283,6 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. Game:init_game_object() Game:update(dt) Game:start_run() - Blind:get_type() ]] function reset_blinds() G.GAME.round_resets.boss_rerolled = false @@ -4375,10 +4394,8 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end }) - -- Create Blind Select UI - function create_UIBox_blind_select() - return SMODS.get_blind_tree():create_ui() - end + -- Create Blind Select UI -> Not used in SMODS.STATES.BLIND_SELECT.on_enter() + function create_UIBox_blind_select() end -- Run Info Tab function G.UIDEF.current_blinds() @@ -4390,12 +4407,12 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- Cash Out - -- AND/OR: Consider separating the Round eval/cashout into its own BTNodeCallback "evaluate_round" + -- Cash Out -> Functionality is handled by custom SMODS.GameStates and BTNodeCallbacks function G.FUNCS.cash_out(e) - + SMODS.get_active_bt_node():trigger_callbacks("cashed_out") end + -- TODO : move to lovely patches function G.FUNCS.evaluate_round() total_cashout_rows = 0 local pitch = 0.95 @@ -4505,15 +4522,171 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. add_round_eval_row({name = 'bottom', dollars = dollars}) end - -- new_round() (also lovely patch maybe) + -- Needs to be patched.. function new_round() - + -- .. because of this; + -- local blhash = '' + -- if G.GAME.round_resets.blind == G.P_BLINDS.bl_small then + -- G.GAME.round_resets.blind_states.Small = 'Current' + -- G.GAME.current_boss_streak = 0 + -- blhash = 'S' + -- elseif G.GAME.round_resets.blind == G.P_BLINDS.bl_big then + -- G.GAME.round_resets.blind_states.Big = 'Current' + -- G.GAME.current_boss_streak = 0 + -- blhash = 'B' + -- else + -- G.GAME.round_resets.blind_states.Boss = 'Current' + -- blhash = 'L' + -- end + -- G.GAME.subhash = (G.GAME.round_resets.ante)..(blhash) end - -- end_round() (also lovely patch maybe) - -- Needs to create an Event to call Blind:defeat() because I removed that from G.FUNCS.evaluate_round() + -- end_round() TODO : move to lovely patches function end_round() + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.2, + func = function() + G.GAME.blind.in_blind = false + local game_over = true + local game_won = false + G.RESET_BLIND_STATES = true + G.RESET_JIGGLES = true + if G.GAME.chips - G.GAME.blind.chips >= 0 then + game_over = false + end + -- context.end_of_round calculations + SMODS.saved = false + G.GAME.saved_text = nil + SMODS.calculate_context({end_of_round = true, game_over = game_over, beat_boss = G.GAME.blind.boss }) + if SMODS.saved then game_over = false end + -- TARGET: main end_of_round evaluation + if G.GAME.round_resets.ante == G.GAME.win_ante and G.GAME.blind:is_type("Boss") then + game_won = true + G.GAME.won = true + end + if game_over then + G.STATE = G.STATES.GAME_OVER + if not G.GAME.won and not G.GAME.seeded and not G.GAME.challenge then + G.PROFILES[G.SETTINGS.profile].high_scores.current_streak.amt = 0 + end + G:save_settings() + G.FILE_HANDLER.force = true + G.STATE_COMPLETE = false + else + G.GAME.unused_discards = (G.GAME.unused_discards or 0) + G.GAME.current_round.discards_left + if G.GAME.blind and G.GAME.blind.config.blind then + discover_card(G.GAME.blind.config.blind) + end + + if G.GAME.blind:is_type("Boss") then + local _handname, _played, _order = 'High Card', -1, 100 + for k, v in pairs(G.GAME.hands) do + if v.played > _played or (v.played == _played and _order > v.order) then + _played = v.played + _handname = k + end + end + G.GAME.current_round.most_played_poker_hand = _handname + end + + if G.GAME.blind:is_type("Boss") and not G.GAME.seeded and not G.GAME.challenge then + G.GAME.current_boss_streak = G.GAME.current_boss_streak + 1 + check_and_set_high_score('boss_streak', G.GAME.current_boss_streak) + end + + if G.GAME.current_round.hands_played == 1 then + inc_career_stat('c_single_hand_round_streak', 1) + else + if not G.GAME.seeded and not G.GAME.challenge then + G.PROFILES[G.SETTINGS.profile].career_stats.c_single_hand_round_streak = 0 + G:save_settings() + end + end + + check_for_unlock({type = 'round_win'}) + set_joker_usage() + if game_won and not G.GAME.win_notified then + G.GAME.win_notified = true + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + blocking = false, + blockable = false, + func = (function() + if G.STATE == SMODS.STATES.ROUND_EVAL then + win_game() + G.GAME.won = true + return true + end + end) + })) + end + 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 + G.FUNCS.draw_from_hand_to_discard() + if G.GAME.blind:is_type("Boss") then + G.GAME.voucher_restock = nil + if G.GAME.modifiers.set_eternal_ante and (G.GAME.round_resets.ante == G.GAME.modifiers.set_eternal_ante) then + for k, v in ipairs(G.jokers.cards) do + v:set_eternal(true) + end + end + if G.GAME.modifiers.set_joker_slots_ante and (G.GAME.round_resets.ante == G.GAME.modifiers.set_joker_slots_ante) then + G.jokers.config.card_limit = 0 + end + 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}) + end + G.FUNCS.draw_from_discard_to_deck() + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.3, + func = function() + SMODS.enter_state(SMODS.STATES.ROUND_EVAL) + G.STATE_COMPLETE = false + + if G.GAME.round_resets.blind == G.P_BLINDS.bl_small then + -- TODO : Check/Replace blind_states + 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 = SMODS.get_next_vouchers() + 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 + + if G.GAME.round_resets.temp_handsize then G.hand:change_size(-G.GAME.round_resets.temp_handsize); G.GAME.round_resets.temp_handsize = nil end + if G.GAME.round_resets.temp_reroll_cost then G.GAME.round_resets.temp_reroll_cost = nil; calculate_reroll_cost(true) end + + reset_idol_card() + reset_mail_rank() + reset_ancient_card() + reset_castle_card() + 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 + for k, v in ipairs(G.playing_cards) do + v.ability.discarded = nil + v.ability.forced_selection = nil + end + return true + end + })) + end + return true + end + })) end -- get_blind_main_colour() @@ -4521,12 +4694,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- Toggle Shop - function G.FUNCS.toggle_shop(e) - - end + -- Toggle Shop -> Replaced by SMODS.GameStates.SHOP:on_exit() + function G.FUNCS.toggle_shop(e) end - -- Select Blind (-> use own func in UI defs) + -- Select Blind -> Replaced by SMODS.GameStates.BLIND:on_enter() function G.FUNCS.select_blind(e) end -- Skip Blind (-> use own func in UI defs) @@ -4572,11 +4743,12 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end - function SMODS.clear_states() - if G.blind_select then G.blind_select:remove(); G.blind_select = nil end - if G.shop then G.shop:remove(); G.shop = nil end - if G.buttons then G.buttons:remove(); G.buttons = nil end - if G.round_eval then G.round_eval:remove(); G.round_eval = nil end + function SMODS.clear_states(exempt_map) + exempt_map = exempt_map or {} + if G.blind_select and not exempt_map[SMODS.STATES.BLIND_SELECT] then G.blind_select:remove(); G.blind_select = nil end + if G.shop and not exempt_map[SMODS.STATES.SHOP] then G.shop:remove(); G.shop = nil end + if G.buttons and not exempt_map[SMODS.STATES.BLIND] then G.buttons:remove(); G.buttons = nil end + if G.round_eval and not exempt_map[SMODS.STATES.ROUND_EVAL] then G.round_eval:remove(); G.round_eval = nil end end function SMODS.enter_state(state, args, hold_state) @@ -4636,15 +4808,308 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. } SMODS.GameState { - key = SMODS.STATES.SHOP + key = SMODS.STATES.SHOP, + on_enter = function (self, args, from_hold) + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + SMODS.clear_states({[SMODS.STATES.SHOP] = true}) + stop_use() + G.STATE_COMPLETE = true + ease_background_colour_blind(G.STATES.SHOP) + local shop_exists = not not G.shop + G.shop = G.shop or UIBox{ + definition = G.UIDEF.shop(), + config = {align='tmi', offset = {x=0,y=G.ROOM.T.y+11},major = G.hand, bond = 'Weak'} + } + -- Moved here from G.FUNCS.cash_out() + G.GAME.current_round.jokers_purchased = 0 + G.GAME.shop_free = nil + G.GAME.shop_d6ed = nil + ------- + G.E_MANAGER:add_event(Event({ + func = function() + G.shop.alignment.offset.y = -5.3 + G.shop.alignment.offset.x = 0 + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.2, + blockable = false, + func = function() + if math.abs(G.shop.T.y - G.shop.VT.y) < 3 then + G.ROOM.jiggle = G.ROOM.jiggle + 3 + play_sound('cardFan2') + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'shop_start'}) + end + local nosave_shop = nil + if not shop_exists then + if G.load_shop_jokers then + nosave_shop = true + G.shop_jokers:load(G.load_shop_jokers) + for k, v in ipairs(G.shop_jokers.cards) do + create_shop_card_ui(v) + if v.ability.consumeable then v:start_materialize() end + for _kk, vvv in ipairs(G.GAME.tags) do + if vvv:apply_to_run({type = 'store_joker_modify', card = v}) then break end + end + end + G.load_shop_jokers = nil + else + for i = 1, G.GAME.shop.joker_max - #G.shop_jokers.cards do + G.shop_jokers:emplace(create_card_for_shop(G.shop_jokers)) + end + end + + if G.load_shop_vouchers then + nosave_shop = true + G.shop_vouchers:load(G.load_shop_vouchers) + for k, v in ipairs(G.shop_vouchers.cards) do + create_shop_card_ui(v) + v:start_materialize() + end + G.load_shop_vouchers = nil + else + 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 + end + + if G.load_shop_booster then + nosave_shop = true + G.shop_booster:load(G.load_shop_booster) + for k, v in ipairs(G.shop_booster.cards) do + create_shop_card_ui(v) + v:start_materialize() + end + G.load_shop_booster = nil + else + 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 {} + if not G.GAME.current_round.used_packs[i] then + G.GAME.current_round.used_packs[i] = get_pack('shop_pack').key + end + + if G.GAME.current_round.used_packs[i] ~= 'USED' then + local card = Card(G.shop_booster.T.x + G.shop_booster.T.w/2, + G.shop_booster.T.y, G.CARD_W*1.27, G.CARD_H*1.27, G.P_CARDS.empty, G.P_CENTERS[G.GAME.current_round.used_packs[i]], {bypass_discovery_center = true, bypass_discovery_ui = true}) + create_shop_card_ui(card, 'Booster', G.shop_booster) + card.ability.booster_pos = i + card:start_materialize() + G.shop_booster:emplace(card) + end + end + + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'voucher_add'}) + end + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'shop_final_pass'}) + end + end + end + + if not nosave_shop then SMODS.calculate_context({starting_shop = true}) end + G.CONTROLLER:snap_to({node = G.shop:get_UIE_by_ID('next_round_button')}) + if not nosave_shop then G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) end + return true + end + end + })) + return true + end + })) + return true + end + })) + end, + on_exit = function (self, args, from_hold) + stop_use() + G.CONTROLLER.locks.toggle_shop = true + if G.shop then + if not from_hold then + SMODS.calculate_context({ending_shop = true}) + end + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + G.shop.alignment.offset.y = G.ROOM.T.y + 29 + G.SHOP_SIGN.alignment.offset.y = -15 + return true + end + })) + if from_hold then + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.5, + func = function () + G.CONTROLLER.locks.toggle_shop = nil + return true + end + })) + return + end + G.E_MANAGER:add_event(Event({ + trigger = 'after', + delay = 0.5, + func = function() + G.shop:remove() + G.shop = nil + G.SHOP_SIGN:remove() + G.SHOP_SIGN = nil + G.STATE_COMPLETE = false + G.CONTROLLER.locks.toggle_shop = nil + return true + end + })) + end + end, } SMODS.GameState { - key = SMODS.STATES.ROUND_EVAL + key = SMODS.STATES.ROUND_EVAL, + on_enter = function (self, args, from_hold) + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + SMODS.clear_states() + stop_use() + G.STATE_COMPLETE = true + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + save_run() + ease_background_colour_blind(G.STATES.ROUND_EVAL) + G.round_eval = UIBox{ + definition = create_UIBox_round_evaluation(), + config = {align="bm", offset = {x=0,y=G.ROOM.T.y + 19},major = G.hand, bond = 'Weak'} + } + G.round_eval.alignment.offset.x = 0 + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + if G.round_eval.alignment.offset.y ~= -7.8 then + G.round_eval.alignment.offset.y = -7.8 + else + if math.abs(G.round_eval.T.y - G.round_eval.VT.y) < 3 then + G.ROOM.jiggle = G.ROOM.jiggle + 3 + play_sound('cardFan2') + delay(0.1) + G.FUNCS.evaluate_round() + return true + end + end + end})) + return true + end + })) + return true + end + })) + end, + on_exit = function (self, args, from_hold) + stop_use() + if G.round_eval then + G.round_eval.alignment.offset.y = G.ROOM.T.y + 15 + G.round_eval.alignment.offset.x = 0 + G.deck:shuffle('cashout'..G.GAME.round_resets.ante) + G.deck:hard_set_T() + delay(0.3) + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + if G.round_eval then + G.round_eval:remove() + G.round_eval = nil + end + -- G.STATE_COMPLETE = false + return true + end + })) + ease_dollars(G.GAME.current_round.dollars) + G.E_MANAGER:add_event(Event({ + func = function() + G.GAME.previous_round.dollars = G.GAME.dollars + return true + end + })) + play_sound("coin7") + G.VIBRATION = G.VIBRATION + 1 + end + ease_chips(0) + reset_blinds() + delay(0.6) + end } SMODS.GameState { key = SMODS.STATES.BLIND, + on_enter = function (self, args, from_hold) + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + SMODS.clear_states() + stop_use() + G.GAME.facing_blind = true + + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + ease_round(1) + inc_career_stat('c_rounds', 1) + if _DEMO then + G.SETTINGS.DEMO_ROUNDS = (G.SETTINGS.DEMO_ROUNDS or 0) + 1 + inc_steam_stat('demo_rounds') + G:save_settings() + end + G.GAME.round_resets.blind = G.P_BLINDS[args.key] + G.GAME.round_resets.blind_states[G.GAME.blind_on_deck] = 'Current' -- TODO : Check this / G.GAME.blind_on_deck + delay(0.2) + return true + end})) + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + new_round() + return true + end + })) + return true + end + })) + end, + on_exit = function (self, args, from_hold) + G.GAME.facing_blind = nil + if not from_hold then + -- Taken from G.FUNC.evaluate_round(), defeats blind + -- The extra nested immediate event should hopefully preserve the vanilla timing + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + G.E_MANAGER:add_event(Event({ + trigger = 'before', + delay = 1.3*math.min(G.GAME.blind.dollars+2, 7)/2*0.15 + 0.5, + func = function() + G.GAME.blind:defeat() + G.GAME.current_round.discards_left = math.max(0, G.GAME.round_resets.discards + G.GAME.round_bonus.discards) + G.GAME.current_round.hands_left = (math.max(1, G.GAME.round_resets.hands + G.GAME.round_bonus.next_hands)) + return true + end + })) + return true + end + })) + ------ + end + + end, ease_background_colour = function (self, blind_override) local blindname = ((blind_override or (G.GAME.blind and G.GAME.blind.name ~= '' and G.GAME.blind.name)) or 'Small Blind') blindname = (blindname == '' and 'Small Blind' or blindname) @@ -4652,7 +5117,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. local boss_col = G.C.BLACK for k, v in pairs(G.P_BLINDS) do if v.name == blindname then - if v.boss.showdown then + if v.boss and v.boss.showdown or v.blind_types and v.blind_types.Showdown then ease_background_colour{new_colour = G.C.BLUE, special_colour = G.C.RED, tertiary_colour = darken(G.C.BLACK, 0.4), contrast = 3} return end @@ -4665,36 +5130,65 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.GameState { key = SMODS.STATES.BLIND_SELECT, - update = function (self, dt) - SMODS.clear_states() - if not G.STATE_COMPLETE then - stop_use() - ease_background_colour_blind(G.STATES.BLIND_SELECT) - G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) - G.STATE_COMPLETE = true - G.CONTROLLER.interrupt.focus = true - G.E_MANAGER:add_event(Event({ func = function() - G.E_MANAGER:add_event(Event({ - trigger = 'immediate', - func = function() - play_sound('cancel') - G.blind_select = SMODS.get_blind_tree():create_ui() - G.blind_select.alignment.offset.y = 0.8-(G.hand.T.y - G.jokers.T.y) + G.blind_select.T.h - G.ROOM.jiggle = G.ROOM.jiggle + 3 - G.blind_select.alignment.offset.x = 0 - G.CONTROLLER.lock_input = false - for i = 1, #G.GAME.tags do - G.GAME.tags[i]:apply_to_run({type = 'immediate'}) - end - for i = 1, #G.GAME.tags do - if G.GAME.tags[i]:apply_to_run({type = 'new_blind_choice'}) then break end + on_enter = function (self, args, from_hold) + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function() + SMODS.clear_states() + stop_use() + ease_background_colour_blind(SMODS.STATES.BLIND_SELECT) + G.E_MANAGER:add_event(Event({ func = function() save_run(); return true end})) + G.CONTROLLER.interrupt.focus = true + G.E_MANAGER:add_event(Event({ func = function() + G.E_MANAGER:add_event(Event({ + trigger = 'immediate', + func = function() + play_sound('cancel') + G.blind_select = SMODS.get_blind_tree():create_ui() + G.blind_select.alignment.offset.y = 0.8-(G.hand.T.y - G.jokers.T.y) + G.blind_select.T.h + G.ROOM.jiggle = G.ROOM.jiggle + 3 + G.blind_select.alignment.offset.x = 0 + G.CONTROLLER.lock_input = false + for i = 1, #G.GAME.tags do + G.GAME.tags[i]:apply_to_run({type = 'immediate'}) + end + 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 - return true - end - })) + })) + return true + end})) return true - end})) - end + end + })) + end, + on_exit = function (self, args, from_hold) + -- TODO : Figure out what this was doing + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext1').config.object.pop_delay = 0 + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext1').config.object:pop_out(5) + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext2').config.object.pop_delay = 0 + G.blind_prompt_box:get_UIE_by_ID('prompt_dynatext2').config.object:pop_out(5) + + G.E_MANAGER:add_event(Event({ + trigger = 'before', delay = 0.2, + func = function() + G.blind_prompt_box.alignment.offset.y = -10 + G.blind_select.alignment.offset.y = 40 + G.blind_select.alignment.offset.x = 0 + return true + end})) + G.E_MANAGER:add_event(Event({ + trigger = "immediate", + func = function () + G.blind_select:remove() + G.blind_prompt_box:remove() + G.blind_select = nil + return true + end + })) + end } From 897b45dfbd2eaabdfb4cd7dbd611f48eb2effd1f Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Sat, 1 Nov 2025 21:49:41 +0100 Subject: [PATCH 13/14] Added new `enter_blind_tree` callback +Title *also fixed manually entered states triggering callbacks by default. --- src/game_object.lua | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index 09a83914f..648235533 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3864,6 +3864,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. {key = "enter_blind", triggers = {selected = true}}, {key = "evaluate_round", triggers = {defeated = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, + {key = "enter_blind_tree", triggers = {shop_ended = true}}, {key = "create_tag", triggers = {skipped = true}}, }, blinds = {"bl_small"}, @@ -3876,6 +3877,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. {key = "enter_blind", triggers = {selected = true}}, {key = "evaluate_round", triggers = {defeated = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, + {key = "enter_blind_tree", triggers = {shop_ended = true}}, {key = "create_tag", triggers = {skipped = true}}, }, blinds = {"bl_big"}, @@ -3888,6 +3890,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. {key = "enter_blind", triggers = {selected = true}}, {key = "ante_up", triggers = {defeated = true}}, {key = "evaluate_round", triggers = {defeated = true}}, + {key = "enter_blind_tree", triggers = {shop_ended = true}}, {key = "next_blind_tree", triggers = {cashed_out = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, }, @@ -4132,7 +4135,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "enter_blind", on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to bt_node:get_blind() - SMODS.enter_state(SMODS.STATES.BLIND, {key = bt_node:get_blind()}) + SMODS.enter_state(SMODS.STATES.BLIND, {key = bt_node:get_blind(), trigger_callbacks = true}) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4148,7 +4151,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "evaluate_round", on_callback = function (self, bt_node, cb, trigger_type) -- Create cashout and evaluate round - SMODS.enter_state(SMODS.STATES.ROUND_EVAL) + SMODS.enter_state(SMODS.STATES.ROUND_EVAL, {trigger_callbacks = true}) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4164,7 +4167,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "enter_shop", on_callback = function (self, bt_node, cb, trigger_type) -- Change game state to shop - SMODS.enter_state(SMODS.STATES.SHOP) + SMODS.enter_state(SMODS.STATES.SHOP, {trigger_callbacks = true}) return true end, create_ui = function (self, bt_node, bt_node_UIE) @@ -4175,6 +4178,18 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. function SMODS.GUI.bt_callback_enter_shop(bt_node, bt_node_UIE) return {} end + + SMODS.BTNodeCallback { + key = "enter_blind_tree", + on_callback = function (self, bt_node, cb, trigger_type) + -- Change game state to blind select + SMODS.enter_state(SMODS.STATES.BLIND_SELECT) + return true + end, + create_ui = function (self, bt_node, bt_node_UIE) + return nil + end + } SMODS.BTNodeCallback { key = "ante_up", @@ -4409,7 +4424,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. -- Cash Out -> Functionality is handled by custom SMODS.GameStates and BTNodeCallbacks function G.FUNCS.cash_out(e) - SMODS.get_active_bt_node():trigger_callbacks("cashed_out") + if SMODS.get_current_state().args.trigger_callbacks then + SMODS.get_active_bt_node():trigger_callbacks("cashed_out") + end end -- TODO : move to lovely patches @@ -4648,7 +4665,9 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. trigger = 'after', delay = 0.3, func = function() - SMODS.enter_state(SMODS.STATES.ROUND_EVAL) + if SMODS.get_current_state().args.trigger_callbacks then + SMODS.get_active_bt_node():trigger_callbacks("defeated") + end G.STATE_COMPLETE = false if G.GAME.round_resets.blind == G.P_BLINDS.bl_small then @@ -4694,8 +4713,12 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- Toggle Shop -> Replaced by SMODS.GameStates.SHOP:on_exit() - function G.FUNCS.toggle_shop(e) end + -- Toggle Shop -> Functionality is handled by custom SMODS.GameStates and BTNodeCallbacks + function G.FUNCS.toggle_shop(e) + if SMODS.get_current_state().args.trigger_callbacks then + SMODS.get_active_bt_node():trigger_callbacks("shop_ended") + end + end -- Select Blind -> Replaced by SMODS.GameStates.BLIND:on_enter() function G.FUNCS.select_blind(e) end @@ -4732,6 +4755,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. SMODS.state_stack = {} + function SMODS.get_current_state() + return #SMODS.state_stack > 0 and SMODS.state_stack[#SMODS.state_stack] + end + function SMODS.push_to_state_stack(state, args) table.insert(SMODS.state_stack, {state=state, args=args}) end From 06d18c38fa49dc0d1d39ef7972a33eed41507f75 Mon Sep 17 00:00:00 2001 From: AllUniversal Date: Tue, 25 Nov 2025 22:40:34 +0100 Subject: [PATCH 14/14] =?UTF-8?q?Rebranded=20to=20AntePath=E2=84=A2?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *Title, as discussed in the Discord. This name should fit a little better. --- lovely/blind.toml | 9 +- src/game_object.lua | 300 +++++++++++++++++++++++--------------------- src/ui.lua | 15 +-- 3 files changed, 172 insertions(+), 152 deletions(-) diff --git a/lovely/blind.toml b/lovely/blind.toml index 0eb345b4c..ef0b2b213 100644 --- a/lovely/blind.toml +++ b/lovely/blind.toml @@ -526,4 +526,11 @@ if G.GAME.modifiers.no_blind_reward and (G.GAME.blind and blind:is_types(G.GAME. target = "functions/state_events.lua" pattern = '''G\.GAME\.blind:get_type\(\) == 'Boss' ''' position = "at" -payload = '''G.GAME.blind:is_type("Boss") ''' \ No newline at end of file +payload = '''G.GAME.blind:is_type("Boss") ''' +# 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''' \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index b5ee10b8f..6ea225d6d 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3824,7 +3824,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. ------------------------------------------------------------------------------------------------- - ------- API CODE SMODS.BlindTree AND RELATED + ------- API CODE SMODS.AntePath AND RELATED ------------------------------------------------------------------------------------------------- ------- UI DEFS @@ -3834,16 +3834,20 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. else -- Needs to set G.blind_prompt_box - ui_out = {} + ui_out = { + n=G.UIT.ROOT, + config={}, + nodes={} + } end return ui_out end - ------- API CODE SMODS.BlindTree - SMODS.BlindTrees = {} - SMODS.BlindTree = SMODS.GameObject:extend { - set = 'BlindTree', - obj_table = SMODS.BlindTrees, + ------- API CODE SMODS.AntePath + SMODS.AntePaths = {} + SMODS.AntePath = SMODS.GameObject:extend { + set = 'AntePath', + obj_table = SMODS.AntePaths, obj_buffer = {}, required_parameters = { 'key', @@ -3859,45 +3863,45 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. key = "vanilla", active_node = 1, nodes = { - SMODS.BTNode { + SMODS.APNode { index = 1, hidden = true, selected = true, }, - SMODS.BTNode { + SMODS.APNode { index = 2, callbacks = { {key = "enter_blind", triggers = {selected = true}}, {key = "evaluate_round", triggers = {defeated = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, - {key = "enter_blind_tree", triggers = {shop_ended = true}}, + {key = "enter_blind_select", triggers = {shop_ended = true}}, {key = "create_tag", triggers = {skipped = true}}, }, blinds = {"bl_small"}, tags = {get_next_tag_key()}, next_nodes_indices = {[3] = true} }, - SMODS.BTNode { + SMODS.APNode { index = 3, callbacks = { {key = "enter_blind", triggers = {selected = true}}, {key = "evaluate_round", triggers = {defeated = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, - {key = "enter_blind_tree", triggers = {shop_ended = true}}, + {key = "enter_blind_select", triggers = {shop_ended = true}}, {key = "create_tag", triggers = {skipped = true}}, }, blinds = {"bl_big"}, tags = {get_next_tag_key()}, next_nodes_indices = {[4] = true} }, - SMODS.BTNode { + SMODS.APNode { index = 4, callbacks = { {key = "enter_blind", triggers = {selected = true}}, {key = "ante_up", triggers = {defeated = true}}, {key = "evaluate_round", triggers = {defeated = true}}, - {key = "enter_blind_tree", triggers = {shop_ended = true}}, - {key = "next_blind_tree", triggers = {cashed_out = true}}, + {key = "enter_blind_select", triggers = {shop_ended = true}}, + {key = "next_ante_path", triggers = {cashed_out = true}}, {key = "enter_shop", triggers = {cashed_out = true}}, }, blinds = {SMODS.get_new_blind({[boss_type] = true})}, @@ -3908,103 +3912,109 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end, create_ui = function (self) -- Vanilla Blind select UI - local canvas_container = SMODS.CanvasContainer { - definition = SMODS.create_CanvasContainer_BlindSelect(), - config = {align="cm", offset = {x=0,y=G.ROOM.T.y + 29}, major = G.ROOM_ATTACH, bond = 'Weak'} + local canvas_container = UIBox { + definition = {n=G.UIT.ROOT, colour = G.C.CLEAR, padding = 0.2}, + config = {align="cm", offset = {x=0,y=G.ROOM.T.y + 29}, major = G.ROOM_ATTACH, bond = 'Weak'}, + nodes = { + SMODS.create_CanvasContainer_BlindSelect() + } } return canvas_container end, create_run_info_ui = function (self) -- Vanilla Run Info Blind Tab UI - local canvas_container = SMODS.CanvasContainer { - definition = SMODS.create_CanvasContainer_BlindSelect(true), - config = {align="cm", offset = {x=0,y=0}, major = G.ROOM_ATTACH, bond = 'Weak'} + local canvas_container = UIBox { + definition = {n=G.UIT.ROOT, colour = G.C.CLEAR, padding = 0.2}, + config = {align="cm", offset = {x=0,y=G.ROOM.T.y}, major = G.ROOM_ATTACH, bond = 'Weak'}, + nodes = { + SMODS.create_CanvasContainer_BlindSelect(true) + } } return canvas_container end, - create_callback_ui = function (self, cb_key, bt_node, bt_node_UIE) - if not cb_key or not SMODS.BTNodeCallbacks[cb_key] then return end - return SMODS.BTNodeCallbacks[cb_key]:create_ui(bt_node, bt_node_UIE) + create_callback_ui = function (self, cb_key, ap_node, ap_node_UIE) + if not cb_key or not SMODS.APNodeCallbacks[cb_key] then return end + return SMODS.APNodeCallbacks[cb_key]:create_ui(ap_node, ap_node_UIE) end } - SMODS.BlindTree { + SMODS.AntePath { key = "vanilla", } - function SMODS.get_blind_tree() - return SMODS.BLIND_TREE and SMODS.BlindTrees[SMODS.BLIND_TREE.key] or SMODS.BlindTrees["vanilla"] + function SMODS.get_ante_path() + return SMODS.ANTE_PATH and SMODS.AntePaths[SMODS.ANTE_PATH.key] or SMODS.AntePaths["vanilla"] end - function SMODS.get_bt_node(index) - return SMODS.BLIND_TREE and SMODS.BLIND_TREE.nodes and SMODS.BLIND_TREE.nodes[index] + function SMODS.get_ap_node(index) + return SMODS.ANTE_PATH and SMODS.ANTE_PATH.nodes and SMODS.ANTE_PATH.nodes[index] end - function SMODS.save_blind_tree() - if not SMODS.BLIND_TREE then return {} end + function SMODS.save_ante_path() + if not SMODS.ANTE_PATH then return {} end local nodes_table = {} - for _, bt_node in ipairs(SMODS.BLIND_TREE.nodes) do - table.insert(nodes_table, bt_node:save()) + for _, ap_node in ipairs(SMODS.ANTE_PATH.nodes) do + table.insert(nodes_table, ap_node:save()) end - local blind_tree_table = { - key = SMODS.BLIND_TREE.key, - active_node = SMODS.BLIND_TREE.active_node, + local ante_path_table = { + key = SMODS.ANTE_PATH.key, + active_node = SMODS.ANTE_PATH.active_node, nodes = nodes_table, } - return blind_tree_table + return ante_path_table end - function SMODS.load_blind_tree(blind_tree_table) - for i, bt_node_table in ipairs(blind_tree_table.nodes) do - blind_tree_table.nodes[i] = SMODS.BTNode{node_table = bt_node_table} + function SMODS.load_ante_path(ante_path_table) + for i, ap_node_table in ipairs(ante_path_table.nodes) do + ante_path_table.nodes[i] = SMODS.APNode{node_table = ap_node_table} end - SMODS.set_blind_tree(blind_tree_table) + SMODS.set_ante_path(ante_path_table) end - function SMODS.next_blind_tree() - SMODS.set_blind_tree(SMODS.get_blind_tree():create_data()) + function SMODS.next_ante_path() + SMODS.set_ante_path(SMODS.get_ante_path():create_data()) end - function SMODS.set_blind_tree(data) - SMODS.BLIND_TREE = data + function SMODS.set_ante_path(data) + SMODS.ANTE_PATH = data end - function SMODS.set_active_bt_node(bt_node) - if type(bt_node) == "number" then - SMODS.BLIND_TREE.active_node = bt_node + function SMODS.set_active_ap_node(ap_node) + if type(ap_node) == "number" then + SMODS.ANTE_PATH.active_node = ap_node else - SMODS.BLIND_TREE.active_node = bt_node.index + SMODS.ANTE_PATH.active_node = ap_node.index end end - function SMODS.get_active_bt_node() - return SMODS.get_bt_node(SMODS.BLIND_TREE.active_node) + function SMODS.get_active_ap_node() + return SMODS.get_ap_node(SMODS.ANTE_PATH.active_node) end local start_run_ref = Game.start_run function Game:start_run(args) start_run_ref(self, args) local saveTable = args.savetext - if saveTable and saveTable.BLIND_TREE then - SMODS.load_blind_tree(saveTable.BLIND_TREE) + if saveTable and saveTable.ANTE_PATH then + SMODS.load_ante_path(saveTable.ANTE_PATH) end end local save_run_ref = save_run function save_run() save_run_ref() - G.ARGS.save_run.BLIND_TREE = recursive_table_cull(SMODS.save_blind_tree()) + G.ARGS.save_run.ANTE_PATH = recursive_table_cull(SMODS.save_ante_path()) end local delete_run_ref = Game.delete_run function Game:delete_run() delete_run_ref(self) - SMODS.BLIND_TREE = nil + SMODS.ANTE_PATH = nil end - ------- API CODE Object.BTNode - SMODS.BTNode = Object:extend() - function SMODS.BTNode:init(...) + ------- API CODE Object.APNode + SMODS.APNode = Object:extend() + function SMODS.APNode:init(...) local args = {...} if args.node_table then @@ -4023,7 +4033,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. self:set_next_nodes(args.next_nodes_indices or {}) end - function SMODS.BTNode:set_callbacks(cbs) + function SMODS.APNode:set_callbacks(cbs) self.callbacks = {} for _, cb in ipairs(cbs) do table.insert(self.callbacks, { @@ -4035,7 +4045,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end - function SMODS.BTNode:set_blinds(blinds) + function SMODS.APNode:set_blinds(blinds) self.blinds = {} for _, blind_key in ipairs(blinds) do table.insert(self.blinds, { @@ -4045,7 +4055,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end - function SMODS.BTNode:set_tags(tags) + function SMODS.APNode:set_tags(tags) self.tags = {} for _, tag_key in ipairs(tags) do table.insert(self.tags, { @@ -4055,11 +4065,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end - function SMODS.BTNode:trigger_callbacks(trigger_type) + function SMODS.APNode:trigger_callbacks(trigger_type) if not trigger_type then return end local hold = false for _, cb in ipairs(self.callbacks) do - local callback = SMODS.BTNodeCallbacks[cb.key] + local callback = SMODS.APNodeCallbacks[cb.key] if not cb.called and (not hold or cb.ignore_hold) and cb.triggers[trigger_type] then cb.called = true hold = hold or callback:on_callback(self, cb, trigger_type) @@ -4067,7 +4077,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end - function SMODS.BTNode:get_blind(keep) + function SMODS.APNode:get_blind(keep) local blind_tuple for _, tup in ipairs(self.blinds) do if not tup.used then @@ -4080,7 +4090,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return blind end - function SMODS.BTNode:get_tag(keep) + function SMODS.APNode:get_tag(keep) local tag_tuple for _, tup in ipairs(self.tags) do if not tup.used then @@ -4093,11 +4103,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return tag end - function SMODS.BTNode:set_next_nodes(nni) + function SMODS.APNode:set_next_nodes(nni) self.next_nodes_indices = nni or {} end - function SMODS.BTNode:save() + function SMODS.APNode:save() local node_table = { index = self.index, hidden = self.hidden, @@ -4110,7 +4120,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return node_table end - function SMODS.BTNode:load(node_table) + function SMODS.APNode:load(node_table) self.index = node_table.index self.hidden = node_table.hidden self.callbacks = node_table.callbacks or {} @@ -4120,11 +4130,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. self.next_nodes_indices = node_table.next_nodes_indices or {} end - ------- API CODE GameObject.BTNodeCallback - SMODS.BTNodeCallbacks = {} - SMODS.BTNodeCallback = SMODS.GameObject:extend { - set = 'BTNodeCallback', - obj_table = SMODS.BTNodeCallbacks, + ------- API CODE GameObject.APNodeCallback + SMODS.APNodeCallbacks = {} + SMODS.APNodeCallback = SMODS.GameObject:extend { + set = 'APNodeCallback', + obj_table = SMODS.APNodeCallbacks, obj_buffer = {}, required_parameters = { 'key', @@ -4133,73 +4143,73 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. inject = function(self) end, - create_ui = function (self, bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) end } - SMODS.BTNodeCallback { + SMODS.APNodeCallback { key = "enter_blind", - on_callback = function (self, bt_node, cb, trigger_type) - -- Change game state to bt_node:get_blind() - SMODS.enter_state(SMODS.STATES.BLIND, {key = bt_node:get_blind(), trigger_callbacks = true}) + on_callback = function (self, ap_node, cb, trigger_type) + -- Change game state to ap_node:get_blind() + SMODS.enter_state(SMODS.STATES.BLIND, {key = ap_node:get_blind(), trigger_callbacks = true}) return true end, - create_ui = function (self, bt_node, bt_node_UIE) - return SMODS.GUI.bt_callback_enter_blind(bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) + return SMODS.GUI.bt_callback_enter_blind(ap_node, ap_node_UIE) end } - function SMODS.GUI.bt_callback_enter_blind(bt_node, bt_node_UIE) + function SMODS.GUI.bt_callback_enter_blind(ap_node, ap_node_UIE) return {} end - SMODS.BTNodeCallback { + SMODS.APNodeCallback { key = "evaluate_round", - on_callback = function (self, bt_node, cb, trigger_type) + on_callback = function (self, ap_node, cb, trigger_type) -- Create cashout and evaluate round SMODS.enter_state(SMODS.STATES.ROUND_EVAL, {trigger_callbacks = true}) return true end, - create_ui = function (self, bt_node, bt_node_UIE) - return SMODS.GUI.bt_callback_evaluate_round(bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) + return SMODS.GUI.bt_callback_evaluate_round(ap_node, ap_node_UIE) end } - function SMODS.GUI.bt_callback_evaluate_round(bt_node, bt_node_UIE) + function SMODS.GUI.bt_callback_evaluate_round(ap_node, ap_node_UIE) return {} end - SMODS.BTNodeCallback { + SMODS.APNodeCallback { key = "enter_shop", - on_callback = function (self, bt_node, cb, trigger_type) + on_callback = function (self, ap_node, cb, trigger_type) -- Change game state to shop SMODS.enter_state(SMODS.STATES.SHOP, {trigger_callbacks = true}) return true end, - create_ui = function (self, bt_node, bt_node_UIE) - return SMODS.GUI.bt_callback_enter_shop(bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) + return SMODS.GUI.bt_callback_enter_shop(ap_node, ap_node_UIE) end } - function SMODS.GUI.bt_callback_enter_shop(bt_node, bt_node_UIE) + function SMODS.GUI.bt_callback_enter_shop(ap_node, ap_node_UIE) return {} end - SMODS.BTNodeCallback { - key = "enter_blind_tree", - on_callback = function (self, bt_node, cb, trigger_type) + SMODS.APNodeCallback { + key = "enter_blind_select", + on_callback = function (self, ap_node, cb, trigger_type) -- Change game state to blind select SMODS.enter_state(SMODS.STATES.BLIND_SELECT) return true end, - create_ui = function (self, bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) return nil end } - SMODS.BTNodeCallback { + SMODS.APNodeCallback { key = "ante_up", - on_callback = function (self, bt_node, cb, trigger_type) + on_callback = function (self, ap_node, cb, trigger_type) -- Ante up SMODS.ante_end = true ease_ante(1) @@ -4209,53 +4219,53 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. ------ return false end, - create_ui = function (self, bt_node, bt_node_UIE) - return SMODS.GUI.bt_callback_ante_up(bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) + return SMODS.GUI.bt_callback_ante_up(ap_node, ap_node_UIE) end } - function SMODS.GUI.bt_callback_ante_up(bt_node, bt_node_UIE) + function SMODS.GUI.bt_callback_ante_up(ap_node, ap_node_UIE) return {} end - SMODS.BTNodeCallback { + SMODS.APNodeCallback { key = "create_tag", - on_callback = function (self, bt_node, cb, trigger_type) - -- Create tag(s) from bt_node:get_tag() - add_tag(bt_node:get_tag()) + on_callback = function (self, ap_node, cb, trigger_type) + -- Create tag(s) from ap_node:get_tag() + add_tag(ap_node:get_tag()) return false end, - create_ui = function (self, bt_node, bt_node_UIE) - return SMODS.GUI.bt_callback_create_tag(bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) + return SMODS.GUI.bt_callback_create_tag(ap_node, ap_node_UIE) end } - function SMODS.GUI.bt_callback_create_tag(bt_node, bt_node_UIE) + function SMODS.GUI.bt_callback_create_tag(ap_node, ap_node_UIE) return {} end - SMODS.BTNodeCallback { - key = "next_blind_tree", - on_callback = function (self, bt_node, cb, trigger_type) + SMODS.APNodeCallback { + key = "next_ante_path", + on_callback = function (self, ap_node, cb, trigger_type) G.E_MANAGER:add_event(Event{ trigger = "immediate", func = function () - SMODS.next_blind_tree() + SMODS.next_ante_path() return true end }) return false end, - create_ui = function (self, bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) return nil end } - ------- API CODE GameObject.BTNodeButton - SMODS.BTNodeButtons = {} - SMODS.BTNodeButton = SMODS.GameObject:extend { - set = 'BTNodeButton', - obj_table = SMODS.BTNodeButtons, + ------- API CODE GameObject.APNodeButton + SMODS.APNodeButtons = {} + SMODS.APNodeButton = SMODS.GameObject:extend { + set = 'APNodeButton', + obj_table = SMODS.APNodeButtons, obj_buffer = {}, required_parameters = { 'key', @@ -4263,36 +4273,36 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. }, inject = function(self) if type(self.on_click) ~= "function" then - sendWarnMessage(("BTNodeButton injected with invalid function '%s'"):format(self.on_click)) + sendWarnMessage(("APNodeButton injected with invalid function '%s'"):format(self.on_click)) end end, - create_ui = function (self, bt_node, bt_node_UIE) + create_ui = function (self, ap_node, ap_node_UIE) end, - on_click = function (self, bt_node) + on_click = function (self, ap_node) end } - SMODS.BTNodeButton { + SMODS.APNodeButton { key = "select", - on_click = function (self, bt_node) - SMODS.set_active_bt_node(bt_node) - bt_node:trigger_callbacks("selected") + on_click = function (self, ap_node) + SMODS.set_active_ap_node(ap_node) + ap_node:trigger_callbacks("selected") end, } - SMODS.BTNodeButton { + SMODS.APNodeButton { key = "skip", - on_click = function (self, bt_node) - SMODS.set_active_bt_node(bt_node) - bt_node:trigger_callbacks("skipped") + on_click = function (self, ap_node) + SMODS.set_active_ap_node(ap_node) + ap_node:trigger_callbacks("skipped") end, } - SMODS.BTNodeButton { + SMODS.APNodeButton { key = "reroll", - on_click = function (self, bt_node) + on_click = function (self, ap_node) -- Reroll Blind end, } @@ -4301,9 +4311,15 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. --[[ - Game:init_game_object() - Game:update(dt) - Game:start_run() + blind_states and loc_blind_states + + Game:update(dt) -> This; + if G.prev_small_state ~= G.GAME.round_resets.blind_states.Small or + G.prev_large_state ~= G.GAME.round_resets.blind_states.Big or + G.prev_boss_state ~= G.GAME.round_resets.blind_states.Boss or G.RESET_BLIND_STATES then ... + can probably be ignored and replaced with own system + + Game:start_run() -> get_next_tag_key() is called, besides that; blind_states is set ]] function reset_blinds() G.GAME.round_resets.boss_rerolled = false @@ -4329,6 +4345,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end end + function get_new_boss() + sendWarnMessage("get_new_boss() is deprecated; Call SMODS.get_new_blind() instead.", "utils") + end + function SMODS.get_new_blind(blind_types) if not blind_types or (blind_types.Boss or blind_types.Showdown) then G.GAME.perscribed_bosses = G.GAME.perscribed_bosses or {} @@ -4420,7 +4440,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. -- Run Info Tab function G.UIDEF.current_blinds() - return SMODS.get_blind_tree():create_run_info_ui() + return SMODS.get_ante_path():create_run_info_ui() end -- Blind Choice Handler @@ -4428,10 +4448,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- Cash Out -> Functionality is handled by custom SMODS.GameStates and BTNodeCallbacks + -- Cash Out -> Functionality is handled by custom SMODS.GameStates and APNodeCallbacks function G.FUNCS.cash_out(e) if SMODS.get_current_state().args.trigger_callbacks then - SMODS.get_active_bt_node():trigger_callbacks("cashed_out") + SMODS.get_active_ap_node():trigger_callbacks("cashed_out") end end @@ -4672,7 +4692,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. delay = 0.3, func = function() if SMODS.get_current_state().args.trigger_callbacks then - SMODS.get_active_bt_node():trigger_callbacks("defeated") + SMODS.get_active_ap_node():trigger_callbacks("defeated") end G.STATE_COMPLETE = false @@ -4719,10 +4739,10 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. end - -- Toggle Shop -> Functionality is handled by custom SMODS.GameStates and BTNodeCallbacks + -- Toggle Shop -> Functionality is handled by custom SMODS.GameStates and APNodeCallbacks function G.FUNCS.toggle_shop(e) if SMODS.get_current_state().args.trigger_callbacks then - SMODS.get_active_bt_node():trigger_callbacks("shop_ended") + SMODS.get_active_ap_node():trigger_callbacks("shop_ended") end end @@ -5177,7 +5197,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. trigger = 'immediate', func = function() play_sound('cancel') - G.blind_select = SMODS.get_blind_tree():create_ui() + G.blind_select = SMODS.get_ante_path():create_ui() G.blind_select.alignment.offset.y = 0.8-(G.hand.T.y - G.jokers.T.y) + G.blind_select.T.h G.ROOM.jiggle = G.ROOM.jiggle + 3 G.blind_select.alignment.offset.x = 0 diff --git a/src/ui.lua b/src/ui.lua index 71882a25a..71fbbd94f 100644 --- a/src/ui.lua +++ b/src/ui.lua @@ -2204,20 +2204,13 @@ end SMODS.CanvasContainer = UIBox:extend() -local function inverted_collides_with_point(self, point) - return not self.parent:collides_with_point(point) +local function clipped_collides_with_point(self, point) + return self.clipper:collides_with_point(point) and UIElement.collides_with_point(self, point) end function SMODS.CanvasContainer:init(args) UIBox.init(self, args) self.canvas = love.graphics.newCanvas(self.T.w, self.T.h, {type = '2d'}) self.canvas:setFilter('linear', 'linear') - self.culler = Node() - self.culler.parent = self - self.culler.states.visible = false - self.culler.states.collide.can = true - self.culler.states.focus.can = true - self.culler.states.drag.can = false - self.culler.collides_with_point = inverted_collides_with_point end function SMODS.CanvasContainer:draw() @@ -2243,8 +2236,6 @@ function SMODS.CanvasContainer:draw() end end - add_to_drawhash(self.culler) - love.graphics.setCanvas(G.CANVAS) self:draw_canvas() @@ -2291,6 +2282,8 @@ function SMODS.CanvasContainer:set_parent_child(node, parent) self.UIRoot = UIE self.UIRoot.parent = self else + UIE.clipper = self.UIRoot + UIE.collides_with_point = clipped_collides_with_point table.insert(parent.children, UIE) end if node.config and node.config.mid then