Compare commits

...

68 commits
2.9.29 ... main

Author SHA1 Message Date
4b8094efab
Ensure table is mutated before use 2026-08-16 16:25:54 +02:00
374aa5ec24
Fix 2 pixel discrepancy 2026-08-12 18:31:50 +02:00
7d2bd84c9f
Add a README. Mod is ready for the public! 2026-08-06 21:03:11 +02:00
31627f836d
Fix logic 2026-08-03 23:37:14 +02:00
58b1ee9a63
Fix incompatibility 2026-08-03 23:29:48 +02:00
407c18dc6e
Add a familiar joker, add new art 2026-08-03 18:38:17 +02:00
ff55a27567
Improve stability of Frozen and debugging tools 2026-07-31 05:25:52 +02:00
178e415614
Account for quantity in leveling up 2026-07-27 20:53:55 +02:00
f126173091
Add compatibility patch 2026-07-27 20:51:42 +02:00
27ce5bbb2e
Make bulk use very fast 2026-07-27 20:23:21 +02:00
4fda3af9c0
Make bulk use consistent with use RNG 2026-07-27 20:17:33 +02:00
ad24529beb
Add bulk use for Afterimage 2026-07-27 19:58:41 +02:00
fd9ad55325
Add qty support in Basket 2026-07-27 19:42:30 +02:00
44367939a6
Make negative copies silent 2026-07-27 19:31:49 +02:00
f170f855a1
Make Arctic Circle retriggers more generic 2026-07-27 14:43:32 +02:00
4619a24736
Rewrite Arctic Circle to be uncapped without crashing 2026-07-27 12:55:58 +02:00
9f19d85769
Fix consumables being usable without any selected cards 2026-07-27 11:46:07 +02:00
8edd1c1c73
Loosen consumable usage requirements 2026-07-24 18:41:43 +02:00
2628ae39e6
Hotfix 2026-07-22 04:51:18 +02:00
5e5abf42aa
Vastly improve funky.lua 2026-07-22 03:58:23 +02:00
b6b49350cb
Do not override editions, again 2026-07-21 21:57:39 +02:00
e8d0b74a1d
Do not override editions 2026-07-21 17:43:37 +02:00
b4b05d37dc
Fix off-by-one 2026-07-21 01:21:49 +02:00
5204ba71a0
Make harsh ante scaling even harsher 2026-07-21 01:05:36 +02:00
ce35879117
Ensure Sleeve's apply saves the game
nice
2026-07-20 22:55:47 +02:00
a639a92c3c
Prevent Polaris from freezing cards with editions 2026-07-19 23:07:43 +02:00
2a880adb68
Fix unit test 2026-07-19 22:30:22 +02:00
1bc58245f2
Rework Business Sleeve to be more interesting 2026-07-19 20:46:22 +02:00
ae14790b7f
Utility fixes 2026-07-19 20:03:04 +02:00
e4d4f2e4dd
Nerf Business Sleeve 2026-07-18 20:40:33 +02:00
7cc27f1ee9
Update .editorconfig 2026-07-17 22:39:11 +02:00
91b6f700cf
Use non-blocking queueing 2026-07-17 04:18:02 +02:00
9813b53ddd
Add Sleeve pronouns 2026-07-17 03:13:39 +02:00
BakersDozenBagels
87b0e5534b
Add art for Dropship Deck + Sleeve 2026-07-17 02:59:16 +02:00
75c102c298
Always allow Crimson Bean 2026-07-15 17:40:48 +02:00
c199c236ab
Add support for summoning blinds 2026-07-15 15:22:58 +02:00
adc431f5cd
Realign 2026-07-15 13:16:41 +02:00
179cecd9fa
Split settings into two columns with proper alignment 2026-07-15 13:15:08 +02:00
798a92b148
Add dedicated file for credits 2026-07-15 01:18:18 +02:00
805be430b0
Make Negative spawn silently 2026-07-13 22:40:06 +02:00
3be69067ab
Fix Frozen playing card jitter, improve funky autofunc 2026-07-13 22:35:14 +02:00
ca0dd10b85
Fix Multiplayer Bean 2026-07-13 21:37:02 +02:00
66aa414ec0
Fix unlock condition 2026-07-13 21:09:46 +02:00
1cc07189db
Bump version 2026-07-13 21:05:32 +02:00
dc83d32487
Add new deck (no art) 2026-07-13 21:04:57 +02:00
876866cc8f
Fix erroneous state in new deck 2026-07-12 23:55:51 +02:00
d9df6e86ca
Fix faster planets (real) 2026-07-12 23:46:20 +02:00
50d35bbb4a
Add compatibility patches 2026-07-12 23:29:36 +02:00
a23810f05d
Use boolean 2026-07-10 20:35:51 +02:00
805dd7c8d7
Coerce 2026-07-09 18:17:51 +02:00
f023e909ad
Refactor 2026-07-09 17:52:26 +02:00
eb022c894d
Add new challenge 2026-07-09 17:28:51 +02:00
9a981e989c
Improve Intellisense 2026-07-09 06:28:44 +02:00
386db180b5
Preserve original table 2026-07-08 19:36:50 +02:00
9ed747a33e
Add Frozen rank and suit support 2026-07-08 19:27:15 +02:00
860381ebff
Rework joker from scratch 2026-07-08 18:19:36 +02:00
d63333d242
Buff Misfortune Cookie 2026-07-08 17:27:45 +02:00
a50c7f4e41
Make Void hidden 2026-07-06 21:29:38 +02:00
4cc001ca1e
Add art for new deck 2026-07-06 20:26:59 +02:00
c555eaef75
Buff Blossom Deck and Sleeve 2026-07-06 20:17:32 +02:00
373b3e6031
Skip animation and add unlock condition for new deck 2026-07-06 19:44:39 +02:00
5d0fab66e8
Fix Cerulean Bell debuff 2026-07-06 18:02:24 +02:00
4b76d018c0
Add new deck, adjust unlock requirements 2026-07-05 23:43:13 +02:00
fdd4bb8e16
Improve debugging for unlock conditions 2026-07-05 14:02:50 +02:00
1f1914e5de
Add unlock condition for Excalibur 2026-07-05 05:14:50 +02:00
d15638243f
Add unlock conditions for blind jokers 2026-07-05 04:59:25 +02:00
5066875317
Let wii phone add boosters 2026-07-02 22:06:30 +02:00
e7a5766812
Apply to Multiplayer joker 2026-06-30 13:08:56 +02:00
41 changed files with 1311 additions and 358 deletions

View file

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

52
README.md Normal file
View file

@ -0,0 +1,52 @@
# 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: 8.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
assets/1x/backExtra.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
assets/2x/backExtra.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -1,8 +1,19 @@
#!/bin/sh #!/bin/sh
directory=$(dirname $(readlink -f "$0")) cd "$(dirname "$0")"
for file in "$directory"/1x/*.png; do for file in 2x/*.png; do
filename=$(basename "$file") filename=$(basename "$file")
output_file="$directory/2x/${filename}" 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/"
magick "$file" -filter point -resize 200% "$output_file" magick "$file" -filter point -resize 200% "$output_file"
fi
done done

View file

@ -6,11 +6,21 @@ return {
text = { text = {
"{C:attention}Small{} and {C:attention}Big Blinds{} are", "{C:attention}Small{} and {C:attention}Big Blinds{} are",
"replaced with {C:attention}Boss Blinds", "replaced with {C:attention}Boss Blinds",
"that are {C:dark_edition}debuffed twice", "that are {C:dark_edition}debuffed thrice",
"", "",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels", "{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 = { b_Roland_swapper = {
name = "Swapper Deck", name = "Swapper Deck",
text = { text = {
@ -20,6 +30,18 @@ return {
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels", "{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
b_Roland_wish = {
name = "Wish Deck",
text = {
"Redeem a random {C:attention}Voucher",
"after {C:attention}Small {}and {C:attention}Big Blinds",
"{C:attention}Vouchers {}no longer",
"appear in the shop",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
unlock = {"Discover all {C:attention}Vouchers"},
},
}, },
BakeryCharm = { BakeryCharm = {
BakeryCharm_Roland_cocacola = { BakeryCharm_Roland_cocacola = {
@ -138,6 +160,7 @@ return {
"Moves before", "Moves before",
"hand is played", "hand is played",
}, },
unlock = {"Defeat {C:attention}#1#"},
}, },
j_Roland_arctic = { j_Roland_arctic = {
name = "Arctic Circle", name = "Arctic Circle",
@ -164,9 +187,9 @@ return {
j_Roland_bulldozer = { j_Roland_bulldozer = {
name = "Bulldozer", name = "Bulldozer",
text = { text = {
"Give {X:red,C:white}XMult{} equal to", "Give the {C:attention}Blind",
"the current {C:attention}Blind Size{C:attention}", "{C:attention}Base {}as {X:red,C:white}XMult",
"{C:inactive}(Currently {X:red,C:white}X#1#{C:inactive})", "{C:inactive}(Currently {X:red,C:white}X#1#{C:inactive} Mult)",
}, },
}, },
j_Roland_cold = { j_Roland_cold = {
@ -184,6 +207,7 @@ return {
"All other {C:attention}Jokers {}cannot", "All other {C:attention}Jokers {}cannot",
"be selected or dragged", "be selected or dragged",
}, },
unlock = {"Defeat {C:blue}#1#"},
}, },
j_Roland_crimson = { j_Roland_crimson = {
name = "Crimson Heart", name = "Crimson Heart",
@ -192,6 +216,7 @@ return {
"{C:red}Debuffs {C:attention}Joker", "{C:red}Debuffs {C:attention}Joker",
"to the left", "to the left",
}, },
unlock = {"Defeat {C:red}#1#"},
}, },
j_Roland_domino = { j_Roland_domino = {
name = "Domino", name = "Domino",
@ -263,9 +288,8 @@ return {
j_Roland_misfortune = { j_Roland_misfortune = {
name = "Misfortune Cookie", name = "Misfortune Cookie",
text = { text = {
"{C:tarot}Tarot{} cards created", "Cards created by",
"by {C:tarot}Purple Seals", "{C:attention}Seals {}become {C:dark_edition}Negative",
"become {C:dark_edition}Negative",
}, },
}, },
j_Roland_monomino = { j_Roland_monomino = {
@ -311,6 +335,14 @@ return {
name = "Phytoestrogens", name = "Phytoestrogens",
text = {"{C:mult}+Chips{} Mult, {X:mult,C:white}X#1#{} Mult"}, text = {"{C:mult}+Chips{} Mult, {X:mult,C:white}X#1#{} Mult"},
}, },
j_Roland_polaris = {
name = "Polaris",
text = {
"The {C:attention}first {}scored",
"card each round",
"becomes {C:dark_edition}Frozen",
},
},
j_Roland_sapling = { j_Roland_sapling = {
name = "Sapling", name = "Sapling",
text = { text = {
@ -366,6 +398,7 @@ return {
"{C:red}Debuffs {}the {C:attention}first", "{C:red}Debuffs {}the {C:attention}first",
"{C:attention}#2# {}scored cards", "{C:attention}#2# {}scored cards",
}, },
unlock = {"Defeat {C:green}#1#"},
}, },
j_Roland_violet = { j_Roland_violet = {
name = "Violet Vessel", name = "Violet Vessel",
@ -374,18 +407,22 @@ return {
"but {X:mult,C:white}X#2#{} Mult", "but {X:mult,C:white}X#2#{} Mult",
"{C:attention}before {}scoring", "{C:attention}before {}scoring",
}, },
unlock = {"Defeat {C:purple}#1#"},
}, },
j_Roland_venerable = { j_Roland_venerable = {
name = "Venerable Visage", name = "Venerable Visage",
text = {"{X:red,C:white}X#1#{} discards each round"}, text = {"{X:red,C:white}X#1#{} discards each round"},
unlock = {"Defeat {C:dark_edition}#1#"},
}, },
j_Roland_yard = { j_Roland_yard = {
name = "Yard Sale", name = "Yard Sale",
text = { text = {
"Gain {C:money}$#1#{} when a", "Gain {C:money}$#1#{} when a",
"card is {C:attention}removed", "card is {C:attention}added",
"or {C:attention}removed",
"from the deck", "from the deck",
}, },
unlock = {"Complete the", "{C:attention}#1#", "challenge"},
}, },
}, },
Other = { Other = {
@ -404,7 +441,7 @@ return {
text = { text = {
"{C:attention}Small{} and {C:attention}Big Blinds{} are", "{C:attention}Small{} and {C:attention}Big Blinds{} are",
"replaced with {C:attention}Boss Blinds", "replaced with {C:attention}Boss Blinds",
"that are {C:dark_edition}debuffed twice", "that are {C:dark_edition}debuffed thrice",
"", "",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels", "{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
@ -413,11 +450,31 @@ return {
name = "Efflorescent Sleeve", name = "Efflorescent Sleeve",
text = { text = {
"{C:dark_edition}Debuff {X:dark_edition,C:white}#1#X<ante>{C:dark_edition} times", "{C:dark_edition}Debuff {X:dark_edition,C:white}#1#X<ante>{C:dark_edition} times",
"{C:inactive}(Instead of twice)", "{C:inactive}(Instead of thrice)",
"", "",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels", "{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 = { sleeve_Roland_swapper = {
name = "Swapper Sleeve", name = "Swapper Sleeve",
text = { text = {
@ -435,6 +492,29 @@ return {
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels", "{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
sleeve_Roland_wish = {
name = "Wish Sleeve",
text = {
"A random {C:attention}voucher{} is",
"{C:attention}redeemed {}at the end",
"of {C:attention}Small {}and {C:attention}Big Blinds",
"{C:attention}Vouchers {}no longer",
"appear in the shop",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
unlock = {"Discover all {C:attention}Vouchers"},
},
sleeve_Roland_wish_alt = {
name = "Miracle Sleeve",
text = {
"All {C:attention}vouchers",
"start {C:attention}redeemed",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
unlock = {"Discover all {C:attention}Vouchers"},
},
}, },
Spectral = { Spectral = {
c_Roland_afterimage = { c_Roland_afterimage = {
@ -524,6 +604,7 @@ return {
c_Roland_Jokerful = "Jokerful", c_Roland_Jokerful = "Jokerful",
c_Roland_Pastries = "Sweet Pastries", c_Roland_Pastries = "Sweet Pastries",
c_Roland_Spin_To_Win = "Spin to Win", c_Roland_Spin_To_Win = "Spin to Win",
c_Roland_Spring_Cleaning = "Spring Cleaning",
}, },
v_dictionary = { v_dictionary = {
b_Roland_add = "ADD", b_Roland_add = "ADD",
@ -537,6 +618,7 @@ return {
b_Roland_entering_shop = "Entering shop!", b_Roland_entering_shop = "Entering shop!",
b_Roland_equinox_assist = "Assist: Only hide text (Equinox)", b_Roland_equinox_assist = "Assist: Only hide text (Equinox)",
b_Roland_faster_planets = "Faster Planets", b_Roland_faster_planets = "Faster Planets",
b_Roland_frozen = "Frozen!",
b_Roland_full = "FULL", b_Roland_full = "FULL",
b_Roland_scribable_basket = "Scribable Basket (overpowered)", b_Roland_scribable_basket = "Scribable Basket (overpowered)",
b_Roland_harsh_ante_scaling = "Harsh ante scaling (Ante 40+)", b_Roland_harsh_ante_scaling = "Harsh ante scaling (Ante 40+)",
@ -570,6 +652,7 @@ return {
ch_c_Roland_Showdown_Crimson = {"Showdown blinds are {C:attention}Crimson Heart {}(stacks with above)"}, 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_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_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,6 +65,19 @@ position = "at"
payload = "elseif G.GAME.blind_on_deck == 'Big' then" payload = "elseif G.GAME.blind_on_deck == 'Big' then"
match_indent = true 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]]
[patches.pattern] [patches.pattern]
target = "card.lua" target = "card.lua"
@ -82,10 +95,33 @@ pattern = "local card = create_card('Tarot',G.consumeables, nil, nil, nil, nil,
position = "after" position = "after"
times = 1 times = 1
payload = """if next(SMODS.find_card "j_Roland_misfortune") then payload = """if next(SMODS.find_card "j_Roland_misfortune") then
card:set_edition "e_negative" card:set_edition("e_negative", true, true)
end""" end"""
match_indent = true match_indent = true
[[patches]]
[patches.pattern]
target = "card.lua"
pattern = "if self.seal == 'Blue' and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit and not self.ability.extra_enhancement then"
position = "at"
payload = """if self.seal == 'Blue' and
(next(SMODS.find_card "j_Roland_misfortune") or
#G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit and
not self.ability.extra_enhancement) then
"""
match_indent = true
[[patches]]
[patches.pattern]
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)
end"""
match_indent = true
# Frozen
[[patches]] [[patches]]
[patches.pattern] [patches.pattern]
target = "card.lua" target = "card.lua"
@ -130,4 +166,49 @@ target = "card.lua"
pattern = "math\\.floor\\(\\(G\\.GAME\\.dollars \\+ \\(G\\.GAME\\.dollar_buffer or 0\\)\\)/self\\.ability\\.extra\\.dollars\\)" pattern = "math\\.floor\\(\\(G\\.GAME\\.dollars \\+ \\(G\\.GAME\\.dollar_buffer or 0\\)\\)/self\\.ability\\.extra\\.dollars\\)"
position = "at" position = "at"
payload = "SMODS.Mods.Roland.bootstraps_mult(self)" 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 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", "id": "Roland",
"name": "Roland", "name": "Roland",
"prefix": "Roland", "prefix": "Roland",
"version": "2.9.29", "version": "3.0.2",
"badge_colour": "8BE9FD", "badge_colour": "8BE9FD",
"display_name": "Roland", "display_name": "Roland",
"main_file": "src/main.lua", "main_file": "src/main.lua",

1
refs/src Symbolic link
View file

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

View file

@ -8,7 +8,7 @@ end
local back = (function() local back = (function()
local x = 0 local x = 0
---@param tbl SMODS.Back|{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[], 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?}
return function(tbl) return function(tbl)
local key = tbl.key local key = tbl.key
local apply = tbl.apply local apply = tbl.apply
@ -32,6 +32,7 @@ local back = (function()
key = key, key = key,
pos = tbl.pos, pos = tbl.pos,
atlas = "sleeve", atlas = "sleeve",
pronouns = tbl.pronouns,
config = tbl.config and f(tbl.config):table() or nil, config = tbl.config and f(tbl.config):table() or nil,
loc_vars = function(self, ...) loc_vars = function(self, ...)
local ret = tbl.loc_vars and tbl.loc_vars(self, ...) or {} local ret = tbl.loc_vars and tbl.loc_vars(self, ...) or {}
@ -39,7 +40,7 @@ local back = (function()
return ret return ret
end, end,
apply = function(self, ...) apply = function(self, ...)
return (tbl.alt_apply and self:is_alt() and tbl.alt_apply or apply)(self, ...) return save((tbl.alt_apply and self:is_alt() and tbl.alt_apply or apply)(self, ...))
end, end,
calculate = function(self, ...) calculate = function(self, ...)
return (tbl.alt_calculate and self:is_alt() and tbl.alt_calculate or calculate)(self, ...) return (tbl.alt_calculate and self:is_alt() and tbl.alt_calculate or calculate)(self, ...)
@ -53,13 +54,20 @@ local back = (function()
end end
end)() end)()
SMODS.Atlas { local back_atlas = SMODS.Atlas {
key = "back", key = "back",
path = "back.png", path = "back.png",
px = 71, px = 71,
py = 95, py = 95,
} }
local extra_atlas = SMODS.Atlas {
key = "back_extra",
path = "backExtra.png",
px = 71,
py = 95,
}
local _ = CardSleeves and SMODS.Atlas { local _ = CardSleeves and SMODS.Atlas {
key = "sleeve", key = "sleeve",
path = "sleeve.png", path = "sleeve.png",
@ -70,49 +78,53 @@ local _ = CardSleeves and SMODS.Atlas {
back { back {
key = "blossom", key = "blossom",
pronouns = "any_all", pronouns = "any_all",
config = {extra = {alt_times = 4, times = 2}}, config = {extra = {alt_times = 9, times = 3}},
attributes = {"boss_blind"}, attributes = {"passive", "boss_blind"},
loc_vars = function(self, _, _) loc_vars = function(self)
return {vars = {self.config.extra.alt_times}} return {vars = {self.config.extra.alt_times}}
end, end,
apply = function(_, _) apply = function()
G.GAME.modifiers.Roland_blossom_deck = true G.GAME.modifiers.Roland_blossom_deck = true
end, end,
calculate = function(self, card, context) calculate = function(self, b, context)
if not context.setting_blind then if not context.forcetrigger and not context.setting_blind then
return return
end end
local blinds = G.GAME.round_resets.blind_states local blinds = G.GAME.round_resets.blind_states
if blinds.Small ~= "Current" and blinds.Big ~= "Current" then if not context.forcetrigger and
blinds.Small ~= "Current" and
blinds.Big ~= "Current" then
return return
end end
local count = self:is_alt() and local function debuff(times)
return function()
if times <= 0 then
return
end
G.GAME.blind:disable()
SMODS.calculate_effect({message = localize "ph_boss_disabled"}, b)
G.GAME.blind:wiggle()
play_sound "timpani"
q {func = debuff(times - 1)}
end
end
local times = self:is_alt() and
G.GAME.round_resets.ante * self.config.extra.alt_times or G.GAME.round_resets.ante * self.config.extra.alt_times or
self.config.extra.times self.config.extra.times
f(count):each(function(i) debuff(times)()
local _ = i == 1 and G.GAME.blind:disable()
q {
delay = 0.4,
func = function()
local _ = i ~= 1 and G.GAME.blind:disable()
SMODS.calculate_effect({message = localize "ph_boss_disabled"}, card)
G.GAME.blind:wiggle()
play_sound "timpani"
end,
}
end)
end, end,
} }
back { back {
key = "swapper", key = "swapper",
pronouns = "he_him", pronouns = "he_him",
attributes = {"spectral", "tarot"}, attributes = {"passive", "editions", "sell_value"},
apply = function(self) apply = function(self)
local modifiers = G.GAME.modifiers local modifiers = G.GAME.modifiers
modifiers.Roland_swapper_deck = true modifiers.Roland_swapper_deck = true
@ -121,16 +133,92 @@ back {
calculate = f.noop, 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 swapper = {Spectral = "Tarot", Tarot = "Spectral"}
local orig_create_card = create_card 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, ...) function create_card(_type, ...)
if not G.GAME.modifiers.Roland_swapper_deck then if not G.GAME.modifiers.Roland_swapper_deck then
return orig_create_card(_type, ...) return create_dropshipped_card(_type, ...)
end end
if not G.GAME.modifiers.Roland_alt_swapper_deck then if not G.GAME.modifiers.Roland_alt_swapper_deck then
return orig_create_card(swapper[_type] or _type, ...) return create_dropshipped_card(swapper[_type] or _type, ...)
end end
local type = f(SMODS.ConsumableTypes):keys():where(function(v) local type = f(SMODS.ConsumableTypes):keys():where(function(v)
@ -138,5 +226,79 @@ function create_card(_type, ...)
end):table() end):table()
local has_type = SMODS.ConsumableTypes[_type] local has_type = SMODS.ConsumableTypes[_type]
return orig_create_card(has_type and pseudorandom_element(type, pseudoseed "Roland_alt_swapper_deck") or _type, ...) return create_dropshipped_card(
has_type and pseudorandom_element(type, pseudoseed "Roland_alt_swapper_deck") or _type, ...)
end
local function redeem(key, fast)
if fast then
G.GAME.used_vouchers[key] = true
end
q {
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:start_materialize()
q {
delay = 1,
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 {
delay = 1,
func = function()
voucher:start_dissolve()
end,
}
end,
}
end,
}
end
back {
key = "wish",
pronouns = "he_him",
attributes = {"passive", "generation"},
unlocked = false,
discovered = false,
check_for_unlock = function(_, args)
return args ~= "Bakery_nothing" and f(G.P_CENTER_POOLS.Voucher):all "discovered"
end,
apply = function(self)
G.GAME.modifiers.Roland_no_voucher = true
local _ = self:is_alt() and f(G.P_CENTER_POOLS.Voucher):map "key":each(redeem)
end,
calculate = function(_, _, context)
local _ = (context.forcetrigger or
context.blind_defeated and
not context.repetition and
not G.GAME.blind.boss) and
f(G.P_CENTER_POOLS.Voucher)
:map "key"
:where(f.index_into(G.GAME.used_vouchers), false)
:any() and
redeem(get_next_voucher_key())
end,
}
local orig_add_voucher_to_shop = SMODS.add_voucher_to_shop
function SMODS.add_voucher_to_shop(...)
if not G.GAME.modifiers.Roland_no_voucher then
return orig_add_voucher_to_shop(...)
end
end end

View file

@ -163,7 +163,7 @@ blind {
local _ = not b.disabled and context.drawing_cards and table.sort(G.deck.cards, sort_by_enhancement) local _ = not b.disabled and context.drawing_cards and table.sort(G.deck.cards, sort_by_enhancement)
end, end,
in_pool = function() in_pool = function()
return G.playing_cards and f(G.playing_cards):any(has_enhancement) return G.playing_cards and not not f(G.playing_cards):any(has_enhancement)
end, end,
} }
@ -257,7 +257,7 @@ blind {
boss = {min = 6}, boss = {min = 6},
boss_colour = HEX "bdaeccff", boss_colour = HEX "bdaeccff",
pronouns = "they_them", pronouns = "they_them",
collection_loc_vars = function(_) collection_loc_vars = function()
return { return {
vars = {localize { vars = {localize {
type = "variable", type = "variable",
@ -265,7 +265,7 @@ blind {
}}, }},
} }
end, end,
loc_vars = function(_) loc_vars = function()
local _, name = common_rank() local _, name = common_rank()
return {vars = {localize(name or "Ace", "ranks")}} return {vars = {localize(name or "Ace", "ranks")}}
end, end,
@ -319,7 +319,7 @@ blind {
dollars = 5, dollars = 5,
defeat = disable_improbable, defeat = disable_improbable,
disable = disable_improbable, disable = disable_improbable,
set_blind = function(_) set_blind = function()
G.GAME.modifiers.Roland_improbable = true G.GAME.modifiers.Roland_improbable = true
end, end,
} }
@ -332,32 +332,56 @@ if cry_prob then
end end
end end
function SMODS.current_mod:calculate(context) local function call(key, value)
local _ = type(G.calccontext) == "function" and G.calccontext(context) local obj = G[key .. "obj"]
local _ = type(obj) == "function" and obj(value)
if type(G.calc) == "function" then if not G[key] or value == "Bakery_nothing" then
G.calc = {G.calc} return
end
if type(G[key]) == "function" then
G[key] = {G[key]}
end
if type(G[key]) ~= "table" then
return
end end
if type(G.calc) == "table" then
local str local str
f(context):keys():map(f.index_into(G.calc)):where(type, "function"):each(function(v) local _ = f(type(value) == "table" and value or {[value] = true})
str = str or f(context):keys():string() :keys()
:map(f.index_into(G[key]))
:where(type, "function")
:each(function(v)
str = str or type(value) == "table" and f(value):keys():string() or value
v(str) v(str)
end) end)
local _ = not str and type(G.calc[1]) == "function" and G.calc[1](f(context):keys():string()) local _ = not str and
type(G[key][1]) == "function" and
G[key][1](type(value) == "table" and f(value):keys():string() or value)
end end
local orig_check_for_unlock = check_for_unlock
function check_for_unlock(args, ...)
call("lock", type(args) == "table" and args.type or args)
return orig_check_for_unlock(args, ...)
end
function SMODS.current_mod:calculate(context)
call("calc", context)
local _ = context.before and local _ = context.before and
f {"v_Roland_ceres", "v_Roland_neptune"} f {"v_Roland_ceres", "v_Roland_neptune"}
:where(f.index_into(G.GAME.used_vouchers)) :where(f.index_into(G.GAME.used_vouchers))
:map(f.index_into(G.P_CENTERS)) :map(f.index_into(G.P_CENTERS))
:where("config.extra.hand_type", context.scoring_name) :where("config.extra.hand_type", context.scoring_name)
:pun "SMODS.Voucher" :pun "SMODS.Voucher"
:each(function(v, _) :each(function(v)
SMODS.smart_level_up_hand(nil, v.config.extra.hand_type, nil, v.config.extra.amount) SMODS.smart_level_up_hand(nil, v.config.extra.hand_type, false, v.config.extra.amount)
end) end)
local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities
@ -380,7 +404,7 @@ function SMODS.current_mod:calculate(context)
local normal = orig.normal local normal = orig.normal
local mt = { G.GAME.probabilities = setmetatable({}, {
orig = orig, orig = orig,
__index = function(_, k) __index = function(_, k)
return k == "normal" and 0 or orig[k] return k == "normal" and 0 or orig[k]
@ -388,11 +412,7 @@ function SMODS.current_mod:calculate(context)
__newindex = function(_, k, v) __newindex = function(_, k, v)
orig[k] = (k == "normal" and v == 0) and normal or v orig[k] = (k == "normal" and v == 0) and normal or v
end, end,
} })
local proxy = {}
setmetatable(proxy, mt)
G.GAME.probabilities = proxy
end end
blind { blind {

View file

@ -1,19 +1,26 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
---@return {banned_cards: {}, banned_tags: {}, banned_other: {}}
local function bans() local function bans()
return {banned_cards = {}, banned_tags = {}, banned_other = {}} return {banned_cards = {}, banned_tags = {}, banned_other = {}}
end end
---@param tbl table
---@return fun(v: SMODS.GameObject)
local function adder(tbl) local function adder(tbl)
return function(v) return function(v)
table.insert(tbl, {id = v.key, type = "blind"}) table.insert(tbl, {id = v.key, type = "blind"})
end end
end end
---@param v SMODS.GameObject
---@return boolean
local function is_banned_from_pastry(v) local function is_banned_from_pastry(v)
return not ({Bakery = true, Roland = true})[(v.mod or {}).id] return not ({Bakery = true, Roland = true})[(v.mod or {}).id]
end end
---@param v SMODS.Center
---@return boolean
local function is_center_banned_from_pastry(v) local function is_center_banned_from_pastry(v)
local arcana_boosters = { local arcana_boosters = {
p_arcana_normal_1 = true, p_arcana_normal_1 = true,
@ -30,21 +37,35 @@ local function is_center_banned_from_pastry(v)
return arcana_boosters[v.key] or pastries_targets[v.set] and is_banned_from_pastry(v) return arcana_boosters[v.key] or pastries_targets[v.set] and is_banned_from_pastry(v)
end 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) local function is_showdown_except(key)
---@param v SMODS.Blind
---@param k string
---@return boolean?
return function(v, k) return function(v, k)
return v.boss and v.boss.showdown and key ~= k return v.boss and v.boss.showdown and key ~= k
end end
end end
local pastries, amber, cerulean, crimson, verdant, violet = ---@param name string
bans(), bans(), bans(), bans(), bans(), bans() ---@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()
SMODS.Challenge { SMODS.Challenge {
key = "Glass", key = "Glass",
rules = {custom = {{id = "Roland_Glass1"}, {id = "Roland_Glass2"}}}, rules = {custom = multirule("Glass", 2)},
pronouns = "they_them", pronouns = "they_them",
config = {extra = {odds = 2}}, config = {extra = {odds = 2}},
calculate = function(self, context) calculate = function(self, context)
@ -61,6 +82,24 @@ SMODS.Challenge {
end, 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 { SMODS.Challenge {
key = "Go", key = "Go",
rules = {custom = {{id = "Roland_Go"}}}, rules = {custom = {{id = "Roland_Go"}}},
@ -116,6 +155,9 @@ SMODS.Challenge {
jokers = {{id = "j_Roland_amber"}}, jokers = {{id = "j_Roland_amber"}},
restrictions = amber, restrictions = amber,
pronouns = "they_them", pronouns = "they_them",
unlocked = function()
return G.P_CENTERS.j_Roland_amber and G.P_CENTERS.j_Roland_amber.check_for_unlock()
end,
calculate = function(_, context) calculate = function(_, context)
local function slide(pitch) local function slide(pitch)
q(function() q(function()
@ -155,6 +197,9 @@ SMODS.Challenge {
jokers = {{id = "j_Roland_cerulean"}}, jokers = {{id = "j_Roland_cerulean"}},
restrictions = cerulean, restrictions = cerulean,
pronouns = "she_her", pronouns = "she_her",
unlocked = function()
return G.P_CENTERS.j_Roland_cerulean and G.P_CENTERS.j_Roland_cerulean.check_for_unlock()
end,
calculate = function(_, context) calculate = function(_, context)
if not context.hand_drawn then if not context.hand_drawn then
return return
@ -182,6 +227,9 @@ SMODS.Challenge {
jokers = {{id = "j_Roland_crimson"}}, jokers = {{id = "j_Roland_crimson"}},
restrictions = crimson, restrictions = crimson,
pronouns = "she_her", pronouns = "she_her",
unlocked = function()
return G.P_CENTERS.j_Roland_crimson and G.P_CENTERS.j_Roland_crimson.check_for_unlock()
end,
calculate = function(_, context) calculate = function(_, context)
local _ = context.blind_defeated and f(G.jokers.cards):each(function(v) local _ = context.blind_defeated and f(G.jokers.cards):each(function(v)
v.ability.Roland_crimson_heart_chosen = nil v.ability.Roland_crimson_heart_chosen = nil
@ -242,6 +290,9 @@ SMODS.Challenge {
jokers = {{id = "j_Roland_verdant"}}, jokers = {{id = "j_Roland_verdant"}},
restrictions = verdant, restrictions = verdant,
pronouns = "she_her", pronouns = "she_her",
unlocked = function()
return G.P_CENTERS.j_Roland_verdant and G.P_CENTERS.j_Roland_verdant.check_for_unlock()
end,
calculate = function(_, context) calculate = function(_, context)
if context.setting_blind then if context.setting_blind then
G.GAME.modifiers.Roland_Eternally_Verdant = true G.GAME.modifiers.Roland_Eternally_Verdant = true
@ -265,6 +316,9 @@ SMODS.Challenge {
jokers = {{id = "j_joker"}, {id = "j_Roland_violet"}}, jokers = {{id = "j_joker"}, {id = "j_Roland_violet"}},
restrictions = violet, restrictions = violet,
pronouns = "she_they", pronouns = "she_they",
unlocked = function()
return G.P_CENTERS.j_Roland_violet and G.P_CENTERS.j_Roland_violet.check_for_unlock()
end,
calculate = function(_, context) calculate = function(_, context)
if not context.setting_blind then if not context.setting_blind then
return return
@ -276,8 +330,33 @@ SMODS.Challenge {
end, end,
} }
q(function() 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)
f { 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_TAGS, is_banned_from_pastry, pastries.banned_tags},
{G.P_BLINDS, is_banned_from_pastry, pastries.banned_other}, {G.P_BLINDS, is_banned_from_pastry, pastries.banned_other},
{G.P_CENTERS, is_center_banned_from_pastry, pastries.banned_cards}, {G.P_CENTERS, is_center_banned_from_pastry, pastries.banned_cards},
@ -289,14 +368,5 @@ q(function()
}:each(function(v) }:each(function(v)
f(v[1]):where(v[2]):each(adder(v[3])) f(v[1]):where(v[2]):each(adder(v[3]))
end) 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

@ -249,11 +249,17 @@ charm {
G.STATE_COMPLETE = false G.STATE_COMPLETE = false
delay(1) delay(1)
q(function()
f((G.GAME.modifiers.extra_boosters or 0) + 2)
:map(f.const(nil))
:each(SMODS.add_booster_to_shop)
q(function() q(function()
card.ability.extra.active = false card.ability.extra.active = false
end) end)
end) end)
end) end)
end)
end, end,
} }

28
src/credits.lua Normal file
View file

@ -0,0 +1,28 @@
-- 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,10 +53,25 @@ local function freeze(card)
return card.ability return card.ability
end end
local config = card.config or {}
(card.ability or {}).Roland_crimson = not not (card.ability or {}).Roland_crimson (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.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.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)} card.Roland_frozen = card.Roland_frozen or {probability = SMODS.get_probability_vars(card, 1, 1)}
if diff then
card:set_sprites(card.config.center, card.config.card)
end
local ability, ret = card.ability, card.Roland_frozen_ability local ability, ret = card.ability, card.Roland_frozen_ability
ability.extra = ability.extra == nil and {} or ability.extra ability.extra = ability.extra == nil and {} or ability.extra
@ -196,10 +211,14 @@ SMODS.Edition {
local _ = not context.fix_probability and not context.mod_probability and freeze(card) local _ = not context.fix_probability and not context.mod_probability and freeze(card)
end, end,
on_remove = function(card) on_remove = function(card)
card.Roland_frozen, card.Roland_frozen_ability, card.Roland_frozen_current_round = nil, nil, nil card.Roland_frozen,
card.Roland_frozen_base,
card.Roland_frozen_card,
card.Roland_frozen_ability,
card.Roland_frozen_current_round = nil, nil, nil, nil, nil
end, end,
get_weight = function(self) get_weight = function(self)
return G.GAME.edition_rate * self.weight return self.weight * G.GAME.edition_rate
end, end,
} }
@ -231,6 +250,31 @@ function Card:click(...)
return ret return ret
end end
local orig_save = Card.save
function Card:save(...)
if false then
---@type Card|{Roland_frozen: table, Roland_frozen_ability: table, Roland_frozen_base: table, Roland_frozen_card: table, Roland_frozen_current_round: table}
self = self
end
local ret = orig_save(self, ...)
ret.Roland_frozen,
ret.Roland_frozen_base,
ret.Roland_frozen_card,
ret.Roland_frozen_ability,
ret.Roland_frozen_current_round =
self.Roland_frozen,
self.Roland_frozen_base,
self.Roland_frozen_card,
self.Roland_frozen_ability,
self.Roland_frozen_current_round
return ret
end
local is_bean = {["Turtle Bean"] = true, j_mp_turtle_bean = true}
local orig_calculate_joker = Card.calculate_joker local orig_calculate_joker = Card.calculate_joker
---@param self Card|{Roland_frozen_ability?: table} ---@param self Card|{Roland_frozen_ability?: table}
@ -242,7 +286,7 @@ function Card:calculate_joker(context, ...)
{xmult = self.ability.extra.Xmult} or nil {xmult = self.ability.extra.Xmult} or nil
end end
if is_frozen and self.ability.name == "Turtle Bean" then if is_frozen and is_bean[self.ability.name] then
return return
end end
@ -285,8 +329,6 @@ function Card:calculate_dollar_bonus(...)
return orig_calculate_dollar_bonus(self, ...) return orig_calculate_dollar_bonus(self, ...)
end end
local orig_get_probability_vars = SMODS.get_probability_vars
function SMODS.current_mod.bootstraps_mult(card) function SMODS.current_mod.bootstraps_mult(card)
local ability = card.Roland_frozen_ability local ability = card.Roland_frozen_ability
local ret = math.floor((G.GAME.dollars + (G.GAME.dollar_buffer or 0)) / card.ability.extra.dollars) local ret = math.floor((G.GAME.dollars + (G.GAME.dollar_buffer or 0)) / card.ability.extra.dollars)
@ -299,9 +341,17 @@ function SMODS.current_mod.bootstraps_mult(card)
return ability.Roland_bootstraps_mult return ability.Roland_bootstraps_mult
end end
local orig_get_probability_vars = SMODS.get_probability_vars
function SMODS.get_probability_vars(trigger_obj, ...) function SMODS.get_probability_vars(trigger_obj, ...)
local numerator, denominator = orig_get_probability_vars(trigger_obj, ...) local numerator, denominator = orig_get_probability_vars(trigger_obj, ...)
return trigger_obj and (trigger_obj.Roland_frozen or {}).probability or numerator, denominator 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
end end
q { q {

View file

@ -22,7 +22,7 @@ local joker = (function()
return ret return ret
end end
---@param tbl SMODS.Joker|{artist?: string, idea?: string, Bakery_can_use: (fun(self: self, card: Card): boolean?), Bakery_use_button_text: (fun(self: self, card: Card): string|table|nil), Bakery_use_joker: fun(self: self, card: Card), attributes?: Attributes[]} ---@param tbl SMODS.Joker|{artist?: string, defeated_blind: string?, idea?: string, Bakery_can_use: (fun(self: self, card: Card): boolean?), Bakery_use_button_text: (fun(self: self, card: Card): string|table|nil), Bakery_use_joker: fun(self: self, card: Card), attributes?: Attributes[]}
return function(tbl) return function(tbl)
tbl.pos = inc() tbl.pos = inc()
tbl.atlas = "joker" tbl.atlas = "joker"
@ -34,28 +34,30 @@ local joker = (function()
tbl.config.extra.back_pos = inc() tbl.config.extra.back_pos = inc()
end end
if tbl.defeated_blind then
function tbl.check_for_unlock()
return Bakery_API.defeated_blinds[tbl.defeated_blind] > 0
end
function tbl.locked_loc_vars()
return G.P_BLINDS[tbl.defeated_blind].discovered and
{vars = {localize {type = "name_text", key = tbl.defeated_blind, set = "Blind"}}} or
{vars = {localize "k_unknown"}}
end
end
if tbl.check_for_unlock then
tbl.discovered, tbl.unlocked = false, false
end
return q(SMODS.Joker(tbl)) return q(SMODS.Joker(tbl))
end end
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 ---@param card Card
---@return boolean
local function is_frozen(card) local function is_frozen(card)
return card.edition and card.edition.key == "e_Roland_frozen" return card and card.edition and card.edition.Roland_frozen
end end
---@param card? Card|false|{suit: string, value: string} ---@param card? Card|false|{suit: string, value: string}
@ -194,8 +196,8 @@ joker {
joker { joker {
key = "sunny", key = "sunny",
pronouns = "it_its", pronouns = "it_its",
artist = "char",
idea = "redstoad", idea = "redstoad",
artist = "ghostlyfield",
attributes = {"food", "on_sell"}, attributes = {"food", "on_sell"},
cost = 2, cost = 2,
rarity = 1, rarity = 1,
@ -216,8 +218,8 @@ joker {
SMODS.calculate_context {drawing_cards = true, draw = {G.deck.cards}} SMODS.calculate_context {drawing_cards = true, draw = {G.deck.cards}}
SMODS.calculate_context { SMODS.calculate_context {
hand_drawn = facing_blind and {G.deck.cards[1]} and true, hand_drawn = facing_blind and {G.deck.cards[1]} or true,
other_drawn = not facing_blind and {G.deck.cards[1]} and true, other_drawn = not facing_blind and {G.deck.cards[1]} or true,
first_hand_drawn = not current_round.any_hand_drawn and facing_blind or nil, first_hand_drawn = not current_round.any_hand_drawn and facing_blind or nil,
} }
@ -241,7 +243,8 @@ joker {
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen) table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
end, end,
calculate = function(_, _, context) calculate = function(_, _, context)
return (context.selling_self or context.forcetrigger) and f(G.hand.cards):each(function(v) return (context.selling_self or context.forcetrigger) and
f(G.hand.cards):where("edition", false):each(function(v)
q { q {
delay = 0.1, delay = 0.1,
func = function() func = function()
@ -271,7 +274,9 @@ joker {
delay = 1, delay = 1,
func = function() func = function()
play_sound "timpani" play_sound "timpani"
SMODS.add_card {edition = "e_negative", key = v.config.center.key}
SMODS.add_card {edition = "e_negative", key = v.config.center.key, silent = true}
.ability.qty = v.ability.qty
end, end,
} }
end) or nil end) or nil
@ -281,7 +286,7 @@ joker {
joker { joker {
key = "monomino", key = "monomino",
pronouns = "it_its", pronouns = "it_its",
artist = "char", artist = "ghostlyfield",
config = {extra = {price = 4, hand_name = "Four of a Kind"}}, config = {extra = {price = 4, hand_name = "Four of a Kind"}},
attributes = {"sell_value", "scaling", "economy", "hand_type"}, attributes = {"sell_value", "scaling", "economy", "hand_type"},
cost = 4, cost = 4,
@ -310,7 +315,7 @@ joker {
joker { joker {
key = "domino", key = "domino",
pronouns = "it_its", pronouns = "it_its",
artist = "char", artist = "ghostlyfield",
config = {extra = {mult_gain = 4, hand_name = "Four of a Kind", mult = 0}}, config = {extra = {mult_gain = 4, hand_name = "Four of a Kind", mult = 0}},
attributes = {"mult", "scaling", "hand_type"}, attributes = {"mult", "scaling", "hand_type"},
cost = 4, cost = 4,
@ -341,7 +346,7 @@ joker {
joker { joker {
key = "trimino", key = "trimino",
pronouns = "it_its", pronouns = "it_its",
artist = "char", artist = "ghostlyfield",
config = {extra = {times = 4, hand_name = "Four of a Kind"}}, config = {extra = {times = 4, hand_name = "Four of a Kind"}},
attributes = {"generation", "hand_type"}, attributes = {"generation", "hand_type"},
cost = 8, cost = 8,
@ -397,6 +402,72 @@ joker {
end, end,
} }
joker {
key = "misfortune",
pronouns = "she_they",
artist = "ghostlyfield",
attributes = {"discard", "passive", "tarot", "planet", "generation", "seals"},
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_SEALS.Purple)
table.insert(info_queue, G.P_SEALS.Blue)
table.insert(info_queue, negative)
end,
in_pool = function()
return not not f(G.playing_cards):map "seal":any(f.index_into {Blue = true, Purple = true})
end,
}
joker {
key = "polaris",
pronouns = "he_him",
artist = "ghostlyfield",
attributes = {"editions", "space"},
cost = 6,
rarity = 1,
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
return {vars = {}}
end,
calculate = function(_, card, context)
if context.end_of_round then
card.Roland_polaris = nil
return
end
local no = context.blueprint or not context.before or card.Roland_polaris
if no and not context.forcetrigger then
return
end
card.Roland_polaris = true
local first = context.scoring_hand[1]
if not first or first.edition then
return
end
q {
delay = 0.1,
func = function()
card:juice_up()
first:juice_up()
first:set_edition {Roland_frozen = true}
end,
}
return {message = localize {type = "variable", key = "b_Roland_frozen"}, colour = G.C.DARK_EDITION}
end,
}
joker { joker {
key = "cold", key = "cold",
pronouns = "he_him", pronouns = "he_him",
@ -427,6 +498,7 @@ joker {
joker { joker {
key = "snowsquall", key = "snowsquall",
pronouns = "he_they", pronouns = "he_they",
artist = "ghostlyfield",
config = {extra = {mult_gain = 3, mult = 0}}, config = {extra = {mult_gain = 3, mult = 0}},
attributes = {"mult", "scaling", "hand_type"}, attributes = {"mult", "scaling", "hand_type"},
cost = 6, cost = 6,
@ -458,13 +530,37 @@ joker {
end, 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 { joker {
key = "arctic", key = "arctic",
pronouns = "they_them", pronouns = "they_them",
artist = "ghostlyfield",
cost = 10, cost = 10,
rarity = 3, rarity = 3,
config = {extra = {frozen = 1, non_frozen = 0, max_stack = 6, color = "DARK_EDITION"}}, config = {extra = {frozen = 1, non_frozen = 0, color = "DARK_EDITION"}},
attributes = {"retrigger", "editions"}, attributes = {"retrigger", "editions", "copying"},
eternal_compat = true, eternal_compat = true,
blueprint_compat = true, blueprint_compat = true,
perishable_compat = true, perishable_compat = true,
@ -474,9 +570,9 @@ joker {
calculate = function(_, card, context) calculate = function(_, card, context)
local extra = card.ability.extra local extra = card.ability.extra
if extra.max_stack and if f(context.blueprint_copiers_stack)
context.blueprint_copiers_stack and :where("config.center.key", card.config.center.key)
extra.max_stack < #context.blueprint_copiers_stack then :any() then
return return
end end
@ -489,14 +585,15 @@ joker {
end end
return SMODS.merge_effects( return SMODS.merge_effects(
f(G.jokers.cards):where(is_frozen):where(function(v) f(G)
return v.config.center.key ~= card.config.center.key :where(getmetatable, CardArea)
end):map(function(v) :flatmap("cards", ipairs)
return SMODS.blueprint_effect(card, v, context) :where(is_frozen)
end):where(type, "table"):map(function(v) :map(copy(card, context))
v.colour = G.C[extra.color] :where(type, "table")
return v :map(f.insert(G.C[extra.color], "colour"))
end):values():table() :values()
:table()
) )
end, end,
} }
@ -504,8 +601,8 @@ joker {
joker { joker {
key = "sapling", key = "sapling",
pronouns = "they_them", pronouns = "they_them",
artist = "char",
idea = "redstoad", idea = "redstoad",
artist = "ghostlyfield",
attributes = {"mult", "suit"}, attributes = {"mult", "suit"},
cost = 4, cost = 4,
rarity = 1, rarity = 1,
@ -536,49 +633,10 @@ joker {
end, end,
} }
joker {
key = "yard",
pronouns = "he_him",
config = {extra = {money = 2}},
attributes = {"economy"},
cost = 4,
rarity = 1,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.money}}
end,
calculate = function(_, card, context)
return (context.remove_playing_cards or context.forcetrigger) and
{dollars = card.ability.extra.money * #context.removed, card = card} or nil
end,
}
joker {
key = "misfortune",
pronouns = "she_they",
artist = "char",
attributes = {"discard", "generation", "seals"},
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_SEALS.Purple)
table.insert(info_queue, negative)
end,
in_pool = function()
return f(G.playing_cards):any(function(v)
return v.seal == "Purple"
end)
end,
}
joker { joker {
key = "amber", key = "amber",
pronouns = "they_them", pronouns = "they_them",
defeated_blind = "bl_final_acorn",
config = {extra = {xmult = 3}}, config = {extra = {xmult = 3}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
attributes = {"xmult"}, attributes = {"xmult"},
@ -626,6 +684,7 @@ joker {
joker { joker {
key = "cerulean", key = "cerulean",
pronouns = "she_her", pronouns = "she_her",
defeated_blind = "bl_final_bell",
config = {extra = {xmult = 3}}, config = {extra = {xmult = 3}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
attributes = {"xmult"}, attributes = {"xmult"},
@ -645,7 +704,7 @@ joker {
{card = card, xmult = card.ability.extra.xmult} or nil {card = card, xmult = card.ability.extra.xmult} or nil
end, end,
update = function(self, card) update = function(self, card)
local _ = card.area == G.jokers and self:cerulean(false) local _ = card.area == G.jokers and self:cerulean(not not card.debuff)
end, end,
cerulean = function(_, value) cerulean = function(_, value)
local _ = G.jokers and f(G.jokers.cards):each(function(v) local _ = G.jokers and f(G.jokers.cards):each(function(v)
@ -659,6 +718,7 @@ joker {
joker { joker {
key = "crimson", key = "crimson",
pronouns = "she_her", pronouns = "she_her",
defeated_blind = "bl_final_heart",
config = {extra = {xmult = 3}}, config = {extra = {xmult = 3}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
attributes = {"xmult"}, attributes = {"xmult"},
@ -675,13 +735,9 @@ joker {
{card = card, xmult = card.ability.extra.xmult} or nil {card = card, xmult = card.ability.extra.xmult} or nil
end, end,
crimson = function() crimson = function()
local _ = G.jokers and f(G.jokers.cards, ipairs_reversed):where(is_frozen, false):each(function(v) local _ = G.jokers and f(G.jokers.cards, f.revpairs):where(is_frozen, false):each(function(v)
local right = G.jokers.cards[v.rank + 1] 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
v.config.center.key ~= "j_turtle_bean" and
right.config.center.key == "j_Roland_crimson"
if debuffed_by_crimson and v.ability.Roland_crimson == nil then if debuffed_by_crimson and v.ability.Roland_crimson == nil then
local debuff = not not v.debuff local debuff = not not v.debuff
@ -706,6 +762,7 @@ end
joker { joker {
key = "verdant", key = "verdant",
pronouns = "she_her", pronouns = "she_her",
defeated_blind = "bl_final_leaf",
config = {extra = {debuffs = 2, xmult = 3}}, config = {extra = {debuffs = 2, xmult = 3}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
attributes = {"xmult"}, attributes = {"xmult"},
@ -756,6 +813,7 @@ end
joker { joker {
key = "violet", key = "violet",
pronouns = "she_they", pronouns = "she_they",
defeated_blind = "bl_final_vessel",
idea = "hamester", idea = "hamester",
config = {extra = {before = 0.1, xmult = 6}}, config = {extra = {before = 0.1, xmult = 6}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
@ -781,6 +839,8 @@ joker {
joker { joker {
key = "venerable", key = "venerable",
pronouns = "any_all", pronouns = "any_all",
artist = "ghostlyfield",
defeated_blind = "bl_Roland_venerable_visage",
config = {extra = {xdiscard = 3}}, config = {extra = {xdiscard = 3}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
attributes = {"discard", "passive"}, attributes = {"discard", "passive"},
@ -806,11 +866,45 @@ joker {
ease_discard(round_resets.discards - discards) ease_discard(round_resets.discards - discards)
end, 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 { joker {
key = "bulldozer", key = "bulldozer",
pronouns = "she_her", pronouns = "she_her",
idea = "redstoad", idea = "redstoad",
artist = "ghostlyfield",
attributes = {"xmult"}, attributes = {"xmult"},
cost = 6, cost = 6,
rarity = 2, rarity = 2,
@ -873,8 +967,8 @@ joker {
joker { joker {
key = "nilly", key = "nilly",
pronouns = "any_all", pronouns = "any_all",
artist = "ghostlyfield",
idea = "redstoad", idea = "redstoad",
artist = "ghostlyfield",
config = {extra = {flipped = false}}, config = {extra = {flipped = false}},
attributes = {"xmult", "bakery_double_sided"}, attributes = {"xmult", "bakery_double_sided"},
cost = 0, cost = 0,
@ -910,6 +1004,7 @@ joker {
key = "martingale", key = "martingale",
pronouns = "he_him", pronouns = "he_him",
idea = "redstoad", idea = "redstoad",
artist = "ghostlyfield",
config = {extra = {odds = 2}}, config = {extra = {odds = 2}},
attributes = {"xmult", "chance"}, attributes = {"xmult", "chance"},
cost = 8, cost = 8,
@ -973,6 +1068,7 @@ joker {
joker { joker {
key = "idle", key = "idle",
pronouns = "any_all", pronouns = "any_all",
artist = "ghostlyfield",
rarity = 2, rarity = 2,
Roland_idle_capacity = 6, Roland_idle_capacity = 6,
cost = G.P_CENTERS.j_idol.cost, cost = G.P_CENTERS.j_idol.cost,
@ -1049,6 +1145,7 @@ joker {
joker { joker {
key = "suitable", key = "suitable",
pronouns = "she_they", pronouns = "she_they",
artist = "ghostlyfield",
attributes = {"suit", "passive", "hearts", "diamonds", "spades", "clubs"}, attributes = {"suit", "passive", "hearts", "diamonds", "spades", "clubs"},
cost = 4, cost = 4,
rarity = 2, rarity = 2,
@ -1101,8 +1198,8 @@ end
joker { joker {
key = "artemis", key = "artemis",
pronouns = "any_all", pronouns = "any_all",
artist = "hamester",
idea = "redstoad", idea = "redstoad",
artist = "hamester",
cost = 6, cost = 6,
rarity = 2, rarity = 2,
config = {extra = { config = {extra = {
@ -1168,6 +1265,7 @@ joker {
key = "excalibur", key = "excalibur",
pronouns = "he_him", pronouns = "he_him",
idea = "redstoad", idea = "redstoad",
artist = "ghostlyfield",
cost = 8, cost = 8,
rarity = 3, rarity = 3,
config = {extra = { config = {extra = {
@ -1178,13 +1276,17 @@ joker {
}}, }},
attributes = {"xmult", "enhancements", "bakery_double_sided"}, attributes = {"xmult", "enhancements", "bakery_double_sided"},
eternal_compat = true, eternal_compat = true,
blueprint_compat = false, blueprint_compat = true,
perishable_compat = true, perishable_compat = true,
loc_vars = function(_, info_queue, card) loc_vars = function(_, info_queue, card)
local extra = card.ability.extra local extra = card.ability.extra
local _ = card.fake_card or table.insert(info_queue, G.P_CENTERS.m_stone) local _ = card.fake_card or table.insert(info_queue, G.P_CENTERS.m_stone)
return {vars = {extra.required, extra.scored, extra.xmult}} return {vars = {extra.required, extra.scored, extra.xmult}}
end, end,
locked_loc_vars = function(_, info_queue, card)
local _ = card.fake_card or table.insert(info_queue, G.P_CENTERS.m_stone)
return {vars = {}}
end,
generate_ui = function(self, info_queue, card, ...) generate_ui = function(self, info_queue, card, ...)
Bakery_API.werewolf_ui "j_Roland_excalibur_Back" (self, info_queue, card, ...) Bakery_API.werewolf_ui "j_Roland_excalibur_Back" (self, info_queue, card, ...)
@ -1235,7 +1337,7 @@ joker {
joker { joker {
key = "oops", key = "oops",
pronouns = "she_they", pronouns = "she_they",
artist = "char", artist = "ghostlyfield",
cost = 7, cost = 7,
rarity = 3, rarity = 3,
config = {extra = {probability = 1, probability_mult = 2, reset = 1}}, config = {extra = {probability = 1, probability_mult = 2, reset = 1}},

View file

@ -8,8 +8,44 @@
local f = {} local f = {}
if not f then 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 ---@generic I, O
---@param first string|fun(v: I): O ---@param first integer|string|fun(v: I): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(first) function f.chain(first)
@ -17,8 +53,8 @@ if not f then
end end
---@generic I, T, O ---@generic I, T, O
---@param first string|fun(v: I): T ---@param first integer|string|fun(v: I): T
---@param second string|fun(v: T): O ---@param second integer|string|fun(v: T): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(first, second) function f.chain(first, second)
@ -26,9 +62,9 @@ if not f then
end end
---@generic I, T1, T2, O ---@generic I, T1, T2, O
---@param first string|fun(v: I): T1 ---@param first integer|string|fun(v: I): T1
---@param second string|fun(v: T1): T2 ---@param second integer|string|fun(v: T1): T2
---@param third string|fun(v: T2): O ---@param third integer|string|fun(v: T2): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(first, second, third) function f.chain(first, second, third)
@ -36,10 +72,10 @@ if not f then
end end
---@generic I, T1, T2, T3, O ---@generic I, T1, T2, T3, O
---@param first string|fun(v: I): T1 ---@param first integer|string|fun(v: I): T1
---@param second string|fun(v: T1): T2 ---@param second integer|string|fun(v: T1): T2
---@param third string|fun(v: T2): T3 ---@param third integer|string|fun(v: T2): T3
---@param fourth string|fun(v: T3): O ---@param fourth integer|string|fun(v: T3): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(first, second, third, fourth) function f.chain(first, second, third, fourth)
@ -47,11 +83,11 @@ if not f then
end end
---@generic I, T1, T2, T3, T4, O ---@generic I, T1, T2, T3, T4, O
---@param first string|fun(v: I): T1 ---@param first integer|string|fun(v: I): T1
---@param second string|fun(v: T1): T2 ---@param second integer|string|fun(v: T1): T2
---@param third string|fun(v: T2): T3 ---@param third integer|string|fun(v: T2): T3
---@param fourth string|fun(v: T3): T4 ---@param fourth integer|string|fun(v: T3): T4
---@param fifth string|fun(v: T4): O ---@param fifth integer|string|fun(v: T4): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(first, second, third, fourth, fifth) function f.chain(first, second, third, fourth, fifth)
@ -59,7 +95,7 @@ if not f then
end end
---@generic I, O ---@generic I, O
---@param all { [1]: (string|fun(v: I): O) } ---@param all { [1]: (integer|string|fun(v: I): O) }
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(all) function f.chain(all)
@ -67,7 +103,7 @@ if not f then
end end
---@generic I, T, O ---@generic I, T, O
---@param all { [1]: (string|fun(v: I): T), [2]: (string|fun(v: T): O) } ---@param all { [1]: (integer|string|fun(v: I): T), [2]: (integer|string|fun(v: T): O) }
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(all) function f.chain(all)
@ -75,7 +111,7 @@ if not f then
end end
---@generic I, T1, T2, O ---@generic I, T1, T2, O
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: 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) }
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(all) function f.chain(all)
@ -83,7 +119,7 @@ if not f then
end end
---@generic I, T1, T2, T3, O ---@generic I, T1, T2, 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) } ---@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) }
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(all) function f.chain(all)
@ -91,13 +127,103 @@ if not f then
end end
---@generic I, T1, T2, T3, T4, O ---@generic I, T1, T2, T3, 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) } ---@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) }
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard ---@nodiscard
function f.chain(all) function f.chain(all)
error(all) error(all)
end 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 ---@generic K, V
---@param self F | { [K]: V } ---@param self F | { [K]: V }
---@return K?, V? ---@return K?, V?
@ -114,7 +240,17 @@ local none
---@return T ---@return T
---@nodiscard ---@nodiscard
local function autofunc(func) local function autofunc(func)
return type(func) == "string" and f.indices(func) or func or f.id 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
end end
---@generic K, V ---@generic K, V
@ -125,7 +261,7 @@ end
---@return V ---@return V
---@nodiscard ---@nodiscard
local function autopairs(tbl, fpairs) local function autopairs(tbl, fpairs)
return (fpairs or (tbl[#tbl] and ipairs or pairs))(tbl) return (fpairs or (tbl[1] and ipairs or pairs))(tbl)
end end
--- Always returns nil. --- Always returns nil.
@ -159,7 +295,7 @@ end
---@generic T ---@generic T
---@param value T ---@param value T
---@return fun(T): boolean ---@return fun(v: T): boolean
---@nodiscard ---@nodiscard
function f.eq(value) function f.eq(value)
return function(v) return function(v)
@ -230,11 +366,11 @@ end
---@nodiscard ---@nodiscard
function f.indices(v) function f.indices(v)
return function(x) return function(x)
for i in v:gmatch "[^.]+" do
if type(x) ~= "table" then if type(x) ~= "table" then
return x return x
end end
for i in v:gmatch "[^.]+" do
x = x[i] x = x[i]
end end
@ -242,6 +378,36 @@ function f.indices(v)
end end
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 ---@param any any
---@return boolean ---@return boolean
---@nodiscard ---@nodiscard
@ -249,10 +415,24 @@ function f.isf(any)
return type(any) == "table" and any.from == f.from and any.new == f.new return type(any) == "table" and any.from == f.from and any.new == f.new
end 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(...) f[true and "chain"] = function(...)
local ret local ret
for _, v in ipairs {...} do for _, v in ipairs {...} do
v = f.isf(v) and v:table() or v
if type(v) == "table" then if type(v) == "table" then
for _, vv in ipairs(v) do for _, vv in ipairs(v) do
local copy = ret local copy = ret
@ -275,6 +455,10 @@ f[true and "chain"] = function(...)
return ret or f.noop return ret or f.noop
end end
f[true and "compose"] = function(...)
return f.chain(f.from({...}, f.revpairs):values())
end
---@generic K, V ---@generic K, V
---@param fnext? fun(): K?, V? ---@param fnext? fun(): K?, V?
---@return F|{ [K]: V } ---@return F|{ [K]: V }
@ -285,10 +469,13 @@ function f.new(fnext)
all = f.all, all = f.all,
any = f.any, any = f.any,
arg = f.arg, arg = f.arg,
call = f.call,
chain = f.chain, chain = f.chain,
compose = f.compose,
concat = f.concat, concat = f.concat,
const = f.const, const = f.const,
count = f.count, count = f.count,
diff = f.diff,
each = f.each, each = f.each,
eq = f.eq, eq = f.eq,
fals = f.fals, fals = f.fals,
@ -299,6 +486,7 @@ function f.new(fnext)
index = f.index, index = f.index,
index_into = f.index_into, index_into = f.index_into,
indices = f.indices, indices = f.indices,
insert = f.insert,
isf = f.isf, isf = f.isf,
keys = f.keys, keys = f.keys,
map = f.map, map = f.map,
@ -308,6 +496,8 @@ function f.new(fnext)
nq = f.nq, nq = f.nq,
peek = f.peek, peek = f.peek,
pun = f.pun, pun = f.pun,
rev = f.rev,
revpairs = f.revpairs,
skip = f.skip, skip = f.skip,
slice = f.slice, slice = f.slice,
string = f.string, string = f.string,
@ -366,10 +556,10 @@ function f.from(iter, fpairs, step)
end end
end) end)
else else
local next, context, k, v = autopairs(iter, type(fpairs) == "function" and fpairs or nil) local next, tbl, k, v = autopairs(iter, type(fpairs) == "function" and fpairs or nil)
return f.new(function() return f.new(function()
k, v = next(context, k) k, v = next(tbl, k)
return k, v return k, v
end) end)
end end
@ -381,12 +571,14 @@ end
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@nodiscard ---@nodiscard
function f:concat(...) function f:concat(...)
self = f.isf(self) and self or f.from(self)
local fsi = 0 local fsi = 0
local fs = {...} local fs = {...}
local sum, last = 0, 0 local sum, last = 0, 0
for i = 1, #fs do for i = 1, #fs do
fs[i] = f.isf(fs[i]) and fs[i] or f.from(fs[i]) local s = fs[i]
fs[i] = f.isf(s) and s or f.from(s)
end end
return f.new(function() return f.new(function()
@ -420,6 +612,7 @@ end
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@nodiscard ---@nodiscard
function f:peek(func) function f:peek(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func) func = autofunc(func)
return f.new(function() return f.new(function()
@ -439,6 +632,7 @@ end
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U } ---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@nodiscard ---@nodiscard
function f:map(func) function f:map(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func) func = autofunc(func)
return f.new(function() return f.new(function()
@ -458,11 +652,14 @@ 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 } ---@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 ---@nodiscard
function f:flatmap(func, fpairs) function f:flatmap(func, fpairs)
self = f.isf(self) and self or f.from(self)
-- local i = 0 -- local i = 0
local vt, vk, vv, vp local vt, vk, vp
func = autofunc(func) func = autofunc(func)
return f.new(function() return f.new(function()
local vv
if vk then if vk then
vk, vv = vp(vt, vk) vk, vv = vp(vt, vk)
@ -508,6 +705,7 @@ end
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string, is?: any): F|{ [K]: V } ---@overload fun(self: F|{ [K]: V }, func: string, is?: any): F|{ [K]: V }
function f:where(func, is) function f:where(func, is)
self = f.isf(self) and self or f.from(self)
func = autofunc(func) func = autofunc(func)
return f.new(function() return f.new(function()
@ -540,6 +738,7 @@ end
---@return F|{ [integer]: K } ---@return F|{ [integer]: K }
---@nodiscard ---@nodiscard
function f:keys() function f:keys()
self = f.isf(self) and self or f.from(self)
local i = 0 local i = 0
return f.new(function() return f.new(function()
@ -557,6 +756,7 @@ end
---@return F|{ [integer]: V } ---@return F|{ [integer]: V }
---@nodiscard ---@nodiscard
function f:values() function f:values()
self = f.isf(self) and self or f.from(self)
local i = 0 local i = 0
return f.new(function() return f.new(function()
@ -574,6 +774,8 @@ end
---@return F|{ [V]: K } ---@return F|{ [V]: K }
---@nodiscard ---@nodiscard
function f:swap() function f:swap()
self = f.isf(self) and self or f.from(self)
return f.new(function() return f.new(function()
local k, v = self:next() local k, v = self:next()
@ -583,6 +785,28 @@ function f:swap()
end) end)
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 ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param skip? integer ---@param skip? integer
@ -590,6 +814,8 @@ end
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@nodiscard ---@nodiscard
function f:slice(skip, take) 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 if (not skip or skip <= 0) and not take then
return self return self
end end
@ -621,6 +847,7 @@ end
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@nodiscard ---@nodiscard
function f:skip(n) function f:skip(n)
self = f.isf(self) and self or f.from(self)
return self:slice(n) return self:slice(n)
end end
@ -630,6 +857,7 @@ end
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@nodiscard ---@nodiscard
function f:take(n) function f:take(n)
self = f.isf(self) and self or f.from(self)
return self:slice(0, n) return self:slice(0, n)
end end
@ -641,6 +869,7 @@ end
---@overload fun(self: F|{ [K]: V }, seed: fun(a: A, v: V, k: K): A): A ---@overload fun(self: F|{ [K]: V }, seed: fun(a: A, v: V, k: K): A): A
---@nodiscard ---@nodiscard
function f:fold(seed, func) function f:fold(seed, func)
self = f.isf(self) and self or f.from(self)
local k, v local k, v
if not func then if not func then
@ -670,6 +899,7 @@ end
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V ---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard ---@nodiscard
function f:any(func) function f:any(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func) func = autofunc(func)
for k, v in self.next do for k, v in self.next do
@ -688,6 +918,7 @@ end
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V ---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard ---@nodiscard
function f:all(func) function f:all(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func) func = autofunc(func)
for k, v in self.next do for k, v in self.next do
@ -706,8 +937,9 @@ end
---@overload fun(self: F|{ [K]: V }, func: string): integer ---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard ---@nodiscard
function f:count(func) function f:count(func)
local ret = 0 self = f.isf(self) and self or f.from(self)
func = autofunc(func) func = autofunc(func)
local ret = 0
for k, v in self.next do for k, v in self.next do
if not func or func(v, k) then if not func or func(v, k) then
@ -718,6 +950,24 @@ function f:count(func)
return ret return ret
end end
---@generic K, V
---@param self F|{ [K]: V }
---@param other { [K]: V }
---@param func? fun(v: V, k: K): any
---@return K?, V?
---@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)
for k, v in self.next do
if (not func or func(v, k)) and other[k] ~= v then
return k, v
end
end
end
---@generic K, V, T ---@generic K, V, T
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param _ `T` ---@param _ `T`
@ -732,6 +982,7 @@ end
---@return string ---@return string
---@nodiscard ---@nodiscard
function f:string() function f:string()
self = f.isf(self) and self or f.from(self)
local k, v = self:next() local k, v = self:next()
if k == nil then if k == nil then
@ -756,6 +1007,7 @@ end
---@return { [K]: V } ---@return { [K]: V }
---@nodiscard ---@nodiscard
function f:table() function f:table()
self = f.isf(self) and self or f.from(self)
local ret = {} local ret = {}
for k, v in self.next do for k, v in self.next do
@ -769,6 +1021,8 @@ end
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func? fun(v: V, k: K): nil ---@param func? fun(v: V, k: K): nil
function f:each(func) function f:each(func)
self = f.isf(self) and self or f.from(self)
for k, v in self.next do for k, v in self.next do
if func then if func then
func(v, k) func(v, k)

View file

@ -28,17 +28,38 @@ CardSleeves.Sleeve = CardSleeves.Sleeve
--- @type {aliases: { [string]: [string] }} --- @type {aliases: { [string]: [string] }}
Cryptid = Cryptid Cryptid = Cryptid
---@return Card
function copy_card(...)
error(...)
end
--- @type fun(area: CardArea, ...: ...): Card --- @type fun(area: CardArea, ...: ...): Card
create_card_for_shop = create_card_for_shop create_card_for_shop = create_card_for_shop
--- @type CardArea
G.Bakery_charm_area = G.Bakery_charm_area
--- @type boolean|table --- @type boolean|table
G.Bakery_charm_area.cards[1].ability.extra = G.Bakery_charm_area.cards[1].ability.extra G.Bakery_charm_area.cards[1].ability.extra = G.Bakery_charm_area.cards[1].ability.extra
SMODS.Mods.Roland.config = require "config" ---@type Card[]
G.playing_cards = G.playing_cards
---@type userdata|{getWidth: fun(self: self): number} ---@type userdata|{getWidth: fun(self: self): number}
SMODS.Atlas.image_data = SMODS.Atlas.image_data 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 table --- @type table
Talisman = Talisman Talisman = Talisman

View file

@ -13,8 +13,26 @@ local function add(v)
return xop return xop
end 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 if not G.P_TAGS[v] then
return SMODS.add_card {no_edition = true, key = v} 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,
}
end end
local tag = Tag(v) local tag = Tag(v)
@ -43,7 +61,9 @@ local function find(it)
end end
local match = simplify(it) local match = simplify(it)
local pool = f(G.P_CENTER_POOLS):any(f.chain(f.arg(2), simplify, f.eq(match)))
local pool = match == "Blind" and G.P_BLINDS or
f(G.P_CENTER_POOLS):any(f.chain(f.arg(2), simplify, f.eq(match)))
if type(pool) == "table" and next(pool) then if type(pool) == "table" and next(pool) then
return pseudorandom_element(pool, pseudoseed "Roland_c").key return pseudorandom_element(pool, pseudoseed "Roland_c").key
@ -56,9 +76,9 @@ local function find(it)
return f(G.localization.descriptions) return f(G.localization.descriptions)
:flatmap(flatten) :flatmap(flatten)
:where(type, "table") :where(type, "table")
:where(f.chain(f.arg(2), f.index_into(G.P_CENTERS), "config"))
:where(function(v, k) :where(function(v, k)
return match == simplify(k) or match == simplify(v.name) return ((G.P_CENTERS[k] or {}).config or G.P_BLINDS[k]) and
(match == simplify(k) or match == simplify(v.name))
end) end)
:keys() :keys()
:any() :any()

View file

@ -1,10 +1,13 @@
local qol = assert(SMODS.load_file "src/lib/shared.lua")() or require "lib.shared" 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] local f, q = qol[1], qol[2]
q { q {
front = true, front = true,
no_delete = true, no_delete = true,
blocking = false, blocking = false,
blockable = false,
func = function() func = function()
local contributors = (Bakery_API or {}).contributors local contributors = (Bakery_API or {}).contributors
@ -12,88 +15,46 @@ q {
return false return false
end end
-- Special shoutout to all contributors. <3 f(assert(SMODS.load_file("src/credits.lua", "Roland"))() or require "credits"):each(function(v, k)
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 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) end)
if not SMODS.Mods.DebugPlus or not SMODS.Mods.Roland.config.import_funky then if SMODS.Mods.DebugPlus and SMODS.Mods.Roland.config.import_funky then
return
end
_G.f, _G.q, _G.u, _G.c = unpack(qol) _G.f, _G.q, _G.u, _G.c = unpack(qol)
end
end, end,
} }
f { f {"challenge", "spectral", "edition", "voucher", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"}
"challenge", :each(function(v)
"spectral",
"edition",
"tweaks",
"blind",
"charm",
"joker",
"tarot",
"back",
"seal",
"tag",
"voucher",
}:each(function(v)
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol) assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
end) end)
if Balatest then local _ = Balatest and f {"joker", "blind", "spectral"}:each(function(v)
f {"joker", "blind", "spectral"}:each(function(v)
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol) assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
end) 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
end end
local function toggle(id) local function toggle(id)
return create_toggle { return realign(create_toggle {
label = localize {type = "variable", key = "b_Roland_" .. id}, label = localize {type = "variable", key = "b_Roland_" .. id},
ref_table = SMODS.Mods.Roland.config, ref_table = SMODS.Mods.Roland.config,
ref_value = id, ref_value = id,
scale = 1.5, scale = 1.5,
} })
end end
local animated = SMODS.Mods.Roland.config.animated_icon
SMODS.Atlas { SMODS.Atlas {
px = 256, px = 256,
py = 256, py = 256,
@ -107,24 +68,30 @@ SMODS.Atlas {
function SMODS.current_mod.config_tab() function SMODS.current_mod.config_tab()
return { return {
n = G.UIT.ROOT, n = G.UIT.ROOT,
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.BLACK}, config = {minw = 1, minh = 1, align = "tr", padding = 0.1, colour = G.C.BLACK},
nodes = {{ nodes = {
{
n = G.UIT.C, n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR}, config = {minw = 1, minh = 1, align = "tr", padding = 0.1, colour = G.C.CLEAR},
nodes = { nodes = {
toggle "animated_icon", toggle "animated_icon",
toggle "cool_phones", toggle "cool_phones",
toggle "faster_planets",
-- toggle "illusion_seal",
-- toggle "no_wild_debuff",
toggle "vitriol", toggle "vitriol",
toggle "equinox_assist", toggle "equinox_assist",
SMODS.Mods.DebugPlus and toggle "import_funky",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket", 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",
Talisman and toggle "harsh_ante_scaling", Talisman and toggle "harsh_ante_scaling",
}, },
}}, },
},
} }
end end
SMODS.current_mod.qol = qol

View file

@ -38,26 +38,32 @@ spectral {
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function() can_use = function()
return u() and #G.hand.cards > 0 return next(G.hand.cards) and u()
end, end,
use = function(_, card, _) use = function(_, card)
local cards = f(G.hand.cards):table() local cards = f(G.hand.cards):table()
pseudoshuffle(cards, pseudoseed "RolandDual") pseudoshuffle(cards, pseudoseed "RolandDual")
f(cards):take(card.ability.extra.amount):each(function(v) f(cards):take(card.ability.extra.amount):each(function(v)
local seal local seal = SMODS.poll_seal {
guaranteed = true,
options = f(G.P_SEALS):keys():where(f.nq "Roland_glass"):values():table(),
}
for _ = 1, 64 do local _ = not card.Roland_immediate and q {
seal = SMODS.poll_seal {guaranteed = true} delay = 0.5,
trigger = "after",
if seal ~= "Roland_glass" then func = function()
break v:set_seal(seal, nil, true)
end end,
end } or v:set_seal(seal, true, true)
v:set_seal(seal == "Roland_glass" and "Red" or seal)
end) end)
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 { spectral {
@ -77,7 +83,7 @@ spectral {
delay = 0.4, delay = 0.4,
func = function() func = function()
card:juice_up(0.3, 0.5) card:juice_up(0.3, 0.5)
play_sound("tarot1") play_sound "tarot1"
end, end,
} }
@ -132,9 +138,10 @@ spectral {
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function(_, card) can_use = function(_, card)
return u() and #Bakery_API.get_highlighted() == card.ability.extra.amount local count = #Bakery_API.get_highlighted()
return u() and count > 0 and count <= card.ability.extra.amount
end, end,
use = function(_, _, _) use = function()
f(Bakery_API.get_highlighted()):each(function(v) f(Bakery_API.get_highlighted()):each(function(v)
v:set_seal "Roland_glass" v:set_seal "Roland_glass"
end) end)
@ -145,6 +152,7 @@ spectral {
key = "void", key = "void",
pronouns = "it_its", pronouns = "it_its",
artist = "aster", artist = "aster",
hidden = true,
soul_rate = 0.003, soul_rate = 0.003,
soul_set = "Spectral", soul_set = "Spectral",
config = {extra = {amount = 2}}, config = {extra = {amount = 2}},

View file

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

View file

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

View file

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

View file

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