Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
27a5a4e
tabs ui for rulesets
SleepyG11 Apr 28, 2026
d145720
Merge branch 'dev' of https://github.com/Balatro-Multiplayer/BalatroM…
SleepyG11 Apr 30, 2026
59d031e
Rulesets adjustments
SleepyG11 Apr 30, 2026
f3e45f8
Fix lobby buttons appear when they shouldn't
SleepyG11 Apr 30, 2026
134be96
Fix various visual issues; Balance no-animation timer
SleepyG11 Apr 30, 2026
8cfc90c
Add time on skip for opponent immediately
SleepyG11 Apr 30, 2026
ab37478
text adjustments
SleepyG11 Apr 30, 2026
1eb817d
remove edition before applying new one (crash preventing)
SleepyG11 Apr 30, 2026
e73284d
Fix not proper gamemode UI display
SleepyG11 Apr 30, 2026
d2e3386
Fix blind name size scale
SleepyG11 Apr 30, 2026
f9cd8c0
timer is 300 now, +50 seconds
SleepyG11 Apr 30, 2026
f62227a
Make glass 1.5x bro
SleepyG11 Apr 30, 2026
aad9d46
Fix gold cards & ticket
SleepyG11 Apr 30, 2026
7916668
force game speed when no-animation part comes into play
SleepyG11 May 1, 2026
2897924
proper patch
SleepyG11 May 1, 2026
49c52a3
pvp_timer foundation
SleepyG11 May 2, 2026
b9f32b0
pvp_timer networking
SleepyG11 May 2, 2026
cf6cd2e
Ruleset & UI
SleepyG11 May 2, 2026
0160747
Correct UI display and prevent sync
SleepyG11 May 2, 2026
c2862e7
Pvp timer working implementation
SleepyG11 May 2, 2026
9fd0271
Merge branch 'dev' of https://github.com/Balatro-Multiplayer/BalatroM…
SleepyG11 May 2, 2026
3e02b60
bruh
SleepyG11 May 2, 2026
c2e71b2
restore rulesets changes
SleepyG11 May 2, 2026
b370a71
just in case
SleepyG11 May 2, 2026
cb4c6bc
Merge branch 'dev' of https://github.com/Balatro-Multiplayer/BalatroM…
SleepyG11 May 3, 2026
f2220a1
this new function is fire
SleepyG11 May 3, 2026
4fe2cad
pvp_timer & experimental modifiers
SleepyG11 May 4, 2026
b1460f0
Some safe checks
SleepyG11 May 5, 2026
7a17729
bruh
SleepyG11 May 5, 2026
0e82291
experimental ruleset w/o balance changes
SleepyG11 May 5, 2026
a8967f3
A lot of timer checks and comments
SleepyG11 May 7, 2026
8cf270a
hide_continue_button implementation
SleepyG11 May 9, 2026
8eac2c0
add timer for enemy when skip; more checks;
SleepyG11 May 9, 2026
84c64fd
show increment seconds always
SleepyG11 May 9, 2026
6d9546c
Merge branch 'Balatro-Multiplayer:dev' into pvp-timer
SleepyG11 May 9, 2026
3211903
restore ranked check
SleepyG11 May 9, 2026
ed35ae5
Merge branch 'dev' of https://github.com/Balatro-Multiplayer/BalatroM…
SleepyG11 May 9, 2026
3b16d26
structure experimental rulesets better
SleepyG11 May 10, 2026
527aee4
Let's make gelato and seltzer melt
SleepyG11 May 10, 2026
ae4c27a
proper server-side timer failing; Stalemate resolving
SleepyG11 May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion compatibility/Preview/InitPreview.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function FN.PRE.start_new_coroutine()
-- Defaults are vanilla: free preview, slow (5s) delay.
-- Layers like pressure_timer set both via ruleset scalars.
local timer_delay, timer_cost = 0, 0
if MP.LOBBY and MP.LOBBY.code and not MP.is_pvp_boss() then
if MP.LOBBY and MP.LOBBY.code and MP.LOBBY.config.timer and not MP.is_pvp_boss() then
timer_delay = 5
timer_cost = 0

Expand All @@ -50,6 +50,10 @@ function FN.PRE.start_new_coroutine()
timer_cost > 0
and FN.PRE.data and not FN.PRE.data.empty
and MP.LOBBY.code and not MP.is_pvp_boss()
and MP.LOBBY.config.timer
and not MP.GAME.timer_started
and not MP.GAME.nemesis_timer_started
and not MP.GAME.timer_consumed
then
MP.UI.consume_timer(timer_cost, nil, math.max(10, timer_cost))
end
Expand Down
6 changes: 5 additions & 1 deletion core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ function MP.is_major_league_ruleset()
return MP.LOBBY and MP.LOBBY.config and MP.LOBBY.config.ruleset == "ruleset_mp_majorleague" and MP.LOBBY.code
end

function MP.current_ruleset()
return {}
end

function MP.load_mp_file(file)
local chunk, err = SMODS.load_file(file, "Multiplayer")
if chunk then
Expand Down Expand Up @@ -292,7 +296,7 @@ MP.load_mp_file(networking_dir .. "/action_handlers.lua")

MP.load_mp_dir("gamemodes")
MP.load_mp_dir("layers")
MP.load_mp_dir("rulesets")
MP.load_mp_dir("rulesets", true)
MP.load_mp_dir("ui", true)

if MP.LOBBY.config.weekly then -- this could be a function but why bother
Expand Down
13 changes: 9 additions & 4 deletions layers/_layers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ end
-- Build an mp_include closure that returns true iff any of the named layers is active.
local function layer_membership_include(owning_layers)
return function(_)
for _, layer_name in ipairs(owning_layers) do
if MP.is_layer_active(layer_name) then return true end
end
return false
return MP.is_any_layer_active(owning_layers)
end
end

Expand Down Expand Up @@ -190,8 +187,16 @@ function MP.RunLayerHooks(hook_name)
end

function MP.is_layer_active(layer_name)
if not layer_name then return false end
for _, name in ipairs(MP.active_layer_chain()) do
if name == layer_name then return true end
end
return false
end

function MP.is_any_layer_active(layers)
for _, layer_name in pairs(layers) do
if MP.is_layer_active(layer_name) then return true end
end
return false
end
2 changes: 1 addition & 1 deletion layers/no_anim_timer.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
MP.Layer("no_animation_timer", {
preview_calculate_delay = 1.5,
preview_calculate_cost = 3.5,
timer_base_multiplier = 4/5,
timer_base_multiplier = 2/3,
})
4 changes: 4 additions & 0 deletions layers/pressure_timer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ MP.Layer("pressure_timer", {
timer_speedup_multiplier = 2,
timer_base_multiplier = 2,
})

MP.Layer("pressure_timer_plus", {
timer_hand_played_increment_seconds = 15
})
4 changes: 4 additions & 0 deletions layers/pvp_timer.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
MP.Layer("pvp_timer", {
pvp_timer_base_seconds = 90,
pvp_timer_hand_played_increment_seconds = 15,
})
2 changes: 1 addition & 1 deletion layers/ranked.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MP.Layer("ranked", {
forced_lobby_options = true,
is_disabled = function(self)
is_disabled = function(self)
return MP.UTILS.check_smods_version() or MP.UTILS.check_lovely_version()
end,
force_lobby_options = function(self)
Expand Down
9 changes: 9 additions & 0 deletions lib/insane_int.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ end
-- This doesn't really fit with the comment at the top,
-- but I needed a way to compare highscores without storing this value seperately for no reason
MP.INSANE_INT.greater_than = function(insane_int_display1, insane_int_display2)
if not insane_int_display1 or not insane_int_display2 then return false end
if insane_int_display1.e_count ~= insane_int_display2.e_count then
return tonumber(insane_int_display1.e_count) > tonumber(insane_int_display2.e_count)
end
Expand All @@ -62,6 +63,14 @@ MP.INSANE_INT.greater_than = function(insane_int_display1, insane_int_display2)
return tonumber(insane_int_display1.coeffiocient) > tonumber(insane_int_display2.coeffiocient)
end

MP.INSANE_INT.equal = function(insane_int_display1, insane_int_display2)
if not insane_int_display1 or not insane_int_display2 then return false end
if insane_int_display1.e_count ~= insane_int_display2.e_count then return false end
if insane_int_display1.exponent ~= insane_int_display2.exponent then return false end
if insane_int_display1.coeffiocient ~= insane_int_display2.coeffiocient then return false end
return true
end

-- ignore deprected warning for math.pow
-- math.pow is used instead of ^ to avoid conflicts with talisman's __pow override
-- theoretically the talisman override only applies to their special big number types and using '^' would be fine,
Expand Down
16 changes: 14 additions & 2 deletions lib/ruleset_utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@ end
-- (set by layers like pressure_timer). The multiplier is applied at timer-init sites,
-- so the lobby UI keeps showing the unmultiplied base.
function MP.UTILS.timer_base()
local base = MP.LOBBY.config.timer_base_seconds or 150
local mult = MP.current_ruleset and MP.current_ruleset().timer_base_multiplier or 1
local ruleset = MP.current_ruleset()
local base = MP.LOBBY.config.timer_base_seconds or ruleset.timer_base_seconds or 150
local mult = ruleset.timer_base_multiplier or 1
return base * mult
end

-- Base PvP timer in seconds, accounting for the active ruleset's pvp_timer_base_multiplier
-- (set by layers like pvp_timer). The multiplier is applied at timer-init sites,
-- so the lobby UI keeps showing the unmultiplied base.
function MP.UTILS.pvp_timer_base()
if not MP.is_layer_active("pvp_timer") then return MP.UTILS.timer_base() end
local ruleset = MP.current_ruleset()
local base = MP.LOBBY.config.pvp_timer_base_seconds or ruleset.pvp_timer_base_seconds or 90
local mult = ruleset.pvp_timer_base_multiplier or 1
return base * mult
end

Expand Down
25 changes: 22 additions & 3 deletions localization/en-us.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,23 @@ return {
k_opts_showdown_starting_antes = "Showdown Starts at Ante",
k_opts_pvp_timer_increment = "Timer Increment",
k_opts_pvp_countdown_seconds = "PvP Countdown Seconds",
k_opts_modifier_timer = "Timer Modifier",
k_opts_modifier_timer = "Timer Implementation",
k_experimental_modifiers_timers = {
"- Default: regular {C:chips}150{} {C:inactive}(1x){} seconds timer",
" ",
"- No Animations: {C:chips}100{} {C:inactive}(0.67x){} seconds timer {C:attention}minus animations{}",
" ",
"- Pressure: {C:chips}300{} {C:inactive}(2x){} seconds timer {C:attention}minus animations{}",
" which starts {C:attention}immediately{}",
" ",
"- Pressure+: Same as {C:attention}Pressure{} plus {C:chips}15{} seconds per hand played",
},
b_opts_modifier_pvp_timer = "PvP Timer",
k_experimental_modifiers_pvp_timer = {
"- Timer which available during {C:mult}PvP{} rounds.",
" {C:chips}90{} seconds plus {C:chips}15{} seconds per hand played {C:attention}minus animations{}.",
" Can \"timer\" opponent only when you have {C:attention}higher{} score"
},
k_bl_life = "Life",
k_bl_or = "or",
k_bl_death = "Death",
Expand All @@ -1303,6 +1319,8 @@ return {
k_experimental = "Experimental",
k_experimental_standard = "Experimental (Standard)",
k_experimental_description = "Standard's bleeding edge.\n\nHeavier balance changes being trialed\nfor a future Standard ruleset.\nExpect things to shift between versions.\n\n(See bans and reworks tabs for details)",
k_experimental_no_balance = "Experimental (No Balance)",
k_experimental_no_balance_description = "Standard's bleeding edge.\n\nNo balance changes, only new features.\nExpect things to shift between versions.",
k_traditional = "Traditional",
k_traditional_description = "Multiplayer content without time pressure.\n\nIncludes Multiplayer jokers and balance changes,\nbut removes time-based mechanics for methodical play.\n\nTime-based jokers are banned.\nTimer is disabled.\n\n(See bans and reworks tabs for details)",
k_majorleague = "Major League",
Expand Down Expand Up @@ -1334,7 +1352,7 @@ return {
k_release = "Release Ver.",
k_release_description = "Allan please add details",
k_mp_ruleset_tab_general = "General",
k_mp_ruleset_tab_torunaments = "Tournaments",
k_mp_ruleset_tab_tournaments = "Tournaments",
k_mp_ruleset_tab_experimental = "Experimental",
k_cost_up = "Cost Up",
k_destabilized = "Destabilized",
Expand Down Expand Up @@ -1393,8 +1411,9 @@ return {
},
ml_mp_modifier_timer_opt = {
"Default",
"No Animation",
"No Animations",
"Pressure",
"Pressure+",
},
k_sc_title = "SHORTCUTS",
k_sc_hint = "Press key or release TAB to close",
Expand Down
Loading