Compare commits

..

65 commits
2.8.3 ... main

Author SHA1 Message Date
91660bfeea
Add support for pools and tags 2026-06-24 16:14:54 +02:00
7c64a5d8fa
Restrict context 2026-06-24 00:00:03 +02:00
dcf9d55fcf
Improve debugging experience, nerf Violet Vessel 2026-06-23 20:47:12 +02:00
2f55344a90
Fix charm-related crash 2026-06-23 18:49:27 +02:00
0bb1eed9d5
Revert Void changes, make tests pass 2026-06-22 18:22:23 +02:00
d118a17e3f
Use new SMODS function 2026-06-21 21:19:45 +02:00
77f308685a
Fix race condition 2026-06-21 13:19:32 +02:00
2b78455ff8
Buff voucher 2026-06-21 00:06:48 +02:00
480c4ffd45
Fix challenge 2026-06-20 12:06:10 +02:00
b1c7ffffbe
Adjust challenges 2026-06-20 12:00:11 +02:00
c9f282bdf2
Nerf challenges 2026-06-20 11:16:11 +02:00
4230a1a4b1
Fix multiplayer-related edgecase 2026-06-20 11:13:15 +02:00
0bf2093d7d
Add The Blizzard art, change Martingale + The Improbable interaction 2026-06-20 11:03:54 +02:00
ee57c92608
Delete voucher.png.kra 2026-06-20 09:25:16 +02:00
67826c039c
Add 2 new vouchers 2026-06-20 09:23:39 +02:00
a6e6289349
Fix faster planets 2026-06-19 19:10:54 +02:00
18e763be35
Aggressively inline all uses of _G 2026-06-19 19:01:57 +02:00
7d4198efbd
Buff Glass House 2026-06-19 02:26:02 +02:00
169ec336ae
Replace starting jokers 2026-06-19 00:03:12 +02:00
4a76ca294c
Prevent crash when disabling cool phones 2026-06-18 22:35:25 +02:00
e9201b3f78
Add alternative charm art
Merged with https://git.gdane.net/bagels/PhoneStore
2026-06-18 22:23:22 +02:00
ce395aedb3
Remove niche debugging function 2026-06-18 19:18:18 +02:00
eb232c1c77
Fix charm order, fix Violet Vessel, improve G.calc 2026-06-18 16:18:58 +02:00
a529b84ae6
Remove redundant duplicate hook 2026-06-18 05:05:32 +02:00
9af0495bf4
Improve localization and Venerable Visage 2026-06-18 05:03:04 +02:00
892c502c61
Improve load time 2026-06-18 02:44:09 +02:00
89690e66d2
Update art 2026-06-17 22:13:12 +02:00
f2494a2011
Fix charms being injected in the wrong order 2026-06-17 13:39:41 +02:00
a294b7f7ea
Prevent edition overrides 2026-06-17 05:12:56 +02:00
3f7a8685be
Add 2 new jokers, add credit, fix credit 2026-06-17 02:43:22 +02:00
bf0562dfab
Add new joker, buff Snowsquall 2026-06-17 00:17:42 +02:00
c48fd5454e
Add new joker 2026-06-16 21:22:24 +02:00
d49f446d6e
Add new utility function 2026-06-16 21:22:05 +02:00
15a50ff9d1
Add new challenge 2026-06-16 14:28:41 +02:00
09cc387b8e
Add new joker 2026-06-16 13:48:39 +02:00
e5ca09df63
Fix crash 2026-06-15 21:51:54 +02:00
8effb33080
Improve funky.lua 2026-06-15 17:13:54 +02:00
645d073e17
Improve compatibility 2026-06-11 15:09:57 +02:00
53064c9356
Increase joker rarity 2026-06-09 17:06:16 +02:00
3e4c551ac0
Account for Frozen 2026-06-08 22:53:39 +02:00
ec06774740
Do not debuff Turtle Bean 2026-06-08 22:42:46 +02:00
69af47d3c7
Add new joker 2026-06-08 22:35:42 +02:00
0a50c41b0d
Update unpack.sh 2026-06-08 22:35:26 +02:00
c343952a19
Add new art 2026-06-08 03:09:50 +02:00
39c7c93315
Fix hook 2026-06-07 21:21:33 +02:00
f723ea8a7e
Improve multiplayer compatibility, again 2026-06-07 19:04:41 +02:00
0e5a7a3d06
Improve multiplayer compatibility 2026-06-07 19:03:00 +02:00
38c477e95f
Fix faster planets erroneously spawning hand area early 2026-06-07 18:18:41 +02:00
2132afb1e8
Make Void always hidden, make hooking more resilient to lag 2026-06-06 19:40:35 +02:00
dff89f4062
Clone exclusively consumables 2026-06-06 13:50:33 +02:00
b8c632f4c9
Nerf Phytoestrogens 2026-06-06 13:10:32 +02:00
63123ec141
Prevent Basket from cloning jokers if they end up there somehow 2026-06-06 04:00:42 +02:00
704e2ab9d1
Fix crash from unequiping fat i phone 2026-06-05 20:37:12 +02:00
66a1a62cad
Add sound and improve quality-of-life 2026-06-05 20:31:49 +02:00
3967afba48
Fix Amber Acorn and wii phone regression 2026-06-05 19:52:14 +02:00
8a82494674
Buff Artemis X, fix Amber Acorn crashing when duplicated 2026-06-05 17:59:16 +02:00
40b9757895
Optimize Arctic Circle 2026-06-05 17:35:03 +02:00
fc29871a43
Buff Phytoestrogens, prevent Arctic from triggering other selves 2026-06-05 13:36:06 +02:00
464babf206
Fix multiplayer-only crash from Amber Acorn being the only joker 2026-06-05 02:58:49 +02:00
0d5acb6cea
Prevent nil dereference from Freeze Tag 2026-06-05 01:37:38 +02:00
57d559cd5b
Fix Amber Acorn triggering multiple times, mark Artemis X 2026-06-05 00:37:27 +02:00
ed250737ab
Fix Frozen on Driver's License 2026-06-04 23:33:18 +02:00
e8fcd6931a
Fix Frozen regressions 2026-06-04 04:14:13 +02:00
0fd9b9546e
Fix bootstraps and erosion mixup 2026-06-04 02:20:38 +02:00
835a001302
Make Amber Acorn uncommon 2026-06-04 00:13:22 +02:00
46 changed files with 1647 additions and 1080 deletions

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 67 KiB

BIN
assets/1x/phorm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/1x/unicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
assets/1x/voucher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 845 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/2x/phorm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/2x/unicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
assets/2x/voucher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/sounds/excalibur.ogg Normal file

Binary file not shown.

BIN
assets/sounds/phone.ogg Executable file

Binary file not shown.

View file

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

View file

@ -1,4 +1,6 @@
return { return {
animated_icon = true,
cool_phones = true,
equinox_assist = false, equinox_assist = false,
faster_planets = false, faster_planets = false,
harsh_ante_scaling = false, harsh_ante_scaling = false,
@ -6,4 +8,5 @@ return {
import_funky = false, import_funky = false,
no_wild_debuff = true, no_wild_debuff = true,
scribable_basket = false, scribable_basket = false,
vitriol = true,
} }

View file

@ -6,7 +6,9 @@ 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 #1# times", "that are {C:dark_edition}debuffed twice",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
b_Roland_swapper = { b_Roland_swapper = {
@ -14,12 +16,14 @@ return {
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",
}, },
}, },
}, },
BakeryCharm = { BakeryCharm = {
BakeryCharm_Roland_cocacola = { BakeryCharm_Roland_cocacola = {
name = "coca cola phone", name = SMODS.Mods.Roland.config.cool_phones and "coca cola phone" or "Pentagram",
text = { text = {
"{C:attention}Discard 0 cards {}to", "{C:attention}Discard 0 cards {}to",
"gain {C:red}#1# {}discards and", "gain {C:red}#1# {}discards and",
@ -28,7 +32,7 @@ return {
}, },
}, },
BakeryCharm_Roland_fat = { BakeryCharm_Roland_fat = {
name = "fat i phone", name = SMODS.Mods.Roland.config.cool_phones and "fat i phone" or "Product",
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",
@ -36,15 +40,15 @@ return {
}, },
}, },
BakeryCharm_Roland_flexible = { BakeryCharm_Roland_flexible = {
name = "flexi ble phone", name = SMODS.Mods.Roland.config.cool_phones and "flexi ble phone" or "Ring",
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 = "hand phone", name = SMODS.Mods.Roland.config.cool_phones and "hand phone" or "Shell",
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 = "wii phone", name = SMODS.Mods.Roland.config.cool_phones and "wii phone" or "Tire",
text = { text = {
"Enter the shop", "Enter the shop",
"when any {C:attention}Blind", "when any {C:attention}Blind",
@ -55,39 +59,47 @@ return {
Blind = { Blind = {
bl_Roland_blizzard = { bl_Roland_blizzard = {
name = "The Blizzard", name = "The Blizzard",
text = {"All cards", "are Frozen"}, text = {"All cards", "are {C:dark_edition}Frozen"},
}, },
bl_Roland_divide = { bl_Roland_divide = {
name = "The Great Divide", name = "The Great Divide",
text = {"Half of the deck", "is discarded"}, text = {"Half of the deck", "is {C:red}discarded"},
}, },
bl_Roland_equinox = { bl_Roland_equinox = {
name = "The Equinox", name = "The Equinox",
text = {"No UI"}, text = {"{C:inactive}No UI"},
}, },
bl_Roland_falseshuffle = { bl_Roland_falseshuffle = {
name = "The False Shuffle", name = "The False Shuffle",
text = {"Enhanced cards", "are drawn last"}, text = {"{C:enhanced}Enhanced {}cards", "are drawn {C:attention}last"},
}, },
bl_Roland_improbable = { bl_Roland_improbable = {
name = "The Improbable", name = "The Improbable",
text = {"All probabilities", "cannot happen"}, text = {"All {C:green}probabilities", "cannot happen"},
}, },
bl_Roland_mitotic = { bl_Roland_mitotic = {
name = "The Mitotic", name = "The Mitotic",
text = {"Discarded cards", "are copied"}, text = {"{C:red}Discarded {}cards", "are {C:attention}copied"},
}, },
bl_Roland_nimble = { bl_Roland_nimble = {
name = "The Nimble", name = "The Nimble",
text = {"The first 5 cards", "drawn are played"}, text = {"The {C:attention}first 5 {}cards", "drawn are {C:blue}played"},
}, },
bl_Roland_tranquilizer = { bl_Roland_tranquilizer = {
name = "The Tranquilizer", name = "The Tranquilizer",
text = {"#1#s are debuffed", "Changes based on", "most common rank"}, text = {
"{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 = {"Infinite discards", "Lose if any card was", "not played or discarded"}, text = {
"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.",
@ -121,7 +133,11 @@ return {
Joker = { Joker = {
j_Roland_amber = { j_Roland_amber = {
name = "Amber Acorn", name = "Amber Acorn",
text = {"{X:mult,C:white}X#1#{} Mult", "Moves before", "hand is played"}, text = {
"{X:mult,C:white}X#1#{} Mult",
"Moves before",
"hand is played",
},
}, },
j_Roland_arctic = { j_Roland_arctic = {
name = "Arctic Circle", name = "Arctic Circle",
@ -161,6 +177,22 @@ return {
"if it is {C:dark_edition}Frozen", "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",
},
},
j_Roland_crimson = {
name = "Crimson Heart",
text = {
"{X:mult,C:white}X#1#{} Mult",
"{C:red}Debuffs {C:attention}Joker",
"to the left",
},
},
j_Roland_domino = { j_Roland_domino = {
name = "Domino", name = "Domino",
text = { text = {
@ -170,6 +202,22 @@ return {
"{C:inactive}(Currently {C:red}+#3#{C:inactive} Mult)", "{C:inactive}(Currently {C:red}+#3#{C:inactive} Mult)",
}, },
}, },
j_Roland_excalibur = {
name = "Excalibur",
text = {
"Score {C:attention}#1# {C:enhanced}Stone Cards",
"to {C:attention}pull {}this {C:attention}Joker",
"{C:inactive}(Currently {C:attention}#2#{C:inactive})",
},
},
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 = {
name = "Hard-Boiled", name = "Hard-Boiled",
text = { text = {
@ -290,7 +338,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 {C:hand}deck", "card of the 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})",
}, },
}, },
@ -311,6 +359,26 @@ 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",
},
},
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",
},
},
j_Roland_venerable = {
name = "Venerable Visage",
text = {"{X:red,C:white}X#1#{} discards each round"},
},
j_Roland_yard = { j_Roland_yard = {
name = "Yard Sale", name = "Yard Sale",
text = { text = {
@ -336,15 +404,18 @@ 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 #1# times", "that are {C:dark_edition}debuffed twice",
"",
"{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:attention}Blinds{} are {C:dark_edition}debuffed", "{C:dark_edition}Debuff {X:dark_edition,C:white}#1#X<ante>{C:dark_edition} times",
"{C:dark_edition}({X:dark_edition,C:white}(ante * #2#){C:dark_edition}) times", "{C:inactive}(Instead of twice)",
"{C:inactive}(Instead of #1#)", "",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
sleeve_Roland_swapper = { sleeve_Roland_swapper = {
@ -352,13 +423,16 @@ return {
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 = {
"{C:attention}All {}consumable", "Swap {C:attention}all {}consumables",
"types swap places", "",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
}, },
}, },
}, },
@ -368,7 +442,7 @@ return {
text = { text = {
"Add random seals", "Add random seals",
"to {C:attention}#1#{} random", "to {C:attention}#1#{} random",
"cards in {C:hands}hand", "cards in hand",
}, },
}, },
c_Roland_primal = { c_Roland_primal = {
@ -384,7 +458,7 @@ return {
text = { text = {
"Add a {C:dark_edition}Glass Seal", "Add a {C:dark_edition}Glass Seal",
"to {C:attention}#1#{} selected", "to {C:attention}#1#{} selected",
"cards in {C:hands}hand", "cards in hand",
}, },
}, },
c_Roland_void = { c_Roland_void = {
@ -421,6 +495,22 @@ return {
}, },
}, },
}, },
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 = {
@ -429,6 +519,7 @@ 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_Go = "Pass GO",
c_Roland_Jokerful = "Jokerful", c_Roland_Jokerful = "Jokerful",
c_Roland_Pastries = "Sweet Pastries", c_Roland_Pastries = "Sweet Pastries",
@ -436,8 +527,10 @@ return {
}, },
v_dictionary = { v_dictionary = {
b_Roland_add = "ADD", 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_disabled = "Disabled",
b_Roland_enabled = "Enabled", b_Roland_enabled = "Enabled",
@ -449,12 +542,13 @@ return {
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_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_toggle = "TOGGLE",
b_Roland_unassigned = "(Unassigned)", b_Roland_unassigned = "(Unassigned)",
b_Roland_vitriol = "Vitriol (Venerable Visage)",
}, },
labels = { labels = {
Roland_frozen = "Frozen", Roland_frozen = "Frozen",
@ -466,6 +560,8 @@ 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_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_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"},

View file

@ -86,10 +86,48 @@ payload = """if next(SMODS.find_card "j_Roland_misfortune") then
end""" end"""
match_indent = true match_indent = true
[[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]]
[patches.regex] [patches.regex]
target = "card.lua" target = "card.lua"
pattern = "math\\.floor\\(\\(G\\.GAME\\.dollars \\+ \\(G\\.GAME\\.dollar_buffer or 0\\)\\)/self\\.ability\\.extra\\.dollars\\)" pattern = "math\\.floor\\(\\(G\\.GAME\\.dollars \\+ \\(G\\.GAME\\.dollar_buffer or 0\\)\\)/self\\.ability\\.extra\\.dollars\\)"
position = "at" position = "at"
payload = "G.P_CENTERS.j_erosion:mult(self)" payload = "G.P_CENTERS.j_bootstraps:mult(self)"
match_indent = true match_indent = true

View file

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

View file

@ -1,4 +1,4 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
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|{artist?: string, 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?} ---@param tbl SMODS.Back|{is_alt: (fun(self: self): boolean), alt_apply?: fun(self: SMODS.Back|table, back: Back|table), alt_calculate?: fun(self: SMODS.Back|table, back: Back|table, context: CalcContext|table): table?, boolean?}
return function(tbl) return function(tbl)
local key = tbl.key local key = tbl.key
local apply = tbl.apply local apply = tbl.apply
@ -22,34 +22,16 @@ local back = (function()
return (calculate and G.GAME.selected_sleeve ~= "sleeve_Roland_" .. key) and calculate(self, ...) or nil return (calculate and G.GAME.selected_sleeve ~= "sleeve_Roland_" .. key) and calculate(self, ...) or nil
end end
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
tbl.pos = {x = x, y = 0} tbl.pos = {x = x, y = 0}
tbl.is_alt = f.fals
tbl.atlas = "back" tbl.atlas = "back"
-- 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
local b = SMODS.Back(tbl)
x = x + 1 x = x + 1
local back = q(SMODS.Back(tbl))
q(function() local _ = CardSleeves and CardSleeves.Sleeve {
Bakery_API.credit(b)
end)
if not sleeve then
return b
end
local s = sleeve {
key = key, key = key,
pos = tbl.pos, pos = tbl.pos,
atlas = "sleeve", atlas = "sleeve",
artist = tbl.artist,
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 {}
@ -67,11 +49,7 @@ local back = (function()
end, end,
} }
q(function() return back
Bakery_API.credit(s)
end)
return b
end end
end)() end)()
@ -82,7 +60,7 @@ SMODS.Atlas {
py = 95, py = 95,
} }
local _ = _G["CardSleeves"] and SMODS.Atlas { local _ = CardSleeves and SMODS.Atlas {
key = "sleeve", key = "sleeve",
path = "sleeve.png", path = "sleeve.png",
px = 73, px = 73,
@ -92,11 +70,10 @@ local _ = _G["CardSleeves"] and SMODS.Atlas {
back { back {
key = "blossom", key = "blossom",
pronouns = "any_all", pronouns = "any_all",
artist = "bakersdozenbagels",
config = {extra = {alt_times = 4, times = 2}}, config = {extra = {alt_times = 4, times = 2}},
attributes = {"boss_blind"}, attributes = {"boss_blind"},
loc_vars = function(self, _, _) loc_vars = function(self, _, _)
return {vars = {self.config.extra.times, self.config.extra.alt_times}} return {vars = {self.config.extra.alt_times}}
end, end,
apply = function(_, _) apply = function(_, _)
G.GAME.modifiers.Roland_blossom_deck = true G.GAME.modifiers.Roland_blossom_deck = true
@ -116,12 +93,13 @@ back {
G.GAME.round_resets.ante * self.config.extra.alt_times or G.GAME.round_resets.ante * self.config.extra.alt_times or
self.config.extra.times self.config.extra.times
f(count):each(function() f(count):each(function(i)
G.GAME.blind:disable() local _ = i == 1 and G.GAME.blind:disable()
q { q {
delay = 0.4, delay = 0.4,
func = function() func = function()
local _ = i ~= 1 and G.GAME.blind:disable()
SMODS.calculate_effect({message = localize "ph_boss_disabled"}, card) SMODS.calculate_effect({message = localize "ph_boss_disabled"}, card)
G.GAME.blind:wiggle() G.GAME.blind:wiggle()
play_sound "timpani" play_sound "timpani"
@ -134,14 +112,13 @@ back {
back { back {
key = "swapper", key = "swapper",
pronouns = "he_him", pronouns = "he_him",
artist = "bakersdozenbagels",
attributes = {"spectral", "tarot"}, attributes = {"spectral", "tarot"},
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 = f.noop,
} }
local swapper = {Spectral = "Tarot", Tarot = "Spectral"} local swapper = {Spectral = "Tarot", Tarot = "Spectral"}

View file

@ -1,16 +1,16 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
local blind = (function() local blind = (function()
local y = 0 local y = 0
---@param tbl SMODS.Blind ---@param tbl SMODS.Blind|{idea?: string}
---@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 ret return q(SMODS.Blind(tbl))
end end
end)() end)()
@ -23,13 +23,9 @@ SMODS.Atlas {
atlas_table = "ANIMATION_ATLAS", atlas_table = "ANIMATION_ATLAS",
} }
SMODS.Sound { SMODS.Sound {key = "kick", path = "kick.ogg"}
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(SMODS.has_no_rank, false):each(function(v)
@ -56,7 +52,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 not not (e and next(e)) return e and next(e)
end end
local function set_freeze(state) local function set_freeze(state)
@ -178,13 +174,7 @@ 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 { q {delay = 0.8, trigger = "after", func = G.FUNCS.draw_from_discard_to_deck}
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.
@ -238,6 +228,7 @@ 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",
@ -248,16 +239,16 @@ blind {
disable = function(self) disable = function(self)
self:defeat() self:defeat()
end, end,
calculate = function(self, b) calculate = function(self, b, context)
return not b.disabled and self.cards() return not b.disabled and
context.hand_drawn and
self.cards()
:where("Roland_blizzard", false) :where("Roland_blizzard", false)
:where("facing", "front") :where("facing", "front")
:each(set_freeze(true)) or nil :each(set_freeze(true)) or nil
end, end,
cards = function() cards = function()
return f(G):where(function(v) return f(G):where(getmetatable, CardArea):flatmap("cards", ipairs)
return type(v) == "table" and type(v.cards) == "table"
end):flatmap("cards", ipairs)
end, end,
} }
@ -343,8 +334,32 @@ end
function SMODS.current_mod:calculate(context) function SMODS.current_mod:calculate(context)
local _ = type(G.calccontext) == "function" and G.calccontext(context) local _ = type(G.calccontext) == "function" and G.calccontext(context)
local _ = type(G.calckeys) == "function" and G.calckeys(f(context):keys())
local _ = type(G.calc) == "function" and G.calc(f(context):keys():string()) if type(G.calc) == "function" then
G.calc = {G.calc}
end
if type(G.calc) == "table" then
local str
f(context):keys():map(f.index_into(G.calc)):where(type, "function"):each(function(v)
str = str or f(context):keys():string()
v(str)
end)
local _ = not str and type(G.calc[1]) == "function" and G.calc[1](f(context):keys():string())
end
local _ = context.before and
f {"v_Roland_ceres", "v_Roland_neptune"}
: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, nil, v.config.extra.amount)
end)
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 local _ = context.end_of_round and
@ -466,15 +481,31 @@ 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
local fail = G.localization.descriptions.Blind.bl_Roland_venerable_visage.fail f(G.playing_cards):each(function(v, k)
local speed = 0.1 return v.area ~= G.hand and draw_card(v.area, G.hand, k * 100 / #G.playing_cards, "up", true, v)
end)
SMODS.draw_cards(#G.deck.cards) local fail = G.localization.descriptions.Blind.bl_Roland_venerable_visage.fail
play_sound("gong", 0.6)
attention_text { attention_text {
text = pseudorandom_element(fail, pseudoseed "RolandVenerableVisage"), text = pseudorandom_element(fail, pseudoseed "RolandVenerableVisage"),
@ -484,46 +515,50 @@ local venerable_visage = blind {
hold = 2, hold = 2,
} }
delay(1) f {0.15, 0.3, 0.45, 0.6}:each(function(v)
play_sound("gong", v)
end)
f(G.playing_cards):each(function(v) ---@type number, number, table
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 = speed, delay = 6 / len,
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)
delay(1) local _ = vitriol and q {
trigger = "before",
delay = 1.5,
func = function()
love.window.setPosition(x, y)
love.window.setMode(w, h, flags)
love.resize(w, h)
love.window.setFullscreen(is_fullscreen)
end,
}
f(G.P_CARDS):each(function(v) delay(1.5)
q {
delay = speed,
func = function()
G.playing_card = (G.playing_card and G.playing_card + 1) or 1
local card = Card(
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)
delay(1)
end, end,
} }

View file

@ -1,4 +1,4 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
local function bans() local function bans()
return {banned_cards = {}, banned_tags = {}, banned_other = {}} return {banned_cards = {}, banned_tags = {}, banned_other = {}}
@ -42,6 +42,25 @@ end
local pastries, amber, cerulean, crimson, verdant, violet = local pastries, amber, cerulean, crimson, verdant, violet =
bans(), bans(), bans(), bans(), bans(), bans() bans(), bans(), bans(), bans(), bans(), bans()
SMODS.Challenge {
key = "Glass",
rules = {custom = {{id = "Roland_Glass1"}, {id = "Roland_Glass2"}}},
pronouns = "they_them",
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 { SMODS.Challenge {
key = "Go", key = "Go",
rules = {custom = {{id = "Roland_Go"}}}, rules = {custom = {{id = "Roland_Go"}}},
@ -67,7 +86,7 @@ SMODS.Challenge {
local spin_to_win = SMODS.Challenge { local spin_to_win = SMODS.Challenge {
key = "Spin_To_Win", key = "Spin_To_Win",
jokers = f(4):map(f().const {id = "j_Bakery_Spinner", eternal = true}):table(), jokers = f(4):map(f.const {id = "j_joker", eternal = true}):table(),
restrictions = {banned_cards = {{id = "c_Roland_coolheaded"}}}, restrictions = {banned_cards = {{id = "c_Roland_coolheaded"}}},
pronouns = "they_them", pronouns = "they_them",
apply = function(self) apply = function(self)
@ -91,12 +110,10 @@ SMODS.Challenge {
pronouns = "she_them", pronouns = "she_them",
} }
local starting_jokers = f(5):map(f().const {id = "j_joker"}):table()
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 = starting_jokers, jokers = {{id = "j_Roland_amber"}},
restrictions = amber, restrictions = amber,
pronouns = "they_them", pronouns = "they_them",
calculate = function(_, context) calculate = function(_, context)
@ -135,7 +152,7 @@ SMODS.Challenge {
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 = starting_jokers, jokers = {{id = "j_Roland_cerulean"}},
restrictions = cerulean, restrictions = cerulean,
pronouns = "she_her", pronouns = "she_her",
calculate = function(_, context) calculate = function(_, context)
@ -162,7 +179,7 @@ SMODS.Challenge {
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 = starting_jokers, jokers = {{id = "j_Roland_crimson"}},
restrictions = crimson, restrictions = crimson,
pronouns = "she_her", pronouns = "she_her",
calculate = function(_, context) calculate = function(_, context)
@ -171,9 +188,8 @@ SMODS.Challenge {
end) end)
local mod = G.GAME.modifiers local mod = G.GAME.modifiers
mod.Roland_Eternally_Crimson = not context.setting_blind and mod.Roland_Eternally_Crimson or nil mod.Roland_Eternally_Crimson = context.setting_blind and true or mod.Roland_Eternally_Crimson
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
@ -223,7 +239,7 @@ SMODS.Challenge {
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 = starting_jokers, jokers = {{id = "j_Roland_verdant"}},
restrictions = verdant, restrictions = verdant,
pronouns = "she_her", pronouns = "she_her",
calculate = function(_, context) calculate = function(_, context)
@ -246,7 +262,7 @@ SMODS.Challenge {
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 = starting_jokers, jokers = {{id = "j_joker"}, {id = "j_Roland_violet"}},
restrictions = violet, restrictions = violet,
pronouns = "she_they", pronouns = "she_they",
calculate = function(_, context) calculate = function(_, context)
@ -261,12 +277,6 @@ SMODS.Challenge {
} }
q(function() q(function()
if not G.P_CENTERS.j_Bakery_Spinner then
f(spin_to_win.jokers):each(function(v)
v.id = "j_joker"
end)
end
f { f {
{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},
@ -280,3 +290,13 @@ q(function()
f(v[1]):where(v[2]):each(adder(v[3])) f(v[1]):where(v[2]):each(adder(v[3]))
end) end)
end) end)
q(function()
if not G.P_CENTERS.j_Bakery_Spinner then
return true
end
f(spin_to_win.jokers):each(function(v)
v.id = "j_Bakery_Spinner"
end)
end)

View file

@ -1,65 +1,63 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
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 = "charm.png", path = cool_phones and "phorm.png" or "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} ---@param tbl SMODS.Joker|{alerted?: boolean, artist?: string, equip?: fun(self: self, card: Card)}
return function(tbl) return function(tbl)
q(function() q {
local current_mod = SMODS.current_mod blocking = false,
SMODS.current_mod = mod no_delete = true,
func = function()
if not Bakery_API or not Bakery_API.Charm then
return false
end
-- SMODS._save_d_u(charm) local current_mod = SMODS.current_mod
tbl.alerted = true SMODS.current_mod = mod
tbl.unlocked = true
tbl.discovered = true
tbl.pos = {x = x, y = 0}
x = x + 1
tbl.atlas = "charm"
local charm = Bakery_API.Charm(tbl) -- SMODS._save_d_u(charm)
charm:inject() tbl.alerted = true
charm:process_loc_text() tbl.unlocked = true
SMODS.current_mod = current_mod tbl.discovered = true
end, true) tbl.pos = {x = x, y = 0}
tbl.atlas = "charm"
tbl.artist = not cool_phones and "Roland_bakersdozenbagels" or nil
x = x + 1
local orig_equip = tbl.equip
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:process_loc_text()
SMODS.current_mod = current_mod
end,
}
end end
end)() end)()
charm {
key = "wii",
pronouns = "they_them",
attributes = {"skip"},
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 = true
play_sound("whoosh1", 1.33333, 0.8)
play_sound("whoosh1", 0.66666, 0.8)
end)
end,
}
local function can_discard_zero() local function can_discard_zero()
return G.GAME.current_round.discards_left > 0 and return G.GAME.current_round.discards_left > 0 and
not next(G.hand.highlighted) and not next(G.hand.highlighted) and
@ -68,63 +66,30 @@ local function can_discard_zero()
end end
charm { charm {
key = "flexible", key = "hand",
pronouns = "any_all", pronouns = "he_him",
} config = {extra = {hands = -2, hand_size = 5}},
attributes = {"hands", "hand_size", "passive"},
local orig_showman = SMODS.showman
function SMODS.showman(...)
return G.GAME.Bakery_charm == "BakeryCharm_Roland_flexible" or orig_showman(...)
end
charm {
key = "fat",
pronouns = "he_they",
attributes = {"passive"},
config = {extra = {mod = 2}},
loc_vars = function(_, _, card) loc_vars = function(_, _, card)
return {vars = {card.ability.extra.mod}} local extra = card.ability.extra
return {vars = {extra.hand_size, extra.hands}}
end, end,
equip = function(_, card) equip = function(_, card)
SMODS.change_booster_limit(card.ability.extra.mod) 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, end,
unequip = function(card) unequip = function(_, card)
SMODS.change_booster_limit(-card.ability.extra.mod) 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, end,
} }
local orig_init = Card.init
function Card:init(X, Y, W, H, card, center, params, ...)
if G.GAME.Bakery_charm ~= "BakeryCharm_Roland_fat" or center.set ~= "Booster" then
return orig_init(self, X, Y, W, H, card, center, params, ...)
end
local key = f {"mini", "jumbo", "normal"}:fold(center.key, function(a, v)
return a:gsub(v, "mega")
end)
if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
end
key = key:gsub("_?%d+$", "_1")
if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
end
key = key:gsub("_?%d+$", "")
if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
end
self.Roland_no_mega = true
return orig_init(self, X, Y, W, H, card, center, params, ...)
end
charm { charm {
key = "cocacola", key = "cocacola",
pronouns = "he_they", pronouns = "he_they",
@ -194,26 +159,111 @@ G.FUNCS.Roland_cocacola = function()
end end
charm { charm {
key = "hand", key = "fat",
pronouns = "he_him", pronouns = "he_they",
config = {extra = {hands = -2, hand_size = 5}}, attributes = {"passive"},
attributes = {"hands", "hand_size", "passive"}, config = {extra = {mod = 2}},
loc_vars = function(_, _, card) loc_vars = function(_, _, card)
local extra = card.ability.extra return {vars = {card.ability.extra.mod}}
return {vars = {extra.hand_size, extra.hands}}
end, end,
equip = function(_, card) equip = function(_, card)
local extra = card.ability.extra SMODS.change_booster_limit(card.ability.extra.mod)
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, end,
unequip = function(_, card) unequip = function(_, card)
local extra = card.ability.extra SMODS.change_booster_limit(-card.ability.extra.mod)
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, end,
} }
local orig_init = Card.init
function Card:init(X, Y, W, H, card, center, params, ...)
if G.GAME.Bakery_charm ~= "BakeryCharm_Roland_fat" or center.set ~= "Booster" then
return orig_init(self, X, Y, W, H, card, center, params, ...)
end
local key = f {"mini", "jumbo", "normal"}:fold(center.key, function(a, v)
return a:gsub(v, "mega")
end)
if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
end
key = key:gsub("_?%d+$", "_1")
if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
end
key = key:gsub("_?%d+$", "")
if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
end
self.Roland_no_mega = true
return orig_init(self, X, Y, W, H, card, center, params, ...)
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
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()
card.ability.extra.active = false
end)
end)
end)
end,
}
local orig_apply_to_run = Tag.apply_to_run
function Tag:apply_to_run(...)
if G.GAME.Bakery_charm == "BakeryCharm_Roland_wii" and
G.Bakery_charm_area.cards[1].ability.extra.active then
return
end
return orig_apply_to_run(self, ...)
end

View file

@ -1,4 +1,4 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Shader { SMODS.Shader {
key = "frozen", key = "frozen",
@ -13,13 +13,13 @@ end
frozen_sound "_click" frozen_sound "_click"
local frozen_blocklist = {CardSleeve = true} 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,
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,19 +46,19 @@ 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):where(function(v, k) return type(x) == "table" and f(x):map(copy):table() or x
return k ~= "nine_tally" or v ~= 0
end):table() or x
end end
if frozen_blocklist[(card.ability or {}).name] then if frozen_blocklist[(card.ability or {}).name] then
return card.ability return card.ability
end end
(card.ability or {}).Roland_crimson = not not (card.ability or {}).Roland_crimson
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.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 and {probability = SMODS.get_probability_vars(card, 1, 1)} card.Roland_frozen = card.Roland_frozen or {probability = SMODS.get_probability_vars(card, 1, 1)}
local ability, ret = card.ability, card.Roland_frozen_ability local ability, ret = card.ability, card.Roland_frozen_ability
ability.extra = ability.extra == nil and {} or ability.extra
if type(ability) ~= "table" then if type(ability) ~= "table" then
return ret return ret
@ -81,6 +81,107 @@ 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",
@ -161,7 +262,7 @@ function Card:calculate_joker(context, ...)
local key = "Roland_frozen_" .. v local key = "Roland_frozen_" .. v
ability[key] = ability[key] or ret[1][v] ability[key] = ability[key] or ret[1][v]
return ability[key] return ability[key]
end):where(f().id):table() end):where(f.id):table()
end end
local orig_calculate_dollar_bonus = Card.calculate_dollar_bonus local orig_calculate_dollar_bonus = Card.calculate_dollar_bonus
@ -186,7 +287,7 @@ end
local orig_get_probability_vars = SMODS.get_probability_vars local orig_get_probability_vars = SMODS.get_probability_vars
function G.P_CENTERS.j_erosion:mult(card) function G.P_CENTERS.j_bootstraps:mult(card)
local ability = card.Roland_frozen_ability local ability = card.Roland_frozen_ability
local ret = math.floor((G.GAME.dollars + (G.GAME.dollar_buffer or 0)) / card.ability.extra.dollars) local ret = math.floor((G.GAME.dollars + (G.GAME.dollar_buffer or 0)) / card.ability.extra.dollars)
@ -194,8 +295,8 @@ function G.P_CENTERS.j_erosion:mult(card)
return ret return ret
end end
ability.Roland_erosion_mult = ability.Roland_erosion_mult or ret ability.Roland_bootstraps_mult = ability.Roland_bootstraps_mult or ret
return ability.Roland_erosion_mult return ability.Roland_bootstraps_mult
end end
function SMODS.get_probability_vars(trigger_obj, ...) function SMODS.get_probability_vars(trigger_obj, ...)
@ -203,120 +304,24 @@ function SMODS.get_probability_vars(trigger_obj, ...)
return trigger_obj and (trigger_obj.Roland_frozen or {}).probability or numerator, denominator return trigger_obj and (trigger_obj.Roland_frozen or {}).probability or numerator, denominator
end end
q(function() q {
local estate = G.P_CENTERS.j_Bakery_Estate blocking = false,
no_delete = true,
func = function()
local orig_flip_double_sided = (Bakery_API or {}).flip_double_sided
if not estate then if not orig_flip_double_sided then
return return false
end
---@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 end
card.Roland_frozen.estate = card.Roland_frozen.estate and function Bakery_API.flip_double_sided(card, ...)
card.Roland_frozen.estate or if not card.edition or not card.edition.Roland_frozen then
(f(card.area.cards):swap():any(function(_, k) return orig_flip_double_sided(card, ...)
return k == card end
end) or 1)
return card.Roland_frozen.estate
end
local orig_calculate = estate.calculate
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
if not context.joker_main then local _ = G.P_CENTERS.j_Bakery_Estate and hook_estate()
return local _ = G.P_CENTERS.c_Bakery_Scribe and hook_scribe()
end local _ = G.P_CENTERS.j_Bakery_Proxy and hook_proxy()
end,
local joker_count = estate_pos(card) }
local extra = card.ability.extra or {}
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 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
---@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) ---@type Card?
end
local orig_calculate = proxy.calculate
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, ...)
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 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)
q(function()
local orig_flip_double_sided = (Bakery_API or {}).flip_double_sided
if not orig_flip_double_sided then
return
end
function Bakery_API.flip_double_sided(card, ...)
if not card.edition or not card.edition.Roland_frozen then
return orig_flip_double_sided(card, ...)
end
end
end)

View file

@ -1,38 +1,58 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
local atlas = SMODS.Atlas {
key = "joker",
path = "joker.png",
px = 71,
py = 95,
}
local negative = {key = "e_negative_consumable", set = "Edition", config = {extra = 1}} local negative = {key = "e_negative_consumable", set = "Edition", config = {extra = 1}}
local joker = (function() local joker = (function()
atlas:inject()
local z = 0 local z = 0
local row
---@return {x: number, y: number} ---@return {x: number, y: number}
local function inc() local function inc()
local ret = {x = z % 6, y = math.floor(z / 6)} row = row or atlas.image_data:getWidth() / atlas.px / G.SETTINGS.GRAPHICS.texture_scaling
local ret = {x = z % row, y = math.floor(z / row)}
z = z + 1 z = z + 1
return ret return ret
end end
---@param tbl SMODS.Joker|{artist?: string, Bakery_can_use: (fun(self: self, card: Card): boolean?), Bakery_use_button_text: (fun(self: self, card: Card): string|table|nil), Bakery_use_joker: fun(self: self, card: Card), sinis?: boolean|{x: number, y: number}, soul_pos?: boolean|{x: number, y: number}, attributes?: Attributes[]} ---@param tbl SMODS.Joker|{artist?: string, idea?: string, Bakery_can_use: (fun(self: self, card: Card): boolean?), Bakery_use_button_text: (fun(self: self, card: Card): string|table|nil), Bakery_use_joker: fun(self: self, card: Card), attributes?: Attributes[]}
return function(tbl) return function(tbl)
tbl.pos = inc() tbl.pos = inc()
tbl.atlas = "joker" tbl.atlas = "joker"
tbl.idea = tbl.idea and "Roland_" .. tbl.idea or nil
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
tbl.soul_pos = tbl.soul_pos and inc() or nil
tbl.sinis = tbl.sinis and inc() or nil
if ((tbl.config or {}).extra or {}).flipped ~= nil then if ((tbl.config or {}).extra or {}).flipped ~= nil then
tbl.config.extra.front_pos = tbl.pos tbl.config.extra.front_pos = tbl.pos
tbl.config.extra.back_pos = inc() tbl.config.extra.back_pos = inc()
end end
local joker = SMODS.Joker(tbl) return q(SMODS.Joker(tbl))
q(function()
Bakery_API.credit(joker)
end)
end end
end)() end)()
---@generic T
---@param tbl T[]
---@return fun(t: T[], i: integer): integer, T
---@return T[]
local function ipairs_reversed(tbl)
return function(t, i)
local k = i and i - 1 or #t
local v = t[k]
if v ~= nil then
return k, v
end
end, tbl
end
---@param card Card ---@param card Card
local function is_frozen(card) local function is_frozen(card)
return card.edition and card.edition.key == "e_Roland_frozen" return card.edition and card.edition.key == "e_Roland_frozen"
@ -84,21 +104,20 @@ local function ranksuitnq(value)
end end
end end
SMODS.Atlas {
key = "joker",
path = "joker.png",
px = 71,
py = 95,
}
SMODS.Sound { SMODS.Sound {
key = "nilly", key = "nilly",
path = "nilly.ogg", path = "nilly.ogg",
} }
SMODS.Sound {
key = "excalibur",
path = "excalibur.ogg",
}
joker { joker {
key = "msjoker", key = "msjoker",
pronouns = "she_her", pronouns = "she_her",
artist = "ghostlyfield",
cost = 1, cost = 1,
rarity = 1, rarity = 1,
eternal_compat = true, eternal_compat = true,
@ -118,6 +137,7 @@ joker {
joker { joker {
key = "jokersr", key = "jokersr",
pronouns = "he_him", pronouns = "he_him",
artist = "ghostlyfield",
config = {extra = {xmult = 1.25}}, config = {extra = {xmult = 1.25}},
attributes = {"xmult"}, attributes = {"xmult"},
cost = 2, cost = 2,
@ -137,7 +157,8 @@ joker {
joker { joker {
key = "mrsbones", key = "mrsbones",
pronouns = "she_her", pronouns = "she_her",
artist = "char", artist = "ghostlyfield",
idea = "redstoad",
config = {extra = {xmult = 4, requirement = 4}}, config = {extra = {xmult = 4, requirement = 4}},
attributes = {"xmult"}, attributes = {"xmult"},
cost = G.P_CENTERS.j_mr_bones.cost - 1, cost = G.P_CENTERS.j_mr_bones.cost - 1,
@ -158,7 +179,7 @@ joker {
G.GAME.chips / card.ability.extra.requirement < G.GAME.blind.chips then G.GAME.chips / card.ability.extra.requirement < G.GAME.blind.chips then
card.getting_sliced = true card.getting_sliced = true
local message = localize {type = "variable", key = "b_Roland_bye"} local message = localize {type = "variable", key = "b_Roland_bye"}
SMODS.calculate_effect({message = message, colour = G.C.RED, message_card = card}, card) SMODS.calculate_effect({message = message, colour = G.C.RED, message_card = card, repetitions = 0}, card)
q(function() q(function()
G.hand_text_area.blind_chips:juice_up() G.hand_text_area.blind_chips:juice_up()
@ -174,6 +195,7 @@ joker {
key = "sunny", key = "sunny",
pronouns = "it_its", pronouns = "it_its",
artist = "char", artist = "char",
idea = "redstoad",
attributes = {"food", "on_sell"}, attributes = {"food", "on_sell"},
cost = 2, cost = 2,
rarity = 1, rarity = 1,
@ -194,9 +216,9 @@ joker {
SMODS.calculate_context {drawing_cards = true, draw = {G.deck.cards}} SMODS.calculate_context {drawing_cards = true, draw = {G.deck.cards}}
SMODS.calculate_context { SMODS.calculate_context {
hand_drawn = facing_blind and {G.deck.cards[1]} and true,
other_drawn = not facing_blind and {G.deck.cards[1]} and true,
first_hand_drawn = not current_round.any_hand_drawn and facing_blind or nil, first_hand_drawn = not current_round.any_hand_drawn and facing_blind or nil,
hand_drawn = facing_blind and {G.deck.cards[1]} --[[@as true]],
other_drawn = not facing_blind and {G.deck.cards[1]},
} }
if type(facing_blind) == "table" then if type(facing_blind) == "table" then
@ -208,6 +230,7 @@ joker {
joker { joker {
key = "hardboiled", key = "hardboiled",
pronouns = "it_its", pronouns = "it_its",
artist = "ghostlyfield",
attributes = {"food", "editions", "modify_card", "on_sell"}, attributes = {"food", "editions", "modify_card", "on_sell"},
cost = 5, cost = 5,
rarity = 2, rarity = 2,
@ -232,6 +255,7 @@ joker {
joker { joker {
key = "basket", key = "basket",
pronouns = "they_them", pronouns = "they_them",
artist = "ghostlyfield",
attributes = {"food", "generation", "on_sell"}, attributes = {"food", "generation", "on_sell"},
cost = 8, cost = 8,
rarity = 3, rarity = 3,
@ -243,7 +267,7 @@ joker {
end, end,
calculate = function(_, _, context) calculate = function(_, _, context)
return (context.selling_self or context.forcetrigger) and f(G.consumeables.cards):each(function(v) return (context.selling_self or context.forcetrigger) and f(G.consumeables.cards):each(function(v)
q { return v.ability.consumeable and q {
delay = 1, delay = 1,
func = function() func = function()
play_sound "timpani" play_sound "timpani"
@ -376,6 +400,7 @@ joker {
joker { joker {
key = "cold", key = "cold",
pronouns = "he_him", pronouns = "he_him",
artist = "ghostlyfield",
config = {extra = {xmult = 2}}, config = {extra = {xmult = 2}},
attributes = {"xmult"}, attributes = {"xmult"},
cost = 4, cost = 4,
@ -402,7 +427,7 @@ joker {
joker { joker {
key = "snowsquall", key = "snowsquall",
pronouns = "he_they", pronouns = "he_they",
config = {extra = {mult_gain = 1, mult = 0}}, config = {extra = {mult_gain = 3, mult = 0}},
attributes = {"mult", "scaling", "hand_type"}, attributes = {"mult", "scaling", "hand_type"},
cost = 6, cost = 6,
rarity = 2, rarity = 2,
@ -438,7 +463,7 @@ joker {
pronouns = "they_them", pronouns = "they_them",
cost = 10, cost = 10,
rarity = 3, rarity = 3,
config = {extra = {frozen = 1}}, config = {extra = {frozen = 1, non_frozen = 0, max_stack = 6, color = "DARK_EDITION"}},
attributes = {"retrigger", "editions"}, attributes = {"retrigger", "editions"},
eternal_compat = true, eternal_compat = true,
blueprint_compat = true, blueprint_compat = true,
@ -449,18 +474,27 @@ joker {
calculate = function(_, card, context) calculate = function(_, card, context)
local extra = card.ability.extra local extra = card.ability.extra
if extra.max_stack and
context.blueprint_copiers_stack and
extra.max_stack < #context.blueprint_copiers_stack then
return
end
if context.repetition and context.other_card then if context.repetition and context.other_card then
local repetitions = is_frozen(context.other_card) and extra.frozen or extra.non_frozen local repetitions = is_frozen(context.other_card) and extra.frozen or extra.non_frozen
return repetitions ~= 0 and {card = card, repetitions = repetitions} or nil
if repetitions and repetitions > 0 then
return {card = card, colour = G.C[extra.color], repetitions = repetitions}
end
end end
return SMODS.merge_effects( return SMODS.merge_effects(
f(G.jokers.cards):where(is_frozen):map(function(v) f(G.jokers.cards):where(is_frozen):where(function(v)
return SMODS.blueprint_effect(card, v, context) or true return v.config.center.key ~= card.config.center.key
end):where(function(v)
return type(v) == "table"
end):map(function(v) end):map(function(v)
v.colour = G.C.DARK_EDITION return SMODS.blueprint_effect(card, v, context)
end):where(type, "table"):map(function(v)
v.colour = G.C[extra.color]
return v return v
end):values():table() end):values():table()
) )
@ -471,6 +505,7 @@ joker {
key = "sapling", key = "sapling",
pronouns = "they_them", pronouns = "they_them",
artist = "char", artist = "char",
idea = "redstoad",
attributes = {"mult", "suit"}, attributes = {"mult", "suit"},
cost = 4, cost = 4,
rarity = 1, rarity = 1,
@ -541,9 +576,241 @@ joker {
end, end,
} }
joker {
key = "amber",
pronouns = "they_them",
config = {extra = {xmult = 3}},
pixel_size = {w = 68, h = 68},
attributes = {"xmult"},
cost = 6,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.xmult}}
end,
calculate = function(_, card, context)
if context.joker_main or context.forcetrigger then
return {card = card, xmult = card.ability.extra.xmult}
end
card.Roland_amber_waiting = card.Roland_amber_waiting and not context.after
local no = card.debuff or
context.blueprint or
card.Roland_amber_waiting or
not context.press_play
if no and not context.forcetrigger then
return
end
card.Roland_amber_waiting = true
local cards = card.area.cards
local keys = f(cards):where(f.nq(card)):keys():table()
if not next(keys) then
return
end
local key = pseudorandom_element(keys, pseudoseed "Roland_amber") or card.rank
local next = card.rank < key and 1 or -1
for i = card.rank, key - next, next do
cards[i], cards[i + next] = cards[i + next], cards[i]
end
end,
}
joker {
key = "cerulean",
pronouns = "she_her",
config = {extra = {xmult = 3}},
pixel_size = {w = 68, h = 68},
attributes = {"xmult"},
cost = 6,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.xmult}}
end,
remove_from_deck = function(self)
self:cerulean(true)
end,
calculate = function(_, card, context)
return (context.joker_main or context.forcetrigger) and
{card = card, xmult = card.ability.extra.xmult} or nil
end,
update = function(self, card)
local _ = card.area == G.jokers and self:cerulean(false)
end,
cerulean = function(_, value)
local _ = G.jokers and f(G.jokers.cards):each(function(v)
f {"click", "drag", "focus", "hover"}:map(f.index_into(v.states)):each(function(s)
s.can = value or v.config.center.key == "j_Roland_cerulean"
end)
end)
end,
}
joker {
key = "crimson",
pronouns = "she_her",
config = {extra = {xmult = 3}},
pixel_size = {w = 68, h = 68},
attributes = {"xmult"},
cost = 6,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.xmult}}
end,
calculate = function(self, card, context)
return (self.crimson() or context.joker_main or context.forcetrigger) and
{card = card, xmult = card.ability.extra.xmult} or nil
end,
crimson = function()
local _ = G.jokers and f(G.jokers.cards, ipairs_reversed):where(is_frozen, false):each(function(v)
local right = G.jokers.cards[v.rank + 1]
local debuffed_by_crimson = right and
not right.debuff and
v.config.center.key ~= "j_turtle_bean" and
right.config.center.key == "j_Roland_crimson"
if debuffed_by_crimson and v.ability.Roland_crimson == nil then
local debuff = not not v.debuff
v:set_debuff(true)
v.ability.Roland_crimson = debuff
elseif not debuffed_by_crimson and v.ability.Roland_crimson ~= nil then
local debuff = v.ability.Roland_crimson
v.ability.Roland_crimson = nil
v:set_debuff(debuff)
end
end)
end,
}
local orig_stop_drag = Card.stop_drag
function Card:stop_drag(...)
local _ = self.area == G.jokers and G.P_CENTERS.j_Roland_crimson.crimson()
return orig_stop_drag(self, ...)
end
joker {
key = "verdant",
pronouns = "she_her",
config = {extra = {debuffs = 2, xmult = 3}},
pixel_size = {w = 68, h = 68},
attributes = {"xmult"},
cost = 6,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.xmult, extra.debuffs}}
end,
calculate = function(self, card, context)
local extra = card.ability.extra
self:verdant(context.scoring_hand, extra.debuffs)
return (context.joker_main or context.forcetrigger) and {card = card, xmult = extra.xmult} or nil
end,
update = function()
local _ = G.STATE == G.STATES.SELECTING_HAND and G.hand and G.hand:parse_highlighted()
end,
verdant = function(_, cards, amount)
return cards and next(cards) and f(G.playing_cards):each(function(v)
table.sort(cards, function(a, b)
return a.rank < b.rank
end)
local should_be_verdant = f(amount):map(f.index_into(cards)):any(f.eq(v))
local has_verdant = v.ability.Roland_verdant ~= nil
if should_be_verdant and not has_verdant then
v.ability.Roland_verdant = not not v.debuff
v:set_debuff(true)
elseif not should_be_verdant and has_verdant then
v:set_debuff(v.ability.Roland_verdant)
v.ability.Roland_verdant = nil
end
end) or nil
end,
}
local orig_unhighlight_all = CardArea.unhighlight_all
function CardArea:unhighlight_all(...)
G.P_CENTERS.j_Roland_verdant:verdant(self.cards)
return orig_unhighlight_all(self, ...)
end
joker {
key = "violet",
pronouns = "she_they",
idea = "hamester",
config = {extra = {before = 0.1, xmult = 6}},
pixel_size = {w = 68, h = 68},
attributes = {"xmult"},
cost = 6,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.xmult, extra.before}}
end,
calculate = function(_, card, context)
local extra = card.ability.extra
return (context.joker_main or context.forcetrigger) and
{card = card, xmult = extra.xmult} or
(context.initial_scoring_step and {card = card, xmult = extra.before} or nil)
end,
}
joker {
key = "venerable",
pronouns = "any_all",
config = {extra = {xdiscard = 3}},
pixel_size = {w = 68, h = 68},
attributes = {"discard", "passive"},
cost = 6,
rarity = 3,
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.xdiscard}}
end,
add_to_deck = function(self, card)
self:venerable(math.ceil(G.GAME.round_resets.discards * card.ability.extra.xdiscard))
end,
remove_from_deck = function(self, card)
self:venerable(math.floor(G.GAME.round_resets.discards / card.ability.extra.xdiscard))
end,
venerable = function(_, amount)
local round_resets = G.GAME.round_resets
local discards = round_resets.discards
round_resets.discards = amount
ease_discard(round_resets.discards - discards)
end,
}
joker { joker {
key = "bulldozer", key = "bulldozer",
pronouns = "she_her", pronouns = "she_her",
idea = "redstoad",
attributes = {"xmult"}, attributes = {"xmult"},
cost = 6, cost = 6,
rarity = 2, rarity = 2,
@ -582,7 +849,8 @@ joker {
joker { joker {
key = "phytoestrogens", key = "phytoestrogens",
pronouns = "she_her", pronouns = "she_her",
config = {extra = {xmult = 0.25}}, artist = "ghostlyfield",
config = {extra = {xmult = 0.3}},
attributes = {"mult", "xmult"}, attributes = {"mult", "xmult"},
cost = 8, cost = 8,
rarity = 3, rarity = 3,
@ -605,6 +873,8 @@ joker {
joker { joker {
key = "nilly", key = "nilly",
pronouns = "any_all", pronouns = "any_all",
artist = "ghostlyfield",
idea = "redstoad",
config = {extra = {flipped = false}}, config = {extra = {flipped = false}},
attributes = {"xmult", "bakery_double_sided"}, attributes = {"xmult", "bakery_double_sided"},
cost = 0, cost = 0,
@ -636,40 +906,10 @@ joker {
end, end,
} }
joker {
key = "amber",
pronouns = "they_them",
config = {extra = {xmult = 3}},
pixel_size = {w = 68, h = 68},
attributes = {"xmult"},
cost = 9,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = false,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.xmult}}
end,
calculate = function(_, card, context)
if context.joker_main or context.forcetrigger then
return {card = card, xmult = card.ability.extra.xmult}
end
if card.debuff or context.blueprint or not context.press_play then
return
end
local keys = f(card.area.cards):where(f().nq(card)):keys():table()
local k = pseudorandom_element(keys, pseudoseed "Roland_amber") or card.rank
card.area.cards[k], card.area.cards[card.rank] =
card.area.cards[card.rank], card.area.cards[k]
end,
}
joker { joker {
key = "martingale", key = "martingale",
pronouns = "he_him", pronouns = "he_him",
idea = "redstoad",
config = {extra = {odds = 2}}, config = {extra = {odds = 2}},
attributes = {"xmult", "chance"}, attributes = {"xmult", "chance"},
cost = 8, cost = 8,
@ -696,10 +936,14 @@ joker {
local extra, numerator, xmult = card.ability.extra, 1, 1 local extra, numerator, xmult = card.ability.extra, 1, 1
if G.GAME.blind.name == "bl_mp_nemesis" then if G.GAME.blind.name == "bl_mp_nemesis" then
return {card = card, xmult = extra.odds} local n, d = SMODS.get_probability_vars(card, 1, extra.odds, self.key)
return n < d and {card = card, xmult = extra.odds} or nil
end end
for _ = 1, 256 do -- 2^38 is the first iteration displayed as scientific notation.
local max, overflown = 38, false
for i = 1, max do
local key = "RolandMartingale" .. tostring(G.GAME.modifiers.Roland_martingale_seed or "") local key = "RolandMartingale" .. tostring(G.GAME.modifiers.Roland_martingale_seed or "")
if SMODS.pseudorandom_probability(card, self.key, 1, extra.odds, key) then if SMODS.pseudorandom_probability(card, self.key, 1, extra.odds, key) then
@ -711,9 +955,18 @@ joker {
xmult = xmult * extra.odds xmult = xmult * extra.odds
local message = number_format(numerator) .. "/" .. number_format(xmult) local message = number_format(numerator) .. "/" .. number_format(xmult)
SMODS.calculate_effect({card = card, repetitions = 1, message = message, message_card = card}, card) SMODS.calculate_effect({card = card, repetitions = 1, message = message, message_card = card}, card)
overflown = overflown or i == max
end end
SMODS.calculate_effect({card = card, xmult = xmult}, card) if overflown then
SMODS.calculate_effect({card = card, score = 1 / 0}, card)
q(function()
card:shatter()
end)
else
SMODS.calculate_effect({card = card, xmult = xmult}, card)
end
end, end,
} }
@ -732,7 +985,6 @@ joker {
local extra = card.ability.extra local extra = card.ability.extra
local col = {} local col = {}
---@type { [number]: string|table|nil }|{colours: {[number]: {[1]: number, [2]: number, [3]: number, [4]: number}}}
local vars = f(self.Roland_idle_capacity):flatmap(function(v) local vars = f(self.Roland_idle_capacity):flatmap(function(v)
local l = localize_card(extra.cards[v], "b_Roland_unassigned").vars local l = localize_card(extra.cards[v], "b_Roland_unassigned").vars
@ -745,7 +997,7 @@ joker {
end):values():table() end):values():table()
table.insert(vars, extra.xmult) table.insert(vars, extra.xmult)
vars.colours = col vars["colours"] = col
return {vars = vars} return {vars = vars}
end, end,
calculate = function(_, card, context) calculate = function(_, card, context)
@ -756,7 +1008,7 @@ joker {
return f(context.scoring_hand):any(function(x) return f(context.scoring_hand):any(function(x)
return x:is_suit(v.suit) and x.base.value == v.value return x:is_suit(v.suit) and x.base.value == v.value
end) or {} end) or {}
end):any(f().eq(context.other_card)) and {xmult = card.ability.extra.xmult} or nil end):any(f.eq(context.other_card)) and {xmult = card.ability.extra.xmult} or nil
end, end,
Bakery_can_use = function(self, card) Bakery_can_use = function(self, card)
local key = self:Roland_idle_status(card) local key = self:Roland_idle_status(card)
@ -849,16 +1101,21 @@ end
joker { joker {
key = "artemis", key = "artemis",
pronouns = "any_all", pronouns = "any_all",
artist = "hamester",
idea = "redstoad",
cost = 6, cost = 6,
rarity = 2, rarity = 2,
config = {extra = { config = {extra = {
dollars = 2, dollars = 3,
increase = 3, increase = 4,
progress = 0, progress = 0,
flipped = false, flipped = false,
sequence = {"c_earth", "c_mars", "c_earth"}, sequence = {"c_earth", "c_mars", "c_earth"},
}}, }},
attributes = {"economy", "scaling", "planet", "space", "bakery_double_sided"}, attributes = {"economy", "scaling", "planet", "space", "bakery_double_sided"},
eternal_compat = true,
blueprint_compat = false,
perishable_compat = false,
calc_dollar_bonus = function(_, card) calc_dollar_bonus = function(_, card)
return card.ability.extra.dollars return card.ability.extra.dollars
end, end,
@ -878,9 +1135,10 @@ joker {
calculate = function(_, card, context) calculate = function(_, card, context)
local extra = card.ability.extra local extra = card.ability.extra
if context.blueprint or if not context.forcetrigger and
not context.using_consumeable or (context.blueprint or
context.consumeable.config.center.key ~= extra.sequence[extra.progress + 1] then not context.using_consumeable or
context.consumeable.config.center.key ~= extra.sequence[extra.progress + 1]) then
return return
end end
@ -906,6 +1164,74 @@ joker {
end, end,
} }
joker {
key = "excalibur",
pronouns = "he_him",
idea = "redstoad",
cost = 8,
rarity = 3,
config = {extra = {
scored = 0,
xmult = 1.5,
required = 15,
flipped = false,
}},
attributes = {"xmult", "enhancements", "bakery_double_sided"},
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, info_queue, card)
local extra = card.ability.extra
local _ = card.fake_card or table.insert(info_queue, G.P_CENTERS.m_stone)
return {vars = {extra.required, extra.scored, extra.xmult}}
end,
generate_ui = function(self, info_queue, card, ...)
Bakery_API.werewolf_ui "j_Roland_excalibur_Back" (self, info_queue, card, ...)
if not card or not card.ability.extra.flipped then
return
end
f(info_queue):keys():each(function(v)
info_queue[v] = nil
end)
table.insert(info_queue, G.P_CENTERS.m_stone)
end,
calculate = function(_, card, context)
local extra = card.ability.extra
if not context.forcetrigger and
(context.end_of_round or
not context.individual or
(context.cardarea ~= G.play and context.cardarea ~= G.hand) or
not SMODS.has_enhancement(context.other_card, "m_stone")) then
return
end
local delta = (context.cardarea == G.play and not card.blueprint) and 1 or 0
extra.scored = extra.scored + delta
if not extra.getting_flipped and
not extra.flipped and
extra.scored == extra.required then
extra.getting_flipped = true
Bakery_API.flip_double_sided(card)
q(function()
play_sound "Roland_excalibur"
end)
end
return extra.scored >= extra.required and {card = card, xmult = card.ability.extra.xmult} or
(delta > 0 and {
message = extra.scored .. "/" .. extra.required,
colour = G.C.JOKER_GREY,
message_card = card,
} or nil)
end,
}
joker { joker {
key = "oops", key = "oops",
pronouns = "she_they", pronouns = "she_they",
@ -932,9 +1258,9 @@ joker {
return {card = card, numerator = extra.probability} return {card = card, numerator = extra.probability}
end end
if context.end_of_round and extra.probability ~= extra.reset then if context.end_of_round and not context.repetition and extra.probability ~= extra.reset then
extra.probability = extra.reset extra.probability = extra.reset
return {message = localize "k_reset", colour = G.C.RED, message_card = card} return {message = localize "k_reset", colour = G.C.RED, message_card = card, repetitions = 0}
end end
if context.after then if context.after then

View file

@ -3,10 +3,101 @@
---@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 I, O
---@param first string|fun(v: I): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first)
error {first}
end
---@generic I, T, O
---@param first string|fun(v: I): T
---@param second string|fun(v: T): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second)
error {first, second}
end
---@generic I, T1, T2, O
---@param first string|fun(v: I): T1
---@param second string|fun(v: T1): T2
---@param third string|fun(v: T2): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third)
error {first, second, third}
end
---@generic I, T1, T2, T3, O
---@param first string|fun(v: I): T1
---@param second string|fun(v: T1): T2
---@param third string|fun(v: T2): T3
---@param fourth string|fun(v: T3): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third, fourth)
error {first, second, third, fourth}
end
---@generic I, T1, T2, T3, T4, O
---@param first string|fun(v: I): T1
---@param second string|fun(v: T1): T2
---@param third string|fun(v: T2): T3
---@param fourth string|fun(v: T3): T4
---@param fifth string|fun(v: T4): O
---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third, fourth, fifth)
error {first, second, third, fourth, fifth}
end
---@generic I, O
---@param all { [1]: (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]: (string|fun(v: I): T), [2]: (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]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
error(all)
end
---@generic I, T1, T2, T3, O
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): T3), [4]: (string|fun(v: T3): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(all)
error(all)
end
---@generic I, T1, T2, T3, T4, O
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): T3), [4]: (string|fun(v: T3): T4), [4]: (string|fun(v: T4): O) }
---@return fun(v: I): O
---@nodiscard
function f.chain(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?
@ -23,7 +114,7 @@ local none
---@return T ---@return T
---@nodiscard ---@nodiscard
local function autofunc(func) local function autofunc(func)
return type(func) == "string" and f.index(func) or func or f.id return type(func) == "string" and f.indices(func) or func or f.id
end end
---@generic K, V ---@generic K, V
@ -34,14 +125,7 @@ end
---@return V ---@return V
---@nodiscard ---@nodiscard
local function autopairs(tbl, fpairs) local function autopairs(tbl, fpairs)
return (fpairs or tbl[#tbl] and ipairs or pairs)(tbl) return (fpairs or (tbl[#tbl] and ipairs or pairs))(tbl)
end
---@param any any
---@return boolean
---@nodiscard
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.
@ -76,6 +160,7 @@ end
---@generic T ---@generic T
---@param value T ---@param value T
---@return fun(T): boolean ---@return fun(T): boolean
---@nodiscard
function f.eq(value) function f.eq(value)
return function(v) return function(v)
return value == v return value == v
@ -85,6 +170,7 @@ end
---@generic T ---@generic T
---@param value T ---@param value T
---@return fun(T): boolean ---@return fun(T): boolean
---@nodiscard
function f.nq(value) function f.nq(value)
return function(v) return function(v)
return value ~= v return value ~= v
@ -109,55 +195,146 @@ 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 x and x[v] return type(x) == "table" and x[v] or x
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)
if type(x) ~= "table" then
return x
end
for i in v:gmatch "[^.]+" do
x = x[i]
end
return x
end
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
f[true and "chain"] = function(...)
local ret
for _, v in ipairs {...} do
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
---@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)
local ret = {next = fnext or f.noop} -- Iterating over `f` is far easier, but we do this for performance sake.
return {
for k, v in pairs(f) do all = f.all,
ret[k] = v any = f.any,
end arg = f.arg,
chain = f.chain,
return ret concat = f.concat,
const = f.const,
count = f.count,
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,
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,
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.
---@generic K, V ---@type FFrom
---@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 == true then if iter == nil 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.index(iter) return f.indices(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
@ -189,7 +366,7 @@ function f.from(iter, fpairs, step)
end end
end) end)
else else
local next, context, k, v = autopairs(iter, fpairs) local next, context, k, v = autopairs(iter, type(fpairs) == "function" and fpairs or nil)
return f.new(function() return f.new(function()
k, v = next(context, k) k, v = next(context, k)
@ -209,7 +386,7 @@ function f:concat(...)
local sum, last = 0, 0 local sum, last = 0, 0
for i = 1, #fs do for i = 1, #fs do
fs[i] = is_f(fs[i]) and fs[i] or f.from(fs[i]) fs[i] = f.isf(fs[i]) and fs[i] or f.from(fs[i])
end end
return f.new(function() return f.new(function()
@ -237,12 +414,30 @@ 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)
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 F|fun(v: V, k: K): U ---@param func fun(v: V, k: K): U
---@return F|{ [K]: U } ---@return F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U } ---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@nodiscard
function f:map(func) function f:map(func)
func = autofunc(func) func = autofunc(func)
@ -257,61 +452,11 @@ end
---@generic K, V, U ---@generic K, V, U
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): { [any]: U } ---@param func 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 }
---@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 } ---@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)
-- local i = 0
local vt, vk, vv, vp
func = autofunc(func)
return f.new(function()
if vk then
vk, vv = vp(vt, vk)
if vk ~= nil then
-- i = i + 1
-- return i, vv
return vk, vv
end
end
while true do
local k, v = self:next()
if k == nil then
return
end
v = func(v, k)
if type(v) ~= "table" then
-- i = i + 1
-- return i, v
return k, v
end
vp, vt, vk = autopairs(v, fpairs)
vk, vv = vp(vt, vk)
if vk ~= nil then
-- i = i + 1
-- return i, vv
return vk, vv
end
end
end)
end
---@generic K, V, U
---@param self F|{ [K]: V }
---@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)
---@return 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)
-- local i = 0 -- local i = 0
local vt, vk, vv, vp local vt, vk, vv, vp
@ -357,7 +502,7 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): boolean ---@param func fun(v: V, k: K): boolean
---@param is? any ---@param is? any
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@nodiscard ---@nodiscard
@ -520,10 +665,10 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): any ---@param func fun(v: V, k: K): any
---@return boolean|V ---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V ---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
function f:any(func) function f:any(func)
func = autofunc(func) func = autofunc(func)
@ -538,10 +683,10 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): any ---@param func fun(v: V, k: K): any
---@return boolean|V ---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V ---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
function f:all(func) function f:all(func)
func = autofunc(func) func = autofunc(func)
@ -556,10 +701,10 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): any ---@param func fun(v: V, k: K): any
---@return integer ---@return integer
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): integer ---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard
function f:count(func) function f:count(func)
local ret = 0 local ret = 0
func = autofunc(func) func = autofunc(func)
@ -622,7 +767,7 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func? fun(v: V, k: K) ---@param func? fun(v: V, k: K): nil
function f:each(func) function f:each(func)
for k, v in self.next do for k, v in self.next do
if func then if func then
@ -633,4 +778,11 @@ end
none = f.new() none = f.new()
return f.from ---@type F|FFrom
local ret = (setmetatable or f.const(f.from))(f, {
__call = function(_, ...)
return f.from(...)
end,
})
return ret

76
src/lib/intellisense.lua Normal file
View file

@ -0,0 +1,76 @@
---@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
--- @type fun(area: CardArea, ...: ...): Card
create_card_for_shop = create_card_for_shop
--- @type boolean|table
G.Bakery_charm_area.cards[1].ability.extra = G.Bakery_charm_area.cards[1].ability.extra
SMODS.Mods.Roland.config = require "config"
---@type userdata|{getWidth: fun(self: self): number}
SMODS.Atlas.image_data = SMODS.Atlas.image_data
--- @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,3 +1,59 @@
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 not G.P_TAGS[v] then
return SMODS.add_card {no_edition = true, key = v}
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 (G.P_CENTERS[it] or {}).config then
return it
end
local match = simplify(it)
local pool = f(G.P_CENTER_POOLS):any(f.chain(f.arg(2), simplify, f.eq(match)))
if type(pool) == "table" and next(pool) then
return pseudorandom_element(pool, pseudoseed "Roland_c").key
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(f.chain(f.arg(2), f.index_into(G.P_CENTERS), "config"))
:where(function(v, k)
return 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)
@ -6,77 +62,83 @@ local function protect(fun)
sendErrorMessage(tostring(ret), "Roland") sendErrorMessage(tostring(ret), "Roland")
end end
return not res or ret ~= false return not res or ret ~= false and not (SMODS.current_mod and ret ~= true)
end end
end end
local function protect_ev(fun) local function protect_ev(fun)
if type(fun) == "table" then if type(fun) == "function" then
fun.func = protect(fun.func) return Event {func = protect(fun)}
fun = getmetatable(fun) == Event and fun or Event(fun) elseif type(fun) ~= "table" then
elseif type(fun) == "function" then error("Expected a function or table, got a " .. type(fun), 3)
fun = Event {func = protect(fun)} elseif type(fun.is) == "function" and fun:is(SMODS.GameObject) then
return Event {
blocking = false,
no_delete = true,
func = protect(function()
if not Bakery_API or not Bakery_API.credit then
return false
end
Bakery_API.credit(fun)
end),
}
else else
error("Expected a function or event, got a " .. type(fun), 3) fun.func = protect(fun.func or fun[1])
end return getmetatable(fun) == Event and fun or Event(fun)
return fun
end
---@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"
if false then
-- This allows for better type inference.
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`, unlike the vanilla ones. --- Note that events added this way implicitly `return true` unless you explicitly `return false`.
--- @param fun (fun():false|nil)|Event The event or a function to run turn into an event. --- For `front`; boolean `true` to add the event to the front of the queue, rather than the end.
--- @param front boolean|nil `true` to add the event to the front of the queue, rather than the end. --- @generic T: fun():boolean?|Event
local function q(fun, front) --- @param fun T|Event|{front?: boolean} The table or function to turn into an event.
G.E_MANAGER:add_event(protect_ev(fun), nil, front) --- @return T fun
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 #G.play.cards > 0 or G.CONTROLLER.locked or 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
(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
return {f, q, u} --- Creates one or more cards.
---@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,45 +1,88 @@
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 f, q = unpack(qol) local f, q = qol[1], qol[2]
f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"} q {
:each(function(v) front = true,
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol) no_delete = true,
end) blocking = false,
func = function()
local contributors = (Bakery_API or {}).contributors
if _G["Balatest"] then if not contributors then
return false
end
-- Special shoutout to all contributors. <3
local credits = {
aster = {
name = "asterSSH",
fg = HEX "f8f8f2ff",
bg = HEX "bd93f9ff",
},
bakersdozenbagels = {
name = "BakersDozenBagels",
fg = HEX "362708ff",
bg = HEX "edd198ff",
},
char = {
name = "char (@irregulester)",
fg = HEX "f8f8f2ff",
bg = HEX "ff79c6ff",
},
ghostlyfield = {
name = "ghostlyfield",
fg = HEX "ffffffff",
bg = HEX "b290e6ff",
},
hamester = {
name = "Hamester",
fg = HEX "ffffffff",
bg = HEX "ffa100ff",
},
redstoad = {
name = "RedsToad",
fg = HEX "ffffffff",
bg = HEX "da4044ff",
},
}
f(credits):each(function(v, k)
contributors["Roland_" .. k] = v
-- G.ARGS.LOC_COLOURS["Bakery_credit_fg_Roland_" .. k] = v.fg
-- G.ARGS.LOC_COLOURS["Bakery_credit_bg_Roland_" .. k] = v.bg
end)
if not SMODS.Mods.DebugPlus or not SMODS.Mods.Roland.config.import_funky then
return
end
_G.f, _G.q, _G.u, _G.c = unpack(qol)
end,
}
f {
"challenge",
"spectral",
"edition",
"tweaks",
"blind",
"charm",
"joker",
"tarot",
"back",
"seal",
"tag",
"voucher",
}:each(function(v)
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
end)
if Balatest then
f {"joker", "blind", "spectral"}:each(function(v) f {"joker", "blind", "spectral"}:each(function(v)
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol) assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
end) end)
end end
q(function()
-- Special shoutout to all contributors. <3
---@type table<string, {name: string, fg: table, bg: table}>
local contributors, mods = Bakery_API.contributors, SMODS.Mods
contributors.Roland_aster = {
name = "asterSSH",
fg = HEX "f8f8f2ff",
bg = HEX "bd93f9ff",
}
contributors.Roland_bakersdozenbagels = {
name = "BakersDozenBagels",
fg = HEX "362708",
bg = HEX "EDD198",
}
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, true)
local function toggle(id) local function toggle(id)
return create_toggle { return create_toggle {
label = localize {type = "variable", key = "b_Roland_" .. id}, label = localize {type = "variable", key = "b_Roland_" .. id},
@ -49,18 +92,18 @@ local function toggle(id)
} }
end end
local animated = SMODS.Mods.Roland.config.animated_icon
SMODS.Atlas { SMODS.Atlas {
px = 256, px = 256,
py = 256, py = 256,
fps = 12,
frames = 12,
key = "modicon", key = "modicon",
path = "icon.png", fps = animated and 12 or nil,
atlas_table = "ANIMATION_ATLAS", frames = animated and 12 or nil,
path = animated and "icon.png" or "unicon.png",
atlas_table = animated and "ANIMATION_ATLAS" or "ASSET_ATLAS",
} }
SMODS.current_mod.qol = qol
function SMODS.current_mod.config_tab() function SMODS.current_mod.config_tab()
return { return {
n = G.UIT.ROOT, n = G.UIT.ROOT,
@ -69,14 +112,19 @@ function SMODS.current_mod.config_tab()
n = G.UIT.C, n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR}, config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR},
nodes = { nodes = {
toggle "animated_icon",
toggle "cool_phones",
toggle "faster_planets", toggle "faster_planets",
toggle "illusion_seal", -- toggle "illusion_seal",
toggle "no_wild_debuff", -- toggle "no_wild_debuff",
_G["Talisman"] and toggle "harsh_ante_scaling", toggle "vitriol",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
toggle "equinox_assist", toggle "equinox_assist",
SMODS.Mods.DebugPlus and toggle "import_funky", SMODS.Mods.DebugPlus and toggle "import_funky",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
Talisman and toggle "harsh_ante_scaling",
}, },
}}, }},
} }
end end
SMODS.current_mod.qol = qol

View file

@ -1,4 +1,4 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Atlas { SMODS.Atlas {
px = 71, px = 71,

View file

@ -1,4 +1,4 @@
local f, q, u = unpack(... or require "lib.shared") local f, q, u = (... or require "lib.shared")[1], (... or require "lib.shared")[2], (... or require "lib.shared")[3]
local spectral = (function() local spectral = (function()
local x = 0 local x = 0
@ -6,19 +6,13 @@ 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.cost = 4
tbl.set = "Spectral"
tbl.atlas = "spectral"
tbl.pos = {x = x, y = 0}
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
local ret = SMODS.Consumable(tbl) tbl.pos = {x = x, y = 0}
tbl.atlas = "spectral"
tbl.set = "Spectral"
tbl.cost = 4
x = x + 1 x = x + 1
return q(SMODS.Consumable(tbl))
q(function()
Bakery_API.credit(ret)
end)
return ret
end end
end)() end)()
@ -75,15 +69,15 @@ spectral {
table.insert(info_queue, G.P_CENTERS.m_stone) table.insert(info_queue, G.P_CENTERS.m_stone)
end, end,
can_use = function() can_use = function()
return u() and next(G.hand.cards) return next(G.hand.cards) and u()
end, end,
use = function(_, card) use = function(_, card)
q { q {
trigger = "after", trigger = "after",
delay = 0.4, delay = 0.4,
func = function() func = function()
play_sound("tarot1")
card:juice_up(0.3, 0.5) card:juice_up(0.3, 0.5)
play_sound("tarot1")
end, end,
} }
@ -95,8 +89,8 @@ spectral {
delay = 0.15, delay = 0.15,
func = function() func = function()
v:flip() v:flip()
play_sound("card1", percent)
v:juice_up(0.3, 0.3) v:juice_up(0.3, 0.3)
play_sound("card1", percent)
return true return true
end, end,
} }
@ -116,8 +110,8 @@ spectral {
delay = 0.15, delay = 0.15,
func = function() func = function()
v:flip() v:flip()
play_sound("tarot2", percent, 0.6)
v:juice_up(0.3, 0.3) v:juice_up(0.3, 0.3)
play_sound("tarot2", percent, 0.6)
return true return true
end, end,
} }
@ -147,7 +141,7 @@ spectral {
end, end,
} }
local void = spectral { spectral {
key = "void", key = "void",
pronouns = "it_its", pronouns = "it_its",
artist = "aster", artist = "aster",
@ -161,7 +155,7 @@ local void = spectral {
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function() can_use = function()
return #G.playing_cards > 1 and not not u() return next(G.playing_cards) and u()
end, end,
use = function(_, card) use = function(_, card)
local function destructible(v) local function destructible(v)
@ -196,16 +190,6 @@ local void = spectral {
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,4 +1,4 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Atlas { SMODS.Atlas {
px = 34, px = 34,
@ -69,12 +69,12 @@ SMODS.Tag {
if not tag.triggered and context.type == "Bakery_play_hand_late" and tag.ability.amount == 0 then if not tag.triggered and context.type == "Bakery_play_hand_late" and tag.ability.amount == 0 then
tag.triggered = true tag.triggered = true
tag:yep("X", G.C.RED, f().tru) tag:yep("X", G.C.RED, f.tru)
end end
if tag.triggered or if tag.triggered or
(tag.ability.amount or self.config.amount) <= 0 or (tag.ability.amount or self.config.amount) <= 0 or
(context.card.edition and context.card.edition.Roland_frozen) or ((context.card or {}).edition or {}).Roland_frozen or
context.type ~= "Bakery_score_card" then context.type ~= "Bakery_score_card" then
return return
end end

View file

@ -1,4 +1,4 @@
local f, q, u = unpack(... or require "lib.shared") local f, q, u = (... or require "lib.shared")[1], (... or require "lib.shared")[2], (... or require "lib.shared")[3]
SMODS.Atlas { SMODS.Atlas {
px = 71, px = 71,
@ -7,7 +7,7 @@ SMODS.Atlas {
path = "tarot.png", path = "tarot.png",
} }
local coolheaded = SMODS.Consumable { q(SMODS.Consumable {
key = "coolheaded", key = "coolheaded",
pos = {x = 0, y = 0}, pos = {x = 0, y = 0},
config = {extra = {amount = 1}}, config = {extra = {amount = 1}},
@ -21,9 +21,16 @@ local coolheaded = SMODS.Consumable {
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function(_, card) can_use = function(_, card)
return u() and f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):count(function(v) if not u() then
return not v.edition return false
end) == card.ability.extra.amount end
local highlighted = Bakery_API.get_highlighted()
return #G.jokers.highlighted + #highlighted == card.ability.extra.amount and
f(G.jokers.highlighted):concat(highlighted):all(function(v)
return not v.edition
end)
end, end,
use = function() use = function()
f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):each(function(v) f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):each(function(v)
@ -35,8 +42,4 @@ local coolheaded = SMODS.Consumable {
} }
end) end)
end, end,
} })
q(function()
Bakery_API.credit(coolheaded)
end)

View file

@ -1,4 +1,4 @@
local f = unpack(... or require "lib.shared") local f = (... or require "lib.shared")[1]
if not Balatest then if not Balatest then
return return
@ -192,9 +192,9 @@ Balatest.TestPlay {
} }
Balatest.TestPlay { Balatest.TestPlay {
category = {"blind", "xerox"}, category = {"blind", "mitotic"},
name = "xerox", name = "mitotic",
blind = "bl_Roland_xerox", blind = "bl_Roland_mitotic",
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}
end, end,
@ -204,9 +204,9 @@ Balatest.TestPlay {
} }
Balatest.TestPlay { Balatest.TestPlay {
category = {"blind", "xerox"}, category = {"blind", "mitotic"},
name = "xerox_disabled", name = "mitotic_disabled",
blind = "bl_Roland_xerox", blind = "bl_Roland_mitotic",
jokers = {"j_chicot"}, jokers = {"j_chicot"},
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}

View file

@ -2,330 +2,6 @@ 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",
@ -334,7 +10,7 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(7 * 2) Balatest.assert_chips(7)
end, end,
} }
@ -347,6 +23,6 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(7 * math.pow(2, 32)) Balatest.assert_chips(1 / 0)
end, end,
} }

View file

@ -2,22 +2,6 @@ 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,13 +1,17 @@
local f, q = unpack(... or require "lib.shared") local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Joker:take_ownership("joker", {cost = 1}, true) SMODS.Joker:take_ownership("joker", {cost = 1}, true)
local orig_set_debuff = Card.set_debuff local orig_set_debuff = Card.set_debuff
function Card:set_debuff(...) function Card:set_debuff(should_debuff, ...)
if self.ability.Roland_crimson ~= nil then
self.ability.Roland_crimson = not not should_debuff
return
end
if SMODS.get_enhancements(self).m_wild and SMODS.Mods.Roland.config.no_wild_debuff then if SMODS.get_enhancements(self).m_wild and SMODS.Mods.Roland.config.no_wild_debuff then
self.debuff = false self.debuff = false
else else
orig_set_debuff(self, ...) orig_set_debuff(self, should_debuff, ...)
end end
end end
@ -26,10 +30,11 @@ function Card:use_consumeable(area, copier, ...)
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)
@ -42,34 +47,30 @@ function Card:use_consumeable(area, copier, ...)
} }
end) end)
q { q {delay = 0.7, trigger = "after", func = Bakery_API.unhighlight_all}
delay = 0.7,
trigger = "after",
func = function()
Bakery_API.unhighlight_all()
end,
}
end end
local orig_use_card = G.FUNCS.use_card local orig_use_card = G.FUNCS.use_card
function G.FUNCS.use_card(e, ...) function G.FUNCS.use_card(e, ...)
local ref = e.config.ref_table or {}
local consumeable = (ref.ability or {}).consumeable or {}
if not SMODS.Mods.Roland.config.faster_planets or if not SMODS.Mods.Roland.config.faster_planets or
not (((e.config.ref_table or {}).ability or {}).consumeable or {}).hand_type then (not consumeable.hand_type and not consumeable.hand_types) then
return orig_use_card(e, ...) return orig_use_card(e, ...)
end end
local normal = ref.area ~= G.pack_cards or not G.GAME.pack_choices or G.GAME.pack_choices <= 0
ref.from_area = ref.from_area or ref.area
play_sound("tarot1", percent, 0.6) play_sound("tarot1", percent, 0.6)
e.config.ref_table:use_consumeable(e.config.ref_table.area) discover_card(ref.config.center)
ref:use_consumeable(ref.area)
SMODS.calculate_context {area = ref.area, consumeable = ref, using_consumeable = true}
SMODS.calculate_context { q(function()
using_consumeable = true, ref:remove()
consumeable = e.config.ref_table, end)
area = e.config.ref_table.from_area,
}
local normal = G.GAME.pack_choices and e.config.ref_table.area ~= G.pack_cards
e.config.ref_table:remove()
if normal then if normal then
return return
@ -82,7 +83,6 @@ 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
@ -102,11 +102,11 @@ local orig_get_blind_amount = get_blind_amount
---@param ante number ---@param ante number
---@return table|number ---@return table|number
local function blind(ante) local function blind(ante)
return ante == 39 and 1e294 or (_G["to_number"] or f().id)(orig_get_blind_amount(ante)) return ante == 39 and 1e294 or (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 _G["Talisman"] or not SMODS.Mods.Roland.config.harsh_ante_scaling return not Talisman or not SMODS.Mods.Roland.config.harsh_ante_scaling
end end
function get_blind_amount(ante, ...) function get_blind_amount(ante, ...)
@ -120,26 +120,10 @@ function get_blind_amount(ante, ...)
return 1 / 0 return 1 / 0
end end
--- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) } local rem = tonumber(blind((ante % loop) + 1))
local big, rem = _G["Big"], tonumber(blind((ante % loop) + 1))
return ante / 15 >= loop and big:new(f(blind(ante - (loop * 15))):map(f().const(10)):table()) or return ante / 15 >= loop and Big:new(f(blind(ante - (loop * 15))):map(f.const(10)):table()) or
(ante / 9 >= loop and big:new(f(ante / loop - 8):map(f().const(10)):concat {rem}:table()) or (ante / 9 >= loop and Big:new(f(ante / loop - 8):map(f.const(10)):concat {rem}:table()) or
(ante / 2 >= loop and big:new {rem, ante / loop} or (ante / 2 >= loop and Big:new {rem, ante / loop} or
(big.constants and big.constants.TEN or big:new {10}):pow(rem))) (Big.constants and Big.constants.TEN or Big:new {10}):pow(rem)))
end end
q(function()
if not G.P_CENTERS.c_Bakery_Scribe then
return
end
local orig_can_use = G.P_CENTERS.c_Bakery_Scribe.can_use
function G.P_CENTERS.c_Bakery_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)

44
src/voucher.lua Normal file
View file

@ -0,0 +1,44 @@
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,
}