Compare commits

..

No commits in common. "main" and "frozen-jokers" have entirely different histories.

67 changed files with 1888 additions and 3973 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 = remove call_arg_parentheses = keep
detect_end_of_line = false detect_end_of_line = false

6
.gitignore vendored
View file

@ -38,9 +38,3 @@ luac.out
*.i*86 *.i*86
*.x86_64 *.x86_64
*.hex *.hex
# Syncthing
.stfolder
# Autogenerated
assets/1x/jokers

View file

@ -65,8 +65,4 @@
"unused-local": "Error", "unused-local": "Error",
"unused-vararg": "Error", "unused-vararg": "Error",
}, },
"Lua.runtime.version": "LuaJIT",
"Lua.workspace.library": [
"${3rd}/love2d/library"
],
} }

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

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: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -1,10 +0,0 @@
#!/bin/sh
directory=$(dirname $(readlink -f "$0"))
mkdir -p "$directory/1x/jokers"
for i in $(seq 0 $(cd "$directory/1x/jokers/" && find *.png | tail -n +2 | wc -l)); do
files="$files $directory/1x/jokers/$i.png";
done
magick montage -background '#00000000' $files -geometry +0+0 "$directory/1x/joker.png"
"$directory/upscale.sh"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,4 +0,0 @@
#!/bin/sh
directory=$(dirname $(readlink -f "$0"))
mkdir -p "$directory/1x/jokers"
magick "$directory/1x/joker.png" -crop 71x95 +repage +adjoin "$directory/1x/jokers/%01d.png"

View file

@ -1,18 +1 @@
@echo off for /F %%x in ('dir /B/D 1x') do magick 1x\%%x -scale 200%% 2x\%%x
for /F %%x in ('dir /B/D 2x') do (
if %%~xx == .png (
if not exist 1x\%%x (
echo Removing deleted file 1x\%%x from 2x\
del 2x\%%x
)
)
)
for /F %%x in ('dir /B/D 1x') do (
if %%~xx == .png (
REM https://stackoverflow.com/questions/1687014/how-do-i-compare-timestamps-of-files-in-a-batch-script/58323817#58323817
xcopy /LDY /-I 1x\%%x 2x\%%x | findstr /B /C:"1 " > nul 2>&1 && (
echo Updating changed file 1x\%%x in 2x\
magick 1x\%%x -filter point -resize 200%% 2x\%%x
)
)
)

View file

@ -1,19 +1,8 @@
#!/bin/sh #!/bin/sh
cd "$(dirname "$0")" directory=$(dirname $(readlink -f "$0"))
for file in 2x/*.png; do for file in "$directory"/1x/*.png; do
filename=$(basename "$file") filename=$(basename "$file")
if [ ! -f "1x/${filename}" ]; then output_file="$directory/2x/${filename}"
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

@ -1,12 +1,9 @@
return { return {
animated_icon = true,
cool_phones = true,
equinox_assist = false, equinox_assist = false,
faster_planets = false, fusable_escapey = false,
harsh_ante_scaling = false, harsh_ante_scaling = true,
illusion_seal = true, illusion_seal = true,
import_funky = false, import_funky = false,
no_highlight_limit = true,
no_wild_debuff = true, no_wild_debuff = true,
scribable_basket = false,
vitriol = true,
} }

View file

@ -6,71 +6,52 @@ 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 thrice", "that are {C:dark_edition}debuffed #1# times",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
b_Roland_dropship = { b_Roland_go = {
name = "Dropship Deck", name = "Pass GO Deck",
text = { text = {
"{C:attention}Jokers {}are {C:money}free", "Start with a {C:attention}Credit Card",
"and {C:attention}Perishable", "Set money to {C:money}$0 {}when",
"", "entering the shop",
"{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 = {
"{C:tarot}Tarot {}and {C:spectral}Spectral", "{C:tarot}Tarot {}and {C:spectral}Spectral",
"cards swap places", "cards swap places",
"",
"{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 = {
name = SMODS.Mods.Roland.config.cool_phones and "coca cola phone" or "Pentagram", name = "coca cola phone",
text = { text = {
"{C:attention}Discard 0 cards {}to", "Values on consumables",
"gain {C:red}#1# {}discards and", "increase by {C:attention}1",
"lose {C:attention}#2# {}hand size", "{C:inactive}(excluding {C:planet}Planets{C:inactive})",
"this round",
}, },
}, },
BakeryCharm_Roland_fat = { BakeryCharm_Roland_fat = {
name = SMODS.Mods.Roland.config.cool_phones and "fat i phone" or "Product", name = "fat i phone",
text = { text = {
"{C:attention}+#1# Booster Pack {}slots", -- "{C:attention}+#1# Booster Pack {}slots",
"All {C:attention}Booster Packs {}are {C:attention}Mega", "All {C:attention}Booster Packs {}are {C:attention}Mega",
"{C:inactive}(Whenever applicable)", "{C:inactive}(Whenever applicable)",
}, },
}, },
BakeryCharm_Roland_flexible = { BakeryCharm_Roland_flexible = {
name = SMODS.Mods.Roland.config.cool_phones and "flexi ble phone" or "Ring", name = "flexi ble phone",
text = {unpack(G.localization.descriptions.Joker.j_ring_master.text)}, text = {unpack(G.localization.descriptions.Joker.j_ring_master.text)},
}, },
BakeryCharm_Roland_hand = { BakeryCharm_Roland_hand = {
name = SMODS.Mods.Roland.config.cool_phones and "hand phone" or "Shell", name = "hand phone",
text = {"{C:attention}+#1# {}hand size", "{C:blue}#2# {}hands"}, text = {"{C:attention}+#1# {}hand size", "{C:blue}#2# {}hands"},
}, },
BakeryCharm_Roland_wii = { BakeryCharm_Roland_wii = {
name = SMODS.Mods.Roland.config.cool_phones and "wii phone" or "Tire", name = "wii phone",
text = { text = {
"Enter the shop", "Enter the shop",
"when any {C:attention}Blind", "when any {C:attention}Blind",
@ -81,47 +62,39 @@ return {
Blind = { Blind = {
bl_Roland_blizzard = { bl_Roland_blizzard = {
name = "The Blizzard", name = "The Blizzard",
text = {"All cards", "are {C:dark_edition}Frozen"}, text = {"All cards", "are Frozen"},
}, },
bl_Roland_divide = { bl_Roland_divide = {
name = "The Great Divide", name = "The Great Divide",
text = {"Half of the deck", "is {C:red}discarded"}, text = {"Half of the deck", "is discarded"},
}, },
bl_Roland_equinox = { bl_Roland_equinox = {
name = "The Equinox", name = "The Equinox",
text = {"{C:inactive}No UI"}, text = {"No UI"},
}, },
bl_Roland_falseshuffle = { bl_Roland_falseshuffle = {
name = "The False Shuffle", name = "The False Shuffle",
text = {"{C:enhanced}Enhanced {}cards", "are drawn {C:attention}last"}, text = {"Enhanced cards", "are drawn last"},
}, },
bl_Roland_improbable = { bl_Roland_improbable = {
name = "The Improbable", name = "The Improbable",
text = {"All {C:green}probabilities", "cannot happen"}, text = {"All probabilities", "cannot happen"},
}, },
bl_Roland_mitotic = { bl_Roland_mitotic = {
name = "The Mitotic", name = "The Mitotic",
text = {"{C:red}Discarded {}cards", "are {C:attention}copied"}, text = {"Discarded cards", "are copied"},
}, },
bl_Roland_nimble = { bl_Roland_nimble = {
name = "The Nimble", name = "The Nimble",
text = {"The {C:attention}first 5 {}cards", "drawn are {C:blue}played"}, text = {"The first 5 cards", "drawn are played"},
}, },
bl_Roland_tranquilizer = { bl_Roland_tranquilizer = {
name = "The Tranquilizer", name = "The Tranquilizer",
text = { text = {"#1#s are debuffed", "Changes based on", "most common rank"},
"{C:attention}#1#s {}are {C:red}debuffed",
"Changes based on",
"most common rank",
},
}, },
bl_Roland_venerable_visage = { bl_Roland_venerable_visage = {
name = "Venerable Visage", name = "Venerable Visage",
text = { text = {"Infinite discards", "Lose if any card was", "not played or discarded"},
"Infinite {C:red}discards",
"Lose if {C:attention}any {}card was",
"not {C:blue}played {}or {C:red}discarded",
},
fail = { fail = {
"Blasphemy.", "Blasphemy.",
"Blunder.", "Blunder.",
@ -153,28 +126,9 @@ return {
}, },
}, },
Joker = { Joker = {
j_Roland_amber = {
name = "Amber Acorn",
text = {
"{X:mult,C:white}X#1#{} Mult",
"Moves before",
"hand is played",
},
unlock = {"Defeat {C:attention}#1#"},
},
j_Roland_arctic = { j_Roland_arctic = {
name = "Arctic Circle", name = "Arctic Circle",
text = {"Retrigger {C:attention}all", "{C:dark_edition}Frozen {}cards"}, text = {"Retrigger all", "{C:dark_edition}Frozen {}cards"},
},
j_Roland_artemis = {
name = "Artemis X",
text = {
"Earn {C:money}$#1# {}at end of round",
"Payout increases by {C:money}$#2#",
"when these {C:planet}Planet {}cards",
"are used in order:",
"{V:1}Earth{C:inactive} - {V:2}Mars{C:inactive} - {V:3}Earth",
},
}, },
j_Roland_basket = { j_Roland_basket = {
name = "Basket", name = "Basket",
@ -187,37 +141,11 @@ return {
j_Roland_bulldozer = { j_Roland_bulldozer = {
name = "Bulldozer", name = "Bulldozer",
text = { text = {
"Give the {C:attention}Blind", "Give {X:red,C:white}XMult{} equal to",
"{C:attention}Base {}as {X:red,C:white}XMult", "the current {C:attention}Blind Size{C:attention}",
"{C:inactive}(Currently {X:red,C:white}X#1#{C:inactive} Mult)", "{C:inactive}(Currently {X:red,C:white}X#1#{C:inactive})",
}, },
}, },
j_Roland_cold = {
name = "Cold Turkey",
text = {
"Third scored card",
"gives {X:red,C:white}X#1#{} Mult",
"if it is {C:dark_edition}Frozen",
},
},
j_Roland_cerulean = {
name = "Cerulean Bell",
text = {
"{X:mult,C:white}X#1#{} Mult",
"All other {C:attention}Jokers {}cannot",
"be selected or dragged",
},
unlock = {"Defeat {C:blue}#1#"},
},
j_Roland_crimson = {
name = "Crimson Heart",
text = {
"{X:mult,C:white}X#1#{} Mult",
"{C:red}Debuffs {C:attention}Joker",
"to the left",
},
unlock = {"Defeat {C:red}#1#"},
},
j_Roland_domino = { j_Roland_domino = {
name = "Domino", name = "Domino",
text = { text = {
@ -227,20 +155,30 @@ return {
"{C:inactive}(Currently {C:red}+#3#{C:inactive} Mult)", "{C:inactive}(Currently {C:red}+#3#{C:inactive} Mult)",
}, },
}, },
j_Roland_excalibur = { j_Roland_escapey = {
name = "Excalibur", name = "Escapey",
text = { text = {
"Score {C:attention}#1# {C:enhanced}Stone Cards", "Use to {C:attention}destroy tags{} or {C:attention}unselected consumables",
"to {C:attention}pull {}this {C:attention}Joker", "in exchange for {C:planet}leveling up {X:planet,C:white}#1#{C:planet} random hands",
"{C:inactive}(Currently {C:attention}#2#{C:inactive})", "{C:inactive,s:0.75,E:1}#2#{C:red,s:1.5,E:1}#3#{s:0.9}#4#{C:blue,E:1,s:0.9}#5#{s:0.9}#6#",
}, },
merge = {"Since none apply, fuse with other ", " jokers"},
quotes = {
marble = {"there is no escape..."},
normal = {
"I can't wait to work with you!",
"Did you need something from me?",
"Oh! I'm just so happy to see you!",
"Can I say something irrelevant? I promise it won't be long.",
"Tell me about your buddies! Assuming you have them, anyway.",
},
scared = {
"What am I going to do?!",
"I'm not scared, you are!",
"Tell me when this is over...",
"I can't keep looking at this!",
"Let me go hide in this corner... Okay?",
}, },
j_Roland_excalibur_Back = {
name = "Excalibur",
text = {
"Scored and held in",
"hand {C:enhanced}Stone Cards",
"each give {X:mult,C:white}X#3#{} Mult",
}, },
}, },
j_Roland_hardboiled = { j_Roland_hardboiled = {
@ -251,23 +189,6 @@ return {
"hand {C:dark_edition}Frozen", "hand {C:dark_edition}Frozen",
}, },
}, },
j_Roland_idle = {
name = "The Idle",
text = {
"Use to add card's",
"rank and suit",
"First {C:attention}scoring {}card",
"of each entry",
"gives {X:mult,C:white}X#19#{} Mult",
"{C:inactive}(Must be distinct)",
"{V:1,s:0.75}#1#{C:inactive,s:0.75}#2#{V:2,s:0.75}#3#{C:inactive,s:0.75}, " ..
"{V:3,s:0.75}#4#{C:inactive,s:0.75}#5#{V:4,s:0.75}#6#{C:inactive,s:0.75}",
"{V:5,s:0.75}#7#{C:inactive,s:0.75}#8#{V:6,s:0.75}#9#, " ..
"{V:7,s:0.75}#10#{C:inactive,s:0.75}#11#{V:8,s:0.75}#12#{C:inactive,s:0.75}",
"{V:9,s:0.75}#13#{C:inactive,s:0.75}#14#{V:10,s:0.75}#15#{C:inactive,s:0.75}, " ..
"{V:11,s:0.75}#16#{C:inactive,s:0.75}#17#{V:12,s:0.75}#18#",
},
},
j_Roland_jokersr = { j_Roland_jokersr = {
name = "Joker Sr.", name = "Joker Sr.",
text = {"{X:mult,C:white}X#1#{} Mult"}, text = {"{X:mult,C:white}X#1#{} Mult"},
@ -288,8 +209,9 @@ return {
j_Roland_misfortune = { j_Roland_misfortune = {
name = "Misfortune Cookie", name = "Misfortune Cookie",
text = { text = {
"Cards created by", "{C:tarot}Tarot{} cards created",
"{C:attention}Seals {}become {C:dark_edition}Negative", "by {C:tarot}Purple Seals",
"become {C:dark_edition}Negative",
}, },
}, },
j_Roland_monomino = { j_Roland_monomino = {
@ -315,14 +237,6 @@ return {
name = "Ms. Joker", name = "Ms. Joker",
text = {"{C:chips}+#1#{} Chips"}, text = {"{C:chips}+#1#{} Chips"},
}, },
j_Roland_nilly = {
name = "Nilly",
text = {
"Toggleable {X:mult,C:white}X0{} Mult",
"{C:inactive}(Currently {C:attention}#1#{C:inactive})",
"{C:inactive,s:0.75,E:1}Wait... what?",
},
},
j_Roland_oops = { j_Roland_oops = {
name = "Oops! All 7s", name = "Oops! All 7s",
text = { text = {
@ -333,15 +247,7 @@ return {
}, },
j_Roland_phytoestrogens = { j_Roland_phytoestrogens = {
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",
@ -352,15 +258,6 @@ return {
"least {C:attention}#2# suits", "least {C:attention}#2# suits",
}, },
}, },
j_Roland_snowsquall = {
name = "Snowsquall",
text = {
"This Joker gains",
"{C:mult}+#1# {}Mult for every",
"played {C:dark_edition}Frozen {}card",
"{C:inactive}(Currently {C:red}+#2#{C:inactive} Mult)",
},
},
j_Roland_suitable = { j_Roland_suitable = {
name = "Suitable", name = "Suitable",
text = {"{V:1}#1# {}are {C:attention}Wild", "Suit changes", "every round"}, text = {"{V:1}#1# {}are {C:attention}Wild", "Suit changes", "every round"},
@ -370,7 +267,7 @@ return {
text = { text = {
"Sell this Joker to", "Sell this Joker to",
"{C:attention}draw{} the bottom", "{C:attention}draw{} the bottom",
"card of the deck", "card of the {C:hand}deck",
"{C:inactive}(Currently {V:1}#1#{C:inactive}#2#{V:2}#3#{C:inactive})", "{C:inactive}(Currently {V:1}#1#{C:inactive}#2#{V:2}#3#{C:inactive})",
}, },
}, },
@ -391,38 +288,13 @@ return {
"{C:attention}#2#", "{C:attention}#2#",
}, },
}, },
j_Roland_verdant = {
name = "Verdant Leaf",
text = {
"{X:mult,C:white}X#1#{} Mult",
"{C:red}Debuffs {}the {C:attention}first",
"{C:attention}#2# {}scored cards",
},
unlock = {"Defeat {C:green}#1#"},
},
j_Roland_violet = {
name = "Violet Vessel",
text = {
"{X:mult,C:white}X#1#{} Mult,",
"but {X:mult,C:white}X#2#{} Mult",
"{C:attention}before {}scoring",
},
unlock = {"Defeat {C:purple}#1#"},
},
j_Roland_venerable = {
name = "Venerable Visage",
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}added", "card is {C:attention}removed",
"or {C:attention}removed",
"from the deck", "from the deck",
}, },
unlock = {"Complete the", "{C:attention}#1#", "challenge"},
}, },
}, },
Other = { Other = {
@ -441,104 +313,78 @@ 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 thrice", "that are {C:dark_edition}debuffed #1# times",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
sleeve_Roland_blossom_alt = { sleeve_Roland_blossom_alt = {
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:attention}Blinds{} are {C:dark_edition}debuffed",
"{C:inactive}(Instead of thrice)", "{C:dark_edition}({X:dark_edition,C:white}ante{} {X:dark_edition,C:white}number{C:dark_edition}) times",
"", "{C:inactive}(Instead of #1#)",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
sleeve_Roland_dropship = { sleeve_Roland_go = {
name = "Dropship Sleeve", name = "Pass GO Sleeve",
text = { text = {
"{C:attention}Jokers {}are {C:money}free", "Start with a {C:attention}Credit Card",
"and {C:attention}Perishable", "Set money to {C:money}$0 {}when",
"", "entering the shop",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
unlock = {"Have exclusively", "{C:attention}Perishable Jokers"},
}, },
sleeve_Roland_dropship_alt = { sleeve_Roland_go_alt = {
name = "Business Sleeve", name = "Free Parking Sleeve",
text = { text = {
"{C:attention}Jokers {}are instead", "Start with an additional",
"{C:dark_edition}Negative {}and {C:money}Rental", "{X:attention,C:white}#1#{C:attention} Credit Cards",
"",
"{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 = {
"{C:tarot}Tarot {}and {C:spectral}Spectral", "{C:tarot}Tarot {}and {C:spectral}Spectral",
"cards swap places", "cards swap places",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
sleeve_Roland_swapper_alt = { sleeve_Roland_swapper_alt = {
name = "Sleeve Swapper", name = "Sleeve Swapper",
text = { text = {
"Swap {C:attention}all {}consumables", "{C:attention}All {}consumable",
"", "types swap places",
"{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 = {
name = "Afterimage", name = "Afterimage",
text = {
"Add {C:dark_edition}Negative {}to {C:attention}#1#",
"selected card in hand",
"{C:red}#2#{} hand size",
},
},
c_Roland_dual = {
name = "Dual",
text = { text = {
"Add random seals", "Add random seals",
"to {C:attention}#1#{} random", "to {C:attention}#1#{} random",
"cards in hand", "cards in {C:hands}hand",
}, },
}, },
c_Roland_primal = { c_Roland_coolheaded = {
name = "Primal Force", name = "Coolheaded",
text = { text = {
"Enhance {C:attention}all {}cards", "Add {C:dark_edition}Frozen {}to {C:attention}#1#",
"held in hand to", "selected {C:attention}playing",
"{C:enhanced}Stone Cards", "{C:attention}card {}or {C:attention}Joker",
}, },
}, },
c_Roland_refract = { c_Roland_mirror = {
name = "Refract", name = "Mirror",
text = { text = {
"Add a {C:dark_edition}Glass Seal", "Add a {C:dark_edition}Glass Tag",
"to {C:attention}#1#{} selected", "to {C:attention}#1#{} selected",
"cards in hand", "card in {C:hands}hand",
}, },
}, },
c_Roland_void = { c_Roland_void = {
@ -550,47 +396,6 @@ return {
}, },
}, },
}, },
Tag = {
tag_Roland_freeze = {
name = "Freeze Tag",
text = {"Apply {C:dark_edition}Frozen", "to the next {C:attention}#1#", "scored cards"},
},
tag_Roland_invisible = {
name = "Invisible Tag",
text = {
"{C:attention}Duplicate {}a random",
"Joker after {C:attention}#1#{} rounds",
"{C:inactive}(Must have room)",
"{C:inactive}(Currently {C:attention}#2#{C:inactive}/#1#)",
},
},
},
Tarot = {
c_Roland_coolheaded = {
name = "Coolheaded",
text = {
"Add {C:dark_edition}Frozen {}to {C:attention}#1#",
"selected {C:attention}playing",
"{C:attention}card {}or {C:attention}Joker",
},
},
},
Voucher = {
v_Roland_ceres = {
name = "Ceres",
text = {
"Level up {C:attention}Flush House",
"by {C:attention}#1# {}when it is played",
},
},
v_Roland_neptune = {
name = "Neptune",
text = {
"Level up {C:attention}Straight Flush",
"by {C:attention}#1# {}when it is played",
},
},
},
}, },
misc = { misc = {
challenge_names = { challenge_names = {
@ -599,38 +404,28 @@ return {
c_Roland_Eternally_Crimson = "Eternally Crimson", c_Roland_Eternally_Crimson = "Eternally Crimson",
c_Roland_Eternally_Verdant = "Eternally Verdant", c_Roland_Eternally_Verdant = "Eternally Verdant",
c_Roland_Eternally_Violet = "Eternally Violet", c_Roland_Eternally_Violet = "Eternally Violet",
c_Roland_Glass = "Glass House",
c_Roland_Go = "Pass GO",
c_Roland_Jokerful = "Jokerful", c_Roland_Jokerful = "Jokerful",
c_Roland_Ornate = "Ornate",
c_Roland_Pastries = "Sweet Pastries", c_Roland_Pastries = "Sweet Pastries",
c_Roland_Spin_To_Win = "Spin to Win", c_Roland_Showdown = "Showdown",
c_Roland_Spring_Cleaning = "Spring Cleaning",
}, },
v_dictionary = { v_dictionary = {
b_Roland_add = "ADD",
b_Roland_animated_icon = "Animated Icon (requires restart)",
b_Roland_bye = "Bye!", b_Roland_bye = "Bye!",
b_Roland_comma = ", ", b_Roland_comma = ", ",
b_Roland_cool_phones = "cool phones (requires restart)",
b_Roland_debuffed = "DEBUFFED", b_Roland_debuffed = "DEBUFFED",
b_Roland_disabled = "Disabled",
b_Roland_enabled = "Enabled",
b_Roland_entering_shop = "Entering shop!", b_Roland_entering_shop = "Entering shop!",
b_Roland_escape = "ESCAPE",
b_Roland_fuse = "FUSE",
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_fusable_escapey = "Fusable Escapey (overpowered)",
b_Roland_frozen = "Frozen!",
b_Roland_full = "FULL",
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+)",
b_Roland_illusion_seal = "Allow seals from Illusion voucher", b_Roland_illusion_seal = "Allow seals from Illusion voucher",
b_Roland_import_funky = "Debug: Import funky.lua", b_Roland_import_funky = "Debug: Import funky.lua",
b_Roland_most_common_card = "Rank", b_Roland_most_common_card = "(Rank)",
b_Roland_no_highlight_limit = "No consumable highlight limit",
b_Roland_no_wild_debuff = "No wild card debuffs", b_Roland_no_wild_debuff = "No wild card debuffs",
b_Roland_na = "N/A", b_Roland_na = "N/A",
b_Roland_of = " of ", b_Roland_of = " of ",
b_Roland_toggle = "TOGGLE",
b_Roland_unassigned = "(Unassigned)",
b_Roland_vitriol = "Vitriol (Venerable Visage)",
}, },
labels = { labels = {
Roland_frozen = "Frozen", Roland_frozen = "Frozen",
@ -642,17 +437,15 @@ return {
ch_c_Roland_Eternally_Crimson = {"{C:attention}Crimson Heart{}'s effect is active every blind"}, ch_c_Roland_Eternally_Crimson = {"{C:attention}Crimson Heart{}'s effect is active every blind"},
ch_c_Roland_Eternally_Verdant = {"{C:attention}Verdant Leaf{}'s effect is active every blind"}, ch_c_Roland_Eternally_Verdant = {"{C:attention}Verdant Leaf{}'s effect is active every blind"},
ch_c_Roland_Eternally_Violet = {"{C:attention}Violet Vessel{}'s effect is active every blind"}, ch_c_Roland_Eternally_Violet = {"{C:attention}Violet Vessel{}'s effect is active every blind"},
ch_c_Roland_Glass1 = {"Played cards with no seal have a"},
ch_c_Roland_Glass2 = {"{C:green}1 in 2{} chance to gain {C:dark_edition}Glass Seal"},
ch_c_Roland_Go = {"Set money to {C:money}$0 {}when entering the shop"},
ch_c_Roland_Jokerful = {"The only jokers are {C:mult}Joker{} and {C:chips}Ms. Joker"}, ch_c_Roland_Jokerful = {"The only jokers are {C:mult}Joker{} and {C:chips}Ms. Joker"},
ch_c_Roland_Ornate = {"Anything vanilla is banned"},
ch_c_Roland_Pastries = {"All blinds, cards, and tags are of {C:gold}Bakery{} or {C:blue}Roland"}, ch_c_Roland_Pastries = {"All blinds, cards, and tags are of {C:gold}Bakery{} or {C:blue}Roland"},
ch_c_Roland_Showdown = {"Showdown blinds are {C:attention}Venerable Visage"},
ch_c_Roland_Showdown_Amber = {"Showdown blinds are {C:attention}Amber Acorn {}(stacks with above)"}, ch_c_Roland_Showdown_Amber = {"Showdown blinds are {C:attention}Amber Acorn {}(stacks with above)"},
ch_c_Roland_Showdown_Cerulean = {"Showdown blinds are {C:attention}Cerulean Bell {}(stacks with above)"}, ch_c_Roland_Showdown_Cerulean = {"Showdown blinds are {C:attention}Cerulean Bell {}(stacks with above)"},
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

@ -12,12 +12,12 @@ priority = -2
target = "game.lua" target = "game.lua"
pattern = "self.GAME.round_resets.blind_choices.Boss = get_new_boss()" pattern = "self.GAME.round_resets.blind_choices.Boss = get_new_boss()"
position = "before" position = "before"
payload = """ payload = '''
if G.GAME.modifiers.Roland_blossom_deck then if G.GAME.modifiers.Roland_blossom_deck then
self.GAME.round_resets.blind_choices.Small = get_new_boss() self.GAME.round_resets.blind_choices.Small = get_new_boss()
self.GAME.round_resets.blind_choices.Big = get_new_boss() self.GAME.round_resets.blind_choices.Big = get_new_boss()
end end
""" '''
match_indent = true match_indent = true
[[patches]] [[patches]]
@ -25,12 +25,12 @@ match_indent = true
target = "functions/common_events.lua" target = "functions/common_events.lua"
pattern = "G.GAME.round_resets.blind_choices.Boss = get_new_boss()" pattern = "G.GAME.round_resets.blind_choices.Boss = get_new_boss()"
position = "before" position = "before"
payload = """ payload = '''
if G.GAME.modifiers.Roland_blossom_deck then if G.GAME.modifiers.Roland_blossom_deck then
G.GAME.round_resets.blind_choices.Small = get_new_boss() G.GAME.round_resets.blind_choices.Small = get_new_boss()
G.GAME.round_resets.blind_choices.Big = get_new_boss() G.GAME.round_resets.blind_choices.Big = get_new_boss()
end end
""" '''
match_indent = true match_indent = true
[[patches]] [[patches]]
@ -65,19 +65,6 @@ 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"
@ -95,120 +82,6 @@ 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", true, true) card:set_edition "e_negative"
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.pattern]
target = "card.lua"
pattern = """if self.ability.name == 'Throwback' then
self.ability.x_mult = 1 + G.GAME.skips*self.ability.extra
end
if self.ability.name == "Driver's License" then"""
position = "at"
payload = """if self.ability.name == 'Throwback' then
self.ability.x_mult = 1 + G.GAME.skips*self.ability.extra
end
if self.ability.name == "Driver's License" and not (self.edition and self.edition.Roland_frozen) then"""
match_indent = true
[[patches]]
[patches.pattern]
target = "card.lua"
pattern = """if self.ability.name == "Steel Joker" then"""
position = "at"
payload = """if self.ability.name == "Steel Joker" and not (self.edition and self.edition.Roland_frozen) then"""
match_indent = true
[[patches]]
[patches.pattern]
target = "card.lua"
pattern = """if self.ability.name == "Cloud 9" then"""
position = "at"
payload = """if self.ability.name == "Cloud 9" and not (self.edition and self.edition.Roland_frozen) then"""
match_indent = true
[[patches]]
[patches.pattern]
target = "card.lua"
pattern = """if self.ability.name == "Stone Joker" then"""
position = "at"
payload = """if self.ability.name == "Stone Joker" and not (self.edition and self.edition.Roland_frozen) then"""
match_indent = true
[[patches]]
[patches.regex]
target = "card.lua"
pattern = "math\\.floor\\(\\(G\\.GAME\\.dollars \\+ \\(G\\.GAME\\.dollar_buffer or 0\\)\\)/self\\.ability\\.extra\\.dollars\\)"
position = "at"
payload = "SMODS.Mods.Roland.bootstraps_mult(self)"
[[patches]]
[patches.regex]
target = "card.lua"
pattern = "if self\\.ability\\.h_size ~= 0 then"
position = "at"
payload = "if self.ability.h_size and self.ability.h_size ~= 0 then"
[[patches]]
[patches.regex]
target = "card.lua"
pattern = "if self\\.ability\\.d_size > 0 then"
position = "at"
payload = "if self.ability.d_size and self.ability.d_size > 0 then"
[[patches]]
[patches.pattern]
target = '=[SMODS _ "src/utils.lua"]'
pattern = "value = value + card.ability[property]"
position = "at"
payload = "value = value + (card.ability[property] or 0)"
match_indent = true
# [[patches]]
# [patches.pattern]
# target = '=[SMODS DebugPlus "debugplus/console.lua"]'
# pattern = "if toCheck == '/' or toCheck == 'kp/' then"
# position = "at"
# payload = "if toCheck == '/' or toCheck == 'kp/' or toCheck == 'f' then"
# match_indent = true
# [[patches]]
# [patches.pattern]
# target = '=[SMODS DebugPlus "debugplus/console.lua"]'
# pattern = 'local cmdName = string.lower(string.gsub(inputText, "^(%S+).*", "%1"))'
# position = "at"
# payload = 'local cmdName = "eval"'
# match_indent = true
# [[patches]]
# [patches.pattern]
# target = '=[SMODS DebugPlus "debugplus/console.lua"]'
# pattern = 'local rawArgs = string.gsub(inputText, "^%S+%s*(.*)", "%1")'
# position = "at"
# payload = 'local cmdName = inputText'
# match_indent = true

View file

@ -3,20 +3,20 @@
"id": "Roland", "id": "Roland",
"name": "Roland", "name": "Roland",
"prefix": "Roland", "prefix": "Roland",
"version": "3.0.2",
"badge_colour": "8BE9FD",
"display_name": "Roland",
"main_file": "src/main.lua",
"badge_text_colour": "44475A",
"description": "Adds mechanics that are meant to have interesting interactions with the base game. Not meant to be balanced, but not entirely broken either.",
"provides": [],
"conflicts": [],
"author": [ "author": [
"Emik" "Emik"
], ],
"version": "2.2.0",
"badge_colour": "8BE9FD",
"main_file": "src/main.lua",
"badge_text_colour": "44475A",
"display_name": "Roland",
"description": "Adds several disconnected funny ideas I had in my head that I couldn't resist implementing in the game.",
"provides": [],
"conflicts": [],
"dependencies": [ "dependencies": [
"Steamodded (>=1.0.0~BETA-1606b)", "Steamodded (>=1.*)",
"Lovely (>=0.6)", "Lovely (>=0.6)",
"Bakery (>=3.2.0~*)" "Bakery (>=1.3.2~*)"
] ]
} }

View file

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

View file

@ -1,4 +1,4 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = unpack(... or require "lib.shared")
local function save(ret) local function save(ret)
q(save_run) q(save_run)
@ -8,7 +8,7 @@ end
local back = (function() local back = (function()
local x = 0 local x = 0
---@param tbl SMODS.Back|{attributes?: Attributes[], pronouns: string, is_alt: (fun(self: self): boolean), alt_apply?: fun(self: SMODS.Back|table, back: Back|table), alt_calculate?: fun(self: SMODS.Back|table, back: Back|table, context: CalcContext|table): table?, boolean?} ---@param tbl SMODS.Back|{is_alt: function, 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
@ -23,16 +23,27 @@ local back = (function()
end end
tbl.pos = {x = x, y = 0} tbl.pos = {x = x, y = 0}
tbl.is_alt = f.fals
tbl.atlas = "back" tbl.atlas = "back"
x = x + 1
local back = q(SMODS.Back(tbl))
local _ = CardSleeves and CardSleeves.Sleeve { -- This API intends that you share the same functions for apply/calculate,
-- meaning that this may be called to check if a deck/sleeve combo is used even
-- when we are from the deck context. We add this to prevent accidental calls to nil.
function tbl:is_alt()
return false
end
local sleeve = (_G["CardSleeves"] or {}).Sleeve
SMODS.Back(tbl)
x = x + 1
if not sleeve then
return
end
sleeve {
key = key, key = key,
pos = tbl.pos, pos = tbl.pos,
atlas = "sleeve", atlas = "back",
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 {}
@ -40,7 +51,7 @@ local back = (function()
return ret return ret
end, end,
apply = function(self, ...) apply = function(self, ...)
return save((tbl.alt_apply and self:is_alt() and tbl.alt_apply or apply)(self, ...)) return (tbl.alt_apply and self:is_alt() and tbl.alt_apply or apply)(self, ...)
end, 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, ...)
@ -49,176 +60,115 @@ local back = (function()
return self.get_current_deck_key() == "b_Roland_" .. key return self.get_current_deck_key() == "b_Roland_" .. key
end, end,
} }
return back
end end
end)() end)()
local back_atlas = SMODS.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 {
key = "sleeve",
path = "sleeve.png",
px = 73,
py = 95,
}
back { back {
key = "blossom", key = "blossom",
pronouns = "any_all", pronouns = "any_all",
config = {extra = {alt_times = 9, times = 3}}, config = {extra = {times = 2}},
attributes = {"passive", "boss_blind"}, loc_vars = function(self, _, _)
loc_vars = function(self) return {vars = {self.config.extra.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, b, context) calculate = function(self, card, context)
if not context.forcetrigger and not context.setting_blind then if 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 not context.forcetrigger and if blinds.Small ~= "Current" and blinds.Big ~= "Current" then
blinds.Small ~= "Current" and
blinds.Big ~= "Current" then
return return
end end
local function debuff(times) local count = self:is_alt() and G.GAME.round_resets.ante or self.config.extra.times
return function()
if times <= 0 then
return
end
for _ = 1, count do
G.GAME.blind:disable() G.GAME.blind:disable()
SMODS.calculate_effect({message = localize "ph_boss_disabled"}, b)
q {
delay = 0.4,
func = function()
SMODS.calculate_effect({message = localize "ph_boss_disabled"}, card)
G.GAME.blind:wiggle() G.GAME.blind:wiggle()
play_sound "timpani" play_sound "timpani"
q {func = debuff(times - 1)} end,
}
end end
end,
}
back {
key = "go",
pronouns = "he_him",
config = {extra = {times = 1, alt_times = 5}},
loc_vars = function(self, info_queue, _)
local _ = info_queue and table.insert(info_queue, G.P_CENTERS.j_credit_card)
return {vars = {self.config.extra.alt_times - self.config.extra.times}}
end,
apply = function(self)
q(function()
local count = self:is_alt() and self.config.extra.alt_times or self.config.extra.times
for _ = 1, count do
local c = create_card("Joker", G.jokers, nil, nil, nil, nil, "j_credit_card", "Roland_go")
c:add_to_deck()
c:start_materialize()
G.jokers:emplace(c)
end
end)
end,
calculate = function(_, card, context)
if not context.starting_shop then
return
end end
local times = self:is_alt() and ease_dollars(-G.GAME.dollars)
G.GAME.round_resets.ante * self.config.extra.alt_times or
self.config.extra.times
debuff(times)() attention_text {
text = localize "k_nope_ex",
backdrop_colour = G.C.MONEY,
offset = {x = 0, y = 0},
silent = true,
major = card,
align = "cm",
scale = 1.3,
hold = 1.4,
}
end, end,
} }
back { back {
key = "swapper", key = "swapper",
pronouns = "he_him", pronouns = "he_him",
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
modifiers.Roland_alt_swapper_deck = modifiers.Roland_alt_swapper_deck or self:is_alt() modifiers.Roland_alt_swapper_deck = modifiers.Roland_alt_swapper_deck or self:is_alt()
end, end,
calculate = f.noop, calculate = function() end,
}
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 create_dropshipped_card(_type, ...) return orig_create_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 create_dropshipped_card(swapper[_type] or _type, ...) return orig_create_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)
@ -226,79 +176,5 @@ function create_card(_type, ...)
end):table() end):table()
local has_type = SMODS.ConsumableTypes[_type] local has_type = SMODS.ConsumableTypes[_type]
return create_dropshipped_card( return orig_create_card(has_type and pseudorandom_element(type, pseudoseed "Roland_alt_swapper_deck") or _type, ...)
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

@ -1,16 +1,16 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = unpack(... or require "lib.shared")
local blind = (function() local blind = (function()
local y = 0 local y = 0
---@param tbl SMODS.Blind|{idea?: string} ---@param tbl SMODS.Blind
---@return SMODS.Blind ---@return SMODS.Blind
return function(tbl) return function(tbl)
tbl.idea = tbl.idea and "Roland_" .. tbl.idea or nil
tbl.pos = {x = 0, y = y} tbl.pos = {x = 0, y = y}
tbl.atlas = "blind" tbl.atlas = "blind"
local ret = SMODS.Blind(tbl)
y = y + 1 y = y + 1
return q(SMODS.Blind(tbl)) return ret
end end
end)() end)()
@ -23,12 +23,18 @@ SMODS.Atlas {
atlas_table = "ANIMATION_ATLAS", atlas_table = "ANIMATION_ATLAS",
} }
SMODS.Sound {key = "kick", path = "kick.ogg"} SMODS.Sound {
key = "kick",
path = "kick.ogg",
}
local function common_rank() local function common_rank()
---@type { [integer]: integer }, { [integer]: string }
local tally, to_name = {}, {} local tally, to_name = {}, {}
f(G.playing_cards):where(SMODS.has_no_rank, false):each(function(v) f(G.playing_cards):where(function(v)
return not SMODS.has_no_rank(v)
end):each(function(v)
local id = v:get_id() local id = v:get_id()
to_name[id] = v.base.value to_name[id] = v.base.value
tally[id] = (tally[id] or 0) + 1 tally[id] = (tally[id] or 0) + 1
@ -52,7 +58,7 @@ end
local function has_enhancement(card) local function has_enhancement(card)
local e = SMODS.get_enhancements(card) local e = SMODS.get_enhancements(card)
return e and next(e) return not not (e and next(e))
end end
local function set_freeze(state) local function set_freeze(state)
@ -62,10 +68,6 @@ local function set_freeze(state)
---@param card Card|{ Roland_blizzard: true|nil } ---@param card Card|{ Roland_blizzard: true|nil }
return function(card) return function(card)
if not card.Roland_blizzard and card.edition and card.edition.Roland_frozen then
return
end
local last_edition = card.Roland_blizzard local last_edition = card.Roland_blizzard
card.Roland_blizzard = state and (copy(card.edition) or true) or nil card.Roland_blizzard = state and (copy(card.edition) or true) or nil
@ -163,7 +165,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 not not f(G.playing_cards):any(has_enhancement) return G.playing_cards and f(G.playing_cards):any(has_enhancement)
end, end,
} }
@ -174,7 +176,13 @@ blind {
pronouns = "he_they", pronouns = "he_they",
disable = function() disable = function()
-- Ensures that this runs after 'set_blind' since it also gets added to queue. -- Ensures that this runs after 'set_blind' since it also gets added to queue.
q {delay = 0.8, trigger = "after", func = G.FUNCS.draw_from_discard_to_deck} q {
delay = 0.8,
trigger = "after",
func = function()
G.FUNCS.draw_from_discard_to_deck()
end,
}
end, end,
set_blind = function() set_blind = function()
-- Allows the background to ease in first before drawing cards. -- Allows the background to ease in first before drawing cards.
@ -216,10 +224,6 @@ blind {
draw_card(G.hand, G.discard, i / count * 100, "down", false, copy, nil, nil, true) draw_card(G.hand, G.discard, i / count * 100, "down", false, copy, nil, nil, true)
end) end)
if not next(cards_added) then
return
end
b:wiggle() b:wiggle()
b.triggered = true b.triggered = true
playing_card_joker_effects(cards_added) playing_card_joker_effects(cards_added)
@ -228,27 +232,25 @@ blind {
blind { blind {
key = "blizzard", key = "blizzard",
idea = "redstoad",
boss = {min = 3}, boss = {min = 3},
boss_colour = HEX "102a41ff", boss_colour = HEX "102a41ff",
pronouns = "it_its", pronouns = "it_its",
defeat = function(self) defeat = function(self)
self.cards():where "Roland_blizzard":each(set_freeze()) self.cards():each(set_freeze())
G.GAME.blind.disabled = true G.GAME.blind.disabled = true
end, end,
disable = function(self) disable = function(self)
self:defeat() self:defeat()
end, end,
calculate = function(self, b, context) calculate = function(self, b)
return not b.disabled and return not b.disabled and self.cards():where(function(v)
context.hand_drawn and return not v.Roland_blizzard and v.facing == "front"
self.cards() end):each(set_freeze(true)) or nil
:where("Roland_blizzard", false)
:where("facing", "front")
:each(set_freeze(true)) or nil
end, end,
cards = function() cards = function()
return f(G):where(getmetatable, CardArea):flatmap("cards", ipairs) return f(G):where(function(v)
return type(v) == "table" and type(v.cards) == "table"
end):flatmap("cards", ipairs)
end, end,
} }
@ -257,7 +259,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 +267,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,
@ -299,12 +301,9 @@ blind {
b:set_text() b:set_text()
end end
end, end,
disable = function()
G.GAME.blind.disabled = true
end,
recalc_debuff = function(self, card) recalc_debuff = function(self, card)
local id, _ = common_rank() local id, _ = common_rank()
local ret = not G.GAME.blind.disabled and id == card:get_id() local ret = not self.disabled and id == card:get_id()
self.triggered = ret self.triggered = ret
return ret return ret
end, end,
@ -319,7 +318,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,65 +331,15 @@ if cry_prob then
end end
end end
local function call(key, value)
local obj = G[key .. "obj"]
local _ = type(obj) == "function" and obj(value)
if not G[key] or value == "Bakery_nothing" then
return
end
if type(G[key]) == "function" then
G[key] = {G[key]}
end
if type(G[key]) ~= "table" then
return
end
local str
local _ = f(type(value) == "table" and value or {[value] = true})
: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)
end)
local _ = not str and
type(G[key][1]) == "function" and
G[key][1](type(value) == "table" and f(value):keys():string() or value)
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) function SMODS.current_mod:calculate(context)
call("calc", context) if context.setting_blind and G.GAME.blind.name == "bl_mp_nemesis" then
local modifiers = G.GAME.modifiers
local _ = context.before and modifiers.Roland_martingale_seed = (modifiers.Roland_martingale_seed or 0) + 1
f {"v_Roland_ceres", "v_Roland_neptune"} end
:where(f.index_into(G.GAME.used_vouchers))
:map(f.index_into(G.P_CENTERS))
:where("config.extra.hand_type", context.scoring_name)
:pun "SMODS.Voucher"
:each(function(v)
SMODS.smart_level_up_hand(nil, v.config.extra.hand_type, false, v.config.extra.amount)
end)
local _ = type(G.calc) == "function" and G.calc(f(context):keys():string())
local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities
local _ = context.end_of_round and
context.main_eval and
not context.blueprint and
G.P_TAGS.tag_Roland_invisible.increment()
-- Normally unreachable since we set it to nil ourselves, -- Normally unreachable since we set it to nil ourselves,
-- but other mods may want to use this modifier. -- but other mods may want to use this modifier.
if improbable == false then if improbable == false then
@ -404,7 +353,7 @@ function SMODS.current_mod:calculate(context)
local normal = orig.normal local normal = orig.normal
G.GAME.probabilities = setmetatable({}, { local mt = {
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]
@ -412,7 +361,11 @@ 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 {
@ -501,31 +454,15 @@ local venerable_visage = blind {
} }
end, end,
vitriol = function(b) vitriol = function(b)
local vitriol = SMODS.Mods.Roland.config.vitriol
local resize_to_w, resize_to_h = 320, 200
local is_fullscreen = love.window.getFullscreen()
if vitriol then
love.window.setFullscreen(false)
delay(1.5)
end
local function jitter()
local x, y = love.window.getDesktopDimensions()
return pseudorandom(pseudoseed "RolandVenerableVisageX", 0, x) - x / 2,
pseudorandom(pseudoseed "RolandVenerableVisageY", 0, y) - y / 2
end
if type(b) == "table" and type(b.wiggle) == "function" then if type(b) == "table" and type(b.wiggle) == "function" then
b:wiggle() b:wiggle()
end end
f(G.playing_cards):each(function(v, k)
return v.area ~= G.hand and draw_card(v.area, G.hand, k * 100 / #G.playing_cards, "up", true, v)
end)
local fail = G.localization.descriptions.Blind.bl_Roland_venerable_visage.fail local fail = G.localization.descriptions.Blind.bl_Roland_venerable_visage.fail
local speed = 0.1
SMODS.draw_cards(#G.deck.cards)
play_sound("gong", 0.6)
attention_text { attention_text {
text = pseudorandom_element(fail, pseudoseed "RolandVenerableVisage"), text = pseudorandom_element(fail, pseudoseed "RolandVenerableVisage"),
@ -535,50 +472,46 @@ local venerable_visage = blind {
hold = 2, hold = 2,
} }
f {0.15, 0.3, 0.45, 0.6}:each(function(v) delay(1)
play_sound("gong", v)
end)
---@type number, number, table f(G.playing_cards):each(function(v)
local w, h, flags = love.window.getMode()
local len = #G.playing_cards
if vitriol then
love.window.setMode(resize_to_w, resize_to_h)
love.resize(resize_to_w, resize_to_h)
end
local x, y = love.window.getPosition()
f(G.playing_cards):each(function(v, i)
q { q {
trigger = "before", trigger = "before",
delay = 6 / len, delay = speed,
func = function() func = function()
if vitriol then
local x_random, y_random = jitter()
love.window.setPosition(x + x_random * i / len, y + y_random * i / len)
end
v:start_dissolve() v:start_dissolve()
v:shatter() v:shatter()
b:wiggle()
end, end,
} }
end) end)
local _ = vitriol and q { delay(1)
trigger = "before",
delay = 1.5, f(G.P_CARDS):each(function(v)
q {
delay = speed,
func = function() func = function()
love.window.setPosition(x, y) G.playing_card = (G.playing_card and G.playing_card + 1) or 1
love.window.setMode(w, h, flags)
love.resize(w, h) local card = Card(
love.window.setFullscreen(is_fullscreen) b and b.T.x or 0,
b and b.T.y or 0,
G.CARD_W,
G.CARD_H,
v,
G.P_CENTERS.m_Bakery_Curse or G.P_CENTERS.c_base,
{playing_card = G.playing_card}
)
table.insert(G.playing_cards, card)
G.deck:emplace(card)
play_sound "card1"
card:add_to_deck()
end, end,
} }
end)
delay(1.5) delay(1)
end, end,
} }

View file

@ -1,26 +1,19 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = unpack(... or require "lib.shared")
---@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,
@ -37,109 +30,40 @@ 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 local function is_joker(v)
---@return boolean|string return v.set == "Joker" and not ({j_joker = true, j_Roland_msjoker = true})[v.key]
local function is_economy(v)
return f(Bakery_API.econ_only_items):where(f.eq(v.key)):any()
end 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
---@param name string local function is_vanilla(v)
---@param amount number return v.set and not v.mod and f(G.P_CENTERS):concat(G.P_CARDS, G.P_TAGS):any(function(c)
---@return {id: string}[] return c.mod and not c.in_pool and c.set == v.set
local function multirule(name, amount) end)
return f(amount):map(function(v)
return {id = "Roland_" .. name .. v}
end):table()
end end
local pastries, amber, cerulean, crimson, verdant, violet, yard = local jokerful, vanillas, pastries, amber, cerulean, crimson, verdant, violet, final
bans(), bans(), bans(), bans(), bans(), bans(), bans() = bans(), bans(), bans(), bans(), bans(), bans(), bans(), bans(), bans()
SMODS.Challenge { SMODS.Challenge {
key = "Glass", key = "Jokerful",
rules = {custom = multirule("Glass", 2)}, rules = {custom = {{id = "Roland_Jokerful"}}},
pronouns = "they_them", restrictions = jokerful,
config = {extra = {odds = 2}},
calculate = function(self, context)
if not context.after or context.blueprint_card then
return
end
f(G.play.cards):where(function(v)
return not v:get_seal(true) and
SMODS.pseudorandom_probability(self, self.key, 1, self.config.extra.odds, self.key)
end):each(function(v)
v:set_seal "Roland_glass"
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 {
key = "Go",
rules = {custom = {{id = "Roland_Go"}}},
jokers = {{id = "j_credit_card"}},
pronouns = "he_him", pronouns = "he_him",
calculate = function(self, context)
if context.starting_shop then
ease_dollars(-G.GAME.dollars)
attention_text {
text = localize "k_nope_ex",
backdrop_colour = G.C.MONEY,
offset = {x = 0, y = 0},
silent = true,
major = self,
align = "cm",
scale = 1.3,
hold = 1.4,
}
end
end,
} }
local spin_to_win = SMODS.Challenge { SMODS.Challenge {
key = "Spin_To_Win", key = "Ornate",
jokers = f(4):map(f.const {id = "j_joker", eternal = true}):table(), rules = {custom = {{id = "Roland_Ornate"}}},
restrictions = {banned_cards = {{id = "c_Roland_coolheaded"}}}, restrictions = vanillas,
pronouns = "they_them", pronouns = "any_all",
apply = function(self)
self.rotation = 0
end,
calculate = function(self, context)
if not context.card_added or context.card.config.center.key ~= "j_Bakery_Spinner" then
return
end
context.card.ability.extra.rotation = self.rotation
self.rotation = self.rotation + 1
end,
rotation = 0,
} }
SMODS.Challenge { SMODS.Challenge {
@ -149,15 +73,12 @@ SMODS.Challenge {
pronouns = "she_them", pronouns = "she_them",
} }
local finalizers = {
SMODS.Challenge { SMODS.Challenge {
key = "Eternally_Amber", key = "Eternally_Amber",
rules = {custom = {{id = "Roland_Eternally_Amber"}, {id = "Roland_Showdown_Amber"}}}, rules = {custom = {{id = "Roland_Eternally_Amber"}, {id = "Roland_Showdown_Amber"}}},
jokers = {{id = "j_Roland_amber"}},
restrictions = amber, restrictions = amber,
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()
@ -189,17 +110,11 @@ SMODS.Challenge {
end, end,
} }
end, end,
} },
SMODS.Challenge { SMODS.Challenge {
key = "Eternally_Cerulean", key = "Eternally_Cerulean",
rules = {custom = {{id = "Roland_Eternally_Cerulean"}, {id = "Roland_Showdown_Cerulean"}}}, rules = {custom = {{id = "Roland_Eternally_Cerulean"}, {id = "Roland_Showdown_Cerulean"}}},
jokers = {{id = "j_Roland_cerulean"}},
restrictions = cerulean, restrictions = cerulean,
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
@ -219,25 +134,20 @@ SMODS.Challenge {
G.hand:add_to_highlighted(v) G.hand:add_to_highlighted(v)
end) end)
end, end,
} },
SMODS.Challenge { SMODS.Challenge {
key = "Eternally_Crimson", key = "Eternally_Crimson",
rules = {custom = {{id = "Roland_Eternally_Crimson"}, {id = "Roland_Showdown_Crimson"}}}, rules = {custom = {{id = "Roland_Eternally_Crimson"}, {id = "Roland_Showdown_Crimson"}}},
jokers = {{id = "j_Roland_crimson"}},
restrictions = crimson, restrictions = crimson,
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
end) end)
local mod = G.GAME.modifiers local mod = G.GAME.modifiers
mod.Roland_Eternally_Crimson = context.setting_blind and true or mod.Roland_Eternally_Crimson mod.Roland_Eternally_Crimson = not context.setting_blind and mod.Roland_Eternally_Crimson or nil
local debuff = context.debuff_card local debuff = context.debuff_card
---@type (SMODS.Joker|{ability?: {Roland_crimson_heart_chosen: boolean?}, debuff: boolean?})[]
local cards = G.jokers.cards local cards = G.jokers.cards
if debuff and if debuff and
@ -265,7 +175,9 @@ SMODS.Challenge {
local _ = v.debuff and SMODS.recalc_debuff(v) local _ = v.debuff and SMODS.recalc_debuff(v)
end) end)
jokers = next(jokers) and jokers or f(cards):where("debuff", false):map(function(v) jokers = next(jokers) and jokers or f(cards):where(function(v)
return not v.debuff
end):map(function(v)
local _ = not prev[v] and table.insert(jokers, v) local _ = not prev[v] and table.insert(jokers, v)
return v return v
end):table() end):table()
@ -282,17 +194,11 @@ SMODS.Challenge {
mod.Roland_Eternally_Crimson = not context.hand_drawn and mod.Roland_Eternally_Crimson = not context.hand_drawn and
mod.Roland_Eternally_Crimson or nil mod.Roland_Eternally_Crimson or nil
end, end,
} },
SMODS.Challenge { SMODS.Challenge {
key = "Eternally_Verdant", key = "Eternally_Verdant",
rules = {custom = {{id = "Roland_Eternally_Verdant"}, {id = "Roland_Showdown_Verdant"}}}, rules = {custom = {{id = "Roland_Eternally_Verdant"}, {id = "Roland_Showdown_Verdant"}}},
jokers = {{id = "j_Roland_verdant"}},
restrictions = verdant, restrictions = verdant,
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
@ -308,55 +214,44 @@ SMODS.Challenge {
end) end)
end end
end, end,
} },
SMODS.Challenge { SMODS.Challenge {
key = "Eternally_Violet", key = "Eternally_Violet",
rules = {custom = {{id = "Roland_Eternally_Violet"}, {id = "Roland_Showdown_Violet"}}}, rules = {custom = {{id = "Roland_Eternally_Violet"}, {id = "Roland_Showdown_Violet"}}},
jokers = {{id = "j_joker"}, {id = "j_Roland_violet"}},
restrictions = violet, restrictions = violet,
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 context.setting_blind then
return G.GAME.blind.chips = G.GAME.blind.chips * (6 / G.GAME.blind.mult)
end
local mult = G.GAME.blind.mult < 6 and G.GAME.blind.mult or 1
G.GAME.blind.chips = G.GAME.blind.chips * 6 / mult
G.GAME.blind.chip_text = number_format(G.GAME.blind.chips) G.GAME.blind.chip_text = number_format(G.GAME.blind.chips)
end
end,
},
}
SMODS.Challenge {
key = "Showdown",
rules = {custom = {
{id = "Roland_Eternally_Amber"},
{id = "Roland_Eternally_Cerulean"},
{id = "Roland_Eternally_Crimson"},
{id = "Roland_Eternally_Verdant"},
{id = "Roland_Eternally_Violet"},
{id = "Roland_Showdown"},
}},
restrictions = final,
calculate = function(_, context)
f(finalizers):each(function(v)
v:calculate(context)
end)
end, end,
} }
q { q(function()
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_CENTERS, is_joker, jokerful.banned_cards},
{G.P_BLINDS, is_economy, yard.banned_other}, {G.P_TAGS, is_vanilla, vanillas.banned_tags},
{G.P_CENTERS, is_economy, yard.banned_cards}, {G.P_BLINDS, is_vanilla, vanillas.banned_other},
{G.P_CENTERS, is_vanilla, vanillas.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},
@ -365,8 +260,8 @@ q {
{G.P_BLINDS, is_showdown_except "bl_final_heart", crimson.banned_other}, {G.P_BLINDS, is_showdown_except "bl_final_heart", crimson.banned_other},
{G.P_BLINDS, is_showdown_except "bl_final_leaf", verdant.banned_other}, {G.P_BLINDS, is_showdown_except "bl_final_leaf", verdant.banned_other},
{G.P_BLINDS, is_showdown_except "bl_final_vessel", violet.banned_other}, {G.P_BLINDS, is_showdown_except "bl_final_vessel", violet.banned_other},
{G.P_BLINDS, is_showdown_except "bl_Roland_venerable_visage", final.banned_other},
}: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)
}

View file

@ -1,32 +1,19 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = unpack(... or require "lib.shared")
local mod = SMODS.current_mod local mod = SMODS.current_mod
local cool_phones = SMODS.Mods.Roland.config.cool_phones
SMODS.Atlas { SMODS.Atlas {
key = "charm", key = "charm",
path = cool_phones and "phorm.png" or "charm.png", path = "charm.png",
px = 68, px = 68,
py = 68, py = 68,
} }
local _ = cool_phones and SMODS.Sound {
key = "phone",
path = "phone.ogg",
}
local charm = (function() local charm = (function()
local x = 0 local x = 0
---@param tbl SMODS.Joker|{alerted?: boolean, artist?: string, equip?: fun(self: self, card: Card)} ---@param tbl SMODS.Joker|{alerted?: boolean}
return function(tbl) return function(tbl)
q { q(function()
blocking = false,
no_delete = true,
func = function()
if not Bakery_API or not Bakery_API.Charm then
return false
end
local current_mod = SMODS.current_mod local current_mod = SMODS.current_mod
SMODS.current_mod = mod SMODS.current_mod = mod
@ -35,143 +22,110 @@ local charm = (function()
tbl.unlocked = true tbl.unlocked = true
tbl.discovered = true tbl.discovered = true
tbl.pos = {x = x, y = 0} tbl.pos = {x = x, y = 0}
tbl.atlas = "charm"
tbl.artist = not cool_phones and "Roland_bakersdozenbagels" or nil
x = x + 1 x = x + 1
tbl.atlas = "charm"
local orig_equip = tbl.equip local charm = Bakery_API.Charm(tbl)
function tbl.equip(...)
local _ = cool_phones and play_sound("Roland_phone", 1, 0.5)
if orig_equip then
return orig_equip(...)
end
end
local charm = Bakery_API.credit(Bakery_API.Charm(tbl))
charm:inject() charm:inject()
charm:process_loc_text() charm:process_loc_text()
SMODS.current_mod = current_mod SMODS.current_mod = current_mod
end, end, true)
}
end end
end)() end)()
local function can_discard_zero() local function add_to_consumable_ability_by(n)
return G.GAME.current_round.discards_left > 0 and local function new(v)
not next(G.hand.highlighted) and return type(v) == "number" and v + n or type(v) == "table" and f(v):map(new):table() or v
G.hand.config.card_limit > 0 and
G.GAME.Bakery_charm == "BakeryCharm_Roland_cocacola"
end end
charm { ---@param card Card
key = "hand", return function(card)
pronouns = "he_him", local ability = card.ability or {}
config = {extra = {hands = -2, hand_size = 5}},
attributes = {"hands", "hand_size", "passive"},
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.hand_size, extra.hands}}
end,
equip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(extra.hands)
G.hand:change_size(extra.hand_size)
round.hands = round.hands + extra.hands
end,
unequip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(-extra.hands)
G.hand:change_size(-extra.hand_size)
round.hands = round.hands - extra.hands
end,
}
charm { local function go(key)
key = "cocacola", ---@type { [string]: number }|number
pronouns = "he_they", local value = ability[key]
attributes = {"passive", "tarot", "spectral"}, --print(key)
config = {extra = {discard = 2, hand_size = 1, refund = 0}}, if not value then
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.discard, extra.hand_size}}
end,
calculate = function(_, card, context)
if not context.end_of_round then
return return
end end
local extra = card.ability.extra if type(value) == "number" then
G.hand:change_size(extra.refund) ability[key] = value + n
extra.refund = 0 return
end,
}
local orig_can_discard = G.FUNCS.can_discard
function G.FUNCS.can_discard(e, ...)
if can_discard_zero() then
e.config.button, e.config.colour = "Roland_cocacola", G.C.RED
else
return orig_can_discard(e, ...)
end
end end
G.FUNCS.Roland_cocacola = function() if type(value) ~= "table" then
stop_use() return
G.CONTROLLER.interrupt.focus = true end
G.CONTROLLER:save_cardarea_focus "hand"
f(G.playing_cards):map "ability":each(function(v) local new_value = f(value):map(new):table()
v.forced_selection = nil
f(new_value):each(function(v, k)
ability[k] = v
end) end)
G.card_area_focus_reset = (G.CONTROLLER.focused.target or {}).area == G.hand and ability[key] = new_value
{area = G.hand, rank = G.CONTROLLER.focused.target.rank} or G.card_area_focus_reset
SMODS.calculate_context {pre_discard = true, full_hand = G.hand.highlighted}
G.GAME.current_round.discards_used = G.GAME.current_round.discards_used + 1
local c = G.Bakery_charm_area.cards[1]
local extra = c.ability.extra or {}
juice_card(c)
ease_discard(extra.discard - 1)
G.hand:change_size(-extra.hand_size)
extra.refund = extra.refund + extra.hand_size
local _ = G.GAME.modifiers.discard_cost and ease_dollars(-G.GAME.modifiers.discard_cost)
G.STATE = G.STATES.DRAW_TO_HAND
q {
trigger = "immediate",
func = function()
if G.SCORING_COROUTINE then
return false
end end
local center_key = (card.config or {}).center_key
if center_key and center_key:sub(1, 2) == "c_" then
f {"extra", "consumeable"}:each(go)
end
end
end
charm {
key = "wii",
pronouns = "they_them",
calculate = function(_, card, context)
if not context.skip_blind then
return
end
local message = localize {type = "variable", key = "b_Roland_entering_shop"}
SMODS.calculate_effect({card = card, message = message, sound = "whoosh1"}, card)
q(function()
G.blind_prompt_box = G.blind_prompt_box and G.blind_prompt_box:remove()
G.blind_select = G.blind_select and G.blind_select:remove()
G.round_eval = G.round_eval and G.round_eval:remove()
G.GAME.current_round.jokers_purchased = 0
G.STATE = G.STATES.SHOP
G.GAME.shop_free = nil
G.GAME.shop_d6ed = nil
G.STATE_COMPLETE = false G.STATE_COMPLETE = false
return true play_sound("whoosh1", 1.33333, 0.8)
play_sound("whoosh1", 0.66666, 0.8)
end)
end, end,
} }
charm {
key = "flexible",
pronouns = "any_all",
}
local orig_showman = SMODS.showman
function SMODS.showman(...)
return G.GAME.Bakery_charm == "BakeryCharm_Roland_flexible" or orig_showman(...)
end end
charm { charm {
key = "fat", key = "fat",
pronouns = "he_they", pronouns = "he_they",
attributes = {"passive"}, -- config = {extra = {mod = 1}},
config = {extra = {mod = 2}}, -- loc_vars = function(_, _, card)
loc_vars = function(_, _, card) -- return {vars = {card.ability.extra.mod}}
return {vars = {card.ability.extra.mod}} -- end,
end, -- equip = function(_, card)
equip = function(_, card) -- SMODS.change_booster_limit(card.ability.extra.mod)
SMODS.change_booster_limit(card.ability.extra.mod) -- end,
end, -- unequip = function(card)
unequip = function(_, card) -- SMODS.change_booster_limit(-card.ability.extra.mod)
SMODS.change_booster_limit(-card.ability.extra.mod) -- end,
end,
} }
local orig_init = Card.init local orig_init = Card.init
@ -181,9 +135,7 @@ function Card:init(X, Y, W, H, card, center, params, ...)
return orig_init(self, X, Y, W, H, card, center, params, ...) return orig_init(self, X, Y, W, H, card, center, params, ...)
end end
local key = f {"mini", "jumbo", "normal"}:fold(center.key, function(a, v) local key = center.key:gsub("normal", "mega"):gsub("jumbo", "mega"):gsub("mini", "mega")
return a:gsub(v, "mega")
end)
if G.P_CENTERS[key] then if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...) return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
@ -206,70 +158,48 @@ function Card:init(X, Y, W, H, card, center, params, ...)
end end
charm { charm {
key = "flexible", key = "cocacola",
pronouns = "any_all", pronouns = "he_they",
} equip = function()
f(G.consumeables.cards):each(add_to_consumable_ability_by(1))
local orig_showman = SMODS.showman end,
unequip = function()
function SMODS.showman(...) f(G.consumeables.cards):each(add_to_consumable_ability_by(-1))
return G.GAME.Bakery_charm == "BakeryCharm_Roland_flexible" or orig_showman(...)
end
charm {
key = "wii",
pronouns = "they_them",
attributes = {"skip"},
config = {extra = {active = true}},
calculate = function(_, card, context)
if context.prevent_tag_trigger and card.ability.extra.active then
return {prevent_trigger = true}
end
if not context.skip_blind then
return
end
card.ability.extra.active = true
local message = localize {type = "variable", key = "b_Roland_entering_shop"}
SMODS.calculate_effect({card = card, message = message, sound = "whoosh1"}, card)
q(function()
G.blind_prompt_box = G.blind_prompt_box and G.blind_prompt_box:remove()
G.blind_select = G.blind_select and G.blind_select:remove()
G.round_eval = G.round_eval and G.round_eval:remove()
G.GAME.current_round.jokers_purchased = 0
G.GAME.shop_free = nil
G.GAME.shop_d6ed = nil
play_sound("whoosh1", 1.33333, 0.8)
play_sound("whoosh1", 0.66666, 0.8)
q(function()
G.STATE = G.STATES.SHOP
G.STATE_COMPLETE = false
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()
card.ability.extra.active = false
end)
end)
end)
end)
end, end,
} }
local orig_apply_to_run = Tag.apply_to_run local orig_set_ability = Card.set_ability
function Tag:apply_to_run(...) function Card:set_ability(center, initial, delay_sprites, ...)
if G.GAME.Bakery_charm == "BakeryCharm_Roland_wii" and local ret = orig_set_ability(self, center, initial, delay_sprites, ...)
G.Bakery_charm_area.cards[1].ability.extra.active then
return if G.GAME.Bakery_charm == "BakeryCharm_Roland_cocacola" then
add_to_consumable_ability_by(1)(self)
end end
return orig_apply_to_run(self, ...) return ret
end end
charm {
key = "hand",
pronouns = "he_him",
config = {extra = {hands = -2, hand_size = 5}},
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.hand_size, extra.hands}}
end,
equip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(extra.hands)
G.hand:change_size(extra.hand_size)
round.hands = round.hands + extra.hands
end,
unequip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(-extra.hands)
G.hand:change_size(-extra.hand_size)
round.hands = round.hands - extra.hands
end,
}

View file

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

View file

@ -1,4 +1,4 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = unpack(... or require "lib.shared")
SMODS.Shader { SMODS.Shader {
key = "frozen", key = "frozen",
@ -12,14 +12,14 @@ local function frozen_sound(suffix)
end end
frozen_sound "_click" frozen_sound "_click"
local frozen_blocklist = {CardSleeve = true} local frozen_sounds = f(4):map(frozen_sound):map("key"):table()
local frozen_sounds = f(4):map(frozen_sound):map "key":table()
local needs_chip_mult_override = { local needs_chip_mult_override = {
Bull = true, Bull = true,
Erosion = true, Erosion = true,
Misprint = true, Misprint = true,
TierList = true, TierList = true,
Bootstraps = true,
["Blue Joker"] = true, ["Blue Joker"] = true,
["Abstract Joker"] = true, ["Abstract Joker"] = true,
["Fortune Teller"] = true, ["Fortune Teller"] = true,
@ -46,42 +46,17 @@ local function freeze(card)
---@param x T ---@param x T
---@return T ---@return T
local function copy(x) local function copy(x)
return type(x) == "table" and f(x):map(copy):table() or x return type(x) == "table" and f(x):map(copy):where(function(v, k)
return k ~= "Roland_frozen_proxy" and (k ~= "nine_tally" or v ~= 0)
end):table() or x
end end
if frozen_blocklist[(card.ability or {}).name] then
return card.ability
end
local config = card.config or {}
(card.ability or {}).Roland_crimson = not not (card.ability or {}).Roland_crimson
card.Roland_frozen_base = card.Roland_frozen_base or copy(card.base)
card.Roland_frozen_card = card.Roland_frozen_card or copy(config.card)
card.Roland_frozen_ability = card.Roland_frozen_ability or copy(card.ability) card.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)} local ret = card.Roland_frozen_ability
local ability = card.ability
if diff then if type(ability) ~= "table" or not ability.name or not G.GAME.current_round then
card:set_sprites(card.config.center, card.config.card)
end
local ability, ret = card.ability, card.Roland_frozen_ability
ability.extra = ability.extra == nil and {} or ability.extra
if type(ability) ~= "table" then
return ret
end
ability.seal = ability.seal or {}
if not ability.name or not G.GAME.current_round then
return ret return ret
end end
@ -96,129 +71,23 @@ local function freeze(card)
return ret return ret
end end
local function hook_estate()
---@param card Card|{ Roland_frozen: {estate: integer} }
local function estate_pos(card)
if card.area ~= G.jokers and card.area.config.type ~= "title" then
return 1
end
card.Roland_frozen.estate = card.Roland_frozen.estate or card.rank or 1
return card.Roland_frozen.estate
end
local estate = G.P_CENTERS.j_Bakery_Estate
local orig_calculate = estate.calculate
function estate:calculate(card, context, ...)
if not (card or {}).Roland_frozen or not (card.edition or {}).Roland_frozen then
return orig_calculate(self, card, context, ...)
end
if not context.joker_main then
return
end
local joker_count = estate_pos(card)
local extra = card.ability.extra
return {chips = extra.chips * joker_count, mult = extra.mult * joker_count}
end
local orig_loc_vars = estate.loc_vars
function estate:loc_vars(info_queue, card, ...)
if not (card or {}).Roland_frozen or not (card.edition or {}).Roland_frozen then
return orig_loc_vars(self, info_queue, card, ...)
end
local joker_count = estate_pos(card)
local extra = card.ability.extra or {}
return {vars = {extra.chips * joker_count, extra.mult * joker_count}}
end
end
local function hook_proxy()
---@param card Card|{ Roland_frozen: {proxy: string} }
local function get_proxied_joker(card)
if not G.jokers or not G.jokers.cards then
return
end
card.Roland_frozen.proxy = card.Roland_frozen.proxy or
(Bakery_API.get_proxied_joker() or card).config.center.key
---@param v Card
local function eq(v)
return v.config.center.key == card.Roland_frozen.proxy
end
return f(G.jokers.cards):any(eq)
end
local proxy = G.P_CENTERS.j_Bakery_Proxy
local orig_calculate = proxy.calculate
function proxy:calculate(card, context, ...)
if not (card or {}).Roland_frozen or not (card.edition or {}).Roland_frozen then
return orig_calculate(self, card, context, ...)
else
return SMODS.blueprint_effect(card, get_proxied_joker(card), context)
end
end
local orig_loc_vars = proxy.loc_vars
function proxy:loc_vars(info_queue, card, ...)
if not (card or {}).Roland_frozen or not (card.edition or {}).Roland_frozen then
return orig_loc_vars(self, info_queue, card, ...)
end
local other = get_proxied_joker(card)
local var = (other and other ~= card) and localize {
type = "name_text",
set = other.config.center.set,
key = other.config.center.key,
} or localize "k_none"
return {vars = {var}}
end
end
local function hook_scribe()
local scribe = G.P_CENTERS.c_Bakery_Scribe
local orig_can_use = scribe.can_use
function scribe.can_use(...)
return orig_can_use(...) and
(SMODS.Mods.Roland.config.scribable_basket or f(G.jokers.highlighted):all(function(v)
return v.config.center.key ~= "j_Roland_basket"
end))
end
end
SMODS.Edition { SMODS.Edition {
key = "frozen", key = "frozen",
shader = "frozen", shader = "frozen",
sound = {sound = "Roland_frozen", per = 1, vol = 0.8}, sound = {sound = "Roland_frozen", per = 1, vol = 0.8},
attributes = {"passive", "scaling", "mod_chance"},
pronouns = "any_all",
weight = 8, weight = 8,
extra_cost = 4, extra_cost = 4,
in_shop = true, in_shop = true,
apply_to_float = false, apply_to_float = false,
calculate = function(_, card, context) calculate = function(_, card)
local _ = not context.fix_probability and not context.mod_probability and freeze(card) freeze(card)
end, end,
on_remove = function(card) on_remove = function(card)
card.Roland_frozen, card.Roland_frozen_estate = nil
card.Roland_frozen_base, card.Roland_frozen_ability = nil
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 self.weight * G.GAME.edition_rate return G.GAME.edition_rate * self.weight
end, end,
} }
@ -250,49 +119,19 @@ 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}
function Card:calculate_joker(context, ...) function Card:calculate_joker(context, ...)
local is_frozen = self.edition and self.edition.Roland_frozen local is_frozen = self.edition and self.edition.Roland_frozen
if is_frozen and self.ability.name == "Loyalty Card" then if is_frozen and self.ability.name == "Turtle Bean" then
return (context.joker_main and self.ability.loyalty_remaining == 0) and
{xmult = self.ability.extra.Xmult} or nil
end
if is_frozen and is_bean[self.ability.name] then
return return
end end
local ret = {orig_calculate_joker(self, context, ...)} local ret = {orig_calculate_joker(self, context, ...)}
if not is_frozen or type(ret[1]) ~= "table" then if not is_frozen or not ret[1] then
return unpack(ret) return unpack(ret)
end end
@ -302,11 +141,24 @@ function Card:calculate_joker(context, ...)
return unpack(ret) return unpack(ret)
end end
return f {mult = "mult_mod", chips = "chip_mod", xmult = "xmult", x_mult = "x_mult"}:map(function(v) ability.Roland_frozen_mult_mod = ability.Roland_frozen_mult_mod or ret[1].mult_mod
local key = "Roland_frozen_" .. v ability.Roland_frozen_chip_mod = ability.Roland_frozen_chip_mod or ret[1].chip_mod
ability[key] = ability[key] or ret[1][v] ability.Roland_frozen_xmult = ability.Roland_frozen_xmult or ret[1].xmult
return ability[key]
end):where(f.id):table() return {
chip_mod = ability.Roland_frozen_chip_mod,
mult_mod = ability.Roland_frozen_mult_mod,
xmult = ability.Roland_frozen_xmult,
message = localize {
type = "variable",
key = "a_mult",
vars = {
ability.Roland_frozen_mult_mod or
ability.Roland_frozen_chip_mod or
ability.Roland_frozen_xmult,
},
},
}
end end
local orig_calculate_dollar_bonus = Card.calculate_dollar_bonus local orig_calculate_dollar_bonus = Card.calculate_dollar_bonus
@ -329,49 +181,106 @@ function Card:calculate_dollar_bonus(...)
return orig_calculate_dollar_bonus(self, ...) return orig_calculate_dollar_bonus(self, ...)
end end
function SMODS.current_mod.bootstraps_mult(card) q(function()
local ability = card.Roland_frozen_ability local estate = G.P_CENTERS.j_Bakery_Estate
local ret = math.floor((G.GAME.dollars + (G.GAME.dollar_buffer or 0)) / card.ability.extra.dollars)
if not ability then if not estate then
return ret return
end end
ability.Roland_bootstraps_mult = ability.Roland_bootstraps_mult or ret local function estate_pos(card)
return ability.Roland_bootstraps_mult if card.area ~= G.jokers and card.area.config.type ~= "title" then
return 1
end end
local orig_get_probability_vars = SMODS.get_probability_vars card.Roland_frozen_estate = card.Roland_frozen_estate and
card.Roland_frozen_estate or
(f(card.area.cards):swap():any(function(_, k)
return k == card
end) or 1)
function SMODS.get_probability_vars(trigger_obj, ...) return card.Roland_frozen_estate
local numerator, denominator = orig_get_probability_vars(trigger_obj, ...)
local override = ((trigger_obj or {}).Roland_frozen or {}).probability
if override then
return type(denominator) == "table" and to_big(override) or override, denominator
else
return numerator, denominator
end
end end
q { local orig_calculate = estate.calculate
blocking = false,
no_delete = true,
func = function()
local orig_flip_double_sided = (Bakery_API or {}).flip_double_sided
if not orig_flip_double_sided then ---@param card Card|{ Roland_frozen_ability: table, Roland_frozen_estate: integer }
return false function estate:calculate(card, context, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_calculate(self, card, context, ...)
end end
function Bakery_API.flip_double_sided(card, ...) if not context.joker_main then
if not card.edition or not card.edition.Roland_frozen then return
return orig_flip_double_sided(card, ...)
end
end end
local _ = G.P_CENTERS.j_Bakery_Estate and hook_estate() local joker_count = estate_pos(card)
local _ = G.P_CENTERS.c_Bakery_Scribe and hook_scribe() local extra = card.ability.extra or {}
local _ = G.P_CENTERS.j_Bakery_Proxy and hook_proxy() return {chips = extra.chips * joker_count, mult = extra.mult * joker_count}
end, end
}
local orig_loc_vars = estate.loc_vars
function estate:loc_vars(info_queue, card, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_loc_vars(self, info_queue, card, ...)
end
local joker_count = estate_pos(card)
local extra = card.ability.extra or {}
return {vars = {extra.chips * joker_count, extra.mult * joker_count}}
end
end)
q(function()
local proxy = G.P_CENTERS.j_Bakery_Proxy
if not proxy then
return
end
local function get_proxied_joker(card)
if not G.jokers or not G.jokers.cards then
return
end
local ability = freeze(card)
---@param v Card
local function eq(v)
return v.unique_val == ability.Roland_frozen_proxy
end
ability.Roland_frozen_proxy = (Bakery_API.get_proxied_joker() or card).unique_val
return f(G.jokers.cards):any(eq) ---@type Card?
end
local orig_calculate = proxy.calculate
---@param card Card|{ Roland_frozen_ability: table }
function proxy:calculate(card, context, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_calculate(self, card, context, ...)
end
return SMODS.blueprint_effect(card, get_proxied_joker(card), context)
end
local orig_loc_vars = proxy.loc_vars
function proxy:loc_vars(info_queue, card, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_loc_vars(self, info_queue, card, ...)
end
local other = get_proxied_joker(card)
local var = (other and other ~= card) and localize {
type = "name_text",
set = other.config.center.set,
key = other.config.center.key,
} or localize "k_none"
return {vars = {var}}
end
end)

File diff suppressed because it is too large Load diff

View file

@ -3,227 +3,10 @@
---@license MPL-2.0 ---@license MPL-2.0
---@version 1.0.0 ---@version 1.0.0
--- ---
---@alias FFrom (fun<K, V>(iter: table<K, V>, fpairs?: fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K?, V?)): F | { [K]: V })|(fun(iter: number, fpairs?: number, step?: number): F | { [number]: number })|(fun<V>(iter: string): (fun(table: { [string]: V }): V))|(fun<V>(iter: string): (fun(table: { [string]: V }): V))|(fun<K, V>(iter: fun(): K, V): F | { [K]: V })|(fun(iter: false): fun(): false)|(fun(iter: true): fun(): true)|(fun(iter: nil): F)
---@class F ---@class F
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
---@param first integer|string|fun(v: I): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first)
error {first}
end
---@generic I, T, O
---@param first integer|string|fun(v: I): T
---@param second integer|string|fun(v: T): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second)
error {first, second}
end
---@generic I, T1, T2, O
---@param first integer|string|fun(v: I): T1
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: T2): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third)
error {first, second, third}
end
---@generic I, T1, T2, T3, O
---@param first integer|string|fun(v: I): T1
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: T2): T3
---@param fourth integer|string|fun(v: T3): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third, fourth)
error {first, second, third, fourth}
end
---@generic I, T1, T2, T3, T4, O
---@param first integer|string|fun(v: I): T1
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: T2): T3
---@param fourth integer|string|fun(v: T3): T4
---@param fifth integer|string|fun(v: T4): O
---@return fun(v: I): O
---@nodiscard
function f.chain(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.chain(all)
error(all)
end
---@generic I, T, O
---@param all { [1]: (integer|string|fun(v: I): T), [2]: (integer|string|fun(v: T): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
error(all)
end
---@generic I, T1, T2, O
---@param all { [1]: (integer|string|fun(v: I): T1), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: T2): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
error(all)
end
---@generic I, T1, T2, T3, O
---@param all { [1]: (integer|string|fun(v: I): T1), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: T2): T3), [4]: (integer|string|fun(v: T3): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
error(all)
end
---@generic I, T1, T2, T3, T4, O
---@param all { [1]: (integer|string|fun(v: I): T1), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: T2): T3), [4]: (integer|string|fun(v: T3): T4), [4]: (integer|string|fun(v: T4): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
error(all)
end
---@generic I, O
---@param first integer|string|fun(v: I): O
---@return fun(v: I): O
---@nodiscard
function f.compose(first)
error {first}
end
---@generic I, T, O
---@param first integer|string|fun(v: T): O
---@param second integer|string|fun(v: I): T
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second)
error {first, second}
end
---@generic I, T1, T2, O
---@param first integer|string|fun(v: T2): O
---@param second integer|string|fun(v: T1): T2
---@param third integer|string|fun(v: I): T1
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second, third)
error {first, second, third}
end
---@generic I, T1, T2, T3, O
---@param first integer|string|fun(v: T3): O
---@param second integer|string|fun(v: T2): T3
---@param third integer|string|fun(v: T1): T2
---@param fourth integer|string|fun(v: I): T1
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second, third, fourth)
error {first, second, third, fourth}
end
---@generic I, T1, T2, T3, T4, O
---@param first integer|string|fun(v: T4): O
---@param second integer|string|fun(v: T3): T4
---@param third integer|string|fun(v: T2): T3
---@param fourth integer|string|fun(v: T1): T2
---@param fifth integer|string|fun(v: I): T1
---@return fun(v: I): O
---@nodiscard
function f.compose(first, second, third, fourth, fifth)
error {first, second, third, fourth, fifth}
end
---@generic I, O
---@param all { [1]: (integer|string|fun(v: I): O) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T, O
---@param all { [1]: (integer|string|fun(v: T): O), [2]: (integer|string|fun(v: I): T) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T1, T2, O
---@param all { [1]: (integer|string|fun(v: T2): O), [2]: (integer|string|fun(v: T1): T2), [3]: (integer|string|fun(v: I): T1) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T1, T2, T3, O
---@param all { [1]: (integer|string|fun(v: T3): O), [2]: (integer|string|fun(v: T2): T3), [3]: (integer|string|fun(v: T1): T2), [4]: (integer|string|fun(v: I): T1) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic I, T1, T2, T3, T4, O
---@param all { [1]: (integer|string|fun(v: T4): O), [2]: (integer|string|fun(v: T3): T4), [3]: (integer|string|fun(v: T2): T3), [4]: (integer|string|fun(v: T1): T2), [4]: (integer|string|fun(v: I): T1) }
---@return fun(v: I): O
---@nodiscard
function f.compose(all)
error(all)
end
---@generic K, V ---@generic K, V
---@param self F | { [K]: V } ---@param self F | { [K]: V }
---@return K?, V? ---@return K?, V?
@ -235,51 +18,35 @@ end
---@type F ---@type F
local none local none
---@generic T: F|function|string|nil
---@param func T
---@return T
---@nodiscard
local function autofunc(func)
if func == false then
return f.fals
elseif func == true then
return f.tru
elseif type(func) == "number" then
return f.arg(func)
elseif type(func) == "string" then
return f.indices(func)
else
return func or f.id
end
end
---@generic K, V ---@generic K, V
---@param tbl table<K, V> ---@param tbl table<K, V>
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V) ---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return fun(tbl: table<K, V>, key: K): K, V ---@return fun(tbl: table<K, V>, key: K): K, V
---@return table<K, V> ---@return K
---@return V ---@return V
---@nodiscard
local function autopairs(tbl, fpairs) local function autopairs(tbl, fpairs)
return (fpairs or (tbl[1] and ipairs or pairs))(tbl) return (fpairs or tbl[#tbl] and ipairs or pairs)(tbl)
end
---@param any any
---@return boolean
local function is_f(any)
return type(any) == "table" and any.from == f.from and any.new == f.new
end end
--- Always returns nil. --- Always returns nil.
---@return nil ---@return nil
---@nodiscard
function f.noop() function f.noop()
end end
--- Always returns false. --- Always returns false.
---@return false ---@return false
---@nodiscard
function f.fals() function f.fals()
return false return false
end end
--- Always returns true. --- Always returns true.
---@return true ---@return true
---@nodiscard
function f.tru() function f.tru()
return true return true
end end
@ -288,31 +55,10 @@ end
---@generic T ---@generic T
---@param ... T ---@param ... T
---@return T ---@return T
---@nodiscard
function f.id(...) function f.id(...)
return ... return ...
end end
---@generic T
---@param value T
---@return fun(v: T): boolean
---@nodiscard
function f.eq(value)
return function(v)
return value == v
end
end
---@generic T
---@param value T
---@return fun(T): boolean
---@nodiscard
function f.nq(value)
return function(v)
return value ~= v
end
end
---@generic T ---@generic T
---@param v T ---@param v T
---@return fun(): T ---@return fun(): T
@ -331,200 +77,55 @@ function f.const(v)
end end
end end
---@param i integer
---@return fun(...: any): any
---@nodiscard
function f.arg(i)
return function(...)
return ({...})[i]
end
end
---@generic K, V ---@generic K, V
---@param v K ---@param v K
---@return fun(x: { [K]: V }): V ---@return fun(x: { [K]: V }): V
---@nodiscard ---@nodiscard
function f.index(v) function f.index(v)
return function(x) return function(x)
return type(x) == "table" and x[v] or x return x[v]
end end
end end
---@generic K, V
---@param v { [K]: V }
---@return fun(x: K): V
---@nodiscard
function f.index_into(v)
return type(v) == "table" and function(x)
return v[x]
end or f.const(v)
end
---@generic V
---@param v string
---@return fun(x: { [string]: V }): V
---@nodiscard
function f.indices(v)
return function(x)
for i in v:gmatch "[^.]+" do
if type(x) ~= "table" then
return x
end
x = x[i]
end
return x
end
end
---@generic K, V
---@param v V
---@param k K
---@return fun(x: { [K]: V }): { [K]: V }
---@nodiscard
function f.insert(v, k)
return function(x)
if type(x) == "table" then
x[k] = v
end
return x
end
end
---@generic T
---@param tbl T[]
---@return fun(t: T[], i?: integer): integer, T
---@return T[]
function f.revpairs(tbl)
return function(t, i)
local k = i and i - 1 or #t
local v = t[k]
if v ~= nil then
return k, v
end
end, tbl
end
---@param any any
---@return boolean
---@nodiscard
function f.isf(any)
return type(any) == "table" and any.from == f.from and any.new == f.new
end
local unpack = table.unpack or unpack
f[true and "call"] = function(self, ...)
local args = {...}
return type(self) == "string" and function(v)
return v[self](v, unpack(args))
end or function(v)
return v(unpack(args))
end
end
f[true and "chain"] = function(...)
local ret
for _, v in ipairs {...} do
v = f.isf(v) and v:table() or v
if type(v) == "table" then
for _, vv in ipairs(v) do
local copy = ret
vv = autofunc(vv)
ret = ret and function(...)
return vv(copy(...))
end or vv
end
else
local copy = ret
v = autofunc(v)
ret = ret and function(...)
return v(copy(...))
end or v
end
end
return ret or f.noop
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 }
---@nodiscard ---@nodiscard
function f.new(fnext) function f.new(fnext)
-- Iterating over `f` is far easier, but we do this for performance sake. local ret = {next = fnext or f.noop}
return {
all = f.all, for k, v in pairs(f) do
any = f.any, ret[k] = v
arg = f.arg, end
call = f.call,
chain = f.chain, return ret
compose = f.compose,
concat = f.concat,
const = f.const,
count = f.count,
diff = f.diff,
each = f.each,
eq = f.eq,
fals = f.fals,
flatmap = f.flatmap,
fold = f.fold,
from = f.from,
id = f.id,
index = f.index,
index_into = f.index_into,
indices = f.indices,
insert = f.insert,
isf = f.isf,
keys = f.keys,
map = f.map,
new = f.new,
next = fnext or f.noop,
noop = f.noop,
nq = f.nq,
peek = f.peek,
pun = f.pun,
rev = f.rev,
revpairs = f.revpairs,
skip = f.skip,
slice = f.slice,
string = f.string,
swap = f.swap,
table = f.table,
take = f.take,
tru = f.tru,
values = f.values,
where = f.where,
}
end end
--- Creates an enumeration. --- Creates an enumeration.
---@type FFrom ---@generic K, V
---@param iter table<K, V>
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K?, V?)
---@param step? nil
---@return F | { [K]: V }
---@overload fun(iter: number, fpairs?: number, step?: number): F | { [number]: number }
---@overload fun(iter: string): (fun(table: { [string]: V }): V)
---@overload fun(iter: fun(): K, V): F | { [K]: V }
---@overload fun(iter: false): fun(): false
---@overload fun(iter: true): fun(): true
---@overload fun(iter: nil): F
function f.from(iter, fpairs, step) function f.from(iter, fpairs, step)
if iter == nil then if iter == true then
return none
elseif iter == true then
return f.tru return f.tru
elseif iter == false then elseif iter == false then
return f.fals return f.fals
elseif iter == nil then
return none
end end
local t = type(iter) local t = type(iter)
if t == "string" then if t == "string" then
return f.indices(iter) return f.index(iter)
elseif t == "number" then elseif t == "number" then
local ik, is, start = 0, step or 1, fpairs and iter or 1 local ik, is, start = 0, step or 1, fpairs and iter or 1
@ -556,10 +157,10 @@ function f.from(iter, fpairs, step)
end end
end) end)
else else
local next, tbl, k, v = autopairs(iter, type(fpairs) == "function" and fpairs or nil) local next, context, k, v = autopairs(iter, fpairs)
return f.new(function() return f.new(function()
k, v = next(tbl, k) k, v = next(context, k)
return k, v return k, v
end) end)
end end
@ -571,14 +172,12 @@ 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
local s = fs[i] fs[i] = is_f(fs[i]) and fs[i] or f.from(fs[i])
fs[i] = f.isf(s) and s or f.from(s)
end end
return f.new(function() return f.new(function()
@ -606,34 +205,14 @@ function f:concat(...)
end) end)
end end
---@generic K, V
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any
---@return F|{ [K]: V }
---@nodiscard
function f:peek(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
return f.new(function()
local k, v = self:next()
if k ~= nil then
func(v, k)
return k, v
end
end)
end
---@generic K, V, U ---@generic K, V, U
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): U ---@param func F|fun(v: V, k: K): U
---@return F|{ [K]: U } ---@return F|{ [K]: U }
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
function f:map(func) function f:map(func)
self = f.isf(self) and self or f.from(self) func = type(func) == "string" and f.index(func) or func
func = autofunc(func)
return f.new(function() return f.new(function()
local k, v = self:next() local k, v = self:next()
@ -646,20 +225,17 @@ end
---@generic K, V, U ---@generic K, V, U
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): { [any]: U } ---@param func F|fun(v: V, k: K): { [any]: U }
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V) ---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return F|{ [K]: U } ---@return 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
---@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 }
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, vp local vt, vk, vv, vp
func = autofunc(func) func = type(func) == "string" and f.index(func) or 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)
@ -699,14 +275,13 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): boolean ---@param func F|fun(v: V, k: K): boolean
---@param is? any ---@param is? any
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@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 = type(func) == "string" and f.index(func) or func
func = autofunc(func)
return f.new(function() return f.new(function()
local k, v local k, v
@ -738,7 +313,6 @@ 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()
@ -756,7 +330,6 @@ 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()
@ -774,8 +347,6 @@ 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()
@ -785,28 +356,6 @@ 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
@ -814,8 +363,6 @@ 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
@ -847,7 +394,6 @@ 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
@ -857,7 +403,6 @@ 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
@ -869,7 +414,6 @@ 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
@ -894,13 +438,12 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any ---@param func F|fun(v: V, k: K): boolean
---@return boolean|V ---@return boolean|V
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean|V
function f:any(func) function f:any(func)
self = f.isf(self) and self or f.from(self) func = type(func) == "string" and f.index(func) or func
func = autofunc(func)
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
@ -913,13 +456,12 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any ---@param func F|fun(v: V, k: K): boolean
---@return boolean|V ---@return boolean|V
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean|V
function f:all(func) function f:all(func)
self = f.isf(self) and self or f.from(self) func = type(func) == "string" and f.index(func) or func
func = autofunc(func)
for k, v in self.next do for k, v in self.next do
if not func or not func(v, k) then if not func or not func(v, k) then
@ -932,14 +474,13 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any ---@param func F|fun(v: V, k: K): boolean
---@return integer ---@return integer
---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): integer
function f:count(func) function f:count(func)
self = f.isf(self) and self or f.from(self)
func = autofunc(func)
local ret = 0 local ret = 0
func = type(func) == "string" and f.index(func) or func
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
@ -950,39 +491,11 @@ 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
---@param self F|{ [K]: V }
---@param _ `T`
---@return F|{ [K]: `T` }
---@nodiscard
function f:pun(_)
return self
end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@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
@ -1007,7 +520,6 @@ 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
@ -1019,10 +531,8 @@ end
---@generic K, V ---@generic K, V
---@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)
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)
@ -1032,11 +542,4 @@ end
none = f.new() none = f.new()
---@type F|FFrom return f.from
local ret = (setmetatable or f.const(f.from))(f, {
__call = function(_, ...)
return f.from(...)
end,
})
return ret

View file

@ -1,97 +0,0 @@
---@meta
---@alias Attributes "mult"|"chips"|"xmult"|"xchips"|"score"|"xscore"|"blindsize"|"xblindsize"|"balance"|"swap"|"retrigger"|"scaling"|"reset"|"suit"|"diamonds"|"hearts"|"spades"|"clubs"|"hand_type"|"rank"|"ace"|"two"|"three"|"four"|"five"|"six"|"seven"|"eight"|"nine"|"ten"|"jack"|"queen"|"king"|"face"|"economy"|"generation"|"destroy_card"|"hands"|"discard"|"hand_size"|"chance"|"joker_slot"|"mod_chance"|"copying"|"full_deck"|"passive"|"joker"|"tarot"|"planet"|"spectral"|"enhancements"|"seals"|"editions"|"tag"|"skip"|"modify_card"|"perma_bonus"|"prevents_death"|"boss_blind"|"reroll"|"on_sell"|"sell_value"|"food"|"space"|"bakery_double_sided"|"bakery_usable"|"bakery_werewolf"
---@type Card[]
CardArea.cards = CardArea.cards
--- @overload fun(tbl: SMODS.Joker): SMODS.GameObject
Bakery_API.Charm = Bakery_API.Charm
--- @generic T: SMODS.GameObject
--- @param obj T
--- @return T
function Bakery_API.credit(obj)
error(obj)
end
---@type table
Balatest = Balatest
--- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) }
Big = Big
--- @type table?
CardSleeves = CardSleeves
--- @type table|fun(obj: SMODS.Back): SMODS.Back
CardSleeves.Sleeve = CardSleeves.Sleeve
--- @type {aliases: { [string]: [string] }}
Cryptid = Cryptid
---@return Card
function copy_card(...)
error(...)
end
--- @type fun(area: CardArea, ...: ...): Card
create_card_for_shop = create_card_for_shop
--- @type CardArea
G.Bakery_charm_area = G.Bakery_charm_area
--- @type boolean|table
G.Bakery_charm_area.cards[1].ability.extra = G.Bakery_charm_area.cards[1].ability.extra
---@type Card[]
G.playing_cards = G.playing_cards
---@type userdata|{getWidth: fun(self: self): number}
SMODS.Atlas.image_data = SMODS.Atlas.image_data
---@param context CalcContext|table|{hand_drawn: true, other_drawn: true}
---@param return_table? table
---@return table?
function SMODS.calculate_context(context, return_table)
error {context, return_table}
end
SMODS.Mods.Roland.config = require "config"
---@type (fun(rem: number): number|table)[]
SMODS.Mods.Roland.harsh_ante_scaling = SMODS.Mods.Roland.harsh_ante_scaling
--- @type table
Talisman = Talisman
--- @type fun(obj: any): number
to_number = to_number
-- This exists to remove the @deprecated warning.
---Returns the elements from the given `list`. This function is equivalent to
---```lua
--- return list[i], list[i+1], ···, list[j]
---```
---
---
---[View documents](command:extension.lua.doc?["en-us/52/manual.html/pdf-unpack"])
---
---@generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
---@param list {
--- [1]?: T1,
--- [2]?: T2,
--- [3]?: T3,
--- [4]?: T4,
--- [5]?: T5,
--- [6]?: T6,
--- [7]?: T7,
--- [8]?: T8,
--- [9]?: T9,
--- [10]?: T10,
---}
---@param i? integer
---@param j? integer
---@return T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
---@nodiscard
function unpack(list, i, j)
error {list, i, j}
end

View file

@ -1,89 +1,3 @@
local f = assert(SMODS.load_file "src/lib/funky.lua")() or require "lib.funky"
---@param v string
---@return Card|Tag
local function add(v)
if v == "op" then
local op = SMODS.add_card {no_edition = true, key = "j_joker"}
op.ability.mult = 1e100
return op
elseif v == "xop" then
local xop = SMODS.add_card {no_edition = true, key = "j_Roland_jokersr"}
xop.ability.extra.xmult = 1e100
return xop
end
if G.P_BLINDS[v] then
if G.STATE ~= G.STATES.MENU then
G.from_boss_tag = true
G.GAME.perscribed_bosses = G.GAME.perscribed_bosses or {}
G.GAME.perscribed_bosses[G.GAME.round_resets.ante] = v
G.FUNCS.reroll_boss()
end
return G.P_BLINDS[v]
end
if not G.P_TAGS[v] then
return SMODS.add_card {
no_edition = true,
key = v,
area = G.P_CENTERS[v] and
(({Default = true, Enhanced = true})[G.P_CENTERS[v].set] and G.hand or
G.P_CENTERS[v].set == "Booster" and G.shop_booster or
G.P_CENTERS[v].set == "Voucher" and G.shop_vouchers) or nil,
}
end
local tag = Tag(v)
if tag.name == "Orbital Tag" then
local hands = f(G.GAME.hands):where "visible":keys():table()
tag.ability.orbital_hand = pseudorandom_element(hands, pseudoseed "Roland_c_orbital_tag")
end
add_tag(tag)
return tag
end
local function simplify(x)
return type(x) == "string" and x:lower():gsub("%s", ""):gsub("_", ""):gsub("^the", "") or x
end
local function flatten(v)
return type(v) ~= "table" and {v} or (f.isf(v) and v:table() or v)
end
---@param it string
local function find(it)
if ({op = true, xop = true})[it] or (G.P_CENTERS[it] or {}).config then
return it
end
local match = simplify(it)
local pool = match == "Blind" and G.P_BLINDS or
f(G.P_CENTER_POOLS):any(f.chain(f.arg(2), simplify, f.eq(match)))
if type(pool) == "table" and next(pool) then
return pseudorandom_element(pool, pseudoseed "Roland_c").key
end
if Cryptid and Cryptid.aliases and Cryptid.aliases[it] then
return Cryptid.aliases[it]
end
return f(G.localization.descriptions)
:flatmap(flatten)
:where(type, "table")
:where(function(v, k)
return ((G.P_CENTERS[k] or {}).config or G.P_BLINDS[k]) and
(match == simplify(k) or match == simplify(v.name))
end)
:keys()
:any()
end
local function protect(fun) local function protect(fun)
return function() return function()
local res, ret = pcall(fun) local res, ret = pcall(fun)
@ -92,83 +6,76 @@ local function protect(fun)
sendErrorMessage(tostring(ret), "Roland") sendErrorMessage(tostring(ret), "Roland")
end end
return not res or ret ~= false and not (SMODS.current_mod and ret ~= true) return not res or ret ~= false
end end
end end
local function protect_ev(fun) local function protect_ev(fun)
if type(fun) == "function" then if type(fun) == "table" then
return Event {func = protect(fun)} fun.func = protect(fun.func)
elseif type(fun) ~= "table" then fun = getmetatable(fun) == Event and fun or Event(fun)
error("Expected a function or table, got a " .. type(fun), 3) elseif type(fun) == "function" then
elseif type(fun.is) == "function" and fun:is(SMODS.GameObject) then fun = Event {func = protect(fun)}
return Event { else
blocking = false, error("Expected a function or event, got a " .. type(fun), 3)
no_delete = true,
func = protect(function()
if not Bakery_API or not Bakery_API.credit then
return false
end end
Bakery_API.credit(fun) return fun
end), end
}
else if false then
fun.func = protect(fun.func or fun[1]) -- This allows for better type inference.
return getmetatable(fun) == Event and fun or Event(fun) SMODS.Mods.Roland.config = require "config"
-- This exists to remove the @deprecated warning.
---Returns the elements from the given `list`. This function is equivalent to
---```lua
--- return list[i], list[i+1], ···, list[j]
---```
---
---
---[View documents](command:extension.lua.doc?["en-us/52/manual.html/pdf-unpack"])
---
---@generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
---@param list {
--- [1]?: T1,
--- [2]?: T2,
--- [3]?: T3,
--- [4]?: T4,
--- [5]?: T5,
--- [6]?: T6,
--- [7]?: T7,
--- [8]?: T8,
--- [9]?: T9,
--- [10]?: T10,
---}
---@param i? integer
---@param j? integer
---@return T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
---@nodiscard
function unpack(list, i, j)
error {list, i, j}
end end
end end
local f = assert(SMODS.load_file "src/lib/funky.lua")() or require "lib.funky"
--- Queues an event to be run. --- Queues an event to be run.
--- Note that events added this way implicitly `return true` unless you explicitly `return false`. --- Note that events added this way implicitly `return true` unless you explicitly `return false`, unlike the vanilla ones.
--- For `front`; boolean `true` to add the event to the front of the queue, rather than the end. --- @param fun (fun():false|nil)|Event The event or a function to run turn into an event.
--- @generic T: fun():boolean?|Event --- @param front boolean|nil `true` to add the event to the front of the queue, rather than the end.
--- @param fun T|Event|{front?: boolean} The table or function to turn into an event. local function q(fun, front)
--- @return T fun G.E_MANAGER:add_event(protect_ev(fun), nil, front)
local function q(fun)
local ev = protect_ev(fun)
G.E_MANAGER:add_event(ev, nil, ev.front)
return fun
end end
--- Determines if a center is allowed to be usable. --- Determines if a center is allowed to be usable.
---@return boolean ---@return boolean
local function u() local function u()
return not ((G.play and next(G.play.cards) or G.CONTROLLER.locked or G.GAME.STOP_USE and G.GAME.STOP_USE > 0) and return not ((G.play and #G.play.cards > 0 or G.CONTROLLER.locked or
(G.GAME.STOP_USE and G.GAME.STOP_USE > 0)) and
G.STATE ~= G.STATES.HAND_PLAYED and G.STATE ~= G.STATES.HAND_PLAYED and
G.STATE ~= G.STATES.DRAW_TO_HAND and G.STATE ~= G.STATES.DRAW_TO_HAND and
G.STATE ~= G.STATES.PLAY_TAROT) G.STATE ~= G.STATES.PLAY_TAROT)
end end
--- Creates one or more cards. return {f, q, u}
---@param ... any
---@return Card|Tag|(Card|Tag[])
local function c(...)
local cards = f {...}
:flatmap(flatten)
:where(type, "string")
:map(string.lower)
:map(find)
:where(f.id)
:map(add)
:values()
:table()
return #cards > 1 and cards or cards[1]
end
return {f, q, u, setmetatable({}, {
__call = function(_, ...)
return c(...)
end,
__index = function(_, k)
return c(k)
end,
__newindex = function(_, k, v)
local n = tonumber(v)
for _ = 1, type(n) == "number" and n or 1 do
c(k)
end
end,
})}

View file

@ -1,97 +1,73 @@
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 local f, q = unpack(qol)
SMODS.current_mod.qol = qol
local f, q = qol[1], qol[2]
q { f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "back", "seal"}:each(function(v)
front = true,
no_delete = true,
blocking = false,
blockable = false,
func = function()
local contributors = (Bakery_API or {}).contributors
if not contributors then
return false
end
f(assert(SMODS.load_file("src/credits.lua", "Roland"))() or require "credits"):each(function(v, k)
contributors["Roland_" .. k] = v
end)
if SMODS.Mods.DebugPlus and SMODS.Mods.Roland.config.import_funky then
_G.f, _G.q, _G.u, _G.c = unpack(qol)
end
end,
}
f {"challenge", "spectral", "edition", "voucher", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"}
:each(function(v)
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol) assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
end) end)
local _ = Balatest and f {"joker", "blind", "spectral"}:each(function(v) if _G["Balatest"] then
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 end
return k ~= "nodes" and type(v) == "table" q(function()
end):each(realign) ---@type table<string, {name: string, fg: table, bg: table}>
local contributors, mods = Bakery_API.contributors, SMODS.Mods
return tbl contributors.Roland_aster = {
name = "asterSSH",
fg = HEX "f8f8f2ff",
bg = HEX "bd93f9ff",
}
contributors.Roland_char = {
name = "char (@irregulester)",
fg = HEX "f8f8f2ff",
bg = HEX "ff79c6ff",
}
if mods.DebugPlus and mods.Roland.config.import_funky then
_G.f, _G.q, _G.u = unpack(qol)
end end
end, true)
local function toggle(id) local function toggle(id)
return realign(create_toggle { return 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
SMODS.Atlas { SMODS.Atlas {
px = 256, px = 256,
py = 256, py = 256,
key = "modicon", key = "modicon",
fps = animated and 12 or nil, path = "icon.png",
frames = animated and 12 or nil,
path = animated and "icon.png" or "unicon.png",
atlas_table = animated and "ANIMATION_ATLAS" or "ASSET_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 = "tr", padding = 0.1, colour = G.C.BLACK}, config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.BLACK},
nodes = { nodes = {{
{
n = G.UIT.C, n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tr", padding = 0.1, colour = G.C.CLEAR}, config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR},
nodes = { nodes = {
toggle "animated_icon",
toggle "cool_phones",
toggle "vitriol",
toggle "equinox_assist",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
},
},
{
n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tr", padding = 0.1, colour = G.C.CLEAR},
nodes = {
toggle "faster_planets",
toggle "illusion_seal", toggle "illusion_seal",
toggle "no_wild_debuff", toggle "no_wild_debuff",
toggle "no_highlight_limit",
_G["Talisman"] and toggle "harsh_ante_scaling",
toggle "fusable_escapey",
toggle "equinox_assist",
SMODS.Mods.DebugPlus and toggle "import_funky", SMODS.Mods.DebugPlus and toggle "import_funky",
Talisman and toggle "harsh_ante_scaling",
},
},
}, },
}},
} }
end end
function SMODS.current_mod.optional_features()
return {cardareas = {deck = true, unscored = true}}
end

View file

@ -1,4 +1,4 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = unpack(... or require "lib.shared")
SMODS.Atlas { SMODS.Atlas {
px = 71, px = 71,
@ -11,9 +11,8 @@ SMODS.Seal {
key = "glass", key = "glass",
atlas = "seal", atlas = "seal",
pos = {x = 0, y = 0}, pos = {x = 0, y = 0},
attributes = {"destroy_card", "tag"},
badge_colour = HEX "a6a6a6ff", badge_colour = HEX "a6a6a6ff",
pronouns = "they_them", pronouns = "he_him",
calculate = function(_, card, context) calculate = function(_, card, context)
local function eq(v) local function eq(v)
return v == card return v == card

View file

@ -1,4 +1,6 @@
local f, q, u = (... or require "lib.shared")[1], (... or require "lib.shared")[2], (... or require "lib.shared")[3] local f, q, u = unpack(... or require "lib.shared")
local negative = {key = "e_negative_consumable", set = "Edition", config = {extra = 1}}
local spectral = (function() local spectral = (function()
local x = 0 local x = 0
@ -6,13 +8,19 @@ local spectral = (function()
---@param tbl SMODS.Consumable|{artist?: string} ---@param tbl SMODS.Consumable|{artist?: string}
---@return SMODS.Consumable ---@return SMODS.Consumable
return function(tbl) return function(tbl)
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
tbl.pos = {x = x, y = 0}
tbl.atlas = "spectral"
tbl.set = "Spectral"
tbl.cost = 4 tbl.cost = 4
tbl.set = "Spectral"
tbl.atlas = "spectral"
tbl.pos = {x = x, y = 0}
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
local ret = SMODS.Consumable(tbl)
x = x + 1 x = x + 1
return q(SMODS.Consumable(tbl))
q(function()
Bakery_API.credit(ret)
end)
return ret
end end
end)() end)()
@ -30,140 +38,122 @@ SMODS.Sound {
spectral { spectral {
key = "afterimage", key = "afterimage",
pronouns = "she_they", pronouns = "he_they",
artist = "aster", artist = "aster",
config = {extra = {amount = 1, hand = -2}},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, negative)
return {vars = {card.ability.extra.amount, card.ability.extra.hand}}
end,
can_use = function(_, card)
return u() and card.ability.extra.amount == #Bakery_API.get_highlighted()
end,
use = function(_, card)
f(Bakery_API.get_highlighted()):each(function(v)
q {
delay = 0.1,
func = function()
v:set_edition {negative = true}
end,
}
end)
q {
delay = 0.1,
func = function()
G.hand:change_size(card.ability.extra.hand)
Bakery_API.unhighlight_all()
end,
}
end,
}
spectral {
key = "coolheaded",
config = {extra = {amount = 1}},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
return {vars = {card.ability.extra.amount}}
end,
can_use = function(_, card)
return u() and f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):count(function(v)
return not v.edition
end) == card.ability.extra.amount
end,
use = function()
f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):each(function(v)
q {
delay = 0.1,
func = function()
v:set_edition {Roland_frozen = true}
end,
}
end)
end,
}
spectral {
key = "dual",
pronouns = "they_them",
config = {extra = {amount = 2}}, config = {extra = {amount = 2}},
attributes = {"seal", "modify_card", "spectral"},
loc_vars = function(_, _, card) loc_vars = function(_, _, card)
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function() can_use = function()
return next(G.hand.cards) and u() return u() and #G.hand.cards > 0
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 = SMODS.poll_seal { local seal
guaranteed = true,
options = f(G.P_SEALS):keys():where(f.nq "Roland_glass"):values():table(),
}
local _ = not card.Roland_immediate and q { for _ = 1, 31 do
delay = 0.5, seal = SMODS.poll_seal {guaranteed = true}
trigger = "after",
func = function() if seal ~= "Roland_glass" then
v:set_seal(seal, nil, true) break
end, end
} or v:set_seal(seal, true, true) end
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 {
key = "primal", key = "mirror",
pronouns = "he_him", pronouns = "he_him",
artist = "aster", config = {extra = {amount = 1}},
attributes = {"enhancements", "modify_card", "spectral"},
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_CENTERS.m_stone)
end,
can_use = function()
return next(G.hand.cards) and u()
end,
use = function(_, card)
q {
trigger = "after",
delay = 0.4,
func = function()
card:juice_up(0.3, 0.5)
play_sound "tarot1"
end,
}
f(G.hand.cards):each(function(v, k)
local percent = 1.15 - (k - 0.999) / (#G.hand.cards - 0.998) * 0.3
q {
trigger = "after",
delay = 0.15,
func = function()
v:flip()
v:juice_up(0.3, 0.3)
play_sound("card1", percent)
return true
end,
}
end)
f(G.hand.cards):each(function(v)
q(function()
v:set_ability(G.P_CENTERS.m_stone)
end)
end)
f(G.hand.cards):each(function(v, k)
local percent = 0.85 + (k - 0.999) / (#G.hand.cards - 0.998) * 0.3
q {
trigger = "after",
delay = 0.15,
func = function()
v:flip()
v:juice_up(0.3, 0.3)
play_sound("tarot2", percent, 0.6)
return true
end,
}
end)
delay(0.5)
end,
}
spectral {
key = "refract",
pronouns = "it_its",
artist = "aster",
config = {extra = {amount = 2}},
attributes = {"seal", "modify_card", "spectral"},
loc_vars = function(_, info_queue, card) loc_vars = function(_, info_queue, card)
table.insert(info_queue, G.P_SEALS.Roland_glass) table.insert(info_queue, G.P_SEALS.Roland_glass)
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function(_, card) can_use = function(_, card)
local count = #Bakery_API.get_highlighted() return u() and #Bakery_API.get_highlighted() == card.ability.extra.amount
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)
end, end,
} }
spectral { local void = 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}},
attributes = {"destroy_card", "generation", "spectral"},
loc_vars = function(_, info_queue, card) loc_vars = function(_, info_queue, card)
table.insert(info_queue, {key = "e_negative_consumable", set = "Edition", config = {extra = 1}}) table.insert(info_queue, negative)
table.insert(info_queue, G.P_CENTERS.c_cryptid) table.insert(info_queue, G.P_CENTERS.c_cryptid)
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function() can_use = function()
return next(G.playing_cards) and u() return #G.playing_cards > 1 and not not u()
end, end,
use = function(_, card) use = function(_, card)
local function destructible(v) local function destructible(v)
@ -189,15 +179,25 @@ spectral {
f(cards):each(destroy) f(cards):each(destroy)
f(G.jokers.cards):each(calculate_joker) f(G.jokers.cards):each(calculate_joker)
f(card.ability.extra.amount):each(function() for _ = 1, card.ability.extra.amount do
local cryptid = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_cryptid", "void") local cryptid = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_cryptid", "void")
cryptid:set_edition({negative = true}, true) cryptid:set_edition({negative = true}, true)
cryptid:add_to_deck() cryptid:add_to_deck()
G.consumeables:emplace(cryptid) G.consumeables:emplace(cryptid)
end) end
end end
play_sound("Roland_void", 1, 0.7) play_sound("Roland_void", 1, 0.7)
q {delay = 0.28, timer = "REAL", trigger = "after", func = void}
q {
delay = 0.28,
timer = "REAL",
trigger = "after",
func = void,
}
end, end,
} }
q(function()
void.hidden = not SMODS.Mods.Cryptid
end)

View file

@ -1,144 +0,0 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Atlas {
px = 34,
py = 34,
key = "tag",
path = "tag.png",
}
local function apply_frozen(tag, context)
return function()
q {
delay = 0.4,
trigger = "after",
func = function()
play_sound "tarot1"
tag:juice_up(0.3, 0.5)
end,
}
q {
delay = 0.15,
trigger = "after",
func = function()
context.card:flip()
play_sound("card1", 0.85)
context.card:juice_up(0.3, 0.3)
end,
}
delay(0.2)
q {
delay = 0.1,
trigger = "after",
func = function()
context.card:set_edition {Roland_frozen = true}
end,
}
q {
delay = 0.15,
trigger = "after",
func = function()
context.card:flip()
play_sound("tarot2", 0.85, 0.6)
context.card:juice_up(0.3, 0.3)
end,
}
delay(0.1)
end
end
SMODS.Tag {
key = "freeze",
atlas = "tag",
pronouns = "any_all",
min_ante = 1,
pos = {x = 0, y = 0},
config = {amount = 5},
loc_vars = function(self, info_queue, tag)
tag.ability = tag.ability or {}
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
return {vars = {tag.ability.amount or self.config.amount}}
end,
apply = function(self, tag, context)
tag.ability = tag.ability or {}
if not tag.triggered and context.type == "Bakery_play_hand_late" and tag.ability.amount == 0 then
tag.triggered = true
tag:yep("X", G.C.RED, f.tru)
end
if tag.triggered or
not context.card or
context.card.edition or
context.type ~= "Bakery_score_card" or
(tag.ability.amount or self.config.amount) <= 0 then
return
end
tag.ability.amount = (tag.ability.amount or self.config.amount) - 1
return {func = apply_frozen(tag, context)}
end,
}
SMODS.Tag {
key = "invisible",
atlas = "tag",
pronouns = "any_all",
min_ante = 2,
pos = {x = 1, y = 0},
config = {invis_rounds = 0, total_rounds = 2},
loc_vars = function(self, _, tag)
tag.ability = tag.ability or f(self.config):table()
local main_end = {}
local _ = f((G.jokers or {}).cards or {}):map "edition":any "negative" and
localize {type = "other", key = "remove_negative", nodes = main_end, vars = {}}
return {
vars = {
tag.ability.total_rounds or self.config.total_rounds,
tag.ability.invis_rounds or self.config.invis_rounds,
},
main_end = main_end[1],
}
end,
increment = function(self)
f(G.GAME.tags):where("key", "tag_Roland_invisible"):each(function(v)
v.ability = v.ability or {}
v.ability.invis_rounds = (v.ability.invis_rounds or self.config.invis_rounds) + 1
v:juice_up()
end)
end,
apply = function(self, tag)
local modifiers = G.GAME.modifiers
tag.ability = tag.ability or {}
local ab = tag.ability
ab.invis_rounds = ab.invis_rounds or self.config.invis_rounds
ab.total_rounds = ab.total_rounds or self.config.total_rounds
if tag.triggered or
not G.jokers or
not next(G.jokers.cards) or
ab.invis_rounds < ab.total_rounds or
#G.jokers.cards + (modifiers.Roland_invisible or 0) >= G.jokers.config.card_limit then
return
end
tag.triggered = true
modifiers.Roland_invisible = (modifiers.Roland_invisible or 0) + 1
tag:yep("!", G.C.GREY, function()
local copied = pseudorandom_element(G.jokers.cards, pseudoseed "Roland_invisible")
local copy = copy_card(copied, nil, nil, nil, copied.edition and copied.edition.negative)
G.jokers:emplace(copy)
copy:add_to_deck()
modifiers.Roland_invisible = (modifiers.Roland_invisible or 0) - 1
return true
end)
end,
}

View file

@ -1,46 +0,0 @@
local f, q, u = (... or require "lib.shared")[1], (... or require "lib.shared")[2], (... or require "lib.shared")[3]
SMODS.Atlas {
px = 71,
py = 95,
key = "tarot",
path = "tarot.png",
}
q(SMODS.Consumable {
key = "coolheaded",
pos = {x = 0, y = 0},
config = {extra = {amount = 1}},
cost = 3,
set = "Tarot",
atlas = "tarot",
artist = "Roland_aster",
attributes = {"editions", "modify_card", "spectral"},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
return {vars = {card.ability.extra.amount}}
end,
can_use = function(_, card)
if not u() then
return false
end
local highlighted = Bakery_API.get_highlighted()
local count = #G.jokers.highlighted + #highlighted
return count > 0 and count <= card.ability.extra.amount and
f(G.jokers.highlighted):concat(highlighted):all(function(v)
return not v.edition
end)
end,
use = function()
f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):each(function(v)
q {
delay = 0.1,
func = function()
v:set_edition {Roland_frozen = true}
end,
}
end)
end,
})

View file

@ -1,4 +1,4 @@
local f = (... or require "lib.shared")[1] local f = unpack(... or require "lib.shared")
if not Balatest then if not Balatest then
return return
@ -68,7 +68,8 @@ Balatest.TestPlay {
} }
end) end)
Balatest.wait_for_input(G.STATES.ROUND_EVAL) Balatest.wait_for_input()
Balatest.wait()
end, end,
assert = function() assert = function()
Balatest.assert_chips(2720) Balatest.assert_chips(2720)
@ -191,9 +192,9 @@ Balatest.TestPlay {
} }
Balatest.TestPlay { Balatest.TestPlay {
category = {"blind", "mitotic"}, category = {"blind", "xerox"},
name = "mitotic", name = "xerox",
blind = "bl_Roland_mitotic", blind = "bl_Roland_xerox",
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}
end, end,
@ -203,9 +204,9 @@ Balatest.TestPlay {
} }
Balatest.TestPlay { Balatest.TestPlay {
category = {"blind", "mitotic"}, category = {"blind", "xerox"},
name = "mitotic_disabled", name = "xerox_disabled",
blind = "bl_Roland_mitotic", blind = "bl_Roland_xerox",
jokers = {"j_chicot"}, jokers = {"j_chicot"},
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}

View file

@ -2,6 +2,330 @@ if not Balatest then
return return
end end
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_none",
jokers = {"j_Roland_escapey"},
execute = function() end,
assert = function()
Balatest.assert(not G.jokers.cards[1].config.center:Bakery_can_use(G.jokers.cards[1]))
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_one_consumable",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_two_consumables",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength", "c_strength"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 3)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_one_tag",
jokers = {"j_Roland_escapey"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_two_tags",
jokers = {"j_Roland_escapey"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 3)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_consumables_and_tags",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_partial_selected_consumables_and_tags",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength", "c_strength"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.consumeables:add_to_highlighted(G.consumeables.cards[1])
end)
Balatest.wait()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_full_selected_consumables_and_tags",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.consumeables:add_to_highlighted(G.consumeables.cards[1])
end)
Balatest.wait()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 3)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_fusion",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_consumable_takes_precedence",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
consumeables = {"c_strength"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 2)
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_tag_takes_precedence",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 2)
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_fusion_takes_precedence",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
consumeables = {"c_strength"},
execute = function()
Balatest.q(function()
G.consumeables:add_to_highlighted(G.consumeables.cards[1])
end)
Balatest.wait()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_scribe_fusion",
jokers = {"j_Roland_escapey"},
execute = function()
if not G.P_CENTERS.c_Bakery_Scribe then
sendWarnMessage("escapey_scribe_fusion cannot run without c_Bakery_Scribe, skipping test.")
return
end
Balatest.q(function()
local scribe = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_Bakery_Scribe", "balatest")
scribe:add_to_deck()
G.consumeables:emplace(scribe)
end)
Balatest.wait()
Balatest.q(function()
G.jokers:add_to_highlighted(G.jokers.cards[1])
end)
Balatest.wait()
Balatest.use(function() return G.consumeables.cards[1] end)
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
if not G.P_CENTERS.c_Bakery_Scribe then
return
end
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 1)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_scribe_fusion_alt",
jokers = {"j_Roland_escapey"},
execute = function()
if not G.P_CENTERS.c_Bakery_Scribe then
sendWarnMessage("escapey_scribe_fusion_alt cannot run without c_Bakery_Scribe, skipping test.")
return
end
Balatest.q(function()
local scribe = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_Bakery_Scribe", "balatest")
scribe:add_to_deck()
G.consumeables:emplace(scribe)
end)
Balatest.wait()
Balatest.q(function()
G.jokers:add_to_highlighted(G.jokers.cards[1])
end)
Balatest.wait()
Balatest.use(function() return G.consumeables.cards[1] end)
Balatest.q(function()
G.jokers:add_to_highlighted(G.jokers.cards[2])
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[2]}}
end)
Balatest.wait()
end,
assert = function()
if not G.P_CENTERS.c_Bakery_Scribe then
return
end
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 1)
end,
}
Balatest.TestPlay { Balatest.TestPlay {
category = {"joker", "martingale"}, category = {"joker", "martingale"},
name = "martingale_oops", name = "martingale_oops",
@ -10,7 +334,7 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(7) Balatest.assert_chips(7 * 2)
end, end,
} }
@ -23,6 +347,6 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(1 / 0) Balatest.assert_chips(7 * math.pow(2, 32))
end, end,
} }

View file

@ -2,6 +2,22 @@ if not Balatest then
return return
end end
Balatest.TestPlay {
category = {"spectral", "afterimage"},
name = "afterimage",
consumeables = {"c_Roland_afterimage"},
deck = {cards = {{s = "S", r = "2"}}},
execute = function()
Balatest.highlight({"2S"})
Balatest.use(G.consumeables.cards[1])
Balatest.end_round()
end,
assert = function()
Balatest.assert_eq(G.hand.config.card_limit, 51)
Balatest.assert(G.deck.cards[1].edition.negative)
end,
}
Balatest.TestPlay { Balatest.TestPlay {
category = {"spectral", "void"}, category = {"spectral", "void"},
name = "spectral", name = "spectral",

View file

@ -1,40 +1,52 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2] local f, q = unpack(... or require "lib.shared")
SMODS.Joker:take_ownership("joker", {cost = 1}, true)
local orig_set_debuff = Card.set_debuff
function Card:set_debuff(should_debuff, ...) SMODS.Joker:take_ownership("joker", {cost = 1}, true)
if self.ability.Roland_crimson ~= nil then local orig_can_highlight = CardArea.can_highlight
self.ability.Roland_crimson = not not should_debuff local orig_set_debuff = Card.set_debuff
return local orig_highlight = Card.highlight
local orig_copy_card = copy_card
function CardArea:can_highlight(...)
if self ~= G.consumeables or not SMODS.Mods.Roland.config.no_highlight_limit then
return orig_can_highlight(self, ...)
end end
if SMODS.get_enhancements(self).m_wild and SMODS.Mods.Roland.config.no_wild_debuff then --- Allows more flexibility when using the Escapey joker to delete specific consumables.
self.config.highlighted_limit = 1 / 0
return true
end
function Card:set_debuff(...)
if self.config and
self.config.center_key == "m_wild" and
SMODS.Mods.Roland.config.no_wild_debuff then
self.debuff = false self.debuff = false
else else
orig_set_debuff(self, should_debuff, ...) orig_set_debuff(self, ...)
end
end
function Card:highlight(is_highlighted, ...)
self.highlighted = is_highlighted
if not G.CONTROLLER.HID.controller or not SMODS.Mods.Roland.config.no_highlight_limit then
return orig_highlight(self, is_highlighted, ...)
end end
end end
local orig_use_consumeable = Card.use_consumeable 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
set_consumeable_usage(self)
level_up_hand(copier or self, self.ability.consumeable.hand_type, true, self.ability.qty or 1)
return
end
local seal_spectrals = {["Deja Vu"] = true, Medium = true, Talisman = true, Trance = true} local seal_spectrals = {["Deja Vu"] = true, Medium = true, Talisman = true, Trance = true}
if not seal_spectrals[self.ability.name] then if not seal_spectrals[self.ability.name] then
return orig_use_consumeable(self, area, copier, ...) return orig_use_consumeable(self, area, copier, ...)
end end
local card = copier or self
f(Bakery_API.get_highlighted()):each(function(v) f(Bakery_API.get_highlighted()):each(function(v)
q(function() q(function()
play_sound "tarot1" play_sound "tarot1"
local card = (copier or self)
card:juice_up(0.3, 0.5) card:juice_up(0.3, 0.5)
end) end)
@ -47,43 +59,42 @@ function Card:use_consumeable(area, copier, ...)
} }
end) end)
q {delay = 0.7, trigger = "after", func = Bakery_API.unhighlight_all} q {
delay = 0.7,
trigger = "after",
func = function()
Bakery_API.unhighlight_all()
end,
}
end end
local orig_use_card = G.FUNCS.use_card function copy_card(other, new_card, ...)
local ret = orig_copy_card(other, new_card, ...)
function G.FUNCS.use_card(e, ...) if new_card and new_card.edition and new_card.edition.key == "e_negative" then
local ref = e.config.ref_table or {} --- Fixes an issue where using 'c_death' will make negative
local consumeable = (ref.ability or {}).consumeable or {} --- cards do the inverse of what they're supposed to do.
local ability = new_card.ability
if not SMODS.Mods.Roland.config.faster_planets or ability.card_limit = math.max(ability.card_limit, 1)
(not consumeable.hand_type and not consumeable.hand_types) then
return orig_use_card(e, ...)
end end
local normal = G.STATE == G.STATES.SHOP or return ret
ref.area ~= G.pack_cards or
not G.GAME.pack_choices or
G.GAME.pack_choices <= 0
ref.from_area = ref.from_area or ref.area
play_sound("tarot1", percent, 0.6)
discover_card(ref.config.center)
ref:use_consumeable(ref.area)
SMODS.calculate_context {area = ref.area, consumeable = ref, using_consumeable = true}
ref:remove()
if normal then
return
end end
G.GAME.pack_choices = G.GAME.pack_choices - 1 q(function()
local _ = G.GAME.pack_choices <= 0 and G.FUNCS.end_consumeable() local orig_can_highlight_area = Bakery_API.can_highlight_area
function Bakery_API.can_highlight_area(area, ...)
return (area == G.consumeables and
SMODS.Mods.Roland.config.no_highlight_limit) or
orig_can_highlight_area(area, ...)
end end
end)
local orig_create_card_for_shop = create_card_for_shop local orig_create_card_for_shop = create_card_for_shop
function create_card_for_shop(...) function create_card_for_shop(...)
---@type Card
local ret = orig_create_card_for_shop(...) local ret = orig_create_card_for_shop(...)
if not SMODS.Mods.Roland.config.illusion_seal or if not SMODS.Mods.Roland.config.illusion_seal or
@ -93,7 +104,11 @@ function create_card_for_shop(...)
return ret return ret
end end
local seal = SMODS.poll_seal {type_key = "Roland_illusion_seal", guaranteed = true} local seal = SMODS.poll_seal {
type_key = "Roland_illusion_seal",
guaranteed = true,
}
ret:set_seal(seal, true, true) ret:set_seal(seal, true, true)
return ret return ret
end end
@ -101,44 +116,31 @@ end
local orig_get_blind_amount = get_blind_amount local orig_get_blind_amount = get_blind_amount
---@param ante number ---@param ante number
---@return table|number ---@return number
local function blind(ante) local function blind(ante)
return ante == 39 and 1e294 or (to_number or f.id)(orig_get_blind_amount(ante)) return ante == 39 and 1e294 or (_G["to_number"] or f().id)(orig_get_blind_amount(ante))
end end
local function no_harsh_ante_scaling() local function no_harsh_ante_scaling()
return not Talisman or not SMODS.Mods.Roland.config.harsh_ante_scaling return not _G["Talisman"] or not SMODS.Mods.Roland.config.harsh_ante_scaling
end end
---@type (fun(rem: number): number|table)[]
SMODS.current_mod.harsh_ante_scaling = {
function(rem)
return (Big.constants and Big.constants.TEN or Big:new {10}):pow(blind(rem + 1))
end,
function(rem)
return Big:new {1, rem + 2}
end,
function(rem)
return Big:new {1, 1, [rem + 2] = 1}
end,
function(rem)
return Big:new {1, 1, [blind(rem) + 2] = 1}
end,
function(rem)
return rem <= 1 and Big:new {1.7976931348623157e308, 1, [1.7975e308] = 1} or 1 / 0
end,
}
function get_blind_amount(ante, ...) function get_blind_amount(ante, ...)
local offset = 40 local loop = 39
if ante < offset or no_harsh_ante_scaling() then if ante < loop or no_harsh_ante_scaling() then
return orig_get_blind_amount(ante, ...) return orig_get_blind_amount(ante, ...)
end end
local loop = 10 if (ante - 9) / 15 >= loop then
return 1 / 0
end
return f(SMODS.Mods.Roland.harsh_ante_scaling, f.revpairs):any(function(_, k) --- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) }
return (ante - offset) / loop + 1 >= k local big, rem = _G["Big"], (ante % loop) + 1
end)((ante - offset) % loop + 1)
return ante / 15 >= loop and big:new(f(blind(ante - (loop * 15))):map(f().const(10)):table()) or
(ante / 9 >= loop and big:new(f(ante / loop - 8):map(f().const(10)):concat {blind(rem)}:table()) or
(ante / 2 >= loop and big:new {blind(rem), ante / loop} or
(big.constants and big.constants.TEN or big:new {10}):pow(blind(rem))))
end end

View file

@ -1,44 +0,0 @@
local voucher = (function()
local x = 0
---@param tbl SMODS.Voucher|{attributes: Attributes[]}
---@return SMODS.Voucher
return function(tbl)
tbl.pos = {x = x, y = 0}
tbl.atlas = "voucher"
tbl.cost = 10
x = x + 1
return SMODS.Voucher(tbl)
end
end)()
SMODS.Atlas {
px = 71,
py = 95,
key = "voucher",
path = "voucher.png",
}
voucher {
key = "ceres",
pronouns = "it_its",
config = {extra = {amount = 1, hand_type = "Flush House"}},
attributes = {"planet", "passive", "hand_type", "space"},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.amount}}
end,
in_pool = function(self)
return G.GAME.hands[self.config.extra.hand_type].visible
end,
}
voucher {
key = "neptune",
pronouns = "it_its",
requires = {"v_Roland_ceres"},
config = {extra = {amount = 2, hand_type = "Straight Flush"}},
attributes = {"planet", "passive", "hand_type", "space"},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.amount}}
end,
}