balatro-mods/HandyBalatro/index.lua
2025-03-25 03:09:00 +08:00

2310 lines
56 KiB
Lua

Handy = setmetatable({
version = "1.4.1",
last_clicked_area = nil,
last_clicked_card = nil,
last_hovered_area = nil,
last_hovered_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
function Handy.utils.serialize_string(s)
return string.format("%q", s)
end
function Handy.utils.serialize(t, indent)
indent = indent or ""
local str = "{\n"
for k, v in ipairs(t) do
str = str .. indent .. "\t"
if type(v) == "number" then
str = str .. v
elseif type(v) == "boolean" then
str = str .. (v and "true" or "false")
elseif type(v) == "string" then
str = str .. Handy.utils.serialize_string(v)
elseif type(v) == "table" then
str = str .. Handy.utils.serialize(v, indent .. "\t")
else
-- not serializable
str = str .. "nil"
end
str = str .. ",\n"
end
for k, v in pairs(t) do
if type(k) == "string" then
str = str .. indent .. "\t" .. "[" .. Handy.utils.serialize_string(k) .. "] = "
if type(v) == "number" then
str = str .. v
elseif type(v) == "boolean" then
str = str .. (v and "true" or "false")
elseif type(v) == "string" then
str = str .. Handy.utils.serialize_string(v)
elseif type(v) == "table" then
str = str .. Handy.utils.serialize(v, indent .. "\t")
else
-- not serializable
str = str .. "nil"
end
str = str .. ",\n"
end
end
str = str .. indent .. "}"
return str
end
--
Handy.config = {
default = {
handy = {
enabled = true,
},
notifications_level = 3,
keybinds_trigger_mode = 1,
insta_actions_trigger_mode = 1,
hide_in_menu = false,
insta_highlight = {
enabled = true,
},
insta_highlight_entire_f_hand = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
insta_buy_n_sell = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
insta_buy_or_sell = {
enabled = true,
key_1 = "Shift",
key_2 = "None",
},
insta_use = {
enabled = true,
key_1 = "Ctrl",
key_2 = "None",
},
move_highlight = {
enabled = true,
swap = {
enabled = true,
key_1 = "Shift",
key_2 = "None",
},
to_end = {
enabled = true,
key_1 = "Ctrl",
key_2 = "None",
},
dx = {
one_left = {
enabled = true,
key_1 = "Left",
key_2 = "None",
},
one_right = {
enabled = true,
key_1 = "Right",
key_2 = "None",
},
},
},
insta_cash_out = {
enabled = true,
key_1 = "Enter",
key_2 = "None",
},
insta_booster_skip = {
enabled = true,
key_1 = "Enter",
key_2 = "None",
},
show_deck_preview = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
dangerous_actions = {
enabled = false,
-- Use it as basic modifier for all dangerous controls
-- Maybe I should change this but idk, backwards compatibility
immediate_buy_and_sell = {
enabled = true,
key_1 = "Middle Mouse",
key_2 = "None",
queue = {
enabled = false,
},
},
sell_all_same = {
enabled = false,
key_1 = "None",
key_2 = "None",
},
sell_all = {
enabled = false,
key_1 = "None",
key_2 = "None",
},
card_remove = {
enabled = false,
key_1 = "None",
key_2 = "None",
},
nopeus_unsafe = {
enabled = true,
},
},
speed_multiplier = {
enabled = true,
key_1 = "Alt",
key_2 = "None",
},
deselect_hand = {
enabled = true,
key_1 = "Right Mouse",
key_2 = "None",
},
regular_keybinds = {
enabled = true,
play = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
discard = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
sort_by_rank = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
sort_by_suit = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
reroll_shop = {
enabled = true,
key_1 = "Q",
key_2 = "None",
},
leave_shop = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
skip_blind = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
select_blind = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
run_info = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
run_info_blinds = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
view_deck = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
},
nopeus_interaction = {
enabled = true,
key_1 = "]",
key_2 = "None",
},
not_just_yet_interaction = {
enabled = true,
key_1 = "Enter",
key_2 = "None",
},
cryptid_code_use_last_interaction = {
enabled = true,
key_1 = "None",
key_2 = "None",
},
},
current = {},
get_module = function(module)
if not module then
return nil
end
local override = Handy.get_module_override(module)
if override then
return Handy.utils.table_merge({}, module, override)
end
return module
end,
save = function()
if SMODS and SMODS.save_mod_config and Handy.current_mod then
Handy.current_mod.config = Handy.config.current
SMODS.save_mod_config(Handy.current_mod)
else
love.filesystem.createDirectory("config")
local serialized = "return " .. Handy.utils.serialize(Handy.config.current)
love.filesystem.write("config/Handy.jkr", serialized)
end
end,
load = function()
Handy.config.current = Handy.utils.table_merge({}, Handy.config.default)
local lovely_mod_config = get_compressed("config/Handy.jkr")
if lovely_mod_config then
Handy.config.current = Handy.utils.table_merge(Handy.config.current, STR_UNPACK(lovely_mod_config))
end
Handy.cc = Handy.config.current
end,
}
-- Shorthand for `Handy.config.current`
Handy.cc = Handy.config.current
Handy.config.load()
function Handy.is_mod_active()
return Handy.cc.handy.enabled
end
function Handy.is_dangerous_actions_active()
return Handy.cc.dangerous_actions.enabled
end
function Handy.get_module_override(module)
return nil
end
-- Ha-ha, funny Cryptid reference
-- Resolve module with overrides
function Handy.m(module)
return Handy.config.get_module(module)
end
--
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 = "None"
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" and parsed_key ~= "None" 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 ~= "None"
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, allow_disabled)
module = Handy.m(module)
return module and (allow_disabled or module.enabled) and Handy.controller.is_down(module.key_1, module.key_2)
end,
is_module_key = function(module, raw_key, allow_disabled)
module = Handy.m(module)
return module
and (allow_disabled or module.enabled)
and Handy.controller.is(raw_key, module.key_1, module.key_2)
end,
is_module_enabled = function(module)
module = Handy.m(module)
return module and module.enabled
end,
is_trigger_on_release = function()
return Handy.cc.keybinds_trigger_mode == 2
end,
is_triggered = function(released)
if Handy.controller.is_trigger_on_release() then
return released
end
return not released
end,
process_key = function(key, released)
if not Handy.is_mod_active() then
return false
end
if G.CONTROLLER.text_input_hook then
return false
end
if not released and Handy.controller.process_bind(key) then
return true
end
if key == "escape" then
return false
end
if not released then
Handy.speed_multiplier.use(key)
end
if G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused then
if Handy.controller.is_triggered(released) then
Handy.insta_actions.use_alt(key)
Handy.move_highlight.use(key)
Handy.regular_keybinds.use(key)
Handy.insta_highlight_entire_f_hand.use(key)
Handy.deselect_hand.use(key)
end
Handy.dangerous_actions.toggle_queue(key, released)
end
Handy.UI.state_panel.update(key, released)
return false
end,
process_mouse = function(mouse, released)
if not Handy.is_mod_active() then
return false
end
if G.CONTROLLER.text_input_hook then
return false
end
local key = Handy.controller.mouse_to_key_table[mouse]
if not released and Handy.controller.process_bind(key) then
return true
end
if not released then
Handy.speed_multiplier.use(key)
end
if G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused and not G.OVERLAY_MENU then
if Handy.controller.is_triggered(released) then
Handy.insta_actions.use_alt(key)
Handy.move_highlight.use(key)
Handy.regular_keybinds.use(key)
Handy.insta_highlight_entire_f_hand.use(key)
Handy.deselect_hand.use(key)
end
Handy.dangerous_actions.toggle_queue(key, released)
end
Handy.UI.state_panel.update(key, released)
return false
end,
process_wheel = function(wheel)
if not Handy.is_mod_active() then
return false
end
if G.CONTROLLER.text_input_hook then
return false
end
local key = Handy.controller.wheel_to_key_table[wheel]
if Handy.controller.process_bind(key) then
return true
end
Handy.speed_multiplier.use(key)
Handy.nopeus_interaction.use(key)
if G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused and not G.OVERLAY_MENU then
Handy.move_highlight.use(key)
Handy.regular_keybinds.use(key)
Handy.insta_highlight_entire_f_hand.use(key)
Handy.deselect_hand.use(key)
end
Handy.UI.state_panel.update(key, false)
return false
end,
process_card_click = function(card)
if not Handy.is_mod_active() then
return false
end
if G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused and not G.OVERLAY_MENU then
if Handy.insta_actions.use(card) then
return true
end
if Handy.dangerous_actions.use_click(card) then
return true
end
Handy.last_clicked_card = card
Handy.last_clicked_area = card.area
end
return false
end,
process_card_hover = function(card)
if not Handy.is_mod_active() then
return false
end
if G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused and not G.OVERLAY_MENU then
if Handy.insta_highlight.use(card) then
return true
end
if Handy.dangerous_actions.use_hover(card) then
return true
end
Handy.last_hovered_card = card
Handy.last_hovered_area = card.area
end
return false
end,
process_tag_click = function(tag)
if not Handy.is_mod_active() then
return false
end
if G.STAGE == G.STAGES.RUN and not G.SETTINGS.paused and not G.OVERLAY_MENU then
if Handy.dangerous_actions.use_tag_click(tag) then
return true
end
end
return false
end,
process_update = function(dt)
Handy.insta_booster_skip.update()
Handy.insta_cash_out.update()
Handy.show_deck_preview.update()
Handy.not_just_yet_interaction.update()
Handy.UI.update(dt)
end,
}
--
Handy.insta_cash_out = {
is_hold = false,
can_skip = false,
is_skipped = false,
can_execute = function()
return not not (
Handy.insta_cash_out.is_hold
and Handy.insta_cash_out.can_skip
and not Handy.insta_cash_out.is_skipped
and G.round_eval
)
end,
execute = function()
Handy.insta_cash_out.is_skipped = true
G.E_MANAGER:add_event(Event({
trigger = "immediate",
func = function()
Handy.fake_events.execute({
func = G.FUNCS.cash_out,
id = "cash_out_button",
})
return true
end,
}))
return true
end,
update = function()
Handy.insta_cash_out.is_hold = (
G.STAGE == G.STAGES.RUN
and Handy.is_mod_active()
and Handy.controller.is_module_key_down(Handy.cc.insta_cash_out)
)
return Handy.insta_cash_out.can_execute() and Handy.insta_cash_out.execute() or false
end,
update_state_panel = function(state, key, released) 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 not Handy.insta_booster_skip.is_skipped
-- and G.booster_pack
-- )
-- end
return not not (
Handy.insta_booster_skip.is_hold
and not Handy.insta_booster_skip.is_skipped
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()
Handy.fake_events.execute({
func = G.FUNCS.skip_booster,
})
return true
end,
}))
return true
end,
update = function()
Handy.insta_booster_skip.is_hold = (
G.STAGE == G.STAGES.RUN
and Handy.is_mod_active()
and Handy.controller.is_module_key_down(Handy.cc.insta_booster_skip)
)
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.cc.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.show_deck_preview = {
is_hold = false,
update = function()
Handy.show_deck_preview.is_hold = (
G.STAGE == G.STAGES.RUN
and Handy.is_mod_active()
and Handy.controller.is_module_key_down(Handy.cc.show_deck_preview)
)
end,
}
--
Handy.deselect_hand = {
should_prevent = function()
return Handy.controller.is_module_enabled(Handy.cc.deselect_hand)
end,
can_execute = function(key)
return not not (
G.hand
and G.hand.highlighted[1]
-- Vanilla check
and not ((G.play and #G.play.cards > 0) or G.CONTROLLER.locked or G.CONTROLLER.locks.frame or (G.GAME.STOP_USE and G.GAME.STOP_USE > 0))
and Handy.controller.is_module_key(Handy.cc.deselect_hand, key)
)
end,
execute = function()
G.hand:unhighlight_all()
end,
use = function(key)
return Handy.deselect_hand.can_execute(key) and Handy.deselect_hand.execute() or false
end,
}
Handy.regular_keybinds = {
shop_reroll_blocker = false,
can_play = function(key)
return not not (
Handy.fake_events.check({
func = G.FUNCS.can_play,
}) and Handy.controller.is_module_key(Handy.cc.regular_keybinds.play, key)
)
end,
play = function()
Handy.fake_events.execute({
func = G.FUNCS.play_cards_from_highlighted,
})
end,
can_discard = function(key)
return not not (
Handy.fake_events.check({
func = G.FUNCS.can_discard,
}) and Handy.controller.is_module_key(Handy.cc.regular_keybinds.discard, key)
)
end,
discard = function()
Handy.fake_events.execute({
func = G.FUNCS.discard_cards_from_highlighted,
})
end,
can_change_sort = function(key)
if Handy.controller.is_module_key(Handy.cc.regular_keybinds.sort_by_rank, key) then
return true, "rank"
elseif Handy.controller.is_module_key(Handy.cc.regular_keybinds.sort_by_suit, key) then
return true, "suit"
else
return false, nil
end
end,
change_sort = function(sorter)
if sorter == "rank" then
Handy.fake_events.execute({
func = G.FUNCS.sort_hand_value,
})
elseif sorter == "suit" then
Handy.fake_events.execute({
func = G.FUNCS.sort_hand_suit,
})
end
end,
can_reroll_shop = function(key)
return not not (
not Handy.regular_keybinds.shop_reroll_blocker
and Handy.fake_events.check({ func = G.FUNCS.can_reroll, button = "reroll_shop" })
and Handy.controller.is_module_key(Handy.cc.regular_keybinds.reroll_shop, key)
)
end,
reroll_shop = function()
Handy.regular_keybinds.shop_reroll_blocker = true
Handy.fake_events.execute({
func = G.FUNCS.reroll_shop,
})
G.E_MANAGER:add_event(Event({
no_delete = true,
func = function()
Handy.regular_keybinds.shop_reroll_blocker = false
return true
end,
}))
end,
can_leave_shop = function(key)
return not not (Handy.controller.is_module_key(Handy.cc.regular_keybinds.leave_shop, key))
end,
leave_shop = function()
Handy.fake_events.execute({
func = G.FUNCS.toggle_shop,
})
end,
can_select_blind = function(key)
return not not (
Handy.controller.is_module_key(Handy.cc.regular_keybinds.select_blind, key)
and G.GAME.blind_on_deck
and G.GAME.round_resets.blind_choices[G.GAME.blind_on_deck]
)
end,
select_blind = function()
Handy.fake_events.execute({
func = G.FUNCS.select_blind,
card = G.P_BLINDS[G.GAME.round_resets.blind_choices[G.GAME.blind_on_deck]],
})
end,
can_skip_blind = function(key)
return not not (
Handy.controller.is_module_key(Handy.cc.regular_keybinds.skip_blind, key)
and G.GAME.blind_on_deck
and G.blind_select_opts[string.lower(G.GAME.blind_on_deck)]
)
end,
skip_blind = function()
Handy.fake_events.execute({
func = G.FUNCS.skip_blind,
UIBox = G.blind_select_opts[string.lower(G.GAME.blind_on_deck)],
})
end,
can_open_run_info = function(key)
if Handy.controller.is_module_key(Handy.cc.regular_keybinds.run_info, key) then
return true, 1
elseif Handy.controller.is_module_key(Handy.cc.regular_keybinds.run_info_blinds, key) then
return true, 2
end
return false, nil
end,
open_run_info = function(tab_index)
if tab_index == 2 then
Handy.override_create_tabs_chosen_by_label = localize("b_blinds")
end
Handy.fake_events.execute({
func = G.FUNCS.run_info,
})
Handy.override_create_tabs_chosen_by_label = nil
end,
can_view_deck = function(key)
return not not (Handy.controller.is_module_key(Handy.cc.regular_keybinds.view_deck, key))
end,
view_deck = function()
Handy.fake_events.execute({
func = G.FUNCS.deck_info,
})
end,
use = function(key)
if not Handy.controller.is_module_enabled(Handy.cc.regular_keybinds) then
return false
end
if not G.SETTINGS.paused and G.STAGE == G.STAGES.RUN then
local can_open_info, info_tab_index = Handy.regular_keybinds.can_open_run_info(key)
if can_open_info then
Handy.regular_keybinds.open_run_info(info_tab_index)
elseif Handy.regular_keybinds.can_view_deck(key) then
Handy.regular_keybinds.view_deck()
elseif G.STATE == G.STATES.SELECTING_HAND then
local need_sort, sorter = Handy.regular_keybinds.can_change_sort(key)
if need_sort then
Handy.regular_keybinds.change_sort(sorter)
elseif Handy.regular_keybinds.can_discard(key) then
Handy.regular_keybinds.discard()
elseif Handy.regular_keybinds.can_play(key) then
Handy.regular_keybinds.play()
end
return false
elseif G.STATE == G.STATES.SHOP then
if Handy.regular_keybinds.can_reroll_shop(key) then
Handy.regular_keybinds.reroll_shop()
elseif Handy.regular_keybinds.can_leave_shop(key) then
Handy.regular_keybinds.leave_shop()
end
return false
elseif G.STATE == G.STATES.BLIND_SELECT then
if Handy.regular_keybinds.can_skip_blind(key) then
Handy.regular_keybinds.skip_blind()
elseif Handy.regular_keybinds.can_select_blind(key) then
Handy.regular_keybinds.select_blind()
end
return false
end
end
return false
end,
}
--
Handy.insta_highlight = {
can_execute = function(card)
return Handy.controller.is_module_enabled(Handy.cc.insta_highlight)
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_highlight_entire_f_hand = {
can_execute = function(key)
return G.hand and Handy.controller.is_module_key(Handy.cc.insta_highlight_entire_f_hand, key)
end,
execute = function(key)
G.hand:unhighlight_all()
local cards_count = math.min(G.hand.config.highlighted_limit, #G.hand.cards)
for i = 1, cards_count do
local card = G.hand.cards[i]
G.hand.cards[i]:highlight(true)
G.hand.highlighted[#G.hand.highlighted + 1] = card
end
if G.STATE == G.STATES.SELECTING_HAND then
G.hand:parse_highlighted()
end
return false
end,
use = function(key)
return Handy.insta_highlight_entire_f_hand.can_execute(key) and Handy.insta_highlight_entire_f_hand.execute(key)
or false
end,
}
Handy.insta_actions = {
action_blocker = false,
get_actions = function()
return {
buy_n_sell = Handy.controller.is_module_key_down(Handy.cc.insta_buy_n_sell),
buy_or_sell = Handy.controller.is_module_key_down(Handy.cc.insta_buy_or_sell),
use = Handy.controller.is_module_key_down(Handy.cc.insta_use),
cryptid_code_use_last_interaction = Handy.controller.is_module_key_down(
Handy.cc.cryptid_code_use_last_interaction
),
}
end,
get_alt_actions = function(key)
return {
buy_n_sell = Handy.controller.is_module_key(Handy.cc.insta_buy_n_sell, key),
buy_or_sell = Handy.controller.is_module_key(Handy.cc.insta_buy_or_sell, key),
use = Handy.controller.is_module_key(Handy.cc.insta_use, key),
cryptid_code_use_last_interaction = Handy.controller.is_module_key(
Handy.cc.cryptid_code_use_last_interaction,
key
),
}
end,
can_execute = function(card, buy_or_sell, use)
return not not (not Handy.insta_actions.action_blocker and (buy_or_sell or use) and card and card.area)
end,
execute = function(card, buy_or_sell, use, only_sell)
if card.REMOVED then
return false
end
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 type(card.ability.extra) == "table" and card.ability.extra.charges then
local success, isaac_changeable_item = pcall(function()
-- G.UIDEF.use_and_sell_buttons(G.jokers.highlighted[1]).nodes[1].nodes[3].nodes[1].nodes[1]
return card_buttons.nodes[1].nodes[3].nodes[1].nodes[1]
end)
if success and isaac_changeable_item then
target_button = isaac_changeable_item
is_custom_button = true
end
elseif 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.insta_actions.action_blocker = true
if Handy.last_clicked_card == card then
Handy.last_clicked_card = nil
Handy.last_clicked_area = nil
end
if Handy.last_hovered_card == card then
Handy.last_hovered_card = nil
Handy.last_hovered_area = nil
end
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,
})
G.E_MANAGER:add_event(Event({
no_delete = true,
func = function()
Handy.insta_actions.action_blocker = false
return true
end,
}))
cleanup()
return true
end
end
cleanup()
return false
end,
process_card = function(card, actions)
if not card or card.REMOVED then
return false
end
if card.ability and card.ability.handy_dangerous_actions_used then
return true
end
if actions.cryptid_code_use_last_interaction then
local cards_events_list = {
c_cry_variable = "variable_apply_previous",
c_cry_pointer = "pointer_apply_previous",
c_cry_class = "class_apply_previous",
c_cry_exploit = "exploit_apply_previous",
}
local success, card_center = pcall(function()
return card.config.center.key
end)
if success and card_center and cards_events_list[card_center] then
local is_code_card_used = Handy.insta_actions.can_execute(card, false, true)
and Handy.insta_actions.execute(card, false, true)
or false
if is_code_card_used then
Handy.fake_events.execute({
func = G.FUNCS[cards_events_list[card_center]],
})
return true
end
end
return false
elseif actions.buy_n_sell then
if
Handy.utils.table_contains({
G.pack_cards,
G.shop_jokers,
G.shop_booster,
G.shop_vouchers,
}, card.area)
and card.ability
and (card.ability.set == "Joker" or card.ability.consumeable)
then
local is_buyed = Handy.insta_actions.can_execute(card, true, false)
and Handy.insta_actions.execute(card, true, false)
or false
if is_buyed then
G.E_MANAGER:add_event(Event({
func = function()
G.E_MANAGER:add_event(Event({
func = function()
return (
Handy.insta_actions.can_execute(card, true, false)
and Handy.insta_actions.execute(card, true, false)
) or true
end,
}))
return true
end,
}))
end
return is_buyed
end
return false
else
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
end,
use = function(card)
return Handy.cc.insta_actions_trigger_mode == 1
and Handy.insta_actions.process_card(card, Handy.insta_actions.get_actions())
end,
use_alt = function(key)
return Handy.cc.insta_actions_trigger_mode == 2
and Handy.insta_actions.process_card(Handy.last_hovered_card, Handy.insta_actions.get_alt_actions(key))
end,
update_state_panel = function(state, key, released)
if G.STAGE ~= G.STAGES.RUN or G.SETTINGS.paused then
return false
end
if Handy.cc.notifications_level < 4 then
return false
end
local result = false
local is_alt_action = Handy.cc.insta_actions_trigger_mode == 2
if is_alt_action and not Handy.controller.is_triggered(released) then
return false
end
local actions = is_alt_action and Handy.insta_actions.get_alt_actions(key) or Handy.insta_actions.get_actions()
if actions.use then
state.items.insta_use = {
text = "Quick use",
hold = not is_alt_action,
order = 10,
}
result = true
end
if actions.buy_or_sell then
state.items.quick_buy_and_sell = {
text = "Quick buy and sell",
hold = not is_alt_action,
order = 11,
}
result = true
end
if actions.buy_n_sell then
state.items.quick_buy_n_sell = {
text = "Quick buy and immediately sell",
hold = not is_alt_action,
order = 12,
}
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.cc.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.cc.move_highlight.swap),
to_end = Handy.controller.is_module_key_down(Handy.cc.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.controller.is_module_enabled(Handy.cc.move_highlight)
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 target = table.remove(Handy.dangerous_actions.sell_queue, 1)
if not target then
stop_use()
return
end
local card = target.card
if target.remove then
card:stop_hover()
card:remove()
else
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,
}))
end
if Handy.last_clicked_card == card then
Handy.last_clicked_card = nil
Handy.last_clicked_area = nil
end
if Handy.last_hovered_card == card then
Handy.last_hovered_card = nil
Handy.last_hovered_area = nil
end
Handy.dangerous_actions.sell_next_card()
end,
get_options = function(card)
return {
use_queue = Handy.controller.is_module_enabled(Handy.cc.dangerous_actions.immediate_buy_and_sell.queue),
remove = Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.card_remove)
and (card.area == G.jokers or card.area == G.consumeables),
}
end,
process_card = function(card, use_queue, remove)
if use_queue then
if not card.ability then
card.ability = {}
end
card.ability.handy_dangerous_actions_used = true
table.insert(Handy.dangerous_actions.sell_queue, { card = card, remove = remove })
Handy.UI.state_panel.update(nil, nil)
return false
elseif remove then
card:stop_hover()
card:remove()
return true
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({
no_delete = true,
func = function()
if card.ability then
card.ability.handy_dangerous_actions_used = nil
end
return true
end,
}))
end
return result
end
end,
can_execute = function(card)
return Handy.is_dangerous_actions_active()
and card
and not (card.ability and card.ability.handy_dangerous_actions_used)
end,
execute_click = function(card)
if Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.immediate_buy_and_sell, true) then
if Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.sell_all) then
local options = Handy.dangerous_actions.get_options(card)
for _, target_card in ipairs(card.area.cards) do
Handy.dangerous_actions.process_card(target_card, true, options.remove)
end
Handy.dangerous_actions.sell_next_card()
return true
elseif Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.sell_all_same) then
local target_cards = {}
local success, card_center_key = pcall(function()
return card.config.center.key
end)
if success and card_center_key then
for _, area_card in ipairs(card.area.cards) do
local _success, area_card_center_key = pcall(function()
return area_card.config.center.key
end)
if _success and area_card_center_key == card_center_key then
table.insert(target_cards, area_card)
end
end
end
local options = Handy.dangerous_actions.get_options(card)
for _, target_card in ipairs(target_cards) do
Handy.dangerous_actions.process_card(target_card, true, options.remove)
end
Handy.dangerous_actions.sell_next_card()
return true
end
end
return false
end,
execute_hover = function(card)
if not Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.immediate_buy_and_sell) then
return false
end
if not Handy.insta_actions.get_actions().buy_or_sell then
return false
end
local options = Handy.dangerous_actions.get_options(card)
return Handy.dangerous_actions.process_card(card, options.use_queue, options.remove)
end,
use_click = function(card)
return Handy.dangerous_actions.can_execute(card) and Handy.dangerous_actions.execute_click(card) or false
end,
use_hover = function(card)
return Handy.dangerous_actions.can_execute(card) and Handy.dangerous_actions.execute_hover(card) or false
end,
can_execute_tag = function(tag)
return Handy.is_dangerous_actions_active() and tag
end,
execute_tag_click = function(tag)
if Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.card_remove) then
local target_tags = {}
for _, target_tag in ipairs(G.GAME.tags) do
table.insert(target_tags, target_tag)
end
if Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.sell_all) then
for _, target_tag in ipairs(target_tags) do
target_tag:remove()
end
return true
elseif Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.sell_all_same) then
local tag_key = tag.key
for _, target_tag in ipairs(target_tags) do
if target_tag.key == tag_key then
target_tag:remove()
end
end
return true
end
end
return false
end,
use_tag_click = function(tag)
return Handy.dangerous_actions.can_execute_tag(tag) and Handy.dangerous_actions.execute_tag_click(tag) or false
end,
toggle_queue = function(key, released)
if Handy.controller.is_module_key(Handy.cc.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 or G.SETTINGS.paused then
return false
end
if Handy.cc.notifications_level < 2 then
return false
end
local is_sell = Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.immediate_buy_and_sell, true)
if not is_sell then
return false
end
if not Handy.controller.is_module_enabled(Handy.cc.dangerous_actions) then
state.items.prevented_dangerous_actions = {
text = "Unsafe actions disabled in mod settings",
hold = true,
order = 99999999,
}
return true
elseif not Handy.is_dangerous_actions_active() then
state.items.prevented_dangerous_actions = {
text = "Unsafe actions disabled by other mod",
hold = true,
order = 99999999,
}
return true
end
local is_insta_sell = Handy.insta_actions.get_actions().buy_or_sell
and Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.immediate_buy_and_sell)
local is_all = Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.sell_all)
local is_all_same = Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.sell_all_same)
local is_remove = Handy.controller.is_module_key_down(Handy.cc.dangerous_actions.card_remove)
state.dangerous = true
state.items.dangerous_hint = {
text = "[Unsafe] Bugs can appear!",
dangerous = true,
hold = true,
order = 99999999,
}
if is_insta_sell then
local text = is_remove and "Instant REMOVE" or "Instant sell"
if Handy.controller.is_module_enabled(Handy.cc.dangerous_actions.immediate_buy_and_sell.queue) 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,
}
elseif is_all then
local text = is_remove and "REMOVE ALL cards/tags in clicked area" or "Sell ALL cards in clicked area"
state.items.sell_all_same = {
text = text,
hold = true,
order = 12,
dangerous = true,
}
elseif is_all_same then
local text = is_remove and "REMOVE all copies of clicked card/tag" or "Sell all copies of clicked card"
state.items.sell_all_same = {
text = text,
hold = true,
order = 12,
dangerous = true,
}
end
return true
end,
}
Handy.speed_multiplier = {
value = 1,
get_value = function()
if not Handy.is_mod_active() or not Handy.controller.is_module_enabled(Handy.cc.speed_multiplier) then
return 1
end
return Handy.speed_multiplier.value
end,
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.controller.is_module_key_down(Handy.cc.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.cc.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.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.is_mod_active()
and Handy.is_dangerous_actions_active()
and Handy.controller.is_module_enabled(Handy.cc.dangerous_actions.nopeus_unsafe)
)
end,
can_execute = function(key)
return not not (
Handy.nopeus_interaction.is_present()
and Handy.controller.is_module_key_down(Handy.cc.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.cc.notifications_level < 2 then
return false
end
else
if Handy.cc.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,
}
if
not Handy.nopeus_interaction.can_dangerous()
and actions.increase
and G.SETTINGS.FASTFORWARD == (#states - 2)
then
state.items.prevent_nopeus_unsafe = {
text = "Unsafe option disabled in mod settings",
hold = false,
order = 4.05,
}
end
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,
update = function()
GLOBAL_njy_vanilla_override = (
G.STAGE == G.STAGES.RUN
and Handy.is_mod_active()
and Handy.controller.is_module_key_down(Handy.cc.not_just_yet_interaction)
)
return Handy.not_just_yet_interaction.can_execute() and Handy.not_just_yet_interaction.execute() or false
end,
}
--
--
Handy.UI = {
show_options_button = true,
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 = (Handy_Preload and Handy_Preload.current_mod) or SMODS.current_mod
Handy.current_mod.config_tab = true
Handy.UI.show_options_button = not Handy.cc.hide_in_menu
Handy.current_mod.extra_tabs = function()
return Handy.UI.get_options_tabs()
end
G.E_MANAGER:add_event(Event({
func = function()
G.njy_keybind = nil
return true
end,
}))
if Handy_Preload then
Handy_Preload = nil
end
end
function G.FUNCS.handy_toggle_module_enabled(arg, module)
if not module then
return
end
module.enabled = arg
Handy.nopeus_interaction.change(0)
Handy.config.save()
end
function G.FUNCS.handy_toggle_menu_button(arg)
Handy.cc.hide_in_menu = arg
Handy.config.save()
if Handy.current_mod then
Handy.UI.show_options_button = not Handy.cc.hide_in_menu
end
end
function G.FUNCS.handy_change_notifications_level(arg)
Handy.cc.notifications_level = arg.to_key
Handy.config.save()
end
function G.FUNCS.handy_change_keybinds_trigger_mode(arg)
Handy.cc.keybinds_trigger_mode = arg.to_key
Handy.config.save()
end
function G.FUNCS.handy_change_insta_actions_trigger_mode(arg)
Handy.cc.insta_actions_trigger_mode = arg.to_key
Handy.config.save()
end
function G.FUNCS.handy_init_keybind_change(e)
Handy.controller.init_bind(e)
end
if Handy_Preload then
Handy.emplace_steamodded()
end