Compare commits

..

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

64 changed files with 1641 additions and 3537 deletions

5
.gitignore vendored
View file

@ -39,8 +39,3 @@ luac.out
*.x86_64
*.hex
# Syncthing
.stfolder
# Autogenerated
assets/1x/jokers

12
.vscode/settings.json vendored
View file

@ -1,8 +1,4 @@
{
"Lua.diagnostics.disable": [
"duplicate-set-field",
"lowercase-global",
],
"Lua.diagnostics.severity": {
"ambiguity-1": "Error",
"assign-type-mismatch": "Error",
@ -22,12 +18,14 @@
"duplicate-doc-field": "Error",
"duplicate-doc-param": "Error",
"duplicate-index": "Error",
"duplicate-set-field": "Error",
"empty-block": "Error",
"global-element": "Error",
"global-in-nil-env": "Error",
"incomplete-signature-doc": "Error",
"inject-field": "Error",
"invisible": "Error",
"lowercase-global": "Error",
"missing-fields": "Error",
"missing-global-doc": "Error",
"missing-local-export-doc": "Error",
@ -64,9 +62,5 @@
"unused-label": "Error",
"unused-local": "Error",
"unused-vararg": "Error",
},
"Lua.runtime.version": "LuaJIT",
"Lua.workspace.library": [
"${3rd}/love2d/library"
],
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

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

View file

@ -1,199 +0,0 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 frozen;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01;
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,6.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
#define DEPTH 21
#define PHI ((1. + sqrt(5.)) / 2.)
#define PI 3.141592653589
#define S(M, O) (0.5 + 0.5 * sin(O + M * pos.x)) / 6.
#define C(M, O) (0.5 + 0.5 * cos(O + M * pos.y)) / 6.
vec2 rotate(vec2 v, float a)
{
float s = sin(a);
float c = cos(a);
mat2 m = mat2(c, -s, s, c);
return m * v;
}
float cr(vec2 a, vec2 b)
{
return a.x * b.y - b.x * a.y;
}
float cr(vec2 a, vec2 b, vec2 c)
{
return cr(b - a, c - b);
}
vec4 effect(vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords)
{
vec4 tex = Texel(texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
if (uv.x > uv.x * 2.) {
uv = frozen;
}
// Slightly adapted from 'snowflake lattice' by pali6.
// https://www.shadertoy.com/view/7sG3zd
// https://www.shadertoy.com/user/pali6
float iTime = 0.0;
vec2 pos = (((texture_coords)*(image_details)) - texture_details.xx*texture_details.ba)/texture_details.ba;
float scale = 72. + 18. * sin(iTime / 10.) + 20. * sin(4. + iTime / 15.) + 30. * sin(iTime / 6.);
vec2 camPos = vec2(0.5, 0.5);
float SF = 1. / pow(scale, 0.3);
camPos += vec2(0.02 * sin(iTime / 15.), -0.07 * cos(iTime / 19.)) * SF;
camPos += vec2(-0.1 * sin(3. + iTime / 37.), -0.1 * cos(4. + iTime / 27.)) * SF;
camPos += vec2(0.07 * sin(9. + iTime / 47.), -0.08 * cos(20. + iTime / 31.)) * SF;
camPos += vec2(0.04 * sin(19. + iTime / 7.), 0.06 * cos(25. + iTime / 5.)) * SF;
pos -= vec2(0.5, 0.5);
pos = rotate(pos, iTime / 30. + PI * cos(iTime / 37.) + 2. * PI * sin(iTime / 87. + 10.));
pos /= scale;
pos += camPos;
vec2 ota = vec2(-1., 0.);
vec2 otb = vec2(2., 0.);
vec2 ta = ota;
vec2 tb = otb;
vec2 tc;
bool gn = true;
float fl = -1.;
float f = 0.;
float nDark = 0.;
for (int i = 0; i < DEPTH; i++)
{
vec2 next;
if (gn)
tc = ta + rotate(tb - ta, fl * PI / 5.) / PHI;
else
tc = ta + rotate(tb - ta, fl * 2. * PI / 5.) * PHI;
if(!(gn && min(length(pos - ta), length(pos - tb)) > length(ta - tb) / 3. || !gn && length(pos - tb) <= length(tc - tb) * PHI / 3.5))
{
nDark += 1.;
}
if (gn)
{
next = tb + (ta - tb) / PHI;
if (abs(cr(next, tc, pos)) * float(i + 1) < 0.000001)
f = 1.;
if (cr(next, tc, pos) * cr(next, tc, tb) > 0.)
{
gn = false;
ta = next;
tb = tc;
fl *= -1.;
i--;
}
else
{
gn = true;
tb = ta;
ta = tc;
}
}
else
{
next = tc + (tb - tc) / PHI;
if (abs(cr(ta, next, pos)) * float(i + 1) < 0.000001)
f = 1.;
if (cr(ta, next, pos) * cr(ta, next, tb) > 0.)
{
gn = false;
ta = tb;
tb = next;
}
else
{
gn = true;
tb = ta;
ta = tc;
}
}
}
colour = (nDark / float(DEPTH)) * vec4(
0.1 + S(30., 10.) + C(10., 11.) + S(50., 16.) + S(99., 15.) + S(1., 42.) + C(1.5, 73.),
0.1 + C(20., 10.) + S(70., 12.) + C(23., 18.) + C(67., 119.) + S(1.2, 49.) + C(0.9, 79.),
0.15 + S(70., 10.5) + C(31., 17.) + S(55., 135.) + C(123., 15.) + S(0.7, 13.) + C(0.11, 31.),
1.
) * 1.5;
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0.,0.,0.,scale);
}
#endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

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

View file

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

View file

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

View file

@ -6,9 +6,15 @@ return {
text = {
"{C:attention}Small{} and {C:attention}Big Blinds{} are",
"replaced with {C:attention}Boss Blinds",
"that are {C:dark_edition}debuffed twice",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
"that are {C:dark_edition}debuffed #1# times",
},
},
b_Roland_go = {
name = "Pass GO Deck",
text = {
"Start with a {C:attention}Credit Card",
"Set money to {C:money}$0",
"when entering the shop",
},
},
b_Roland_swapper = {
@ -16,39 +22,32 @@ return {
text = {
"{C:tarot}Tarot {}and {C:spectral}Spectral",
"cards swap places",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
},
},
BakeryCharm = {
BakeryCharm_Roland_cocacola = {
name = SMODS.Mods.Roland.config.cool_phones and "coca cola phone" or "Pentagram",
text = {
"{C:attention}Discard 0 cards {}to",
"gain {C:red}#1# {}discards and",
"lose {C:attention}#2# {}hand size",
"this round",
},
name = "coca cola phone",
text = {"Values on consumables", "increase by {C:attention}1"},
},
BakeryCharm_Roland_fat = {
name = SMODS.Mods.Roland.config.cool_phones and "fat i phone" or "Product",
name = "fat i phone",
text = {
"{C:attention}+#1# Booster Pack {}slots",
-- "{C:attention}+#1# Booster Pack {}slots",
"All {C:attention}Booster Packs {}are {C:attention}Mega",
"{C:inactive}(Whenever applicable)",
},
},
BakeryCharm_Roland_flexible = {
name = SMODS.Mods.Roland.config.cool_phones and "flexi ble phone" or "Ring",
name = "flexi ble phone",
text = {unpack(G.localization.descriptions.Joker.j_ring_master.text)},
},
BakeryCharm_Roland_hand = {
name = SMODS.Mods.Roland.config.cool_phones and "hand phone" or "Shell",
name = "hand phone",
text = {"{C:attention}+#1# {}hand size", "{C:blue}#2# {}hands"},
},
BakeryCharm_Roland_wii = {
name = SMODS.Mods.Roland.config.cool_phones and "wii phone" or "Tire",
name = "wii phone",
text = {
"Enter the shop",
"when any {C:attention}Blind",
@ -57,49 +56,37 @@ return {
},
},
Blind = {
bl_Roland_blizzard = {
name = "The Blizzard",
text = {"All cards", "are {C:dark_edition}Frozen"},
},
bl_Roland_divide = {
name = "The Great Divide",
text = {"Half of the deck", "is {C:red}discarded"},
text = {"Half of the deck", "is discarded"},
},
bl_Roland_equinox = {
name = "The Equinox",
text = {"{C:inactive}No UI"},
text = {"No UI"},
},
bl_Roland_falseshuffle = {
name = "The False Shuffle",
text = {"{C:enhanced}Enhanced {}cards", "are drawn {C:attention}last"},
text = {"Enhanced cards", "are drawn last"},
},
bl_Roland_improbable = {
name = "The Improbable",
text = {"All {C:green}probabilities", "cannot happen"},
text = {"All probabilities", "cannot happen"},
},
bl_Roland_mitotic = {
name = "The Mitotic",
text = {"{C:red}Discarded {}cards", "are {C:attention}copied"},
text = {"Discarded cards", "are copied"},
},
bl_Roland_nimble = {
name = "The Nimble",
text = {"The {C:attention}first 5 {}cards", "drawn are {C:blue}played"},
text = {"The first 5 cards", "drawn are played"},
},
bl_Roland_tranquilizer = {
name = "The Tranquilizer",
text = {
"{C:attention}#1#s {}are {C:red}debuffed",
"Changes based on",
"most common rank",
},
text = {"#1#s are debuffed", "Changes based on", "most common rank"},
},
bl_Roland_venerable_visage = {
name = "Venerable Visage",
text = {
"Infinite {C:red}discards",
"Lose if {C:attention}any {}card was",
"not {C:blue}played {}or {C:red}discarded",
},
text = {"Infinite discards", "Lose if any card was", "not played or discarded"},
fail = {
"Blasphemy.",
"Blunder.",
@ -118,12 +105,6 @@ return {
},
},
},
Edition = {
e_Roland_frozen = {
name = "Frozen",
text = {"Values on", "this card do", "not change"},
},
},
Enhanced = {
m_wild = {
name = "Wild Card",
@ -131,36 +112,6 @@ return {
},
},
Joker = {
j_Roland_amber = {
name = "Amber Acorn",
text = {
"{X:mult,C:white}X#1#{} Mult",
"Moves before",
"hand is played",
},
},
j_Roland_arctic = {
name = "Arctic Circle",
text = {"Retrigger {C:attention}all", "{C:dark_edition}Frozen {}cards"},
},
j_Roland_artemis = {
name = "Artemis X",
text = {
"Earn {C:money}$#1# {}at end of round",
"Payout increases by {C:money}$#2#",
"when these {C:planet}Planet {}cards",
"are used in order:",
"{V:1}Earth{C:inactive} - {V:2}Mars{C:inactive} - {V:3}Earth",
},
},
j_Roland_basket = {
name = "Basket",
text = {
"Sell this Joker to",
"create {C:dark_edition}Negative {}copies",
"of every consumable",
},
},
j_Roland_bulldozer = {
name = "Bulldozer",
text = {
@ -169,28 +120,43 @@ return {
"{C:inactive}(Currently {X:red,C:white}X#1#{C:inactive})",
},
},
j_Roland_cold = {
name = "Cold Turkey",
j_Roland_escapey = {
name = "Escapey",
text = {
"Third scored card",
"gives {X:red,C:white}X#1#{} Mult",
"if it is {C:dark_edition}Frozen",
"Use to {C:attention}destroy tags{} or {C:attention}unselected consumables",
"in exchange for {C:planet}leveling up {X:planet,C:white}#1#{C:planet} random hands",
"{C:inactive,s:0.75,E:1}#2#{C:red,s:1.5,E:1}#3#{s:0.9}#4#{C:blue,E:1,s:0.9}#5#{s:0.9}#6#",
},
merge = {"Since none apply, fuse with other ", " jokers"},
quotes = {
marble = {"there is no escape..."},
normal = {
"I can't wait to work with you!",
"Did you need something from me?",
"Oh! I'm just so happy to see you!",
"Can I say something irrelevant? I promise it won't be long.",
"Tell me about your buddies! Assuming you have them, anyway.",
},
scared = {
"What am I going to do?!",
"I'm not scared, you are!",
"Tell me when this is over...",
"I can't keep looking at this!",
"Let me go hide in this corner... Okay?",
},
},
j_Roland_cerulean = {
name = "Cerulean Bell",
},
j_Roland_phytoestrogens = {
name = "Phytoestrogens",
text = {"{C:mult}+Chips{} Mult", "{X:mult,C:white}X#1#{} Mult"},
},
j_Roland_monomino = {
name = "Monomino",
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",
"Gains {C:money}$#1#{} of",
"{C:attention}sell value{} if",
"played hand is a",
"{C:attention}#2#",
},
},
j_Roland_domino = {
@ -202,51 +168,24 @@ return {
"{C:inactive}(Currently {C:red}+#3#{C:inactive} Mult)",
},
},
j_Roland_excalibur = {
name = "Excalibur",
j_Roland_trimino = {
name = "Trimino",
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})",
"Copies {C:attention}#1# {C:green}random",
"scoring cards and",
"{C:red,E:1}self-destructs {}if",
"played hand is a",
"{C:attention}#2#",
},
},
j_Roland_excalibur_Back = {
name = "Excalibur",
j_Roland_misfortune = {
name = "Misfortune Cookie",
text = {
"Scored and held in",
"hand {C:enhanced}Stone Cards",
"each give {X:mult,C:white}X#3#{} Mult",
"{C:tarot}Tarot{} cards created",
"by {C:tarot}Purple Seals",
"become {C:dark_edition}Negative",
},
},
j_Roland_hardboiled = {
name = "Hard-Boiled",
text = {
"Sell this Joker to",
"turn all cards in",
"hand {C:dark_edition}Frozen",
},
},
j_Roland_idle = {
name = "The Idle",
text = {
"Use to add card's",
"rank and suit",
"First {C:attention}scoring {}card",
"of each entry",
"gives {X:mult,C:white}X#19#{} Mult",
"{C:inactive}(Must be distinct)",
"{V:1,s:0.75}#1#{C:inactive,s:0.75}#2#{V:2,s:0.75}#3#{C:inactive,s:0.75}, " ..
"{V:3,s:0.75}#4#{C:inactive,s:0.75}#5#{V:4,s:0.75}#6#{C:inactive,s:0.75}",
"{V:5,s:0.75}#7#{C:inactive,s:0.75}#8#{V:6,s:0.75}#9#, " ..
"{V:7,s:0.75}#10#{C:inactive,s:0.75}#11#{V:8,s:0.75}#12#{C:inactive,s:0.75}",
"{V:9,s:0.75}#13#{C:inactive,s:0.75}#14#{V:10,s:0.75}#15#{C:inactive,s:0.75}, " ..
"{V:11,s:0.75}#16#{C:inactive,s:0.75}#17#{V:12,s:0.75}#18#",
},
},
j_Roland_jokersr = {
name = "Joker Sr.",
text = {"{X:mult,C:white}X#1#{} Mult"},
},
j_Roland_martingale = {
name = "Martingale",
text = {
@ -260,23 +199,6 @@ return {
"{s:0}Otherwise, {C:green,s:0}#1# in #3#{s:0} chance to give {X:mult,C:white,s:0}X#9#{s:0} Mult",
},
},
j_Roland_misfortune = {
name = "Misfortune Cookie",
text = {
"{C:tarot}Tarot{} cards created",
"by {C:tarot}Purple Seals",
"become {C:dark_edition}Negative",
},
},
j_Roland_monomino = {
name = "Monomino",
text = {
"Gains {C:money}$#1#{} of",
"{C:attention}sell value{} if",
"played hand is a",
"{C:attention}#2#",
},
},
j_Roland_mrsbones = {
name = "Mrs. Bones",
text = {
@ -291,14 +213,6 @@ return {
name = "Ms. Joker",
text = {"{C:chips}+#1#{} Chips"},
},
j_Roland_nilly = {
name = "Nilly",
text = {
"Toggleable {X:mult,C:white}X0{} Mult",
"{C:inactive}(Currently {C:attention}#1#{C:inactive})",
"{C:inactive,s:0.75,E:1}Wait... what?",
},
},
j_Roland_oops = {
name = "Oops! All 7s",
text = {
@ -307,10 +221,6 @@ return {
"{C:inactive}(Currently {X:green,C:white}X#2#{C:inactive})",
},
},
j_Roland_phytoestrogens = {
name = "Phytoestrogens",
text = {"{C:mult}+Chips{} Mult, {X:mult,C:white}X#1#{} Mult"},
},
j_Roland_sapling = {
name = "Sapling",
text = {
@ -320,25 +230,16 @@ return {
"least {C:attention}#2# suits",
},
},
j_Roland_snowsquall = {
name = "Snowsquall",
text = {
"This Joker gains",
"{C:mult}+#1# {}Mult for every",
"played {C:dark_edition}Frozen {}card",
"{C:inactive}(Currently {C:red}+#2#{C:inactive} Mult)",
},
},
j_Roland_suitable = {
name = "Suitable",
text = {"{V:1}#1# {}are {C:attention}Wild", "Suit changes", "every round"},
j_Roland_srjoker = {
name = "Sr. Joker",
text = {"{X:mult,C:white}X#1#{} Mult"},
},
j_Roland_sunny = {
name = "Sunny Side Up",
text = {
"Sell this Joker to",
"{C:attention}draw{} the bottom",
"card of the deck",
"card of the {C:hand}deck",
"{C:inactive}(Currently {V:1}#1#{C:inactive}#2#{V:2}#3#{C:inactive})",
},
},
@ -349,36 +250,6 @@ return {
"give {X:red,C:white}X#1#{} Mult",
},
},
j_Roland_trimino = {
name = "Trimino",
text = {
"Copies {C:attention}#1# {C:green}random",
"scoring cards and",
"{C:red,E:1}self-destructs {}if",
"played hand is a",
"{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 = {
name = "Yard Sale",
text = {
@ -404,18 +275,30 @@ return {
text = {
"{C:attention}Small{} and {C:attention}Big Blinds{} are",
"replaced with {C:attention}Boss Blinds",
"that are {C:dark_edition}debuffed twice",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
"that are {C:dark_edition}debuffed #1# times",
},
},
sleeve_Roland_blossom_alt = {
name = "Efflorescent Sleeve",
text = {
"{C:dark_edition}Debuff {X:dark_edition,C:white}#1#X<ante>{C:dark_edition} times",
"{C:inactive}(Instead of twice)",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
"{C:attention}Blinds{} are {C:dark_edition}debuffed",
"{C:dark_edition}({X:dark_edition,C:white}ante{} {X:dark_edition,C:white}number{C:dark_edition}) times",
"{C:inactive}(Instead of #1#)",
},
},
sleeve_Roland_go = {
name = "Pass GO Sleeve",
text = {
"Start with a {C:attention}Credit Card",
"Set money to {C:money}$0",
"when entering the shop",
},
},
sleeve_Roland_go_alt = {
name = "Free Parking Sleeve",
text = {
"Start with an additional",
"{X:attention,C:white}#1#{C:attention} Credit Cards",
},
},
sleeve_Roland_swapper = {
@ -423,42 +306,39 @@ return {
text = {
"{C:tarot}Tarot {}and {C:spectral}Spectral",
"cards swap places",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
},
},
sleeve_Roland_swapper_alt = {
name = "Sleeve Swapper",
text = {
"Swap {C:attention}all {}consumables",
"",
"{C:Bakery_credit_fg_Roland_bakersdozenbagels,s:0.75}Art: BakersDozenBagels",
"{C:attention}All {}consumable",
"types swap places",
},
},
},
Spectral = {
c_Roland_afterimage = {
name = "Afterimage",
text = {
"Add {C:dark_edition}Negative {}to {C:attention}#1#",
"selected card in hand",
"{C:red}#2#{} hand size",
},
},
c_Roland_dual = {
name = "Dual",
text = {
"Add random seals",
"to {C:attention}#1#{} random",
"cards in hand",
"cards in {C:hands}hand",
},
},
c_Roland_primal = {
name = "Primal Force",
c_Roland_mirror = {
name = "Mirror",
text = {
"Enhance {C:attention}all {}cards",
"held in hand to",
"{C:enhanced}Stone Cards",
},
},
c_Roland_refract = {
name = "Refract",
text = {
"Add a {C:dark_edition}Glass Seal",
"Add a {C:dark_edition}Glass Tag",
"to {C:attention}#1#{} selected",
"cards in hand",
"card in {C:hands}hand",
},
},
c_Roland_void = {
@ -470,47 +350,6 @@ return {
},
},
},
Tag = {
tag_Roland_freeze = {
name = "Freeze Tag",
text = {"Apply {C:dark_edition}Frozen", "to the next {C:attention}#1#", "scored cards"},
},
tag_Roland_invisible = {
name = "Invisible Tag",
text = {
"{C:attention}Duplicate {}a random",
"Joker after {C:attention}#1#{} rounds",
"{C:inactive}(Must have room)",
"{C:inactive}(Currently {C:attention}#2#{C:inactive}/#1#)",
},
},
},
Tarot = {
c_Roland_coolheaded = {
name = "Coolheaded",
text = {
"Add {C:dark_edition}Frozen {}to {C:attention}#1#",
"selected {C:attention}playing",
"{C:attention}card {}or {C:attention}Joker",
},
},
},
Voucher = {
v_Roland_ceres = {
name = "Ceres",
text = {
"Level up {C:attention}Flush House",
"by {C:attention}#1# {}when it is played",
},
},
v_Roland_neptune = {
name = "Neptune",
text = {
"Level up {C:attention}Straight Flush",
"by {C:attention}#1# {}when it is played",
},
},
},
},
misc = {
challenge_names = {
@ -519,39 +358,25 @@ return {
c_Roland_Eternally_Crimson = "Eternally Crimson",
c_Roland_Eternally_Verdant = "Eternally Verdant",
c_Roland_Eternally_Violet = "Eternally Violet",
c_Roland_Glass = "Glass House",
c_Roland_Go = "Pass GO",
c_Roland_Jokerful = "Jokerful",
c_Roland_Ornate = "Ornate",
c_Roland_Pastries = "Sweet Pastries",
c_Roland_Spin_To_Win = "Spin to Win",
c_Roland_Showdown = "Showdown",
},
v_dictionary = {
b_Roland_add = "ADD",
b_Roland_animated_icon = "Animated Icon (requires restart)",
b_Roland_bye = "Bye!",
b_Roland_comma = ", ",
b_Roland_cool_phones = "cool phones (requires restart)",
b_Roland_debuffed = "DEBUFFED",
b_Roland_disabled = "Disabled",
b_Roland_enabled = "Enabled",
b_Roland_entering_shop = "Entering shop!",
b_Roland_equinox_assist = "Assist: Only hide text (Equinox)",
b_Roland_faster_planets = "Faster Planets",
b_Roland_full = "FULL",
b_Roland_scribable_basket = "Scribable Basket (overpowered)",
b_Roland_harsh_ante_scaling = "Harsh ante scaling (Ante 40+)",
b_Roland_illusion_seal = "Allow seals from Illusion voucher",
b_Roland_import_funky = "Debug: Import funky.lua",
b_Roland_most_common_card = "Rank",
b_Roland_no_wild_debuff = "No wild card debuffs",
b_Roland_escape = "ESCAPE",
b_Roland_fuse = "FUSE",
b_Roland_debug_export = "Debug: Import funky.lua to _G",
b_Roland_equinox_assist = "Assist: Only hide text during Equinox",
b_Roland_most_common_card = "(Rank)",
b_Roland_na = "N/A",
b_Roland_of = " of ",
b_Roland_toggle = "TOGGLE",
b_Roland_unassigned = "(Unassigned)",
b_Roland_vitriol = "Vitriol (Venerable Visage)",
},
labels = {
Roland_frozen = "Frozen",
roland_glass_seal = "Glass Seal",
},
v_text = {
@ -560,11 +385,10 @@ return {
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_Violet = {"{C:attention}Violet Vessel{}'s effect is active every blind"},
ch_c_Roland_Glass1 = {"Played cards with no seal have a"},
ch_c_Roland_Glass2 = {"{C:green}1 in 2{} chance to gain {C:dark_edition}Glass Seal"},
ch_c_Roland_Go = {"Set money to {C:money}$0 {}when entering the shop"},
ch_c_Roland_Jokerful = {"The only jokers are {C:mult}Joker{} and {C:chips}Ms. Joker"},
ch_c_Roland_Ornate = {"Anything vanilla is banned"},
ch_c_Roland_Pastries = {"All blinds, cards, and tags are of {C:gold}Bakery{} or {C:blue}Roland"},
ch_c_Roland_Showdown = {"Showdown blinds are {C:attention}Venerable Visage"},
ch_c_Roland_Showdown_Amber = {"Showdown blinds are {C:attention}Amber Acorn {}(stacks with above)"},
ch_c_Roland_Showdown_Cerulean = {"Showdown blinds are {C:attention}Cerulean Bell {}(stacks with above)"},
ch_c_Roland_Showdown_Crimson = {"Showdown blinds are {C:attention}Crimson Heart {}(stacks with above)"},

View file

@ -12,12 +12,12 @@ priority = -2
target = "game.lua"
pattern = "self.GAME.round_resets.blind_choices.Boss = get_new_boss()"
position = "before"
payload = """
payload = '''
if G.GAME.modifiers.Roland_blossom_deck then
self.GAME.round_resets.blind_choices.Small = get_new_boss()
self.GAME.round_resets.blind_choices.Big = get_new_boss()
end
"""
'''
match_indent = true
[[patches]]
@ -25,12 +25,12 @@ match_indent = true
target = "functions/common_events.lua"
pattern = "G.GAME.round_resets.blind_choices.Boss = get_new_boss()"
position = "before"
payload = """
payload = '''
if G.GAME.modifiers.Roland_blossom_deck then
G.GAME.round_resets.blind_choices.Small = get_new_boss()
G.GAME.round_resets.blind_choices.Big = get_new_boss()
end
"""
'''
match_indent = true
[[patches]]
@ -85,49 +85,3 @@ payload = """if next(SMODS.find_card "j_Roland_misfortune") then
card:set_edition "e_negative"
end"""
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.regex]
target = "card.lua"
pattern = "math\\.floor\\(\\(G\\.GAME\\.dollars \\+ \\(G\\.GAME\\.dollar_buffer or 0\\)\\)/self\\.ability\\.extra\\.dollars\\)"
position = "at"
payload = "G.P_CENTERS.j_bootstraps:mult(self)"
match_indent = true

View file

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

1
refs/Cryptid Symbolic link
View file

@ -0,0 +1 @@
../../Cryptid/

View file

@ -1,4 +1,4 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
local f, q = unpack(... or require "lib.shared")
local function save(ret)
q(save_run)
@ -8,30 +8,45 @@ end
local back = (function()
local x = 0
---@param tbl SMODS.Back|{is_alt: (fun(self: self): boolean), alt_apply?: fun(self: SMODS.Back|table, back: Back|table), alt_calculate?: fun(self: SMODS.Back|table, back: Back|table, context: CalcContext|table): table?, boolean?}
---@param tbl SMODS.Back|{is_alt: function, alt_apply?: fun(self: SMODS.Back|table, back: Back|table), alt_calculate?: fun(self: SMODS.Back|table, back: Back|table, context: CalcContext|table): table?, boolean?}
return function(tbl)
local key = tbl.key
local apply = tbl.apply
local calculate = tbl.calculate
function tbl:apply(...)
local _ = apply and G.GAME.selected_sleeve ~= "sleeve_Roland_" .. key and save(apply(self, ...))
---@diagnostic disable-next-line: redundant-return-value
return apply and G.GAME.selected_sleeve ~= "sleeve_Roland_" .. key and save(apply(self, ...))
end
function tbl:calculate(...)
return (calculate and G.GAME.selected_sleeve ~= "sleeve_Roland_" .. key) and calculate(self, ...) or nil
---@diagnostic disable-next-line: return-type-mismatch
return calculate and G.GAME.selected_sleeve ~= "sleeve_Roland_" .. key and calculate(self, ...)
end
tbl.pos = {x = x, y = 0}
tbl.is_alt = f.fals
tbl.atlas = "back"
x = x + 1
local back = q(SMODS.Back(tbl))
local _ = CardSleeves and CardSleeves.Sleeve {
-- This API intends that you share the same functions for apply/calculate,
-- meaning that this may be called to check if a deck/sleeve combo is used even
-- when we are from the deck context. We add this to prevent accidental calls to nil.
function tbl:is_alt()
return false
end
SMODS.Back(tbl)
x = x + 1
---@diagnostic disable-next-line: undefined-global
if not CardSleeves then
return
end
---@diagnostic disable-next-line: undefined-global
CardSleeves.Sleeve {
key = key,
pos = tbl.pos,
atlas = "sleeve",
atlas = "back",
config = tbl.config and f(tbl.config):table() or nil,
loc_vars = function(self, ...)
local ret = tbl.loc_vars and tbl.loc_vars(self, ...) or {}
@ -48,8 +63,6 @@ local back = (function()
return self.get_current_deck_key() == "b_Roland_" .. key
end,
}
return back
end
end)()
@ -60,20 +73,12 @@ SMODS.Atlas {
py = 95,
}
local _ = CardSleeves and SMODS.Atlas {
key = "sleeve",
path = "sleeve.png",
px = 73,
py = 95,
}
back {
key = "blossom",
pronouns = "any_all",
config = {extra = {alt_times = 4, times = 2}},
attributes = {"boss_blind"},
config = {extra = {times = 2}},
loc_vars = function(self, _, _)
return {vars = {self.config.extra.alt_times}}
return {vars = {self.config.extra.times}}
end,
apply = function(_, _)
G.GAME.modifiers.Roland_blossom_deck = true
@ -89,41 +94,78 @@ back {
return
end
local count = self:is_alt() and
G.GAME.round_resets.ante * self.config.extra.alt_times or
self.config.extra.times
local count = self:is_alt() and G.GAME.round_resets.ante or self.config.extra.times
f(count):each(function(i)
local _ = i == 1 and G.GAME.blind:disable()
for _ = 1, count do
G.GAME.blind:disable()
q {
delay = 0.4,
func = function()
local _ = i ~= 1 and G.GAME.blind:disable()
SMODS.calculate_effect({message = localize "ph_boss_disabled"}, card)
G.GAME.blind:wiggle()
play_sound "timpani"
end,
}
end
end,
}
back {
key = "go",
pronouns = "he_him",
config = {extra = {times = 1, alt_times = 5}},
loc_vars = function(self, info_queue, _)
local _ = info_queue and table.insert(info_queue, G.P_CENTERS.j_credit_card)
return {vars = {self.config.extra.alt_times - self.config.extra.times}}
end,
apply = function(self)
q(function()
local count = self:is_alt() and self.config.extra.alt_times or self.config.extra.times
for _ = 1, count do
local c = create_card("Joker", G.jokers, nil, nil, nil, nil, "j_credit_card", "Roland_go")
c:add_to_deck()
c:start_materialize()
G.jokers:emplace(c)
end
end)
end,
calculate = function(_, card, context)
if not context.starting_shop then
return
end
ease_dollars(-G.GAME.dollars)
attention_text {
text = localize "k_nope_ex",
backdrop_colour = G.C.MONEY,
offset = {x = 0, y = 0},
silent = true,
major = card,
align = "cm",
scale = 1.3,
hold = 1.4,
}
end,
}
back {
key = "swapper",
pronouns = "he_him",
attributes = {"spectral", "tarot"},
apply = function(self)
local modifiers = G.GAME.modifiers
modifiers.Roland_swapper_deck = true
modifiers.Roland_alt_swapper_deck = modifiers.Roland_alt_swapper_deck or self:is_alt()
end,
calculate = f.noop,
calculate = function() end,
}
local swapper = {Spectral = "Tarot", Tarot = "Spectral"}
local orig_create_card = create_card
---@diagnostic disable-next-line: lowercase-global
function create_card(_type, ...)
if not G.GAME.modifiers.Roland_swapper_deck then
return orig_create_card(_type, ...)

View file

@ -1,16 +1,16 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
local f, q = unpack(... or require "lib.shared")
local blind = (function()
local y = 0
---@param tbl SMODS.Blind|{idea?: string}
---@param tbl SMODS.Blind
---@return SMODS.Blind
return function(tbl)
tbl.idea = tbl.idea and "Roland_" .. tbl.idea or nil
tbl.pos = {x = 0, y = y}
tbl.atlas = "blind"
local ret = SMODS.Blind(tbl)
y = y + 1
return q(SMODS.Blind(tbl))
return ret
end
end)()
@ -23,19 +23,37 @@ SMODS.Atlas {
atlas_table = "ANIMATION_ATLAS",
}
SMODS.Sound {key = "kick", path = "kick.ogg"}
SMODS.Sound {
key = "kick",
path = "kick.ogg",
}
local function common_rank()
local tally, to_name = {}, {}
local tally = {}
local to_name = {}
f(G.playing_cards):where(SMODS.has_no_rank, false):each(function(v)
--- Tallies up a card area's cards.
---@param card_area CardArea
local function tally_up(card_area)
f(card_area.cards):where(function(v)
return not SMODS.has_no_rank(v)
end):each(function(v)
local id = v:get_id()
to_name[id] = v.base.value
tally[id] = (tally[id] or 0) + 1
end)
end
local max_key = f(tally, pairs):fold({-1 / 0, -1 / 0}, function(a, v, k)
return (v > a[1] or v == a[1] and k > a[2]) and {v, k} or a
tally_up(G.deck)
tally_up(G.hand)
tally_up(G.discard)
local max_key = f(tally):fold({-1 / 0, -1 / 0}, function(a, v, k)
if v > a[1] or k > a[2] and v == a[1] then
return {v, k}
end
return a
end)[2]
return max_key, to_name[max_key]
@ -52,35 +70,7 @@ end
local function has_enhancement(card)
local e = SMODS.get_enhancements(card)
return e and next(e)
end
local function set_freeze(state)
local function copy(x)
return type(x) == "table" and f(x):map(copy):table() or x
end
---@param card Card|{ Roland_blizzard: true|nil }
return function(card)
if not card.Roland_blizzard and card.edition and card.edition.Roland_frozen then
return
end
local last_edition = card.Roland_blizzard
card.Roland_blizzard = state and (copy(card.edition) or true) or nil
q {
delay = 0.1,
func = function()
local edition = state and {Roland_frozen = true} or last_edition or card.Roland_blizzard
card:set_edition(edition ~= true and edition or nil)
end,
}
end
end
local function sort_by_enhancement(v1, v2)
return has_enhancement(v1) and not has_enhancement(v2)
return not not (e and next(e))
end
local function hsv_to_rgb(h, s, v)
@ -122,23 +112,28 @@ blind {
boss_colour = HEX "0291fbff",
pronouns = "she_her",
config = {draw = 5},
defeat = function()
G.GAME.Roland_nimble_disabled = nil
end,
disable = function()
G.GAME.Roland_nimble_disabled = true
end,
drawn_to_hand = function(self)
local function force_hand()
if is_locked() then
return false
end
f(G.hand.cards):take(self.config.draw):each(function(v)
f(G.hand.cards, ipairs):take(self.config.draw):each(function(v)
G.hand:add_to_highlighted(v, true)
end)
G.FUNCS.play_cards_from_highlighted()
G.FUNCS.play_cards_from_highlighted(nil)
self:disable()
end
local g = G.GAME
if not g.blind.disabled and not g.Roland_nimble_disabled then
g.Roland_nimble_disabled = true
if not G.GAME.Roland_nimble_disabled then
G.GAME.Roland_nimble_disabled = true
q {func = force_hand, blocking = false}
end
end,
@ -153,14 +148,29 @@ blind {
boss_colour = HEX "ff7f3dff",
pronouns = "any_all",
disable = function()
G.FUNCS.draw_from_hand_to_deck()
q(function()
f(G.hand.cards):each(function(v, i)
draw_card(G.hand, G.deck, i / #G.hand.cards * 100, "up", false, v)
end)
end)
q(function()
pseudoshuffle(G.deck.cards, pseudoseed "RolandFalseShuffle")
end)
end,
calculate = function(_, b, context)
local _ = not b.disabled and context.drawing_cards and table.sort(G.deck.cards, sort_by_enhancement)
if b.disabled or not context.drawing_cards then
return
end
b:wiggle()
table.sort(
G.deck.cards,
function(v1, v2)
return has_enhancement(v1) and not has_enhancement(v2)
end
)
end,
in_pool = function()
return G.playing_cards and f(G.playing_cards):any(has_enhancement)
@ -174,7 +184,13 @@ blind {
pronouns = "he_they",
disable = function()
-- Ensures that this runs after 'set_blind' since it also gets added to queue.
q {delay = 0.8, trigger = "after", func = G.FUNCS.draw_from_discard_to_deck}
q {
delay = 0.8,
trigger = "after",
func = function()
G.FUNCS.draw_from_discard_to_deck()
end,
}
end,
set_blind = function()
-- Allows the background to ease in first before drawing cards.
@ -207,7 +223,7 @@ blind {
local cards_added = {}
local count = #G.hand.highlighted
f(G.hand.highlighted):take(count):each(function(v, i)
f(G.hand.highlighted, ipairs):take(count):each(function(v, i)
local copy = copy_card(v)
copy:add_to_deck()
table.insert(G.hand, copy)
@ -216,42 +232,12 @@ blind {
draw_card(G.hand, G.discard, i / count * 100, "down", false, copy, nil, nil, true)
end)
if not next(cards_added) then
return
end
b:wiggle()
b.triggered = true
playing_card_joker_effects(cards_added)
end,
}
blind {
key = "blizzard",
idea = "redstoad",
boss = {min = 3},
boss_colour = HEX "102a41ff",
pronouns = "it_its",
defeat = function(self)
self.cards():where "Roland_blizzard":each(set_freeze())
G.GAME.blind.disabled = true
end,
disable = function(self)
self:defeat()
end,
calculate = function(self, b, context)
return not b.disabled and
context.hand_drawn and
self.cards()
:where("Roland_blizzard", false)
:where("facing", "front")
:each(set_freeze(true)) or nil
end,
cards = function()
return f(G):where(getmetatable, CardArea):flatmap("cards", ipairs)
end,
}
blind {
key = "tranquilizer",
boss = {min = 6},
@ -269,6 +255,9 @@ blind {
local _, name = common_rank()
return {vars = {localize(name or "Ace", "ranks")}}
end,
disable = function(self)
self.disabled = true
end,
calculate = function(self, b, context)
if not context.card_added and
not context.drawing_cards and
@ -299,12 +288,9 @@ blind {
b:set_text()
end
end,
disable = function()
G.GAME.blind.disabled = true
end,
recalc_debuff = function(self, card)
local id, _ = common_rank()
local ret = not G.GAME.blind.disabled and id == card:get_id()
local ret = not self.disabled and id == card:get_id()
self.triggered = ret
return ret
end,
@ -312,7 +298,7 @@ blind {
blind {
key = "improbable",
boss = {min = 3},
boss = {min = 4},
boss_colour = HEX "009966ff",
pronouns = "it_its",
mult = 2,
@ -327,46 +313,21 @@ blind {
if cry_prob then
local orig_cry_prob = cry_prob
---@diagnostic disable-next-line: lowercase-global
function cry_prob(...)
return G.GAME.modifiers.Roland_improbable and 0 or orig_cry_prob(...)
end
end
function SMODS.current_mod:calculate(context)
local _ = type(G.calccontext) == "function" and G.calccontext(context)
if type(G.calc) == "function" then
G.calc = {G.calc}
if context.setting_blind and G.GAME.blind.name == "bl_mp_nemesis" then
local modifiers = G.GAME.modifiers
modifiers.Roland_martingale_seed = (modifiers.Roland_martingale_seed or 0) + 1
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 _ = type(G.calc) == "function" and G.calc(f(context):keys():string())
local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities
local _ = context.end_of_round and
context.main_eval and
not context.blueprint and
G.P_TAGS.tag_Roland_invisible.increment()
-- Normally unreachable since we set it to nil ourselves,
-- but other mods may want to use this modifier.
if improbable == false then
@ -421,6 +382,7 @@ end
local orig_draw = Card.draw
---@diagnostic disable-next-line: duplicate-set-field
function Card:draw(...)
if equinox() and
not SMODS.Mods.Roland.config.equinox_assist and
@ -434,6 +396,7 @@ end
local orig_draw_self = UIElement.draw_self
---@diagnostic disable-next-line: duplicate-set-field
function UIElement:draw_self(...)
if equinox() and
not self.config.button and
@ -449,7 +412,6 @@ local venerable_visage = blind {
boss = {showdown = true},
boss_colour = HEX "f6f6f2ff",
pronouns = "any_all",
dollars = 8,
calculate = function(self, b, context)
if b.disabled then
return
@ -462,10 +424,7 @@ local venerable_visage = blind {
end)
end
if b.Roland_vitriol or
not context.end_of_round or
G.GAME.chips == G.GAME.blind.chips or
not (next(G.deck.cards) or next(G.hand.cards)) then
if b.Roland_vitriol or not context.end_of_round or not next(G.deck.cards) or not next(G.hand.cards) then
return
end
@ -481,31 +440,15 @@ local venerable_visage = blind {
}
end,
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
b:wiggle()
end
f(G.playing_cards):each(function(v, k)
return v.area ~= G.hand and draw_card(v.area, G.hand, k * 100 / #G.playing_cards, "up", true, v)
end)
local fail = G.localization.descriptions.Blind.bl_Roland_venerable_visage.fail
local speed = 0.1
SMODS.draw_cards(#G.deck.cards)
play_sound("gong", 0.6)
attention_text {
text = pseudorandom_element(fail, pseudoseed "RolandVenerableVisage"),
@ -515,55 +458,52 @@ local venerable_visage = blind {
hold = 2,
}
f {0.15, 0.3, 0.45, 0.6}:each(function(v)
play_sound("gong", v)
end)
delay(1)
---@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)
f(G.playing_cards):each(function(v)
q {
trigger = "before",
delay = 6 / len,
delay = speed,
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:shatter()
b:wiggle()
end,
}
end)
local _ = vitriol and q {
trigger = "before",
delay = 1.5,
delay(1)
f(G.P_CARDS):each(function(v)
q {
delay = speed,
func = function()
love.window.setPosition(x, y)
love.window.setMode(w, h, flags)
love.resize(w, h)
love.window.setFullscreen(is_fullscreen)
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.5)
delay(1)
end,
}
local orig_game_draw = Game.draw
---@diagnostic disable-next-line: duplicate-set-field
function Game.draw(...)
orig_game_draw(...)
local boss_colour = venerable_visage.boss_colour

View file

@ -1,4 +1,4 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
local f, q = unpack(... or require "lib.shared")
local function bans()
return {banned_cards = {}, banned_tags = {}, banned_other = {}}
@ -30,6 +30,10 @@ local function is_center_banned_from_pastry(v)
return arcana_boosters[v.key] or pastries_targets[v.set] and is_banned_from_pastry(v)
end
local function is_joker(v)
return v.set == "Joker" and not ({j_joker = true, j_Roland_msjoker = true})[v.key]
end
local function is_showdown_except(key)
---@param v SMODS.Blind
---@param k string
@ -39,68 +43,27 @@ local function is_showdown_except(key)
end
end
local pastries, amber, cerulean, crimson, verdant, violet =
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"
local function is_vanilla(v)
return v.set and not v.mod and f(G.P_CENTERS):concat(G.P_CARDS, G.P_TAGS):any(function(c)
return c.mod and not c.in_pool and c.set == v.set
end)
end,
end
local jokerful, vanillas, pastries, amber, cerulean, crimson, verdant, violet, final
= bans(), bans(), bans(), bans(), bans(), bans(), bans(), bans(), bans()
SMODS.Challenge {
key = "Jokerful",
rules = {custom = {{id = "Roland_Jokerful"}}},
restrictions = jokerful,
pronouns = "he_him",
}
SMODS.Challenge {
key = "Go",
rules = {custom = {{id = "Roland_Go"}}},
jokers = {{id = "j_credit_card"}},
pronouns = "he_him",
calculate = function(self, context)
if context.starting_shop then
ease_dollars(-G.GAME.dollars)
attention_text {
text = localize "k_nope_ex",
backdrop_colour = G.C.MONEY,
offset = {x = 0, y = 0},
silent = true,
major = self,
align = "cm",
scale = 1.3,
hold = 1.4,
}
end
end,
}
local spin_to_win = SMODS.Challenge {
key = "Spin_To_Win",
jokers = f(4):map(f.const {id = "j_joker", eternal = true}):table(),
restrictions = {banned_cards = {{id = "c_Roland_coolheaded"}}},
pronouns = "they_them",
apply = function(self)
self.rotation = 0
end,
calculate = function(self, context)
if not context.card_added or context.card.config.center.key ~= "j_Bakery_Spinner" then
return
end
context.card.ability.extra.rotation = self.rotation
self.rotation = self.rotation + 1
end,
rotation = 0,
key = "Ornate",
rules = {custom = {{id = "Roland_Ornate"}}},
restrictions = vanillas,
pronouns = "any_all",
}
SMODS.Challenge {
@ -110,12 +73,12 @@ SMODS.Challenge {
pronouns = "she_them",
}
local finalizers = {
SMODS.Challenge {
key = "Eternally_Amber",
rules = {custom = {{id = "Roland_Eternally_Amber"}, {id = "Roland_Showdown_Amber"}}},
jokers = {{id = "j_Roland_amber"}},
restrictions = amber,
pronouns = "they_them",
calculate = function(_, context)
local function slide(pitch)
q(function()
@ -147,14 +110,11 @@ SMODS.Challenge {
end,
}
end,
}
},
SMODS.Challenge {
key = "Eternally_Cerulean",
rules = {custom = {{id = "Roland_Eternally_Cerulean"}, {id = "Roland_Showdown_Cerulean"}}},
jokers = {{id = "j_Roland_cerulean"}},
restrictions = cerulean,
pronouns = "she_her",
calculate = function(_, context)
if not context.hand_drawn then
return
@ -174,22 +134,20 @@ SMODS.Challenge {
G.hand:add_to_highlighted(v)
end)
end,
}
},
SMODS.Challenge {
key = "Eternally_Crimson",
rules = {custom = {{id = "Roland_Eternally_Crimson"}, {id = "Roland_Showdown_Crimson"}}},
jokers = {{id = "j_Roland_crimson"}},
restrictions = crimson,
pronouns = "she_her",
calculate = function(_, context)
local _ = context.blind_defeated and f(G.jokers.cards):each(function(v)
v.ability.Roland_crimson_heart_chosen = nil
end)
local mod = G.GAME.modifiers
mod.Roland_Eternally_Crimson = context.setting_blind and true or mod.Roland_Eternally_Crimson
mod.Roland_Eternally_Crimson = not context.setting_blind and mod.Roland_Eternally_Crimson or nil
local debuff = context.debuff_card
---@type (SMODS.Joker|{ability?: {Roland_crimson_heart_chosen: boolean?}, debuff: boolean?})[]
local cards = G.jokers.cards
if debuff and
@ -217,7 +175,9 @@ SMODS.Challenge {
local _ = v.debuff and SMODS.recalc_debuff(v)
end)
jokers = next(jokers) and jokers or f(cards):where("debuff", false):map(function(v)
jokers = next(jokers) and jokers or f(cards):where(function(v)
return not v.debuff
end):map(function(v)
local _ = not prev[v] and table.insert(jokers, v)
return v
end):table()
@ -234,14 +194,11 @@ SMODS.Challenge {
mod.Roland_Eternally_Crimson = not context.hand_drawn and
mod.Roland_Eternally_Crimson or nil
end,
}
},
SMODS.Challenge {
key = "Eternally_Verdant",
rules = {custom = {{id = "Roland_Eternally_Verdant"}, {id = "Roland_Showdown_Verdant"}}},
jokers = {{id = "j_Roland_verdant"}},
restrictions = verdant,
pronouns = "she_her",
calculate = function(_, context)
if context.setting_blind then
G.GAME.modifiers.Roland_Eternally_Verdant = true
@ -257,27 +214,44 @@ SMODS.Challenge {
end)
end
end,
}
},
SMODS.Challenge {
key = "Eternally_Violet",
rules = {custom = {{id = "Roland_Eternally_Violet"}, {id = "Roland_Showdown_Violet"}}},
jokers = {{id = "j_joker"}, {id = "j_Roland_violet"}},
restrictions = violet,
pronouns = "she_they",
calculate = function(_, context)
if not context.setting_blind then
return
end
local mult = G.GAME.blind.mult < 6 and G.GAME.blind.mult or 1
G.GAME.blind.chips = G.GAME.blind.chips * 6 / mult
if context.setting_blind then
G.GAME.blind.chips = G.GAME.blind.chips * (6 / G.GAME.blind.mult)
G.GAME.blind.chip_text = number_format(G.GAME.blind.chips)
end
end,
},
}
SMODS.Challenge {
key = "Showdown",
rules = {custom = {
{id = "Roland_Eternally_Amber"},
{id = "Roland_Eternally_Cerulean"},
{id = "Roland_Eternally_Crimson"},
{id = "Roland_Eternally_Verdant"},
{id = "Roland_Eternally_Violet"},
{id = "Roland_Showdown"},
}},
restrictions = final,
calculate = function(_, context)
f(finalizers):each(function(v)
v:calculate(context)
end)
end,
}
q(function()
f {
{G.P_CENTERS, is_joker, jokerful.banned_cards},
{G.P_TAGS, is_vanilla, vanillas.banned_tags},
{G.P_BLINDS, is_vanilla, vanillas.banned_other},
{G.P_CENTERS, is_vanilla, vanillas.banned_cards},
{G.P_TAGS, is_banned_from_pastry, pastries.banned_tags},
{G.P_BLINDS, is_banned_from_pastry, pastries.banned_other},
{G.P_CENTERS, is_center_banned_from_pastry, pastries.banned_cards},
@ -286,17 +260,8 @@ q(function()
{G.P_BLINDS, is_showdown_except "bl_final_heart", crimson.banned_other},
{G.P_BLINDS, is_showdown_except "bl_final_leaf", verdant.banned_other},
{G.P_BLINDS, is_showdown_except "bl_final_vessel", violet.banned_other},
{G.P_BLINDS, is_showdown_except "bl_Roland_venerable_visage", final.banned_other},
}:each(function(v)
f(v[1]):where(v[2]):each(adder(v[3]))
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,32 +1,19 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
local f, q = unpack(... or require "lib.shared")
local mod = SMODS.current_mod
local cool_phones = SMODS.Mods.Roland.config.cool_phones
SMODS.Atlas {
key = "charm",
path = cool_phones and "phorm.png" or "charm.png",
path = "charm.png",
px = 68,
py = 68,
}
local _ = cool_phones and SMODS.Sound {
key = "phone",
path = "phone.ogg",
}
local charm = (function()
local x = 0
---@param tbl SMODS.Joker|{alerted?: boolean, artist?: string, equip?: fun(self: self, card: Card)}
---@param tbl SMODS.Joker|{alerted?: boolean}
return function(tbl)
q {
blocking = false,
no_delete = true,
func = function()
if not Bakery_API or not Bakery_API.Charm then
return false
end
q(function()
local current_mod = SMODS.current_mod
SMODS.current_mod = mod
@ -35,155 +22,122 @@ local charm = (function()
tbl.unlocked = true
tbl.discovered = true
tbl.pos = {x = x, y = 0}
tbl.atlas = "charm"
tbl.artist = not cool_phones and "Roland_bakersdozenbagels" or nil
x = x + 1
tbl.atlas = "charm"
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))
local charm = Bakery_API.Charm(tbl)
charm:inject()
charm:process_loc_text()
SMODS.current_mod = current_mod
end,
}
end, true)
end
end)()
local function can_discard_zero()
return G.GAME.current_round.discards_left > 0 and
not next(G.hand.highlighted) and
G.hand.config.card_limit > 0 and
G.GAME.Bakery_charm == "BakeryCharm_Roland_cocacola"
local function add_to_consumable_ability_by(n)
local function new(v)
return type(v) == "number" and v + n or type(v) == "table" and f(v):map(new):table() or v
end
charm {
key = "hand",
pronouns = "he_him",
config = {extra = {hands = -2, hand_size = 5}},
attributes = {"hands", "hand_size", "passive"},
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.hand_size, extra.hands}}
end,
equip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(extra.hands)
G.hand:change_size(extra.hand_size)
round.hands = round.hands + extra.hands
end,
unequip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(-extra.hands)
G.hand:change_size(-extra.hand_size)
round.hands = round.hands - extra.hands
end,
}
---@param card Card
return function(card)
local ability = card.ability or {}
charm {
key = "cocacola",
pronouns = "he_they",
attributes = {"passive", "tarot", "spectral"},
config = {extra = {discard = 2, hand_size = 1, refund = 0}},
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.discard, extra.hand_size}}
end,
calculate = function(_, card, context)
if not context.end_of_round then
local function go(key)
---@type { [string]: number }|number
local value = ability[key]
--print(key)
if not value then
return
end
local extra = card.ability.extra
G.hand:change_size(extra.refund)
extra.refund = 0
end,
}
local orig_can_discard = G.FUNCS.can_discard
function G.FUNCS.can_discard(e, ...)
if can_discard_zero() then
e.config.button, e.config.colour = "Roland_cocacola", G.C.RED
else
return orig_can_discard(e, ...)
end
if type(value) == "number" then
ability[key] = value + n
return
end
G.FUNCS.Roland_cocacola = function()
stop_use()
G.CONTROLLER.interrupt.focus = true
G.CONTROLLER:save_cardarea_focus "hand"
if type(value) ~= "table" then
return
end
f(G.playing_cards):map "ability":each(function(v)
v.forced_selection = nil
local new_value = f(value):map(new):table()
f(new_value):each(function(v, k)
ability[k] = v
end)
G.card_area_focus_reset = (G.CONTROLLER.focused.target or {}).area == G.hand and
{area = G.hand, rank = G.CONTROLLER.focused.target.rank} or G.card_area_focus_reset
SMODS.calculate_context {pre_discard = true, full_hand = G.hand.highlighted}
G.GAME.current_round.discards_used = G.GAME.current_round.discards_used + 1
local c = G.Bakery_charm_area.cards[1]
local extra = c.ability.extra or {}
juice_card(c)
ease_discard(extra.discard - 1)
G.hand:change_size(-extra.hand_size)
extra.refund = extra.refund + extra.hand_size
local _ = G.GAME.modifiers.discard_cost and ease_dollars(-G.GAME.modifiers.discard_cost)
G.STATE = G.STATES.DRAW_TO_HAND
q {
trigger = "immediate",
func = function()
if G.SCORING_COROUTINE then
return false
ability[key] = new_value
end
local center_key = (card.config or {}).center_key
if center_key and center_key:sub(1, 2) == "c_" then
f {"extra", "consumeable"}:each(go)
end
end
end
charm {
key = "wii",
pronouns = "they_them",
calculate = function(_, card, context)
if not context.skip_blind then
return
end
local message = localize {type = "variable", key = "b_Roland_entering_shop"}
SMODS.calculate_effect({card = card, message = message, sound = "whoosh1"}, card)
q(function()
G.blind_prompt_box = G.blind_prompt_box and G.blind_prompt_box:remove()
G.blind_select = G.blind_select and G.blind_select:remove()
G.round_eval = G.round_eval and G.round_eval:remove()
G.GAME.current_round.jokers_purchased = 0
G.STATE = G.STATES.SHOP
G.GAME.shop_free = nil
G.GAME.shop_d6ed = nil
G.STATE_COMPLETE = false
return true
play_sound("whoosh1", 1.33333, 0.8)
play_sound("whoosh1", 0.66666, 0.8)
end)
end,
}
charm {
key = "flexible",
pronouns = "any_all",
}
local orig_showman = SMODS.showman
---@diagnostic disable-next-line: duplicate-set-field
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)
return {vars = {card.ability.extra.mod}}
end,
equip = function(_, card)
SMODS.change_booster_limit(card.ability.extra.mod)
end,
unequip = function(_, card)
SMODS.change_booster_limit(-card.ability.extra.mod)
end,
-- config = {extra = {mod = 1}},
-- loc_vars = function(_, _, card)
-- return {vars = {card.ability.extra.mod}}
-- end,
-- equip = function(_, card)
-- SMODS.change_booster_limit(card.ability.extra.mod)
-- end,
-- unequip = function(card)
-- SMODS.change_booster_limit(-card.ability.extra.mod)
-- end,
}
local orig_init = Card.init
---@diagnostic disable-next-line: duplicate-set-field
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)
local key = center.key:gsub("normal", "mega"):gsub("jumbo", "mega"):gsub("mini", "mega")
if G.P_CENTERS[key] then
return orig_init(self, X, Y, W, H, card, G.P_CENTERS[key], params, ...)
@ -206,64 +160,49 @@ function Card:init(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)
key = "cocacola",
pronouns = "he_they",
equip = function()
f(G.consumeables.cards):each(add_to_consumable_ability_by(1))
end,
unequip = function()
f(G.consumeables.cards):each(add_to_consumable_ability_by(-1))
end,
}
local orig_apply_to_run = Tag.apply_to_run
local orig_set_ability = Card.set_ability
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
---@diagnostic disable-next-line: duplicate-set-field
function Card:set_ability(center, initial, delay_sprites, ...)
local ret = orig_set_ability(self, center, initial, delay_sprites, ...)
if G.GAME.Bakery_charm == "BakeryCharm_Roland_cocacola" then
add_to_consumable_ability_by(1)(self)
end
return orig_apply_to_run(self, ...)
return ret
end
charm {
key = "hand",
pronouns = "he_him",
config = {extra = {hands = -2, hand_size = 5}},
loc_vars = function(_, _, card)
local extra = card.ability.extra
return {vars = {extra.hand_size, extra.hands}}
end,
equip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(extra.hands)
G.hand:change_size(extra.hand_size)
round.hands = round.hands + extra.hands
end,
unequip = function(_, card)
local extra = card.ability.extra
local round = G.GAME.round_resets
ease_hands_played(-extra.hands)
G.hand:change_size(-extra.hand_size)
round.hands = round.hands - extra.hands
end,
}

View file

@ -1,327 +0,0 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Shader {
key = "frozen",
path = "frozen.fs",
}
---@param suffix string
local function frozen_sound(suffix)
local key = "frozen" .. suffix
return SMODS.Sound {key = key, path = key .. ".ogg"}
end
frozen_sound "_click"
local frozen_blocklist = {CardSleeve = true}
local frozen_sounds = f(4):map(frozen_sound):map "key":table()
local needs_chip_mult_override = {
Bull = true,
Erosion = true,
Misprint = true,
TierList = true,
["Blue Joker"] = true,
["Abstract Joker"] = true,
["Fortune Teller"] = true,
}
local current_round_overrides = {
Castle = "castle_card",
Farmer = "farmer_card",
Tuxedo = "tuxedo_card",
["Go Fish"] = "fish_rank",
["The Idol"] = "idol_card",
["Mail-In Rebate"] = "mail_card",
Obelisk = "most_played_poker_hand",
["Ancient Joker"] = "ancient_card",
Wherewolf = "Bakery_Wherewolf_card",
j_Roland_suitable = "Roland_suitable",
}
SMODS.current_mod.frozen_chip_mult = needs_chip_mult_override
SMODS.current_mod.frozen_current_round = current_round_overrides
local function freeze(card)
---@generic T
---@param x T
---@return T
local function copy(x)
return type(x) == "table" and f(x):map(copy):table() or x
end
if frozen_blocklist[(card.ability or {}).name] then
return card.ability
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.ability = card.Roland_frozen_ability and copy(card.Roland_frozen_ability) or card.ability
card.Roland_frozen = card.Roland_frozen or {probability = SMODS.get_probability_vars(card, 1, 1)}
local ability, ret = card.ability, card.Roland_frozen_ability
ability.extra = ability.extra == nil and {} or ability.extra
if type(ability) ~= "table" then
return ret
end
ability.seal = ability.seal or {}
if not ability.name or not G.GAME.current_round then
return ret
end
local key = current_round_overrides[ability.name]
if not key then
return ret
end
card.Roland_frozen_current_round = card.Roland_frozen_current_round or copy(G.GAME.current_round[key])
G.GAME.current_round[key] = copy(card.Roland_frozen_current_round)
return ret
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 {
key = "frozen",
shader = "frozen",
sound = {sound = "Roland_frozen", per = 1, vol = 0.8},
attributes = {"passive", "scaling", "mod_chance"},
pronouns = "any_all",
weight = 8,
extra_cost = 4,
in_shop = true,
apply_to_float = false,
calculate = function(_, card, context)
local _ = not context.fix_probability and not context.mod_probability and freeze(card)
end,
on_remove = function(card)
card.Roland_frozen, card.Roland_frozen_ability, card.Roland_frozen_current_round = nil, nil, nil
end,
get_weight = function(self)
return G.GAME.edition_rate * self.weight
end,
}
local orig_play_sound = play_sound
function play_sound(sound, pitch, ...)
if sound ~= "Roland_frozen" and sound ~= "Roland_frozen_click" then
return orig_play_sound(sound, pitch, ...)
end
local overriden_sound = sound == "Roland_frozen" and
pseudorandom_element(frozen_sounds, pseudoseed "Roland_frozen") or
sound
local added_pitch = pseudorandom(pseudoseed "Roland_frozen_pitch") / 4 + 0.8
return orig_play_sound(overriden_sound, pitch + added_pitch, ...)
end
local orig_click = Card.click
function Card:click(...)
local highlight = self.highlighted
local ret = orig_click(self, ...)
if self.edition and self.edition.Roland_frozen and highlight ~= self.highlighted then
play_sound("Roland_frozen_click", highlight and 0 or 0.5, 0.6)
end
return ret
end
local orig_calculate_joker = Card.calculate_joker
---@param self Card|{Roland_frozen_ability?: table}
function Card:calculate_joker(context, ...)
local is_frozen = self.edition and self.edition.Roland_frozen
if is_frozen and self.ability.name == "Loyalty Card" then
return (context.joker_main and self.ability.loyalty_remaining == 0) and
{xmult = self.ability.extra.Xmult} or nil
end
if is_frozen and self.ability.name == "Turtle Bean" then
return
end
local ret = {orig_calculate_joker(self, context, ...)}
if not is_frozen or type(ret[1]) ~= "table" then
return unpack(ret)
end
local ability = freeze(self)
if not needs_chip_mult_override[ability.name] then
return unpack(ret)
end
return f {mult = "mult_mod", chips = "chip_mod", xmult = "xmult", x_mult = "x_mult"}:map(function(v)
local key = "Roland_frozen_" .. v
ability[key] = ability[key] or ret[1][v]
return ability[key]
end):where(f.id):table()
end
local orig_calculate_dollar_bonus = Card.calculate_dollar_bonus
---@param self Card|{Roland_frozen_ability?: table}
function Card:calculate_dollar_bonus(...)
if not self.edition or not self.edition.Roland_frozen then
return orig_calculate_dollar_bonus(self, ...)
end
local ability = freeze(self)
if ability.name == "Satellite" then
ability.Roland_frozen_planets_used = ability.Roland_frozen_planets_used or
orig_calculate_dollar_bonus(self, ...)
return self.ability.extra * ability.Roland_frozen_planets_used
end
return orig_calculate_dollar_bonus(self, ...)
end
local orig_get_probability_vars = SMODS.get_probability_vars
function G.P_CENTERS.j_bootstraps:mult(card)
local ability = card.Roland_frozen_ability
local ret = math.floor((G.GAME.dollars + (G.GAME.dollar_buffer or 0)) / card.ability.extra.dollars)
if not ability then
return ret
end
ability.Roland_bootstraps_mult = ability.Roland_bootstraps_mult or ret
return ability.Roland_bootstraps_mult
end
function SMODS.get_probability_vars(trigger_obj, ...)
local numerator, denominator = orig_get_probability_vars(trigger_obj, ...)
return trigger_obj and (trigger_obj.Roland_frozen or {}).probability or numerator, denominator
end
q {
blocking = false,
no_delete = true,
func = function()
local orig_flip_double_sided = (Bakery_API or {}).flip_double_sided
if not orig_flip_double_sided then
return false
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
local _ = G.P_CENTERS.j_Bakery_Estate and hook_estate()
local _ = G.P_CENTERS.c_Bakery_Scribe and hook_scribe()
local _ = G.P_CENTERS.j_Bakery_Proxy and hook_proxy()
end,
}

File diff suppressed because it is too large Load diff

View file

@ -3,101 +3,10 @@
---@license MPL-2.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
local f = {}
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
---@param self F | { [K]: V }
---@return K?, V?
@ -109,74 +18,39 @@ end
---@type F
local none
---@generic T: F|function|string|nil
---@param func T
---@return T
---@nodiscard
local function autofunc(func)
return type(func) == "string" and f.indices(func) or func or f.id
end
---@generic K, V
---@param tbl table<K, V>
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return fun(tbl: table<K, V>, key: K): K, V
---@return table<K, V>
---@return K
---@return V
---@nodiscard
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
local function is_f(any)
return type(any) == "table" and any.from == f.from and any.new == f.new
end
--- Always returns nil.
---@return nil
---@nodiscard
function f.noop()
end
--- Always returns false.
---@return false
---@nodiscard
function f.fals()
return false
end
--- Always returns true.
---@return true
---@nodiscard
function f.tru()
return true
end
--- Returns the arguments.
---@generic T
---@param ... T
---@return T
---@nodiscard
function f.id(...)
return ...
end
---@generic T
---@param value T
---@return fun(T): boolean
---@nodiscard
function f.eq(value)
return function(v)
return value == v
end
end
---@generic T
---@param value T
---@return fun(T): boolean
---@nodiscard
function f.nq(value)
return function(v)
return value ~= v
end
end
---@generic T
---@param v T
---@return fun(): T
@ -195,158 +69,67 @@ function f.const(v)
end
end
---@param i integer
---@return fun(...: any): any
---@nodiscard
function f.arg(i)
return function(...)
return ({...})[i]
end
end
---@generic K, V
---@param v K
---@return fun(x: { [K]: V }): V
---@nodiscard
function f.index(v)
return function(x)
return type(x) == "table" and x[v] or x
return x[v]
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
---@param fnext? fun(): K?, V?
---@return F|{ [K]: V }
---@nodiscard
function f.new(fnext)
-- Iterating over `f` is far easier, but we do this for performance sake.
return {
all = f.all,
any = f.any,
arg = f.arg,
chain = f.chain,
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,
}
local ret = {next = fnext or f.noop}
for k, v in pairs(f) do
ret[k] = v
end
return ret
end
--- Creates an enumeration.
---@type FFrom
function f.from(iter, fpairs, step)
if iter == nil then
return none
elseif iter == true then
---@generic K, V
---@param tbl 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(tbl: number, fpairs?: number, step?: number): F | { [number]: number }
---@overload fun(tbl: string): (fun(table: { [string]: V }): V)
---@overload fun(tbl: false): fun(): false
---@overload fun(tbl: true): fun(): true
---@overload fun(tbl: nil): F
function f.from(tbl, fpairs, step)
if tbl == true then
return f.tru
elseif iter == false then
elseif tbl == false then
return f.fals
elseif tbl == nil then
return none
end
local t = type(iter)
local tbl_type = type(tbl)
if t == "string" then
return f.indices(iter)
elseif t == "number" then
local ik, is, start = 0, step or 1, fpairs and iter or 1
if tbl_type == "string" then
return f.index(tbl)
elseif tbl_type == "number" then
local ik = 0
local start = fpairs and tbl or 1
local stop = not fpairs and iter or
local stop = not fpairs and tbl or
(type(fpairs) == "number") and fpairs or error("Invalid argument type for 'fpairs': " .. type(fpairs))
if start ~= stop and (is == 0 or ((is < 0) == (start < stop))) then
if step and step ~= 0 and ((step < 0) == (start < stop)) then
return none
end
return f.new(function()
local iv = start + ik * is
local iv = tbl + ik * (step or 1)
ik = ik + 1
if start > stop and iv >= stop or
@ -355,25 +138,17 @@ function f.from(iter, fpairs, step)
return ik, iv
end
end)
elseif t == "function" then
return f.new(iter)
elseif t == "thread" then
return f.new(function()
local s, k, v = coroutine.resume(iter)
if s then
return k, v
elseif tbl_type ~= "table" then
error("Invalid argument type for 'tbl': " .. type(tbl))
end
end)
else
local next, context, k, v = autopairs(iter, type(fpairs) == "function" and fpairs or nil)
local next, context, k, v = autopairs(tbl, fpairs)
return f.new(function()
k, v = next(context, k)
return k, v
end)
end
end
---@generic K, V
---@param self F|{ [K]: V }
@ -383,63 +158,44 @@ end
function f:concat(...)
local fsi = 0
local fs = {...}
local sum, last = 0, 0
for i = 1, #fs do
fs[i] = f.isf(fs[i]) and fs[i] or f.from(fs[i])
if not is_f(fs[i]) then ---@diagnostic disable-next-line: assign-type-mismatch
fs[i] = f.from(fs[i])
end
end
return f.new(function()
if fsi == 0 then
local k, v = self:next()
last = type(k) == "number" and math.max(k, last) or last
if k ~= nil then
return k, v
end
fsi, sum, last = 1, last, 0
fsi = 1
end
while fsi <= #fs do
local k, v = fs[fsi]:next()
last = type(k) == "number" and math.max(k, last) or last
if k ~= nil then
return type(k) == "number" and k + sum or k, v
end
fsi, sum, last = fsi + 1, sum + last, 0
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
fsi = fsi + 1
end
end)
end
---@generic K, V, U
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): U
---@param func F|fun(v: V, k: K): U
---@return F|{ [K]: U }
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
function f:map(func)
func = autofunc(func)
func = type(func) == "string" and f.index(func) or func
return f.new(function()
local k, v = self:next()
@ -452,15 +208,15 @@ end
---@generic K, V, U
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): { [any]: U }
---@param func F|fun(v: V, k: K): { [any]: U }
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return F|{ [K]: U }
---@overload fun(self: F|{ [K]: V }, func: string, fpairs?: fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)): F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
function f:flatmap(func, fpairs)
-- local i = 0
local vt, vk, vv, vp
func = autofunc(func)
func = type(func) == "string" and f.index(func) or func
return f.new(function()
if vk then
@ -502,13 +258,12 @@ end
---@generic K, V
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): boolean
---@param is? any
---@param func F|fun(v: V, k: K): boolean
---@return F|{ [K]: V }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string, is?: any): F|{ [K]: V }
function f:where(func, is)
func = autofunc(func)
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: V }
function f:where(func)
func = type(func) == "string" and f.index(func) or func
return f.new(function()
local k, v
@ -520,17 +275,9 @@ function f:where(func, is)
return
end
if is == nil then
if func(v, k) then
return k, v
end
elseif is == false then
if not func(v, k) then
return k, v
end
elseif is == func(v, k) then
return k, v
end
end
end)
end
@ -569,20 +316,6 @@ function f:values()
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@return F|{ [V]: K }
---@nodiscard
function f:swap()
return f.new(function()
local k, v = self:next()
if k ~= nil then
return v, k
end
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@param skip? integer
@ -665,12 +398,12 @@ end
---@generic K, V
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any
---@param func F|fun(v: V, k: K): boolean
---@return boolean|V
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean
function f:any(func)
func = autofunc(func)
func = type(func) == "string" and f.index(func) or func
for k, v in self.next do
if not func or func(v, k) then
@ -683,12 +416,12 @@ end
---@generic K, V
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any
---@param func F|fun(v: V, k: K): boolean
---@return boolean|V
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean
function f:all(func)
func = autofunc(func)
func = type(func) == "string" and f.index(func) or func
for k, v in self.next do
if not func or not func(v, k) then
@ -701,13 +434,13 @@ end
---@generic K, V
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any
---@param func F|fun(v: V, k: K): boolean
---@return integer
---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): integer
function f:count(func)
local ret = 0
func = autofunc(func)
func = type(func) == "string" and f.index(func) or func
for k, v in self.next do
if not func or func(v, k) then
@ -718,15 +451,6 @@ function f:count(func)
return ret
end
---@generic K, V, T
---@param self F|{ [K]: V }
---@param _ `T`
---@return F|{ [K]: `T` }
---@nodiscard
function f:pun(_)
return self
end
---@generic K, V
---@param self F|{ [K]: V }
---@return string
@ -767,7 +491,7 @@ end
---@generic K, V
---@param self F|{ [K]: V }
---@param func? fun(v: V, k: K): nil
---@param func? fun(v: V, k: K)
function f:each(func)
for k, v in self.next do
if func then
@ -778,11 +502,4 @@ end
none = f.new()
---@type F|FFrom
local ret = (setmetatable or f.const(f.from))(f, {
__call = function(_, ...)
return f.from(...)
end,
})
return ret
return f.from

View file

@ -1,76 +0,0 @@
---@meta
---@alias Attributes "mult"|"chips"|"xmult"|"xchips"|"score"|"xscore"|"blindsize"|"xblindsize"|"balance"|"swap"|"retrigger"|"scaling"|"reset"|"suit"|"diamonds"|"hearts"|"spades"|"clubs"|"hand_type"|"rank"|"ace"|"two"|"three"|"four"|"five"|"six"|"seven"|"eight"|"nine"|"ten"|"jack"|"queen"|"king"|"face"|"economy"|"generation"|"destroy_card"|"hands"|"discard"|"hand_size"|"chance"|"joker_slot"|"mod_chance"|"copying"|"full_deck"|"passive"|"joker"|"tarot"|"planet"|"spectral"|"enhancements"|"seals"|"editions"|"tag"|"skip"|"modify_card"|"perma_bonus"|"prevents_death"|"boss_blind"|"reroll"|"on_sell"|"sell_value"|"food"|"space"|"bakery_double_sided"|"bakery_usable"|"bakery_werewolf"
---@type Card[]
CardArea.cards = CardArea.cards
--- @overload fun(tbl: SMODS.Joker): SMODS.GameObject
Bakery_API.Charm = Bakery_API.Charm
--- @generic T: SMODS.GameObject
--- @param obj T
--- @return T
function Bakery_API.credit(obj)
error(obj)
end
---@type table
Balatest = Balatest
--- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) }
Big = Big
--- @type table?
CardSleeves = CardSleeves
--- @type table|fun(obj: SMODS.Back): SMODS.Back
CardSleeves.Sleeve = CardSleeves.Sleeve
--- @type {aliases: { [string]: [string] }}
Cryptid = Cryptid
--- @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,59 +1,3 @@
local f = assert(SMODS.load_file "src/lib/funky.lua")() or require "lib.funky"
---@param v string
---@return Card|Tag
local function add(v)
if 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)
return function()
local res, ret = pcall(fun)
@ -62,83 +6,73 @@ local function protect(fun)
sendErrorMessage(tostring(ret), "Roland")
end
return not res or ret ~= false and not (SMODS.current_mod and ret ~= true)
return not res or ret ~= false
end
end
local function protect_ev(fun)
if type(fun) == "function" then
return Event {func = protect(fun)}
elseif type(fun) ~= "table" then
error("Expected a function or table, got a " .. type(fun), 3)
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
if type(fun) == "table" then
fun.func = protect(fun.func)
fun = getmetatable(fun) == Event and fun or Event(fun)
elseif type(fun) == "function" then
fun = Event {func = protect(fun)}
else
error("Expected a function or event, got a " .. type(fun), 3)
end
Bakery_API.credit(fun)
end),
}
else
fun.func = protect(fun.func or fun[1])
return getmetatable(fun) == Event and fun or Event(fun)
return fun
end
-- This exists to remove the @deprecated warning.
if false then
---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
local f = assert(SMODS.load_file "src/lib/funky.lua")() or require "lib.funky"
--- Queues an event to be run.
--- Note that events added this way implicitly `return true` unless you explicitly `return false`.
--- For `front`; boolean `true` to add the event to the front of the queue, rather than the end.
--- @generic T: fun():boolean?|Event
--- @param fun T|Event|{front?: boolean} The table or function to turn into an event.
--- @return T fun
local function q(fun)
local ev = protect_ev(fun)
G.E_MANAGER:add_event(ev, nil, ev.front)
return fun
--- Note that events added this way implicitly `return true` unless you explicitly `return false`, unlike the vanilla ones.
--- @param fun (fun():false|nil)|Event The event or a function to run turn into an event.
--- @param front boolean|nil `true` to add the event to the front of the queue, rather than the end.
local function q(fun, front)
G.E_MANAGER:add_event(protect_ev(fun), nil, front)
end
--- Determines if a center is allowed to be usable.
---@return boolean
local function u()
return not ((G.play and next(G.play.cards) or G.CONTROLLER.locked or G.GAME.STOP_USE and G.GAME.STOP_USE > 0) and
return not ((G.play and #G.play.cards > 0 or G.CONTROLLER.locked or
(G.GAME.STOP_USE and G.GAME.STOP_USE > 0)) and
G.STATE ~= G.STATES.HAND_PLAYED and
G.STATE ~= G.STATES.DRAW_TO_HAND and
G.STATE ~= G.STATES.PLAY_TAROT)
end
--- 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,
})}
return {f, q, u}

View file

@ -1,130 +1,73 @@
local qol = assert(SMODS.load_file "src/lib/shared.lua")() or require "lib.shared"
local f, q = qol[1], qol[2]
q {
front = true,
no_delete = true,
blocking = false,
func = function()
local contributors = (Bakery_API or {}).contributors
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)
qol[1] {"challenge", "spectral", "tweaks", "blind", "charm", "joker", "back", "seal"}:each(function(v)
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
end)
if Balatest then
f {"joker", "blind", "spectral"}:each(function(v)
qol[1] {"joker", "blind", "spectral"}:each(function(v)
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
end)
end
local function toggle(id)
return create_toggle {
label = localize {type = "variable", key = "b_Roland_" .. id},
ref_table = SMODS.Mods.Roland.config,
ref_value = id,
scale = 1.5,
}
end
qol[2](function()
---@type table<string, {name: string, fg: table, bg: table}>
local contributors = Bakery_API.contributors
local animated = SMODS.Mods.Roland.config.animated_icon
contributors.Roland_aster = {
name = "asterSSH",
fg = HEX "f8f8f2ff",
bg = HEX "bd93f9ff",
}
contributors.Roland_char = {
name = "char (@irregulester)",
fg = HEX "f8f8f2ff",
bg = HEX "ff79c6ff",
}
end, true)
SMODS.Atlas {
px = 256,
py = 256,
key = "modicon",
fps = animated and 12 or nil,
frames = animated and 12 or nil,
path = animated and "icon.png" or "unicon.png",
atlas_table = animated and "ANIMATION_ATLAS" or "ASSET_ATLAS",
path = "icon.png",
}
---@diagnostic disable-next-line: duplicate-set-field
function SMODS.current_mod.config_tab()
return {
n = G.UIT.ROOT,
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.BLACK},
config = {r = 0.1, minw = 4, minh = 4, align = "tr", padding = 0.2, colour = G.C.BLACK},
nodes = {{
n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR},
config = {minw = 1, minh = 1, align = "tr", padding = 0.2, colour = G.C.CLEAR},
nodes = {
toggle "animated_icon",
toggle "cool_phones",
toggle "faster_planets",
-- toggle "illusion_seal",
-- toggle "no_wild_debuff",
toggle "vitriol",
toggle "equinox_assist",
SMODS.Mods.DebugPlus and toggle "import_funky",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
Talisman and toggle "harsh_ante_scaling",
create_toggle {
label = localize {type = "variable", key = "b_Roland_equinox_assist"},
ref_table = SMODS.Mods.Roland.config,
ref_value = "equinox_assist",
scale = 2,
},
SMODS.Mods.DebugPlus and UIBox_button {
label = {localize {type = "variable", key = "b_Roland_debug_export"}},
func = G.P_CENTERS.j_Roland_escapey.debug_export,
button = "Roland_debug_export",
colour = G.C.MULT,
col = true,
scale = 5,
minw = 5,
},
},
}},
}
end
SMODS.current_mod.qol = qol
function SMODS.current_mod.optional_features()
return {cardareas = {deck = true, unscored = true}}
end
function G.FUNCS.Roland_debug_export(_, tbl)
local to = tbl or _G
to.f, to.q, to.u = unpack(qol)
end

View file

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

View file

@ -1,4 +1,6 @@
local f, q, u = (... or require "lib.shared")[1], (... or require "lib.shared")[2], (... or require "lib.shared")[3]
local f, q, u = unpack(... or require "lib.shared")
local negative = {key = "e_negative_consumable", set = "Edition", config = {extra = 1}}
local spectral = (function()
local x = 0
@ -6,13 +8,19 @@ local spectral = (function()
---@param tbl SMODS.Consumable|{artist?: string}
---@return SMODS.Consumable
return function(tbl)
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
tbl.pos = {x = x, y = 0}
tbl.atlas = "spectral"
tbl.set = "Spectral"
tbl.cost = 4
tbl.set = "Spectral"
tbl.atlas = "spectral"
tbl.pos = {x = x, y = 0}
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
local ret = SMODS.Consumable(tbl)
x = x + 1
return q(SMODS.Consumable(tbl))
q(function()
Bakery_API.credit(ret)
end)
return ret
end
end)()
@ -30,10 +38,41 @@ SMODS.Sound {
spectral {
key = "afterimage",
pronouns = "she_they",
pronouns = "he_they",
artist = "aster",
config = {extra = {amount = 1, hand = -2}},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, negative)
return {vars = {card.ability.extra.amount, card.ability.extra.hand}}
end,
can_use = function(_, card)
return u() and card.ability.extra.amount == #Bakery_API.get_highlighted()
end,
use = function(_, card)
f(Bakery_API.get_highlighted()):each(function(v)
q {
delay = 0.1,
func = function()
v:set_edition {negative = true}
v:juice_up(0.5, 0.5)
end,
}
end)
q {
delay = 0.1,
func = function()
G.hand:change_size(card.ability.extra.hand)
Bakery_API.unhighlight_all()
end,
}
end,
}
spectral {
key = "dual",
pronouns = "they_them",
config = {extra = {amount = 2}},
attributes = {"seal", "modify_card", "spectral"},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.amount}}
end,
@ -41,13 +80,13 @@ spectral {
return u() and #G.hand.cards > 0
end,
use = function(_, card, _)
local cards = f(G.hand.cards):table()
local cards = f(G.hand.cards, ipairs):table()
pseudoshuffle(cards, pseudoseed "RolandDual")
f(cards):take(card.ability.extra.amount):each(function(v)
local seal
for _ = 1, 64 do
for _ = 1, 31 do
seal = SMODS.poll_seal {guaranteed = true}
if seal ~= "Roland_glass" then
@ -61,72 +100,9 @@ spectral {
}
spectral {
key = "primal",
key = "mirror",
pronouns = "he_him",
artist = "aster",
attributes = {"enhancements", "modify_card", "spectral"},
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_CENTERS.m_stone)
end,
can_use = function()
return next(G.hand.cards) and u()
end,
use = function(_, card)
q {
trigger = "after",
delay = 0.4,
func = function()
card:juice_up(0.3, 0.5)
play_sound("tarot1")
end,
}
f(G.hand.cards):each(function(v, k)
local percent = 1.15 - (k - 0.999) / (#G.hand.cards - 0.998) * 0.3
q {
trigger = "after",
delay = 0.15,
func = function()
v:flip()
v:juice_up(0.3, 0.3)
play_sound("card1", percent)
return true
end,
}
end)
f(G.hand.cards):each(function(v)
q(function()
v:set_ability(G.P_CENTERS.m_stone)
end)
end)
f(G.hand.cards):each(function(v, k)
local percent = 0.85 + (k - 0.999) / (#G.hand.cards - 0.998) * 0.3
q {
trigger = "after",
delay = 0.15,
func = function()
v:flip()
v:juice_up(0.3, 0.3)
play_sound("tarot2", percent, 0.6)
return true
end,
}
end)
delay(0.5)
end,
}
spectral {
key = "refract",
pronouns = "it_its",
artist = "aster",
config = {extra = {amount = 2}},
attributes = {"seal", "modify_card", "spectral"},
config = {extra = {amount = 1}},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, G.P_SEALS.Roland_glass)
return {vars = {card.ability.extra.amount}}
@ -141,21 +117,20 @@ spectral {
end,
}
spectral {
local void = spectral {
key = "void",
pronouns = "it_its",
artist = "aster",
soul_rate = 0.003,
soul_set = "Spectral",
config = {extra = {amount = 2}},
attributes = {"destroy_card", "generation", "spectral"},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, {key = "e_negative_consumable", set = "Edition", config = {extra = 1}})
table.insert(info_queue, negative)
table.insert(info_queue, G.P_CENTERS.c_cryptid)
return {vars = {card.ability.extra.amount}}
end,
can_use = function()
return next(G.playing_cards) and u()
return #G.playing_cards > 1 and not not u()
end,
use = function(_, card)
local function destructible(v)
@ -181,15 +156,25 @@ spectral {
f(cards):each(destroy)
f(G.jokers.cards):each(calculate_joker)
f(card.ability.extra.amount):each(function()
for _ = 1, card.ability.extra.amount do
local cryptid = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_cryptid", "void")
cryptid:set_edition({negative = true}, true)
cryptid:add_to_deck()
G.consumeables:emplace(cryptid)
end)
end
end
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,
}
q(function()
void.hidden = not SMODS.Mods.Cryptid
end)

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,22 @@ if not Balatest then
return
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 {
category = {"spectral", "void"},
name = "spectral",

View file

@ -1,40 +1,54 @@
local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Joker:take_ownership("joker", {cost = 1}, true)
local orig_set_debuff = Card.set_debuff
local f, q = unpack(... or require "lib.shared")
function Card:set_debuff(should_debuff, ...)
if self.ability.Roland_crimson ~= nil then
self.ability.Roland_crimson = not not should_debuff
return
SMODS.Joker:take_ownership("joker", {cost = 1}, true)
local orig_can_highlight = CardArea.can_highlight
local orig_set_debuff = Card.set_debuff
local orig_highlight = Card.highlight
local orig_copy_card = copy_card
---@diagnostic disable-next-line: duplicate-set-field
function CardArea:can_highlight(...)
if self ~= G.consumeables then
return orig_can_highlight(self, ...)
end
if SMODS.get_enhancements(self).m_wild and SMODS.Mods.Roland.config.no_wild_debuff then
--- Allows more flexibility when using the Escapey joker to delete specific consumables.
self.config.highlighted_limit = 1 / 0
return true
end
---@diagnostic disable-next-line: duplicate-set-field
function Card:set_debuff(...)
if self.config and self.config.center_key == "m_wild" then
self.debuff = false
else
orig_set_debuff(self, should_debuff, ...)
orig_set_debuff(self, ...)
end
end
---@diagnostic disable-next-line: duplicate-set-field
function Card:highlight(is_highlighted, ...)
self.highlighted = is_highlighted
if not G.CONTROLLER.HID.controller then
return orig_highlight(self, is_highlighted, ...)
end
end
local orig_use_consumeable = Card.use_consumeable
---@diagnostic disable-next-line: duplicate-set-field
function Card:use_consumeable(area, copier, ...)
if SMODS.Mods.Roland.config.faster_planets and self.ability.consumeable.hand_type then
set_consumeable_usage(self)
level_up_hand(copier or self, self.ability.consumeable.hand_type, true)
return
end
local seal_spectrals = {["Deja Vu"] = true, Medium = true, Talisman = true, Trance = true}
if not seal_spectrals[self.ability.name] then
return orig_use_consumeable(self, area, copier, ...)
end
local card = copier or self
f(Bakery_API.get_highlighted()):each(function(v)
q(function()
play_sound "tarot1"
local card = (copier or self)
card:juice_up(0.3, 0.5)
end)
@ -47,83 +61,55 @@ function Card:use_consumeable(area, copier, ...)
}
end)
q {delay = 0.7, trigger = "after", func = Bakery_API.unhighlight_all}
q {
delay = 0.7,
trigger = "after",
func = function()
Bakery_API.unhighlight_all()
end,
}
end
local orig_use_card = G.FUNCS.use_card
---@diagnostic disable-next-line: lowercase-global
function copy_card(other, new_card, ...)
local ret = orig_copy_card(other, new_card, ...)
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
(not consumeable.hand_type and not consumeable.hand_types) then
return orig_use_card(e, ...)
if new_card and new_card.edition and new_card.edition.key == "e_negative" then
--- Fixes an issue where using 'c_death' will make negative
--- cards do the inverse of what they're supposed to do.
new_card.ability.card_limit = math.max(new_card.ability.card_limit, 1)
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)
discover_card(ref.config.center)
ref:use_consumeable(ref.area)
SMODS.calculate_context {area = ref.area, consumeable = ref, using_consumeable = true}
return ret
end
q(function()
ref:remove()
local orig_can_highlight_area = Bakery_API.can_highlight_area
---@diagnostic disable-next-line: duplicate-set-field
function Bakery_API.can_highlight_area(area, ...)
return area == G.consumeables or orig_can_highlight_area(area, ...)
end
end)
if normal then
return
end
G.GAME.pack_choices = G.GAME.pack_choices - 1
local _ = G.GAME.pack_choices <= 0 and G.FUNCS.end_consumeable()
end
local orig_create_card_for_shop = create_card_for_shop
---@diagnostic disable-next-line: lowercase-global
function create_card_for_shop(...)
---@type Card
local ret = orig_create_card_for_shop(...)
if not SMODS.Mods.Roland.config.illusion_seal or
not G.GAME.used_vouchers.v_illusion or
if not G.GAME.used_vouchers.v_illusion or
not ({Default = true, Enhanced = true})[(((ret or {}).config or {}).center or {}).set] or
pseudorandom(pseudoseed "Roland_illusion") <= 0.8 then
return ret
end
local seal = SMODS.poll_seal {type_key = "Roland_illusion_seal", guaranteed = true}
local seal = SMODS.poll_seal {
type_key = "Roland_illusion_seal",
guaranteed = true,
}
ret:set_seal(seal, true, true)
return ret
end
local orig_get_blind_amount = get_blind_amount
---@param ante number
---@return table|number
local function blind(ante)
return ante == 39 and 1e294 or (to_number or f.id)(orig_get_blind_amount(ante))
end
local function no_harsh_ante_scaling()
return not Talisman or not SMODS.Mods.Roland.config.harsh_ante_scaling
end
function get_blind_amount(ante, ...)
local loop = 39
if ante < loop or no_harsh_ante_scaling() then
return orig_get_blind_amount(ante, ...)
end
if (ante - 9) / 15 >= loop then
return 1 / 0
end
local rem = tonumber(blind((ante % loop) + 1))
return ante / 15 >= loop and Big:new(f(blind(ante - (loop * 15))):map(f.const(10)):table()) or
(ante / 9 >= loop and Big:new(f(ante / loop - 8):map(f.const(10)):concat {rem}:table()) or
(ante / 2 >= loop and Big:new {rem, ante / loop} or
(Big.constants and Big.constants.TEN or Big:new {10}):pow(rem)))
end

View file

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