Compare commits

..

No commits in common. "main" and "2.9.44" have entirely different histories.
main ... 2.9.44

30 changed files with 289 additions and 947 deletions

View file

@ -33,7 +33,7 @@ table_separator_style = none
trailing_table_separator = smart
# keep/remove/remove_table_only/remove_string_only
call_arg_parentheses = remove
call_arg_parentheses = keep
detect_end_of_line = false

View file

@ -1,52 +0,0 @@
# Roland
[![License](https://img.shields.io/github/license/Emik03/Roland.svg?color=6272a4&style=for-the-badge)](https://github.com/Emik03/Roland/blob/main/LICENSE)
This is a [Steamodded](https://github.com/Steamodded/smods) mod for the video game [Balatro (2024)](https://playbalatro.com).
This mod has been in the works since early 2025, and been worked on continously until its official public release as a celebration for my 23rd birthday.
---
- [About](#about)
- [Contents](#contents)
- [Installation](#installation)
- [License](#license)
---
## About
The main theme of this mod centers around the addition of a new edition: Frozen.
Frozen can be applied to playing cards or jokers and will cause whatever state they have to be locked in place, removing any possibility of future scaling; positive or negative.
The primary goal is to create interesting interactions in-and-outside of the added editions. This does not necessarily mean that these mechanics have to be balanced, but does hopefully make new strategies viable and breathe new life into some of the base game's weaker jokers. It is designed to work very well with its parent mod [Bakery](https://github.com/BakersDozenBagels/BalatroBakery), but can work just as well on its own or with other content mods.
The mod offers a config for accessibility options, general convenience, modifications that I believe make the game healthier, and otherwise purely cosmetic.
## Contents
- 31 Jokers
- 1 Tarot Card
- 4 Spectral Cards
- 2 Vouchers
- 1 Seal
- 1 Edition
- 2 Tags
- 9 Blinds
- 4 Decks
- 4 [Deck Sleeves (CardSleeves)](https://github.com/larswijn/CardSleeves)
- 5 [Charms (Bakery)](https://github.com/BakersDozenBagels/BalatroBakery)
## Installation
1. Set up [Steamodded](https://github.com/Steamodded/smods/wiki#i-want-to-play-with-mods).
2. Set up [Bakery](https://github.com/BakersDozenBagels). You are free to use the [Lite version](https://github.com/BakersDozenBagels/BalatroBakery/tree/lite) if you want to play Roland standalone.
3. Clone the repository into your mods folder, or download it as a zip and place it there.
## License
This repository falls under the [MPL-2 license](https://www.mozilla.org/en-US/MPL/2.0/).
You may copy individual or multiple source files, granted that you keep the copyright license, and disclose the source back to this repository.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -1,19 +1,8 @@
#!/bin/sh
cd "$(dirname "$0")"
directory=$(dirname $(readlink -f "$0"))
for file in 2x/*.png; do
for file in "$directory"/1x/*.png; do
filename=$(basename "$file")
if [ ! -f "1x/${filename}" ]; then
echo "Removing deleted file ${file} from 2x"
rm "2x/${filename}"
fi
done
for file in 1x/*.png; do
filename=$(basename "$file")
output_file="2x/${filename}"
if [ ! -f "$output_file" ] || [ "$file" -nt "$output_file" ]; then
echo "Updating changed file ${file} in 2x/"
output_file="$directory/2x/${filename}"
magick "$file" -filter point -resize 200% "$output_file"
fi
done

View file

@ -11,16 +11,6 @@ return {
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
},
b_Roland_dropship = {
name = "Dropship Deck",
text = {
"{C:attention}Jokers {}are {C:money}free",
"and {C:attention}Perishable",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
unlock = {"Have exclusively", "{C:attention}Perishable Jokers"},
},
b_Roland_swapper = {
name = "Swapper Deck",
text = {
@ -189,7 +179,7 @@ return {
text = {
"Give the {C:attention}Blind",
"{C:attention}Base {}as {X:red,C:white}XMult",
"{C:inactive}(Currently {X:red,C:white}X#1#{C:inactive} Mult)",
"{C:inactive}(Currently {X:red,C:white}X#1#{C:inactive})",
},
},
j_Roland_cold = {
@ -414,16 +404,6 @@ return {
text = {"{X:red,C:white}X#1#{} discards each round"},
unlock = {"Defeat {C:dark_edition}#1#"},
},
j_Roland_yard = {
name = "Yard Sale",
text = {
"Gain {C:money}$#1#{} when a",
"card is {C:attention}added",
"or {C:attention}removed",
"from the deck",
},
unlock = {"Complete the", "{C:attention}#1#", "challenge"},
},
},
Other = {
roland_glass_seal = {
@ -455,26 +435,6 @@ return {
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
},
sleeve_Roland_dropship = {
name = "Dropship Sleeve",
text = {
"{C:attention}Jokers {}are {C:money}free",
"and {C:attention}Perishable",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
unlock = {"Have exclusively", "{C:attention}Perishable Jokers"},
},
sleeve_Roland_dropship_alt = {
name = "Business Sleeve",
text = {
"{C:attention}Jokers {}are instead",
"{C:dark_edition}Negative {}and {C:money}Rental",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
unlock = {"Have exclusively", "{C:attention}Perishable Jokers"},
},
sleeve_Roland_swapper = {
name = "Swapper Sleeve",
text = {
@ -604,7 +564,6 @@ return {
c_Roland_Jokerful = "Jokerful",
c_Roland_Pastries = "Sweet Pastries",
c_Roland_Spin_To_Win = "Spin to Win",
c_Roland_Spring_Cleaning = "Spring Cleaning",
},
v_dictionary = {
b_Roland_add = "ADD",
@ -652,7 +611,6 @@ return {
ch_c_Roland_Showdown_Crimson = {"Showdown blinds are {C:attention}Crimson Heart {}(stacks with above)"},
ch_c_Roland_Showdown_Verdant = {"Showdown blinds are {C:attention}Verdant Leaf {}(stacks with above)"},
ch_c_Roland_Showdown_Violet = {"Showdown blinds are {C:attention}Violet Vessel {}(stacks with above)"},
ch_c_Roland_Spring_Cleaning = {"Earn {C:red}no money {}from sources other than {C:attention}Yard Sale"},
},
},
}

View file

@ -65,19 +65,6 @@ position = "at"
payload = "elseif G.GAME.blind_on_deck == 'Big' then"
match_indent = true
# Prevent Incantation from overriding Faster Planets
[[patches]]
[patches.pattern]
target = '=[SMODS incantation "Incantation.lua"]'
pattern = "function Card:use_consumeable(area, copier)"
position = "after"
payload = """if SMODS.Mods.Roland.config.faster_planets and
self.ability.consumeable.hand_type then
return useconsumeref(self, area, copier)
end"""
match_indent = true
# Misfortune Cookie
[[patches]]
[patches.pattern]
target = "card.lua"
@ -95,7 +82,7 @@ pattern = "local card = create_card('Tarot',G.consumeables, nil, nil, nil, nil,
position = "after"
times = 1
payload = """if next(SMODS.find_card "j_Roland_misfortune") then
card:set_edition("e_negative", true, true)
card:set_edition "e_negative"
end"""
match_indent = true
@ -117,11 +104,10 @@ target = "card.lua"
pattern = "local card = create_card(card_type,G.consumeables, nil, nil, nil, nil, _planet, 'blusl')"
position = "after"
payload = """if next(SMODS.find_card "j_Roland_misfortune") then
card:set_edition("e_negative", true, true)
card:set_edition "e_negative"
end"""
match_indent = true
# Frozen
[[patches]]
[patches.pattern]
target = "card.lua"
@ -166,49 +152,4 @@ target = "card.lua"
pattern = "math\\.floor\\(\\(G\\.GAME\\.dollars \\+ \\(G\\.GAME\\.dollar_buffer or 0\\)\\)/self\\.ability\\.extra\\.dollars\\)"
position = "at"
payload = "SMODS.Mods.Roland.bootstraps_mult(self)"
[[patches]]
[patches.regex]
target = "card.lua"
pattern = "if self\\.ability\\.h_size ~= 0 then"
position = "at"
payload = "if self.ability.h_size and self.ability.h_size ~= 0 then"
[[patches]]
[patches.regex]
target = "card.lua"
pattern = "if self\\.ability\\.d_size > 0 then"
position = "at"
payload = "if self.ability.d_size and self.ability.d_size > 0 then"
[[patches]]
[patches.pattern]
target = '=[SMODS _ "src/utils.lua"]'
pattern = "value = value + card.ability[property]"
position = "at"
payload = "value = value + (card.ability[property] or 0)"
match_indent = true
# [[patches]]
# [patches.pattern]
# target = '=[SMODS DebugPlus "debugplus/console.lua"]'
# pattern = "if toCheck == '/' or toCheck == 'kp/' then"
# position = "at"
# payload = "if toCheck == '/' or toCheck == 'kp/' or toCheck == 'f' then"
# match_indent = true
# [[patches]]
# [patches.pattern]
# target = '=[SMODS DebugPlus "debugplus/console.lua"]'
# pattern = 'local cmdName = string.lower(string.gsub(inputText, "^(%S+).*", "%1"))'
# position = "at"
# payload = 'local cmdName = "eval"'
# match_indent = true
# [[patches]]
# [patches.pattern]
# target = '=[SMODS DebugPlus "debugplus/console.lua"]'
# pattern = 'local rawArgs = string.gsub(inputText, "^%S+%s*(.*)", "%1")'
# position = "at"
# payload = 'local cmdName = inputText'
# match_indent = true

View file

@ -3,7 +3,7 @@
"id": "Roland",
"name": "Roland",
"prefix": "Roland",
"version": "3.0.2",
"version": "2.9.44",
"badge_colour": "8BE9FD",
"display_name": "Roland",
"main_file": "src/main.lua",

View file

@ -1 +0,0 @@
../../smods/src

View file

@ -8,7 +8,7 @@ end
local back = (function()
local x = 0
---@param tbl SMODS.Back|{attributes?: Attributes[], pronouns: string, is_alt: (fun(self: self): boolean), alt_apply?: fun(self: SMODS.Back|table, back: Back|table), alt_calculate?: fun(self: SMODS.Back|table, back: Back|table, context: CalcContext|table): table?, boolean?}
---@param tbl SMODS.Back|{attributes?: Attributes[], is_alt: (fun(self: self): boolean), alt_apply?: fun(self: SMODS.Back|table, back: Back|table), alt_calculate?: fun(self: SMODS.Back|table, back: Back|table, context: CalcContext|table): table?, boolean?}
return function(tbl)
local key = tbl.key
local apply = tbl.apply
@ -32,7 +32,6 @@ local back = (function()
key = key,
pos = tbl.pos,
atlas = "sleeve",
pronouns = tbl.pronouns,
config = tbl.config and f(tbl.config):table() or nil,
loc_vars = function(self, ...)
local ret = tbl.loc_vars and tbl.loc_vars(self, ...) or {}
@ -40,7 +39,7 @@ local back = (function()
return ret
end,
apply = function(self, ...)
return save((tbl.alt_apply and self:is_alt() and tbl.alt_apply or apply)(self, ...))
return (tbl.alt_apply and self:is_alt() and tbl.alt_apply or apply)(self, ...)
end,
calculate = function(self, ...)
return (tbl.alt_calculate and self:is_alt() and tbl.alt_calculate or calculate)(self, ...)
@ -54,20 +53,13 @@ local back = (function()
end
end)()
local back_atlas = SMODS.Atlas {
SMODS.Atlas {
key = "back",
path = "back.png",
px = 71,
py = 95,
}
local extra_atlas = SMODS.Atlas {
key = "back_extra",
path = "backExtra.png",
px = 71,
py = 95,
}
local _ = CardSleeves and SMODS.Atlas {
key = "sleeve",
path = "sleeve.png",
@ -80,10 +72,10 @@ back {
pronouns = "any_all",
config = {extra = {alt_times = 9, times = 3}},
attributes = {"passive", "boss_blind"},
loc_vars = function(self)
loc_vars = function(self, _, _)
return {vars = {self.config.extra.alt_times}}
end,
apply = function()
apply = function(_, _)
G.GAME.modifiers.Roland_blossom_deck = true
end,
calculate = function(self, b, context)
@ -124,7 +116,7 @@ back {
back {
key = "swapper",
pronouns = "he_him",
attributes = {"passive", "editions", "sell_value"},
attributes = {"passive", "spectral", "tarot"},
apply = function(self)
local modifiers = G.GAME.modifiers
modifiers.Roland_swapper_deck = true
@ -133,92 +125,16 @@ back {
calculate = f.noop,
}
local b_dropship = back {
key = "dropship",
pronouns = "he_him",
attributes = {"passive", "generation"},
unlocked = false,
discovered = false,
check_for_unlock = function(_, args)
return args ~= "Bakery_nothing" and
G.jokers and
next((G.jokers or {}).cards) and
not not f(G.jokers.cards):map "ability":all "perishable"
end,
apply = function(self)
G.GAME.modifiers.Roland_dropship = not self:is_alt()
G.GAME.modifiers.Roland_dropship_alt = self:is_alt()
end,
calculate = f.noop,
}
local dropship_tape
q {
blocking = false,
no_delete = true,
func = function()
dropship_tape = SMODS.create_sprite(0, 0, G.CARD_W, G.CARD_H, extra_atlas, {x = 0, y = 0})
end,
}
SMODS.DrawStep {
key = "dropship_tape",
order = 5,
conditions = {vortex = false, facing = "back"},
func = function(self)
if self.children.back.atlas ~= back_atlas or self.children.back.sprite_pos.x ~= b_dropship.pos.x then
return
end
dropship_tape.role.draw_major = self
local deck = self.area.config.type == "deck"
local sticker_offset = self.sticker_offset or {}
-- Don't draw dissolve since it's baked into the sprite already. Also, I had to boost its alpha for the
-- shine to look correct, so drawing this would look *awful*.
-- dropship_tape:draw_shader("dissolve", nil, nil, deck, self.children.center, self.sticker_rotation, sticker_offset.x, sticker_offset.y)
dropship_tape:draw_shader(
"voucher",
nil,
self.ARGS.send_to_shader,
deck,
self.children.center,
self.sticker_rotation,
sticker_offset.x,
sticker_offset.y
)
end,
}
local swapper = {Spectral = "Tarot", Tarot = "Spectral"}
local orig_create_card = create_card
local function create_dropshipped_card(_type, ...)
local ret = orig_create_card(_type, ...)
local ok = _type == "Joker" and ret.ability
if ok and G.GAME.modifiers.Roland_dropship_alt then
ret.ability.rental = true
local _ = ret:set_edition({negative = true}, true, true)
end
if ok and G.GAME.modifiers.Roland_dropship then
ret.ability.perish_tally = 5
ret.ability.perishable = true
ret.cost, ret.sell_cost = 0, 0
end
return ret
end
function create_card(_type, ...)
if not G.GAME.modifiers.Roland_swapper_deck then
return create_dropshipped_card(_type, ...)
return orig_create_card(_type, ...)
end
if not G.GAME.modifiers.Roland_alt_swapper_deck then
return create_dropshipped_card(swapper[_type] or _type, ...)
return orig_create_card(swapper[_type] or _type, ...)
end
local type = f(SMODS.ConsumableTypes):keys():where(function(v)
@ -226,8 +142,7 @@ function create_card(_type, ...)
end):table()
local has_type = SMODS.ConsumableTypes[_type]
return create_dropshipped_card(
has_type and pseudorandom_element(type, pseudoseed "Roland_alt_swapper_deck") or _type, ...)
return orig_create_card(has_type and pseudorandom_element(type, pseudoseed "Roland_alt_swapper_deck") or _type, ...)
end
local function redeem(key, fast)
@ -239,22 +154,21 @@ local function redeem(key, fast)
delay = 1,
func = function()
local voucher = SMODS.create_card {key = key, set = "Voucher"}
voucher.cost = 0
voucher.T.x = (fast and pseudorandom(pseudoseed "Roland_wish_deck") + 0.5 or 1) * 9
voucher.T.y = (fast and pseudorandom(pseudoseed "Roland_wish_deck") + 0.5 or 1) * 4.25
voucher.T.x = (fast and pseudorandom(pseudoseed "Roland_wish_deck") + 0.5 or 1) * 9
voucher.cost = 0
voucher:start_materialize()
q {
delay = 1,
front = true,
func = function()
if fast then
play_sound "card1"
voucher:apply_to_run()
voucher:juice_up(0.3, 0.5)
else
local state = G.STATE
voucher:redeem()
G.STATE = state
end
q {

View file

@ -163,7 +163,7 @@ blind {
local _ = not b.disabled and context.drawing_cards and table.sort(G.deck.cards, sort_by_enhancement)
end,
in_pool = function()
return G.playing_cards and not not f(G.playing_cards):any(has_enhancement)
return G.playing_cards and f(G.playing_cards):any(has_enhancement)
end,
}
@ -257,7 +257,7 @@ blind {
boss = {min = 6},
boss_colour = HEX "bdaeccff",
pronouns = "they_them",
collection_loc_vars = function()
collection_loc_vars = function(_)
return {
vars = {localize {
type = "variable",
@ -265,7 +265,7 @@ blind {
}},
}
end,
loc_vars = function()
loc_vars = function(_)
local _, name = common_rank()
return {vars = {localize(name or "Ace", "ranks")}}
end,
@ -319,7 +319,7 @@ blind {
dollars = 5,
defeat = disable_improbable,
disable = disable_improbable,
set_blind = function()
set_blind = function(_)
G.GAME.modifiers.Roland_improbable = true
end,
}
@ -380,8 +380,8 @@ function SMODS.current_mod:calculate(context)
:map(f.index_into(G.P_CENTERS))
:where("config.extra.hand_type", context.scoring_name)
:pun "SMODS.Voucher"
:each(function(v)
SMODS.smart_level_up_hand(nil, v.config.extra.hand_type, false, v.config.extra.amount)
:each(function(v, _)
SMODS.smart_level_up_hand(nil, v.config.extra.hand_type, nil, v.config.extra.amount)
end)
local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities
@ -404,7 +404,7 @@ function SMODS.current_mod:calculate(context)
local normal = orig.normal
G.GAME.probabilities = setmetatable({}, {
local mt = {
orig = orig,
__index = function(_, k)
return k == "normal" and 0 or orig[k]
@ -412,7 +412,11 @@ function SMODS.current_mod:calculate(context)
__newindex = function(_, k, v)
orig[k] = (k == "normal" and v == 0) and normal or v
end,
})
}
local proxy = {}
setmetatable(proxy, mt)
G.GAME.probabilities = proxy
end
blind {

View file

@ -1,26 +1,19 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
---@return {banned_cards: {}, banned_tags: {}, banned_other: {}}
local function bans()
return {banned_cards = {}, banned_tags = {}, banned_other = {}}
end
---@param tbl table
---@return fun(v: SMODS.GameObject)
local function adder(tbl)
return function(v)
table.insert(tbl, {id = v.key, type = "blind"})
end
end
---@param v SMODS.GameObject
---@return boolean
local function is_banned_from_pastry(v)
return not ({Bakery = true, Roland = true})[(v.mod or {}).id]
end
---@param v SMODS.Center
---@return boolean
local function is_center_banned_from_pastry(v)
local arcana_boosters = {
p_arcana_normal_1 = true,
@ -37,35 +30,21 @@ local function is_center_banned_from_pastry(v)
return arcana_boosters[v.key] or pastries_targets[v.set] and is_banned_from_pastry(v)
end
---@param v SMODS.GameObject
---@return boolean|string
local function is_economy(v)
return f(Bakery_API.econ_only_items):where(f.eq(v.key)):any()
end
---@param key string
---@return fun(v: SMODS.Blind, k: string): boolean?
local function is_showdown_except(key)
---@param v SMODS.Blind
---@param k string
---@return boolean?
return function(v, k)
return v.boss and v.boss.showdown and key ~= k
end
end
---@param name string
---@param amount number
---@return {id: string}[]
local function multirule(name, amount)
return f(amount):map(function(v)
return {id = "Roland_" .. name .. v}
end):table()
end
local pastries, amber, cerulean, crimson, verdant, violet, yard =
bans(), bans(), bans(), bans(), bans(), bans(), bans()
local pastries, amber, cerulean, crimson, verdant, violet =
bans(), bans(), bans(), bans(), bans(), bans()
SMODS.Challenge {
key = "Glass",
rules = {custom = multirule("Glass", 2)},
rules = {custom = {{id = "Roland_Glass1"}, {id = "Roland_Glass2"}}},
pronouns = "they_them",
config = {extra = {odds = 2}},
calculate = function(self, context)
@ -82,24 +61,6 @@ SMODS.Challenge {
end,
}
SMODS.Challenge {
key = "Spring_Cleaning",
config = {no_interest = true},
consumeables = {{id = "c_hanged_man"}},
jokers = {{id = "j_Roland_yard"}, {id = "j_Roland_yard"}},
vouchers = {{id = "v_magic_trick"}, {id = "v_overstock_norm"}},
rules = {custom = {{id = "Roland_Spring_Cleaning"}}},
restrictions = yard,
apply = function()
G.GAME.modifiers.no_blind_reward = {Small = true, Big = true, Boss = true}
G.GAME.modifiers.no_extra_hand_money = true
f(Bakery_API.econ_only_items):keys():where(f.nq "j_Roland_yard"):each(function(v)
G.GAME.banned_keys[v] = true
end)
end,
}
SMODS.Challenge {
key = "Go",
rules = {custom = {{id = "Roland_Go"}}},
@ -330,33 +291,8 @@ SMODS.Challenge {
end,
}
q {
blocking = false,
no_delete = true,
func = function()
if not Bakery_API or not Bakery_API.no_money_decks then
return false
end
table.insert(Bakery_API.econ_only_items, "j_Roland_yard")
table.insert(Bakery_API.econ_only_items, "j_Roland_artemis")
local orig = Bakery_API.no_money_decks
Bakery_API.no_money_decks = setmetatable({}, {
orig = orig,
__index = function(_, k)
return (G.GAME or {}).challenge == "c_Roland_Spring_Cleaning" or orig[k]
end,
})
f(spin_to_win.jokers):each(function(v)
v.id = G.P_CENTERS.j_Bakery_Spinner and "j_Bakery_Spinner" or v.id
end)
q(function()
f {
{G.P_TAGS, is_economy, yard.banned_tags},
{G.P_BLINDS, is_economy, yard.banned_other},
{G.P_CENTERS, is_economy, yard.banned_cards},
{G.P_TAGS, is_banned_from_pastry, pastries.banned_tags},
{G.P_BLINDS, is_banned_from_pastry, pastries.banned_other},
{G.P_CENTERS, is_center_banned_from_pastry, pastries.banned_cards},
@ -368,5 +304,14 @@ q {
}:each(function(v)
f(v[1]):where(v[2]):each(adder(v[3]))
end)
end,
}
end)
q(function()
if not G.P_CENTERS.j_Bakery_Spinner then
return true
end
f(spin_to_win.jokers):each(function(v)
v.id = "j_Bakery_Spinner"
end)
end)

View file

@ -1,28 +0,0 @@
-- Special shoutout to all contributors. <3
return {
aster = {
name = "asterSSH",
fg = HEX "f8f8f2ff",
bg = HEX "bd93f9ff",
},
bakersdozenbagels = {
name = "BakersDozenBagels",
fg = HEX "362708ff",
bg = HEX "edd198ff",
},
ghostlyfield = {
name = "ghostlyfield",
fg = HEX "ffffffff",
bg = HEX "b290e6ff",
},
hamester = {
name = "Hamester",
fg = HEX "ffffffff",
bg = HEX "ffa100ff",
},
redstoad = {
name = "RedsToad",
fg = HEX "ffffffff",
bg = HEX "da4044ff",
},
}

View file

@ -53,22 +53,20 @@ local function freeze(card)
return card.ability
end
(card.ability or {}).Roland_crimson = not not (card.ability or {}).Roland_crimson
local config = card.config or {}
(card.ability or {}).Roland_crimson = not not (card.ability or {}).Roland_crimson
card.Roland_frozen_base = card.Roland_frozen_base or copy(card.base)
card.Roland_frozen_card = card.Roland_frozen_card or copy(config.card)
card.Roland_frozen_ability = card.Roland_frozen_ability or copy(card.ability)
card.base = card.Roland_frozen_base and copy(card.Roland_frozen_base) or card.base
local diff = type(card.set_sprites) == "function" and
f(card.Roland_frozen_card):diff(config.card, f.chain(type, f.nq "table"))
config.card = card.Roland_frozen_card and copy(card.Roland_frozen_card) or config.card
card.ability = card.Roland_frozen_ability and copy(card.Roland_frozen_ability) or card.ability
card.Roland_frozen = card.Roland_frozen or {probability = SMODS.get_probability_vars(card, 1, 1)}
if diff then
if type(card.set_sprites) == "function" and
f(card.Roland_frozen_card):diff(config.card) then
card:set_sprites(card.config.center, card.config.card)
end
@ -274,7 +272,6 @@ function Card:save(...)
return ret
end
local is_bean = {["Turtle Bean"] = true, j_mp_turtle_bean = true}
local orig_calculate_joker = Card.calculate_joker
---@param self Card|{Roland_frozen_ability?: table}
@ -286,7 +283,7 @@ function Card:calculate_joker(context, ...)
{xmult = self.ability.extra.Xmult} or nil
end
if is_frozen and is_bean[self.ability.name] then
if is_frozen and self.ability.name == "Turtle Bean" then
return
end
@ -329,6 +326,8 @@ function Card:calculate_dollar_bonus(...)
return orig_calculate_dollar_bonus(self, ...)
end
local orig_get_probability_vars = SMODS.get_probability_vars
function SMODS.current_mod.bootstraps_mult(card)
local ability = card.Roland_frozen_ability
local ret = math.floor((G.GAME.dollars + (G.GAME.dollar_buffer or 0)) / card.ability.extra.dollars)
@ -341,17 +340,9 @@ function SMODS.current_mod.bootstraps_mult(card)
return ability.Roland_bootstraps_mult
end
local orig_get_probability_vars = SMODS.get_probability_vars
function SMODS.get_probability_vars(trigger_obj, ...)
local numerator, denominator = orig_get_probability_vars(trigger_obj, ...)
local override = ((trigger_obj or {}).Roland_frozen or {}).probability
if override then
return type(denominator) == "table" and to_big(override) or override, denominator
else
return numerator, denominator
end
return trigger_obj and (trigger_obj.Roland_frozen or {}).probability or numerator, denominator
end
q {

View file

@ -54,10 +54,24 @@ local joker = (function()
end
end)()
---@generic T
---@param tbl T[]
---@return fun(t: T[], i: integer): integer, T
---@return T[]
local function ipairs_reversed(tbl)
return function(t, i)
local k = i and i - 1 or #t
local v = t[k]
if v ~= nil then
return k, v
end
end, tbl
end
---@param card Card
---@return boolean
local function is_frozen(card)
return card and card.edition and card.edition.Roland_frozen
return card.edition and card.edition.key == "e_Roland_frozen"
end
---@param card? Card|false|{suit: string, value: string}
@ -196,8 +210,8 @@ joker {
joker {
key = "sunny",
pronouns = "it_its",
artist = "char",
idea = "redstoad",
artist = "ghostlyfield",
attributes = {"food", "on_sell"},
cost = 2,
rarity = 1,
@ -218,8 +232,8 @@ joker {
SMODS.calculate_context {drawing_cards = true, draw = {G.deck.cards}}
SMODS.calculate_context {
hand_drawn = facing_blind and {G.deck.cards[1]} or true,
other_drawn = not facing_blind and {G.deck.cards[1]} or true,
hand_drawn = facing_blind and {G.deck.cards[1]} and true,
other_drawn = not facing_blind and {G.deck.cards[1]} and true,
first_hand_drawn = not current_round.any_hand_drawn and facing_blind or nil,
}
@ -243,8 +257,7 @@ joker {
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
end,
calculate = function(_, _, context)
return (context.selling_self or context.forcetrigger) and
f(G.hand.cards):where("edition", false):each(function(v)
return (context.selling_self or context.forcetrigger) and f(G.hand.cards):each(function(v)
q {
delay = 0.1,
func = function()
@ -274,9 +287,7 @@ joker {
delay = 1,
func = function()
play_sound "timpani"
SMODS.add_card {edition = "e_negative", key = v.config.center.key, silent = true}
.ability.qty = v.ability.qty
SMODS.add_card {edition = "e_negative", key = v.config.center.key}
end,
}
end) or nil
@ -286,7 +297,7 @@ joker {
joker {
key = "monomino",
pronouns = "it_its",
artist = "ghostlyfield",
artist = "char",
config = {extra = {price = 4, hand_name = "Four of a Kind"}},
attributes = {"sell_value", "scaling", "economy", "hand_type"},
cost = 4,
@ -315,7 +326,7 @@ joker {
joker {
key = "domino",
pronouns = "it_its",
artist = "ghostlyfield",
artist = "char",
config = {extra = {mult_gain = 4, hand_name = "Four of a Kind", mult = 0}},
attributes = {"mult", "scaling", "hand_type"},
cost = 4,
@ -346,7 +357,7 @@ joker {
joker {
key = "trimino",
pronouns = "it_its",
artist = "ghostlyfield",
artist = "char",
config = {extra = {times = 4, hand_name = "Four of a Kind"}},
attributes = {"generation", "hand_type"},
cost = 8,
@ -405,7 +416,7 @@ joker {
joker {
key = "misfortune",
pronouns = "she_they",
artist = "ghostlyfield",
artist = "char",
attributes = {"discard", "passive", "tarot", "planet", "generation", "seals"},
cost = 6,
rarity = 2,
@ -425,7 +436,6 @@ joker {
joker {
key = "polaris",
pronouns = "he_him",
artist = "ghostlyfield",
attributes = {"editions", "space"},
cost = 6,
rarity = 1,
@ -451,7 +461,7 @@ joker {
card.Roland_polaris = true
local first = context.scoring_hand[1]
if not first or first.edition then
if not first or first.edition and first.edition.Roland_frozen then
return
end
@ -498,7 +508,6 @@ joker {
joker {
key = "snowsquall",
pronouns = "he_they",
artist = "ghostlyfield",
config = {extra = {mult_gain = 3, mult = 0}},
attributes = {"mult", "scaling", "hand_type"},
cost = 6,
@ -530,37 +539,13 @@ joker {
end,
}
---comment
---@param card Card
---@param context table|CalcContext
---@return function
local function copy(card, context)
---@param v Card
return function(v)
local override_compat = v.config.center.blueprint_compat == nil
if override_compat then
v.config.center.blueprint_compat = true
end
local ret = SMODS.blueprint_effect(card, v, context)
if override_compat then
v.config.center.blueprint_compat = nil
end
return ret
end
end
joker {
key = "arctic",
pronouns = "they_them",
artist = "ghostlyfield",
cost = 10,
rarity = 3,
config = {extra = {frozen = 1, non_frozen = 0, color = "DARK_EDITION"}},
attributes = {"retrigger", "editions", "copying"},
config = {extra = {frozen = 1, non_frozen = 0, max_stack = 6, color = "DARK_EDITION"}},
attributes = {"retrigger", "editions"},
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
@ -570,9 +555,9 @@ joker {
calculate = function(_, card, context)
local extra = card.ability.extra
if f(context.blueprint_copiers_stack)
:where("config.center.key", card.config.center.key)
:any() then
if extra.max_stack and
context.blueprint_copiers_stack and
extra.max_stack < #context.blueprint_copiers_stack then
return
end
@ -585,15 +570,14 @@ joker {
end
return SMODS.merge_effects(
f(G)
:where(getmetatable, CardArea)
:flatmap("cards", ipairs)
:where(is_frozen)
:map(copy(card, context))
:where(type, "table")
:map(f.insert(G.C[extra.color], "colour"))
:values()
:table()
f(G.jokers.cards):where(is_frozen):where(function(v)
return v.config.center.key ~= card.config.center.key
end):map(function(v)
return SMODS.blueprint_effect(card, v, context)
end):where(type, "table"):map(function(v)
v.colour = G.C[extra.color]
return v
end):values():table()
)
end,
}
@ -601,8 +585,8 @@ joker {
joker {
key = "sapling",
pronouns = "they_them",
artist = "char",
idea = "redstoad",
artist = "ghostlyfield",
attributes = {"mult", "suit"},
cost = 4,
rarity = 1,
@ -735,9 +719,13 @@ joker {
{card = card, xmult = card.ability.extra.xmult} or nil
end,
crimson = function()
local _ = G.jokers and f(G.jokers.cards, f.revpairs):where(is_frozen, false):each(function(v)
local _ = G.jokers and f(G.jokers.cards, ipairs_reversed):where(is_frozen, false):each(function(v)
local right = G.jokers.cards[v.rank + 1]
local debuffed_by_crimson = right and not right.debuff and right.config.center.key == "j_Roland_crimson"
local debuffed_by_crimson = right and
not right.debuff and
right.config.center.key == "j_Roland_crimson" and
not ({j_mp_turtle_bean = true, j_turtle_bean = true})[v.config.center.key]
if debuffed_by_crimson and v.ability.Roland_crimson == nil then
local debuff = not not v.debuff
@ -839,7 +827,6 @@ joker {
joker {
key = "venerable",
pronouns = "any_all",
artist = "ghostlyfield",
defeated_blind = "bl_Roland_venerable_visage",
config = {extra = {xdiscard = 3}},
pixel_size = {w = 68, h = 68},
@ -866,45 +853,11 @@ joker {
ease_discard(round_resets.discards - discards)
end,
}
joker {
key = "yard",
pronouns = "he_him",
artist = "ghostlyfield",
config = {extra = {dollars = 3}},
attributes = {"economy"},
cost = 6,
rarity = 1,
unlocked = true,
discovered = true,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
check_for_unlock = function()
return not not f.indices "challenge_progress.completed.c_Roland_Spring_Cleaning" (G.PROFILES[G.SETTINGS.profile])
end,
locked_loc_vars = function()
return {vars = {localize("c_Roland_Spring_Cleaning", "challenge_names")}}
end,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.dollars}}
end,
calculate = function(_, card, c)
local ret = (c.forcetrigger or c.playing_card_added or c.remove_playing_cards) and
{dollars = card.ability.extra.dollars * #(c.cards or c.removed or {c.forcetrigger})} or nil
if ret and (G.GAME or {}).challenge == "c_Roland_Spring_Cleaning" then
G.GAME.dollars = G.GAME.dollars + ret.dollars
else
return ret
end
end,
}
joker {
key = "bulldozer",
pronouns = "she_her",
idea = "redstoad",
artist = "ghostlyfield",
attributes = {"xmult"},
cost = 6,
rarity = 2,
@ -967,8 +920,8 @@ joker {
joker {
key = "nilly",
pronouns = "any_all",
idea = "redstoad",
artist = "ghostlyfield",
idea = "redstoad",
config = {extra = {flipped = false}},
attributes = {"xmult", "bakery_double_sided"},
cost = 0,
@ -1004,7 +957,6 @@ joker {
key = "martingale",
pronouns = "he_him",
idea = "redstoad",
artist = "ghostlyfield",
config = {extra = {odds = 2}},
attributes = {"xmult", "chance"},
cost = 8,
@ -1068,7 +1020,6 @@ joker {
joker {
key = "idle",
pronouns = "any_all",
artist = "ghostlyfield",
rarity = 2,
Roland_idle_capacity = 6,
cost = G.P_CENTERS.j_idol.cost,
@ -1145,7 +1096,6 @@ joker {
joker {
key = "suitable",
pronouns = "she_they",
artist = "ghostlyfield",
attributes = {"suit", "passive", "hearts", "diamonds", "spades", "clubs"},
cost = 4,
rarity = 2,
@ -1198,8 +1148,8 @@ end
joker {
key = "artemis",
pronouns = "any_all",
idea = "redstoad",
artist = "hamester",
idea = "redstoad",
cost = 6,
rarity = 2,
config = {extra = {
@ -1265,7 +1215,6 @@ joker {
key = "excalibur",
pronouns = "he_him",
idea = "redstoad",
artist = "ghostlyfield",
cost = 8,
rarity = 3,
config = {extra = {
@ -1276,7 +1225,7 @@ joker {
}},
attributes = {"xmult", "enhancements", "bakery_double_sided"},
eternal_compat = true,
blueprint_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, info_queue, card)
local extra = card.ability.extra
@ -1337,7 +1286,7 @@ joker {
joker {
key = "oops",
pronouns = "she_they",
artist = "ghostlyfield",
artist = "char",
cost = 7,
rarity = 3,
config = {extra = {probability = 1, probability_mult = 2, reset = 1}},

View file

@ -8,44 +8,8 @@
local f = {}
if not f then
---@generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R
---@param self F
---@param first? T1
---@param second? T2
---@param third? T3
---@param fourth? T4
---@param fifth? T5
---@param sixth? T6
---@param seventh? T7
---@param eighth? T8
---@param ninth? T9
---@param tenth? T10
---@return fun(v: fun(first: T1, second: T2, third: T3, fourth: T4, fifth: T5, sixth: T6, seventh: T7, eighth: T8, ninth: T9, tenth: T10): R): R
---@nodiscard
function f:call(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth)
error {first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth}
end
---@generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R
---@param self string
---@param first? T1
---@param second? T2
---@param third? T3
---@param fourth? T4
---@param fifth? T5
---@param sixth? T6
---@param seventh? T7
---@param eighth? T8
---@param ninth? T9
---@param tenth? T10
---@return fun(v: {[string]: fun(self: self, first: T1, second: T2, third: T3, fourth: T4, fifth: T5, sixth: T6, seventh: T7, eighth: T8, ninth: T9, tenth: T10): R}): R
---@nodiscard
function f.call(self, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth)
error {self, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth}
end
---@generic I, O
---@param first integer|string|fun(v: I): O
---@param first string|fun(v: I): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first)
@ -53,8 +17,8 @@ if not f then
end
---@generic I, T, O
---@param first integer|string|fun(v: I): T
---@param second integer|string|fun(v: T): O
---@param first string|fun(v: I): T
---@param second string|fun(v: T): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second)
@ -62,9 +26,9 @@ if not f then
end
---@generic I, T1, T2, O
---@param first integer|string|fun(v: I): T1
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: T2): O
---@param first string|fun(v: I): T1
---@param second string|fun(v: T1): T2
---@param third string|fun(v: T2): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third)
@ -72,10 +36,10 @@ if not f then
end
---@generic I, T1, T2, T3, O
---@param first integer|string|fun(v: I): T1
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: T2): T3
---@param fourth integer|string|fun(v: T3): O
---@param first string|fun(v: I): T1
---@param second string|fun(v: T1): T2
---@param third string|fun(v: T2): T3
---@param fourth string|fun(v: T3): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third, fourth)
@ -83,11 +47,11 @@ if not f then
end
---@generic I, T1, T2, T3, T4, O
---@param first integer|string|fun(v: I): T1
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: T2): T3
---@param fourth integer|string|fun(v: T3): T4
---@param fifth integer|string|fun(v: T4): O
---@param first string|fun(v: I): T1
---@param second string|fun(v: T1): T2
---@param third string|fun(v: T2): T3
---@param fourth string|fun(v: T3): T4
---@param fifth string|fun(v: T4): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third, fourth, fifth)
@ -95,7 +59,7 @@ if not f then
end
---@generic I, O
---@param all { [1]: (integer|string|fun(v: I): O) }
---@param all { [1]: (string|fun(v: I): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
@ -103,7 +67,7 @@ if not f then
end
---@generic I, T, O
---@param all { [1]: (integer|string|fun(v: I): T), [2]: (integer|string|fun(v: T): O) }
---@param all { [1]: (string|fun(v: I): T), [2]: (string|fun(v: T): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
@ -111,7 +75,7 @@ if not f then
end
---@generic I, T1, T2, O
---@param all { [1]: (integer|string|fun(v: I): T1), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: T2): O) }
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
@ -119,7 +83,7 @@ if not f then
end
---@generic I, T1, T2, T3, O
---@param all { [1]: (integer|string|fun(v: I): T1), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: T2): T3), [4]: (integer|string|fun(v: T3): O) }
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): T3), [4]: (string|fun(v: T3): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
@ -127,103 +91,13 @@ if not f then
end
---@generic I, T1, T2, T3, T4, O
---@param all { [1]: (integer|string|fun(v: I): T1), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: T2): T3), [4]: (integer|string|fun(v: T3): T4), [4]: (integer|string|fun(v: T4): O) }
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): T3), [4]: (string|fun(v: T3): T4), [4]: (string|fun(v: T4): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
error(all)
end
---@generic I, O
---@param first integer|string|fun(v: I): O
---@return fun(v: I): O
---@nodiscard
function f.compose(first)
error {first}
end
---@generic I, T, O
---@param first integer|string|fun(v: T): O
---@param second integer|string|fun(v: I): T
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second)
error {first, second}
end
---@generic I, T1, T2, O
---@param first integer|string|fun(v: T2): O
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: I): T1
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second, third)
error {first, second, third}
end
---@generic I, T1, T2, T3, O
---@param first integer|string|fun(v: T3): O
---@param second integer|string|fun(v: T2): T3
---@param third integer|string|fun(v: T1): T2
---@param fourth integer|string|fun(v: I): T1
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second, third, fourth)
error {first, second, third, fourth}
end
---@generic I, T1, T2, T3, T4, O
---@param first integer|string|fun(v: T4): O
---@param second integer|string|fun(v: T3): T4
---@param third integer|string|fun(v: T2): T3
---@param fourth integer|string|fun(v: T1): T2
---@param fifth integer|string|fun(v: I): T1
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second, third, fourth, fifth)
error {first, second, third, fourth, fifth}
end
---@generic I, O
---@param all { [1]: (integer|string|fun(v: I): O) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T, O
---@param all { [1]: (integer|string|fun(v: T): O), [2]: (integer|string|fun(v: I): T) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T1, T2, O
---@param all { [1]: (integer|string|fun(v: T2): O), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: I): T1) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T1, T2, T3, O
---@param all { [1]: (integer|string|fun(v: T3): O), [2]: (integer|string|fun(v: T2): T3), [3]: (integer|string|fun(v: T1): T2), [4]: (integer|string|fun(v: I): T1) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T1, T2, T3, T4, O
---@param all { [1]: (integer|string|fun(v: T4): O), [2]: (integer|string|fun(v: T3): T4), [3]: (integer|string|fun(v: T2): T3), [4]: (integer|string|fun(v: T1): T2), [4]: (integer|string|fun(v: I): T1) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic K, V
---@param self F | { [K]: V }
---@return K?, V?
@ -240,17 +114,7 @@ local none
---@return T
---@nodiscard
local function autofunc(func)
if func == false then
return f.fals
elseif func == true then
return f.tru
elseif type(func) == "number" then
return f.arg(func)
elseif type(func) == "string" then
return f.indices(func)
else
return func or f.id
end
return type(func) == "string" and f.indices(func) or func or f.id
end
---@generic K, V
@ -261,7 +125,7 @@ end
---@return V
---@nodiscard
local function autopairs(tbl, fpairs)
return (fpairs or (tbl[1] and ipairs or pairs))(tbl)
return (fpairs or (tbl[#tbl] and ipairs or pairs))(tbl)
end
--- Always returns nil.
@ -295,7 +159,7 @@ end
---@generic T
---@param value T
---@return fun(v: T): boolean
---@return fun(T): boolean
---@nodiscard
function f.eq(value)
return function(v)
@ -366,11 +230,11 @@ end
---@nodiscard
function f.indices(v)
return function(x)
for i in v:gmatch "[^.]+" do
if type(x) ~= "table" then
return x
end
for i in v:gmatch "[^.]+" do
x = x[i]
end
@ -378,36 +242,6 @@ function f.indices(v)
end
end
---@generic K, V
---@param v V
---@param k K
---@return fun(x: { [K]: V }): { [K]: V }
---@nodiscard
function f.insert(v, k)
return function(x)
if type(x) == "table" then
x[k] = v
end
return x
end
end
---@generic T
---@param tbl T[]
---@return fun(t: T[], i?: integer): integer, T
---@return T[]
function f.revpairs(tbl)
return function(t, i)
local k = i and i - 1 or #t
local v = t[k]
if v ~= nil then
return k, v
end
end, tbl
end
---@param any any
---@return boolean
---@nodiscard
@ -415,24 +249,10 @@ function f.isf(any)
return type(any) == "table" and any.from == f.from and any.new == f.new
end
local unpack = table.unpack or unpack
f[true and "call"] = function(self, ...)
local args = {...}
return type(self) == "string" and function(v)
return v[self](v, unpack(args))
end or function(v)
return v(unpack(args))
end
end
f[true and "chain"] = function(...)
local ret
for _, v in ipairs {...} do
v = f.isf(v) and v:table() or v
if type(v) == "table" then
for _, vv in ipairs(v) do
local copy = ret
@ -455,10 +275,6 @@ f[true and "chain"] = function(...)
return ret or f.noop
end
f[true and "compose"] = function(...)
return f.chain(f.from({...}, f.revpairs):values())
end
---@generic K, V
---@param fnext? fun(): K?, V?
---@return F|{ [K]: V }
@ -469,9 +285,7 @@ function f.new(fnext)
all = f.all,
any = f.any,
arg = f.arg,
call = f.call,
chain = f.chain,
compose = f.compose,
concat = f.concat,
const = f.const,
count = f.count,
@ -486,7 +300,6 @@ function f.new(fnext)
index = f.index,
index_into = f.index_into,
indices = f.indices,
insert = f.insert,
isf = f.isf,
keys = f.keys,
map = f.map,
@ -496,8 +309,6 @@ function f.new(fnext)
nq = f.nq,
peek = f.peek,
pun = f.pun,
rev = f.rev,
revpairs = f.revpairs,
skip = f.skip,
slice = f.slice,
string = f.string,
@ -556,10 +367,10 @@ function f.from(iter, fpairs, step)
end
end)
else
local next, tbl, k, v = autopairs(iter, type(fpairs) == "function" and fpairs or nil)
local next, context, k, v = autopairs(iter, type(fpairs) == "function" and fpairs or nil)
return f.new(function()
k, v = next(tbl, k)
k, v = next(context, k)
return k, v
end)
end
@ -571,14 +382,12 @@ end
---@return F|{ [K]: V }
---@nodiscard
function f:concat(...)
self = f.isf(self) and self or f.from(self)
local fsi = 0
local fs = {...}
local sum, last = 0, 0
for i = 1, #fs do
local s = fs[i]
fs[i] = f.isf(s) and s or f.from(s)
fs[i] = f.isf(fs[i]) and fs[i] or f.from(fs[i])
end
return f.new(function()
@ -612,7 +421,6 @@ end
---@return F|{ [K]: V }
---@nodiscard
function f:peek(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
return f.new(function()
@ -632,7 +440,6 @@ end
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@nodiscard
function f:map(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
return f.new(function()
@ -652,14 +459,11 @@ end
---@overload fun(self: F|{ [K]: V }, func: string, fpairs?: fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)): F|{ [K]: U }
---@nodiscard
function f:flatmap(func, fpairs)
self = f.isf(self) and self or f.from(self)
-- local i = 0
local vt, vk, vp
local vt, vk, vv, vp
func = autofunc(func)
return f.new(function()
local vv
if vk then
vk, vv = vp(vt, vk)
@ -705,7 +509,6 @@ end
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string, is?: any): F|{ [K]: V }
function f:where(func, is)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
return f.new(function()
@ -738,7 +541,6 @@ end
---@return F|{ [integer]: K }
---@nodiscard
function f:keys()
self = f.isf(self) and self or f.from(self)
local i = 0
return f.new(function()
@ -756,7 +558,6 @@ end
---@return F|{ [integer]: V }
---@nodiscard
function f:values()
self = f.isf(self) and self or f.from(self)
local i = 0
return f.new(function()
@ -774,8 +575,6 @@ end
---@return F|{ [V]: K }
---@nodiscard
function f:swap()
self = f.isf(self) and self or f.from(self)
return f.new(function()
local k, v = self:next()
@ -785,28 +584,6 @@ function f:swap()
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@return F|{ [K]: V }
---@nodiscard
function f:rev()
self = f.isf(self) and self or f.from(self)
local t, p, k
return f.new(function()
if not t then
p, t = f.revpairs(self:table())
end
local v
k, v = p(t, k)
if k ~= nil then
return k, v
end
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@param skip? integer
@ -814,8 +591,6 @@ end
---@return F|{ [K]: V }
---@nodiscard
function f:slice(skip, take)
self = f.isf(self) and self or f.from(self)
if (not skip or skip <= 0) and not take then
return self
end
@ -847,7 +622,6 @@ end
---@return F|{ [K]: V }
---@nodiscard
function f:skip(n)
self = f.isf(self) and self or f.from(self)
return self:slice(n)
end
@ -857,7 +631,6 @@ end
---@return F|{ [K]: V }
---@nodiscard
function f:take(n)
self = f.isf(self) and self or f.from(self)
return self:slice(0, n)
end
@ -869,7 +642,6 @@ end
---@overload fun(self: F|{ [K]: V }, seed: fun(a: A, v: V, k: K): A): A
---@nodiscard
function f:fold(seed, func)
self = f.isf(self) and self or f.from(self)
local k, v
if not func then
@ -899,7 +671,6 @@ end
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
function f:any(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
for k, v in self.next do
@ -918,7 +689,6 @@ end
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
function f:all(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
for k, v in self.next do
@ -937,9 +707,8 @@ end
---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard
function f:count(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
local ret = 0
func = autofunc(func)
for k, v in self.next do
if not func or func(v, k) then
@ -953,17 +722,13 @@ end
---@generic K, V
---@param self F|{ [K]: V }
---@param other { [K]: V }
---@param func? fun(v: V, k: K): any
---@return K?, V?
---@return K?
---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard
function f:diff(other, func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
function f:diff(other)
for k, v in self.next do
if (not func or func(v, k)) and other[k] ~= v then
return k, v
if other[k] ~= v then
return k
end
end
end
@ -982,7 +747,6 @@ end
---@return string
---@nodiscard
function f:string()
self = f.isf(self) and self or f.from(self)
local k, v = self:next()
if k == nil then
@ -1007,7 +771,6 @@ end
---@return { [K]: V }
---@nodiscard
function f:table()
self = f.isf(self) and self or f.from(self)
local ret = {}
for k, v in self.next do
@ -1021,8 +784,6 @@ end
---@param self F|{ [K]: V }
---@param func? fun(v: V, k: K): nil
function f:each(func)
self = f.isf(self) and self or f.from(self)
for k, v in self.next do
if func then
func(v, k)

View file

@ -28,37 +28,19 @@ CardSleeves.Sleeve = CardSleeves.Sleeve
--- @type {aliases: { [string]: [string] }}
Cryptid = Cryptid
---@return Card
function copy_card(...)
error(...)
end
--- @type fun(area: CardArea, ...: ...): Card
create_card_for_shop = create_card_for_shop
--- @type CardArea
G.Bakery_charm_area = G.Bakery_charm_area
--- @type boolean|table
G.Bakery_charm_area.cards[1].ability.extra = G.Bakery_charm_area.cards[1].ability.extra
---@type Card[]
G.playing_cards = G.playing_cards
---@type userdata|{getWidth: fun(self: self): number}
SMODS.Atlas.image_data = SMODS.Atlas.image_data
---@param context CalcContext|table|{hand_drawn: true, other_drawn: true}
---@param return_table? table
---@return table?
function SMODS.calculate_context(context, return_table)
error {context, return_table}
end
SMODS.Mods.Roland.config = require "config"
---@type (fun(rem: number): number|table)[]
SMODS.Mods.Roland.harsh_ante_scaling = SMODS.Mods.Roland.harsh_ante_scaling
---@type userdata|{getWidth: fun(self: self): number}
SMODS.Atlas.image_data = SMODS.Atlas.image_data
--- @type table
Talisman = Talisman

View file

@ -13,26 +13,8 @@ local function add(v)
return xop
end
if G.P_BLINDS[v] then
if G.STATE ~= G.STATES.MENU then
G.from_boss_tag = true
G.GAME.perscribed_bosses = G.GAME.perscribed_bosses or {}
G.GAME.perscribed_bosses[G.GAME.round_resets.ante] = v
G.FUNCS.reroll_boss()
end
return G.P_BLINDS[v]
end
if not G.P_TAGS[v] then
return SMODS.add_card {
no_edition = true,
key = v,
area = G.P_CENTERS[v] and
(({Default = true, Enhanced = true})[G.P_CENTERS[v].set] and G.hand or
G.P_CENTERS[v].set == "Booster" and G.shop_booster or
G.P_CENTERS[v].set == "Voucher" and G.shop_vouchers) or nil,
}
return SMODS.add_card {no_edition = true, key = v}
end
local tag = Tag(v)
@ -61,9 +43,7 @@ local function find(it)
end
local match = simplify(it)
local pool = match == "Blind" and G.P_BLINDS or
f(G.P_CENTER_POOLS):any(f.chain(f.arg(2), simplify, f.eq(match)))
local pool = f(G.P_CENTER_POOLS):any(f.chain(f.arg(2), simplify, f.eq(match)))
if type(pool) == "table" and next(pool) then
return pseudorandom_element(pool, pseudoseed "Roland_c").key
@ -76,9 +56,9 @@ local function find(it)
return f(G.localization.descriptions)
:flatmap(flatten)
:where(type, "table")
:where(f.chain(f.arg(2), f.index_into(G.P_CENTERS), "config"))
:where(function(v, k)
return ((G.P_CENTERS[k] or {}).config or G.P_BLINDS[k]) and
(match == simplify(k) or match == simplify(v.name))
return match == simplify(k) or match == simplify(v.name)
end)
:keys()
:any()

View file

@ -1,13 +1,10 @@
local qol = assert(SMODS.load_file "src/lib/shared.lua")() or require "lib.shared"
local animated = SMODS.current_mod.config.animated_icon
SMODS.current_mod.qol = qol
local f, q = qol[1], qol[2]
q {
front = true,
no_delete = true,
blocking = false,
blockable = false,
func = function()
local contributors = (Bakery_API or {}).contributors
@ -15,46 +12,88 @@ q {
return false
end
f(assert(SMODS.load_file("src/credits.lua", "Roland"))() or require "credits"):each(function(v, k)
-- Special shoutout to all contributors. <3
local credits = {
aster = {
name = "asterSSH",
fg = HEX "f8f8f2ff",
bg = HEX "bd93f9ff",
},
bakersdozenbagels = {
name = "BakersDozenBagels",
fg = HEX "362708ff",
bg = HEX "edd198ff",
},
char = {
name = "char (@irregulester)",
fg = HEX "f8f8f2ff",
bg = HEX "ff79c6ff",
},
ghostlyfield = {
name = "ghostlyfield",
fg = HEX "ffffffff",
bg = HEX "b290e6ff",
},
hamester = {
name = "Hamester",
fg = HEX "ffffffff",
bg = HEX "ffa100ff",
},
redstoad = {
name = "RedsToad",
fg = HEX "ffffffff",
bg = HEX "da4044ff",
},
}
f(credits):each(function(v, k)
contributors["Roland_" .. k] = v
-- G.ARGS.LOC_COLOURS["Bakery_credit_fg_Roland_" .. k] = v.fg
-- G.ARGS.LOC_COLOURS["Bakery_credit_bg_Roland_" .. k] = v.bg
end)
if SMODS.Mods.DebugPlus and SMODS.Mods.Roland.config.import_funky then
_G.f, _G.q, _G.u, _G.c = unpack(qol)
if not SMODS.Mods.DebugPlus or not SMODS.Mods.Roland.config.import_funky then
return
end
_G.f, _G.q, _G.u, _G.c = unpack(qol)
end,
}
f {"challenge", "spectral", "edition", "voucher", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"}
:each(function(v)
f {
"challenge",
"spectral",
"edition",
"tweaks",
"blind",
"charm",
"joker",
"tarot",
"back",
"seal",
"tag",
"voucher",
}:each(function(v)
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
end)
local _ = Balatest and f {"joker", "blind", "spectral"}:each(function(v)
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
end)
local function realign(tbl)
f(tbl):where(function(v, k)
if k == "align" and type(v) == "string" then
tbl[k] = "tr"
end
return k ~= "nodes" and type(v) == "table"
end):each(realign)
return tbl
if Balatest then
f {"joker", "blind", "spectral"}:each(function(v)
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
end)
end
local function toggle(id)
return realign(create_toggle {
return create_toggle {
label = localize {type = "variable", key = "b_Roland_" .. id},
ref_table = SMODS.Mods.Roland.config,
ref_value = id,
scale = 1.5,
})
}
end
local animated = SMODS.Mods.Roland.config.animated_icon
SMODS.Atlas {
px = 256,
py = 256,
@ -68,30 +107,24 @@ SMODS.Atlas {
function SMODS.current_mod.config_tab()
return {
n = G.UIT.ROOT,
config = {minw = 1, minh = 1, align = "tr", padding = 0.1, colour = G.C.BLACK},
nodes = {
{
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.BLACK},
nodes = {{
n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tr", padding = 0.1, colour = G.C.CLEAR},
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR},
nodes = {
toggle "animated_icon",
toggle "cool_phones",
toggle "faster_planets",
-- toggle "illusion_seal",
-- toggle "no_wild_debuff",
toggle "vitriol",
toggle "equinox_assist",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
},
},
{
n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tr", padding = 0.1, colour = G.C.CLEAR},
nodes = {
toggle "faster_planets",
toggle "illusion_seal",
toggle "no_wild_debuff",
SMODS.Mods.DebugPlus and toggle "import_funky",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
Talisman and toggle "harsh_ante_scaling",
},
},
},
}},
}
end
SMODS.current_mod.qol = qol

View file

@ -38,32 +38,26 @@ spectral {
return {vars = {card.ability.extra.amount}}
end,
can_use = function()
return next(G.hand.cards) and u()
return u() and #G.hand.cards > 0
end,
use = function(_, card)
use = function(_, card, _)
local cards = f(G.hand.cards):table()
pseudoshuffle(cards, pseudoseed "RolandDual")
f(cards):take(card.ability.extra.amount):each(function(v)
local seal = SMODS.poll_seal {
guaranteed = true,
options = f(G.P_SEALS):keys():where(f.nq "Roland_glass"):values():table(),
}
local seal
local _ = not card.Roland_immediate and q {
delay = 0.5,
trigger = "after",
func = function()
v:set_seal(seal, nil, true)
end,
} or v:set_seal(seal, true, true)
for _ = 1, 64 do
seal = SMODS.poll_seal {guaranteed = true}
if seal ~= "Roland_glass" then
break
end
end
v:set_seal(seal == "Roland_glass" and "Red" or seal)
end)
end,
bulk_use = function(self, card, area, copier, number)
card.Roland_immediate = true
f(number):map(f.const(self)):each(f.call("use", card, area, copier))
card.Roland_immediate = nil
end,
}
spectral {
@ -83,7 +77,7 @@ spectral {
delay = 0.4,
func = function()
card:juice_up(0.3, 0.5)
play_sound "tarot1"
play_sound("tarot1")
end,
}
@ -138,10 +132,9 @@ spectral {
return {vars = {card.ability.extra.amount}}
end,
can_use = function(_, card)
local count = #Bakery_API.get_highlighted()
return u() and count > 0 and count <= card.ability.extra.amount
return u() and #Bakery_API.get_highlighted() == card.ability.extra.amount
end,
use = function()
use = function(_, _, _)
f(Bakery_API.get_highlighted()):each(function(v)
v:set_seal "Roland_glass"
end)

View file

@ -60,8 +60,8 @@ SMODS.Tag {
pos = {x = 0, y = 0},
config = {amount = 5},
loc_vars = function(self, info_queue, tag)
info_queue[#info_queue + 1] = G.P_CENTERS.e_Roland_frozen
tag.ability = tag.ability or {}
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
return {vars = {tag.ability.amount or self.config.amount}}
end,
apply = function(self, tag, context)
@ -73,10 +73,9 @@ SMODS.Tag {
end
if tag.triggered or
not context.card or
context.card.edition or
context.type ~= "Bakery_score_card" or
(tag.ability.amount or self.config.amount) <= 0 then
(tag.ability.amount or self.config.amount) <= 0 or
((context.card or {}).edition or {}).Roland_frozen or
context.type ~= "Bakery_score_card" then
return
end
@ -122,7 +121,6 @@ SMODS.Tag {
ab.total_rounds = ab.total_rounds or self.config.total_rounds
if tag.triggered or
not G.jokers or
not next(G.jokers.cards) or
ab.invis_rounds < ab.total_rounds or
#G.jokers.cards + (modifiers.Roland_invisible or 0) >= G.jokers.config.card_limit then

View file

@ -26,9 +26,8 @@ q(SMODS.Consumable {
end
local highlighted = Bakery_API.get_highlighted()
local count = #G.jokers.highlighted + #highlighted
return count > 0 and count <= card.ability.extra.amount and
return #G.jokers.highlighted + #highlighted == card.ability.extra.amount and
f(G.jokers.highlighted):concat(highlighted):all(function(v)
return not v.edition
end)

View file

@ -68,7 +68,8 @@ Balatest.TestPlay {
}
end)
Balatest.wait_for_input(G.STATES.ROUND_EVAL)
Balatest.wait_for_input()
Balatest.wait()
end,
assert = function()
Balatest.assert_chips(2720)

View file

@ -20,7 +20,7 @@ local orig_use_consumeable = Card.use_consumeable
function Card:use_consumeable(area, copier, ...)
if SMODS.Mods.Roland.config.faster_planets and self.ability.consumeable.hand_type then
set_consumeable_usage(self)
level_up_hand(copier or self, self.ability.consumeable.hand_type, true, self.ability.qty or 1)
level_up_hand(copier or self, self.ability.consumeable.hand_type, true)
return
end
@ -61,17 +61,16 @@ function G.FUNCS.use_card(e, ...)
return orig_use_card(e, ...)
end
local normal = G.STATE == G.STATES.SHOP or
ref.area ~= G.pack_cards or
not G.GAME.pack_choices or
G.GAME.pack_choices <= 0
local normal = ref.area ~= G.pack_cards or not G.GAME.pack_choices or G.GAME.pack_choices <= 0
ref.from_area = ref.from_area or ref.area
play_sound("tarot1", percent, 0.6)
discover_card(ref.config.center)
ref:use_consumeable(ref.area)
SMODS.calculate_context {area = ref.area, consumeable = ref, using_consumeable = true}
q(function()
ref:remove()
end)
if normal then
return
@ -110,35 +109,21 @@ local function no_harsh_ante_scaling()
return not Talisman or not SMODS.Mods.Roland.config.harsh_ante_scaling
end
---@type (fun(rem: number): number|table)[]
SMODS.current_mod.harsh_ante_scaling = {
function(rem)
return (Big.constants and Big.constants.TEN or Big:new {10}):pow(blind(rem + 1))
end,
function(rem)
return Big:new {1, rem + 2}
end,
function(rem)
return Big:new {1, 1, [rem + 2] = 1}
end,
function(rem)
return Big:new {1, 1, [blind(rem) + 2] = 1}
end,
function(rem)
return rem <= 1 and Big:new {1.7976931348623157e308, 1, [1.7975e308] = 1} or 1 / 0
end,
}
function get_blind_amount(ante, ...)
local offset = 40
local loop = 39
if ante < offset or no_harsh_ante_scaling() then
if ante < loop or no_harsh_ante_scaling() then
return orig_get_blind_amount(ante, ...)
end
local loop = 10
if (ante - 9) / 15 >= loop then
return 1 / 0
end
return f(SMODS.Mods.Roland.harsh_ante_scaling, f.revpairs):any(function(_, k)
return (ante - offset) / loop + 1 >= k
end)((ante - offset) % loop + 1)
local rem = tonumber(blind((ante % loop) + 1))
return ante / 15 >= loop and Big:new(f(blind(ante - (loop * 15))):map(f.const(10)):table()) or
(ante / 9 >= loop and Big:new(f(ante / loop - 8):map(f.const(10)):concat {rem}:table()) or
(ante / 2 >= loop and Big:new {rem, ante / loop} or
(Big.constants and Big.constants.TEN or Big:new {10}):pow(rem)))
end