Handy = setmetatable({ last_clicked_area = nil, last_clicked_card = nil, utils = {}, }, {}) --- @generic T --- @generic S --- @param target T --- @param source S --- @param ... any --- @return T | S function Handy.utils.table_merge(target, source, ...) assert(type(target) == "table", "Target is not a table") local tables_to_merge = { source, ... } if #tables_to_merge == 0 then return target end for k, t in ipairs(tables_to_merge) do assert(type(t) == "table", string.format("Expected a table as parameter %d", k)) end for i = 1, #tables_to_merge do local from = tables_to_merge[i] for k, v in pairs(from) do if type(k) == "number" then table.insert(target, v) elseif type(k) == "string" then if type(v) == "table" then target[k] = target[k] or {} target[k] = Handy.utils.table_merge(target[k], v) else target[k] = v end end end end return target end function Handy.utils.table_contains(t, value) for i = #t, 1, -1 do if t[i] and t[i] == value then return true end end return false end -- Handy.config = { default = { notifications_level = 3, insta_highlight = { enabled = true, }, insta_buy_or_sell = { enabled = true, key_1 = "Shift", key_2 = nil, }, insta_use = { enabled = true, key_1 = "Ctrl", key_2 = nil, }, move_highlight = { enabled = true, swap = { enabled = true, key_1 = "Shift", key_2 = nil, }, to_end = { enabled = true, key_1 = "Ctrl", key_2 = nil, }, dx = { one_left = { enabled = true, key_1 = "Left", key_2 = nil, }, one_right = { enabled = true, key_1 = "Right", key_2 = nil, }, }, }, insta_cash_out = { enabled = true, key_1 = "Enter", key_2 = nil, }, insta_booster_skip = { enabled = true, key_1 = "Enter", key_2 = nil, }, dangerous_actions = { enabled = false, immediate_buy_and_sell = { enabled = true, key_1 = "Middle Mouse", key_2 = nil, queue = { enabled = false, }, }, nopeus_unsafe = { enabled = true, }, }, speed_multiplier = { enabled = true, key_1 = "Alt", key_2 = nil, }, shop_reroll = { enabled = true, key_1 = "Q", key_2 = nil, }, play_and_discard = { enabled = true, play = { enabled = true, key_1 = nil, key_2 = nil, }, discard = { enabled = true, key_1 = nil, key_2 = nil, }, }, nopeus_interaction = { enabled = true, key_1 = "]", key_2 = nil, }, not_just_yet_interaction = { enabled = true, key_1 = "Enter", key_2 = nil, }, }, current = {}, save = function() if Handy.current_mod then Handy.current_mod.config = Handy.config.current SMODS.save_mod_config(Handy.current_mod) end end, } Handy.config.current = Handy.utils.table_merge({}, Handy.config.default) -- Handy.fake_events = { check = function(arg) local fake_event = { UIBox = arg.UIBox, config = { ref_table = arg.card, button = arg.button, id = arg.id, }, } arg.func(fake_event) return fake_event.config.button ~= nil, fake_event.config.button end, execute = function(arg) if type(arg.func) == "function" then arg.func({ UIBox = arg.UIBox, config = { ref_table = arg.card, button = arg.button, id = arg.id, }, }) end end, } Handy.controller = { bind_module = nil, bind_key = nil, bind_button = nil, update_bind_button_text = function(text) local button_text = Handy.controller.bind_button.children[1].children[1] button_text.config.text_drawable = nil button_text.config.text = text button_text:update_text() button_text.UIBox:recalculate() end, init_bind = function(button) button.config.button = nil Handy.controller.bind_button = button Handy.controller.bind_module = button.config.ref_table.module Handy.controller.bind_key = button.config.ref_table.key Handy.controller.update_bind_button_text( "[" .. (Handy.controller.bind_module[Handy.controller.bind_key] or "None") .. "]" ) end, complete_bind = function(key) Handy.controller.bind_module[Handy.controller.bind_key] = key Handy.controller.update_bind_button_text(key or "None") Handy.controller.bind_button.config.button = "handy_init_keybind_change" Handy.controller.bind_button = nil Handy.controller.bind_module = nil Handy.controller.bind_key = nil end, cancel_bind = function() Handy.controller.update_bind_button_text(Handy.controller.bind_module[Handy.controller.bind_key] or "None") Handy.controller.bind_button.config.button = "handy_init_keybind_change" Handy.controller.bind_button = nil Handy.controller.bind_module = nil Handy.controller.bind_key = nil end, process_bind = function(key) if not Handy.controller.bind_button then return false end local parsed_key = Handy.controller.parse(key) if parsed_key == "Escape" then parsed_key = nil end Handy.controller.complete_bind(parsed_key) Handy.config.save() return true end, parse_table = { ["mouse1"] = "Left Mouse", ["mouse2"] = "Right Mouse", ["mouse3"] = "Middle Mouse", ["mouse4"] = "Mouse 4", ["mouse5"] = "Mouse 5", ["wheelup"] = "Wheel Up", ["wheeldown"] = "Wheel Down", ["lshift"] = "Shift", ["rshift"] = "Shift", ["lctrl"] = "Ctrl", ["rctrl"] = "Ctrl", ["lalt"] = "Alt", ["ralt"] = "Alt", ["lgui"] = "GUI", ["rgui"] = "GUI", ["return"] = "Enter", ["kpenter"] = "Enter", ["pageup"] = "Page Up", ["pagedown"] = "Page Down", ["numlock"] = "Num Lock", ["capslock"] = "Caps Lock", ["scrolllock"] = "Scroll Lock", }, resolve_table = { ["Left Mouse"] = { "mouse1" }, ["Right Mouse"] = { "mouse2" }, ["Middle Mouse"] = { "mouse3" }, ["Mouse 4"] = { "mouse4" }, ["Mouse 5"] = { "mouse5" }, ["Wheel Up"] = { "wheelup" }, ["Wheel Down"] = { "wheeldown" }, ["Shift"] = { "lshift", "rshift" }, ["Ctrl"] = { "lctrl", "rctrl" }, ["Alt"] = { "lalt", "ralt" }, ["GUI"] = { "lgui", "rgui" }, ["Enter"] = { "return", "kpenter" }, ["Page Up"] = { "pageup" }, ["Page Down"] = { "pagedown" }, ["Num Lock"] = { "numlock" }, ["Caps Lock"] = { "capslock" }, ["Scroll Lock"] = { "scrolllock" }, }, mouse_to_key_table = { [1] = "mouse1", [2] = "mouse2", [3] = "mouse3", [4] = "mouse4", [5] = "mouse5", }, wheel_to_key_table = { [1] = "wheelup", [2] = "wheeldown", }, mouse_buttons = { ["Left Mouse"] = 1, ["Right Mouse"] = 2, ["Middle Mouse"] = 3, ["Mouse 4"] = 4, ["Mouse 5"] = 5, }, wheel_buttons = { ["Wheel Up"] = 1, ["Wheel Down"] = 2, }, parse = function(raw_key) if not raw_key then return nil end if Handy.controller.parse_table[raw_key] then return Handy.controller.parse_table[raw_key] elseif string.sub(raw_key, 1, 2) == "kp" then return "NUM " .. string.sub(raw_key, 3) else return string.upper(string.sub(raw_key, 1, 1)) .. string.sub(raw_key, 2) end end, resolve = function(parsed_key) if not parsed_key then return nil end if Handy.controller.resolve_table[parsed_key] then return unpack(Handy.controller.resolve_table[parsed_key]) elseif string.sub(parsed_key, 1, 4) == "NUM " then return "kp" .. string.sub(parsed_key, 5) else local str = string.gsub(string.lower(parsed_key), "%s+", "") return str end end, is_down = function(...) local parsed_keys = { ... } for i = 1, #parsed_keys do local parsed_key = parsed_keys[i] if parsed_key and parsed_key ~= "Unknown" then if Handy.controller.wheel_buttons[parsed_key] then -- Well, skip elseif Handy.controller.mouse_buttons[parsed_key] then if love.mouse.isDown(Handy.controller.mouse_buttons[parsed_key]) then return true end else local success, is_down = pcall(function() return love.keyboard.isDown(Handy.controller.resolve(parsed_key)) end) if success and is_down then return true end end end end return false end, is = function(raw_key, ...) if not raw_key then return false end local parsed_keys = { ... } for i = 1, #parsed_keys do local parsed_key = parsed_keys[i] if parsed_key then local resolved_key_1, resolved_key_2 = Handy.controller.resolve(parsed_key) if raw_key and raw_key ~= "Unknown" and (raw_key == resolved_key_1 or raw_key == resolved_key_2) then return true end end end return false end, is_module_key_down = function(module) return module and module.enabled and Handy.controller.is_down(module.key_1, module.key_2) end, is_module_key = function(module, raw_key) return module and module.enabled and Handy.controller.is(raw_key, module.key_1, module.key_2) end, process_key = function(key, released) if not released then if Handy.controller.process_bind(key) then return true end Handy.move_highlight.use(key) Handy.speed_multiplier.use(key) Handy.shop_reroll.use(key) Handy.play_and_discard.use(key) end Handy.insta_booster_skip.use(key, released) Handy.insta_cash_out.use(key, released) Handy.not_just_yet_interaction.use(key, released) Handy.dangerous_actions.toggle_queue(key, released) Handy.UI.state_panel.update(key, released) return false end, process_mouse = function(mouse, released) local key = Handy.controller.mouse_to_key_table[mouse] if not released then if Handy.controller.process_bind(key) then return true end Handy.move_highlight.use(key) Handy.speed_multiplier.use(key) Handy.shop_reroll.use(key) Handy.play_and_discard.use(key) end Handy.insta_booster_skip.use(key, released) Handy.insta_cash_out.use(key, released) Handy.not_just_yet_interaction.use(key, released) Handy.dangerous_actions.toggle_queue(key, released) Handy.UI.state_panel.update(key, released) return false end, process_wheel = function(wheel) local key = Handy.controller.wheel_to_key_table[wheel] if Handy.controller.process_bind(key) then return true end Handy.move_highlight.use(key) Handy.speed_multiplier.use(key) Handy.nopeus_interaction.use(key) Handy.shop_reroll.use(key) Handy.play_and_discard.use(key) Handy.UI.state_panel.update(key, false) end, process_card_click = function(card) if Handy.insta_actions.use(card) then return true end Handy.last_clicked_card = card Handy.last_clicked_area = card.area return false end, process_card_hover = function(card) if Handy.insta_highlight.use(card) then return true end if Handy.dangerous_actions.use(card) then return true end return false end, process_update = function(dt) Handy.insta_booster_skip.update() Handy.insta_cash_out.update() Handy.not_just_yet_interaction.update() Handy.UI.update(dt) end, } -- Handy.insta_cash_out = { is_hold = false, is_skipped = false, is_button_created = false, dollars = nil, can_execute = function(check) if check then return not not ( Handy.insta_cash_out.is_hold and G.STAGE == G.STAGES.RUN and Handy.insta_cash_out.is_skipped and not G.SETTINGS.paused and G.round_eval ) else return not not ( Handy.insta_cash_out.is_hold and G.STAGE == G.STAGES.RUN and not Handy.insta_cash_out.is_skipped and Handy.insta_cash_out.dollars and not G.SETTINGS.paused and G.round_eval ) end end, execute = function() Handy.insta_cash_out.is_skipped = true if Handy.insta_cash_out.is_button_created then G.GAME.current_round.dollars = Handy.insta_cash_out.dollars Handy.insta_cash_out.dollars = nil end G.E_MANAGER:add_event(Event({ trigger = "immediate", func = function() G.FUNCS.cash_out({ config = { id = "cash_out_button", }, }) return true end, })) return true end, use = function(key, released) if Handy.controller.is_module_key(Handy.config.current.insta_cash_out, key) then Handy.insta_cash_out.is_hold = not released end return false end, update = function() if not Handy.config.current.insta_cash_out.enabled then Handy.insta_cash_out.is_hold = false end return Handy.insta_cash_out.can_execute() and Handy.insta_cash_out.execute() or false end, update_state_panel = function(state, key, released) -- if G.STAGE ~= G.STAGES.RUN then -- return false -- end -- if Handy.config.current.notifications_level < 4 then -- return false -- end -- if Handy.insta_cash_out.can_execute(true) then -- state.items.insta_cash_out = { -- text = "Skip Cash Out", -- hold = false, -- order = 10, -- } -- return true -- end -- return false end, } Handy.insta_booster_skip = { is_hold = false, is_skipped = false, can_execute = function(check) if check then return not not ( Handy.insta_booster_skip.is_hold and G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused and G.booster_pack ) end return not not ( Handy.insta_booster_skip.is_hold and not Handy.insta_booster_skip.is_skipped and G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused and G.booster_pack and Handy.fake_events.check({ func = G.FUNCS.can_skip_booster, }) ) end, execute = function() Handy.insta_booster_skip.is_skipped = true G.E_MANAGER:add_event(Event({ func = function() G.FUNCS.skip_booster() return true end, })) return true end, use = function(key, released) if Handy.controller.is_module_key(Handy.config.current.insta_booster_skip, key) then Handy.insta_booster_skip.is_hold = not released end return false end, update = function() if not Handy.config.current.insta_booster_skip.enabled then Handy.insta_booster_skip.is_hold = false end return Handy.insta_booster_skip.can_execute() and Handy.insta_booster_skip.execute() or false end, update_state_panel = function(state, key, released) if G.STAGE ~= G.STAGES.RUN then return false end if Handy.config.current.notifications_level < 4 then return false end if Handy.insta_booster_skip.can_execute(true) then state.items.insta_booster_skip = { text = "Skip Booster Packs", hold = Handy.insta_booster_skip.is_hold, order = 10, } return true end return false end, } Handy.insta_highlight = { can_execute = function(card) return G.STAGE == G.STAGES.RUN and Handy.config.current.insta_highlight.enabled and card and card.area == G.hand -- TODO: fix it and not next(love.touch.getTouches()) and love.mouse.isDown(1) and not card.highlighted end, execute = function(card) card.area:add_to_highlighted(card) return false end, use = function(card) return Handy.insta_highlight.can_execute(card) and Handy.insta_highlight.execute(card) or false end, update_state_panel = function(state, key, released) end, } Handy.insta_actions = { get_actions = function() return { buy_or_sell = Handy.controller.is_module_key_down(Handy.config.current.insta_buy_or_sell), use = Handy.controller.is_module_key_down(Handy.config.current.insta_use), } end, can_execute = function(card, buy_or_sell, use) return not not (G.STAGE == G.STAGES.RUN and (buy_or_sell or use) and card and card.area) end, execute = function(card, buy_or_sell, use, only_sell) local target_button = nil local is_shop_button = false local is_custom_button = false local is_playable_consumeable = false local base_background = G.UIDEF.card_focus_ui(card) local base_attach = base_background:get_UIE_by_ID("ATTACH_TO_ME").children local card_buttons = G.UIDEF.use_and_sell_buttons(card) local result_funcs = {} for _, node in ipairs(card_buttons.nodes) do if node.config and node.config.func then result_funcs[node.config.func] = node end end local is_booster_pack_card = (G.pack_cards and card.area == G.pack_cards) and not card.ability.consumeable if use then if card.area == G.hand and card.ability.consumeable then local success, playale_consumeable_button = pcall(function() -- G.UIDEF.use_and_sell_buttons(G.hand.highlighted[1]).nodes[1].nodes[2].nodes[1].nodes[1] return card_buttons.nodes[1].nodes[2].nodes[1].nodes[1] end) if success and playale_consumeable_button then target_button = playale_consumeable_button is_custom_button = true is_playable_consumeable = true end elseif result_funcs.can_select_alchemical or result_funcs.can_select_crazy_card then -- Prevent cards to be selected when usage is required: -- Alchemical cards, Cines else target_button = base_attach.buy_and_use or (not is_booster_pack_card and base_attach.use) or card.children.buy_and_use_button is_shop_button = target_button == card.children.buy_and_use_button end elseif buy_or_sell then target_button = card.children.buy_button or result_funcs.can_select_crazy_card -- Cines or result_funcs.can_select_alchemical -- Alchemical cards or result_funcs.can_use_mupack -- Multipacks or result_funcs.can_reserve_card -- Code cards, for example or base_attach.buy or base_attach.redeem or base_attach.sell or (is_booster_pack_card and base_attach.use) if only_sell and target_button ~= base_attach.sell then target_button = nil end is_shop_button = target_button == card.children.buy_button end if target_button and not is_custom_button and not is_shop_button then for _, node in ipairs(card_buttons.nodes) do if target_button == node then is_custom_button = true end end end local target_button_UIBox local target_button_definition local cleanup = function() base_background:remove() if target_button_UIBox and is_custom_button then target_button_UIBox:remove() end end if target_button then if is_playable_consumeable then card.area:add_to_highlighted(card) if not card.highlighted then cleanup() return false end end target_button_UIBox = (is_custom_button and UIBox({ definition = target_button, config = {}, })) or target_button target_button_definition = (is_custom_button and target_button) or (is_shop_button and target_button.definition) or target_button.definition.nodes[1] local check, button = Handy.fake_events.check({ func = G.FUNCS[target_button_definition.config.func], button = nil, id = target_button_definition.config.id, card = card, UIBox = target_button_UIBox, }) if check then Handy.fake_events.execute({ func = G.FUNCS[button or target_button_definition.config.button], button = nil, id = target_button_definition.config.id, card = card, UIBox = target_button_UIBox, }) cleanup() return true end end cleanup() return false end, use = function(card) if card.ability and card.ability.handy_dangerous_actions_used then return true end local actions = Handy.insta_actions.get_actions() return Handy.insta_actions.can_execute(card, actions.buy_or_sell, actions.use) and Handy.insta_actions.execute(card, actions.buy_or_sell, actions.use) or false end, update_state_panel = function(state, key, released) if G.STAGE ~= G.STAGES.RUN then return false end if Handy.config.current.notifications_level < 4 then return false end local result = false local actions = Handy.insta_actions.get_actions() if actions.use then state.items.insta_use = { text = "Quick use", hold = true, order = 10, } result = true end if actions.buy_or_sell then state.items.quick_buy_and_sell = { text = "Quick buy and sell", hold = true, order = 11, } result = true end return result end, } Handy.move_highlight = { dx = { one_left = -1, one_right = 1, }, get_dx = function(key, area) for module_key, module in pairs(Handy.config.current.move_highlight.dx) do if Handy.controller.is_module_key(module, key) then return Handy.move_highlight.dx[module_key] end end return nil end, get_actions = function(key, area) return { swap = Handy.controller.is_module_key_down(Handy.config.current.move_highlight.swap), to_end = Handy.controller.is_module_key_down(Handy.config.current.move_highlight.to_end), } end, can_swap = function(key, area) if not area then return false end return not Handy.utils.table_contains({ G.pack_cards, G.shop_jokers, G.shop_booster, G.shop_vouchers, }, area) end, cen_execute = function(key, area) return not not ( Handy.config.current.move_highlight.enabled and G.STAGE == G.STAGES.RUN and area and area.highlighted and area.highlighted[1] and Handy.utils.table_contains({ G.consumeables, G.jokers, G.cine_quests, G.pack_cards, G.shop_jokers, G.shop_booster, G.shop_vouchers, }, area) ) end, execute = function(key, area) local dx = Handy.move_highlight.get_dx(key, area) if not dx then return false end local current_card = area.highlighted[1] for current_index = #area.cards, 1, -1 do if area.cards[current_index] == current_card then local actions = Handy.move_highlight.get_actions(key, area) local next_index = actions.to_end and (dx > 0 and #area.cards or 1) or ((#area.cards + current_index + dx - 1) % #area.cards) + 1 if current_index == next_index then return end local next_card = area.cards[next_index] if not next_card then return end if actions.swap and Handy.move_highlight.can_swap(key, area) then if actions.to_end or next_index == 1 or next_index == #area.cards then table.remove(area.cards, current_index) table.insert(area.cards, next_index, current_card) else area.cards[next_index] = current_card area.cards[current_index] = next_card end else area:remove_from_highlighted(current_card) area:add_to_highlighted(next_card) end return end end end, use = function(key, area) area = area or Handy.last_clicked_area return Handy.move_highlight.cen_execute(key, area) and Handy.move_highlight.execute(key, area) or false end, update_state_panel = function(state, key, released) end, } Handy.dangerous_actions = { sell_queue = {}, sell_next_card = function() local card = table.remove(Handy.dangerous_actions.sell_queue, 1) if not card then stop_use() return end G.GAME.STOP_USE = 0 Handy.insta_actions.execute(card, true, false, true) G.E_MANAGER:add_event(Event({ blocking = false, func = function() if card.ability then card.ability.handy_dangerous_actions_used = nil end return true end, })) Handy.dangerous_actions.sell_next_card() end, can_execute = function(card) return G.STAGE == G.STAGES.RUN and Handy.config.current.dangerous_actions.enabled and card and not (card.ability and card.ability.handy_dangerous_actions_used) end, execute = function(card) if Handy.controller.is_module_key_down(Handy.config.current.dangerous_actions.immediate_buy_and_sell) then if Handy.config.current.dangerous_actions.immediate_buy_and_sell.queue.enabled then if not card.ability then card.ability = {} end card.ability.handy_dangerous_actions_used = true table.insert(Handy.dangerous_actions.sell_queue, card) Handy.UI.state_panel.update(nil, nil) return false else local result = Handy.insta_actions.execute(card, true, false) if result then if not card.ability then card.ability = {} end card.ability.handy_dangerous_actions_used = true G.CONTROLLER.locks.selling_card = nil G.CONTROLLER.locks.use = nil G.GAME.STOP_USE = 0 G.E_MANAGER:add_event(Event({ func = function() if card.ability then card.ability.handy_dangerous_actions_used = nil end return true end, })) end return result end end return false end, use = function(card) return Handy.dangerous_actions.can_execute(card) and Handy.dangerous_actions.execute(card) or false end, toggle_queue = function(key, released) if Handy.controller.is_module_key(Handy.config.current.dangerous_actions.immediate_buy_and_sell, key) then if released then Handy.dangerous_actions.sell_next_card() else Handy.dangerous_actions.sell_queue = {} end end end, update_state_panel = function(state, key, released) if G.STAGE ~= G.STAGES.RUN then return false end if not Handy.config.current.dangerous_actions.enabled then return false end if Handy.config.current.notifications_level < 2 then return false end if Handy.controller.is_module_key_down(Handy.config.current.dangerous_actions.immediate_buy_and_sell) then state.dangerous = true state.items.dangerous_hint = { text = "[Unsafe] Bugs can appear!", dangerous = true, hold = true, order = 99999999, } if state.items.quick_buy_and_sell then state.items.quick_buy_and_sell.dangerous = true elseif Handy.insta_actions.get_actions().buy_or_sell then local text = "Quick sell" if Handy.config.current.dangerous_actions.immediate_buy_and_sell.queue.enabled then text = text .. " [" .. #Handy.dangerous_actions.sell_queue .. " in queue]" end state.items.quick_buy_and_sell = { text = text, hold = true, order = 11, dangerous = true, } end return true end return false end, } Handy.speed_multiplier = { value = 1, get_actions = function(key) return { multiply = key == Handy.controller.wheel_to_key_table[1], divide = key == Handy.controller.wheel_to_key_table[2], } end, can_execute = function(key) return Handy.config.current.speed_multiplier.enabled and not G.OVERLAY_MENU and Handy.controller.is_module_key_down(Handy.config.current.speed_multiplier) end, execute = function(key) local actions = Handy.speed_multiplier.get_actions(key) if actions.multiply then Handy.speed_multiplier.multiply() end if actions.divide then Handy.speed_multiplier.divide() end return false end, multiply = function() Handy.speed_multiplier.value = math.min(512, Handy.speed_multiplier.value * 2) end, divide = function() Handy.speed_multiplier.value = math.max(0.001953125, Handy.speed_multiplier.value / 2) end, use = function(key) return Handy.speed_multiplier.can_execute(key) and Handy.speed_multiplier.execute(key) or false end, update_state_panel = function(state, key, released) if not key or not Handy.speed_multiplier.can_execute(key) then return false end if Handy.config.current.notifications_level < 3 then return false end local actions = Handy.speed_multiplier.get_actions(key) if actions.multiply or actions.divide then state.items.change_speed_multiplier = { text = "Game speed multiplier: " .. ( Handy.speed_multiplier.value >= 1 and Handy.speed_multiplier.value or ("1/" .. (1 / Handy.speed_multiplier.value)) ), hold = false, order = 5, } return true end return false end, } Handy.shop_reroll = { can_execute = function(key) return G.STATE == G.STATES.SHOP and Handy.fake_events.check({ func = G.FUNCS.can_reroll, button = "reroll_shop" }) and Handy.controller.is_module_key(Handy.config.current.shop_reroll, key) end, execute = function(key) G.FUNCS.reroll_shop() return false end, use = function(key) return Handy.shop_reroll.can_execute(key) and Handy.shop_reroll.execute(key) or false end, } Handy.play_and_discard = { get_actions = function(key) return { discard = Handy.controller.is_module_key(Handy.config.current.play_and_discard.discard, key), play = Handy.controller.is_module_key(Handy.config.current.play_and_discard.play, key), } end, can_execute = function(play, discard) return not not ( Handy.config.current.play_and_discard.enabled and G.STATE == G.STATES.SELECTING_HAND and ( (discard and Handy.fake_events.check({ func = G.FUNCS.can_discard, })) or (play and Handy.fake_events.check({ func = G.FUNCS.can_play, })) ) ) end, execute = function(play, discard) if discard then Handy.fake_events.execute({ func = G.FUNCS.discard_cards_from_highlighted, }) elseif play then Handy.fake_events.execute({ func = G.FUNCS.play_cards_from_highlighted, }) end return false end, use = function(key) local actions = Handy.play_and_discard.get_actions(key) return Handy.play_and_discard.can_execute(actions.play, actions.discard) and Handy.play_and_discard.execute(actions.play, actions.discard) or false end, } Handy.nopeus_interaction = { is_present = function() return type(Nopeus) == "table" end, get_actions = function(key) return { increase = key == Handy.controller.wheel_to_key_table[1], decrease = key == Handy.controller.wheel_to_key_table[2], } end, can_dangerous = function() return not not ( Handy.config.current.dangerous_actions.enabled and Handy.config.current.dangerous_actions.nopeus_unsafe.enabled ) end, can_execute = function(key) return not not ( Handy.config.current.nopeus_interaction.enabled and Handy.nopeus_interaction.is_present() and not G.OVERLAY_MENU and Handy.controller.is_module_key_down(Handy.config.current.nopeus_interaction) ) end, execute = function(key) local actions = Handy.nopeus_interaction.get_actions(key) if actions.increase then Handy.nopeus_interaction.increase() end if actions.decrease then Handy.nopeus_interaction.decrease() end end, change = function(dx) if not Handy.nopeus_interaction.is_present() then G.SETTINGS.FASTFORWARD = 0 elseif Nopeus.Optimised then G.SETTINGS.FASTFORWARD = math.min( Handy.nopeus_interaction.can_dangerous() and 4 or 3, math.max(0, (G.SETTINGS.FASTFORWARD or 0) + dx) ) else G.SETTINGS.FASTFORWARD = math.min( Handy.nopeus_interaction.can_dangerous() and 3 or 2, math.max(0, (G.SETTINGS.FASTFORWARD or 0) + dx) ) end end, increase = function() Handy.nopeus_interaction.change(1) end, decrease = function() Handy.nopeus_interaction.change(-1) end, use = function(key) return Handy.nopeus_interaction.can_execute(key) and Handy.nopeus_interaction.execute(key) or false end, update_state_panel = function(state, key, released) if not Handy.nopeus_interaction.is_present() then return false end if not key or not Handy.nopeus_interaction.can_execute(key) then return false end local actions = Handy.nopeus_interaction.get_actions(key) if actions.increase or actions.decrease then local states = { Nopeus.Off, Nopeus.Planets, Nopeus.On, Nopeus.Unsafe, } if Nopeus.Optimised then states = { Nopeus.Off, Nopeus.Planets, Nopeus.On, Nopeus.Optimised, Nopeus.Unsafe, } end local is_dangerous = G.SETTINGS.FASTFORWARD == (#states - 1) if is_dangerous then state.dangerous = true if Handy.config.current.notifications_level < 2 then return false end else if Handy.config.current.notifications_level < 3 then return false end end state.items.change_nopeus_fastforward = { text = "Nopeus fast-forward: " .. states[(G.SETTINGS.FASTFORWARD or 0) + 1], hold = false, order = 4, dangerous = is_dangerous, } return true end return false end, } Handy.not_just_yet_interaction = { is_present = function() return G and G.FUNCS and G.FUNCS.njy_endround ~= nil end, can_execute = function(check) return not not ( Handy.not_just_yet_interaction.is_present() and GLOBAL_njy_vanilla_override and G.STATE_COMPLETE and G.buttons and G.buttons.states and G.buttons.states.visible and G.GAME and G.GAME.chips and G.GAME.blind and G.GAME.blind.chips and to_big(G.GAME.chips) >= to_big(G.GAME.blind.chips) ) end, execute = function() stop_use() G.STATE = G.STATES.NEW_ROUND end_round() end, use = function(key, released) if Handy.controller.is_module_key(Handy.config.current.not_just_yet_interaction, key) then GLOBAL_njy_vanilla_override = not released end return false end, update = function() if not Handy.config.current.not_just_yet_interaction.enabled then GLOBAL_njy_vanilla_override = nil end return Handy.not_just_yet_interaction.can_execute() and Handy.not_just_yet_interaction.execute() or false end, } -- -- Handy.UI = { counter = 1, C = { TEXT = HEX("FFFFFF"), BLACK = HEX("000000"), RED = HEX("FF0000"), DYN_BASE_APLHA = { CONTAINER = 0.6, TEXT = 1, TEXT_DANGEROUS = 1, }, DYN = { CONTAINER = HEX("000000"), TEXT = HEX("FFFFFF"), TEXT_DANGEROUS = HEX("FFEEEE"), }, }, state_panel = { element = nil, title = nil, items = nil, previous_state = { dangerous = false, title = {}, items = {}, sub_items = {}, hold = false, }, current_state = { dangerous = false, title = {}, items = {}, sub_items = {}, hold = false, }, get_definition = function() local state_panel = Handy.UI.state_panel local items_raw = {} for _, item in pairs(state_panel.current_state.items) do table.insert(items_raw, item) end table.sort(items_raw, function(a, b) return a.order < b.order end) local items = {} for _, item in ipairs(items_raw) do table.insert(items, { n = G.UIT.R, config = { align = "cm", padding = 0.035, }, nodes = { { n = G.UIT.T, config = { text = item.text, scale = 0.225, colour = item.dangerous and Handy.UI.C.DYN.TEXT_DANGEROUS or Handy.UI.C.DYN.TEXT, shadow = true, }, }, }, }) end return { n = G.UIT.ROOT, config = { align = "cm", padding = 0.1, r = 0.1, colour = G.C.CLEAR, id = "handy_state_panel" }, nodes = { { n = G.UIT.C, config = { align = "cm", padding = 0.125, r = 0.1, colour = Handy.UI.C.DYN.CONTAINER, }, nodes = { { n = G.UIT.R, config = { align = "cm", }, nodes = { { n = G.UIT.T, config = { text = state_panel.current_state.title.text, scale = 0.3, colour = Handy.UI.C.DYN.TEXT, shadow = true, id = "handy_state_title", }, }, }, }, { n = G.UIT.R, config = { align = "cm", }, nodes = { { n = G.UIT.C, config = { align = "cm", id = "handy_state_items", }, nodes = items, }, }, }, }, }, }, } end, emplace = function() if Handy.UI.state_panel.element then Handy.UI.state_panel.element:remove() end local element = UIBox({ definition = Handy.UI.state_panel.get_definition(), config = { instance_type = "ALERT", align = "cm", major = G.ROOM_ATTACH, can_collide = false, offset = { x = 0, y = 3.5, }, }, }) Handy.UI.state_panel.element = element Handy.UI.state_panel.title = element:get_UIE_by_ID("handy_state_title") Handy.UI.state_panel.items = element:get_UIE_by_ID("handy_state_items") end, update = function(key, released) local state_panel = Handy.UI.state_panel local state = { dangerous = false, title = {}, items = {}, sub_items = {}, } local is_changed = false for _, part in ipairs({ Handy.speed_multiplier, Handy.insta_booster_skip, Handy.insta_cash_out, Handy.insta_actions, Handy.insta_highlight, Handy.move_highlight, Handy.nopeus_interaction, Handy.dangerous_actions, }) do local temp_result = part.update_state_panel(state, key, released) is_changed = is_changed or temp_result or false end if is_changed then if state.dangerous then state.title.text = "Dangerous actions" else state.title.text = "Quick actions" end for _, item in pairs(state.items) do if item.hold then state.hold = true end end local color = Handy.UI.C.DYN.CONTAINER local target_color = state.dangerous and Handy.UI.C.RED or Handy.UI.C.BLACK color[1] = target_color[1] color[2] = target_color[2] color[3] = target_color[3] Handy.UI.counter = 0 state_panel.previous_state = state_panel.current_state state_panel.current_state = state state_panel.emplace() else state_panel.current_state.hold = false end end, }, update = function(dt) if Handy.UI.state_panel.current_state.hold then Handy.UI.counter = 0 elseif Handy.UI.counter < 1 then Handy.UI.counter = Handy.UI.counter + dt end local multiplier = math.min(1, math.max(0, (1 - Handy.UI.counter) * 2)) for key, color in pairs(Handy.UI.C.DYN) do color[4] = (Handy.UI.C.DYN_BASE_APLHA[key] or 1) * multiplier end end, } function Handy.UI.init() Handy.UI.counter = 1 Handy.UI.state_panel.emplace() Handy.UI.update(0) end -- local love_update_ref = love.update function love.update(dt, ...) love_update_ref(dt, ...) Handy.controller.process_update(dt) end local wheel_moved_ref = love.wheelmoved or function() end function love.wheelmoved(x, y) wheel_moved_ref(x, y) Handy.controller.process_wheel(y > 0 and 1 or 2) end -- function Handy.emplace_steamodded() Handy.current_mod = SMODS.current_mod Handy.config.current = Handy.utils.table_merge({}, Handy.config.default, SMODS.current_mod.config) Handy.current_mod.extra_tabs = function() return { { label = "Overall", tab_definition_function = function() return Handy.UI.get_config_tab("Overall") end, }, { label = "Interactions", tab_definition_function = function() return Handy.UI.get_config_tab("Interactions") end, }, { label = "Dangerous", tab_definition_function = function() return Handy.UI.get_config_tab("Dangerous") end, }, { label = "Keybinds", tab_definition_function = function() return Handy.UI.get_config_tab("Keybinds") end, }, { label = "More keybinds", tab_definition_function = function() return Handy.UI.get_config_tab("Keybinds 2") end, }, } end G.E_MANAGER:add_event(Event({ func = function() G.njy_keybind = nil return true end, })) end function G.FUNCS.handy_toggle_module_enabled(arg, module) if not module then return end module.enabled = arg if module == Handy.config.current.speed_multiplier then Handy.speed_multiplier.value = 1 elseif module == Handy.config.current.dangerous_actions or module == Handy.config.current.nopeus_interaction or module == Handy.config.current.dangerous_actions.nopeus_unsafe then Handy.nopeus_interaction.change(0) end Handy.config.save() end function G.FUNCS.handy_change_notifications_level(arg) Handy.config.current.notifications_level = arg.to_key Handy.config.save() end function G.FUNCS.handy_init_keybind_change(e) Handy.controller.init_bind(e) end