Compare commits

...

36 commits
2.0.7 ... main

Author SHA1 Message Date
918ebbc30b
Shorten text 2026-04-27 16:59:01 +02:00
8bcd0e074d
Add Invisible Tag 2026-04-27 16:55:58 +02:00
f23dad31cc
Add attributes, buff fat phone, buff temple 2026-04-26 20:38:32 +02:00
7da197900e
Add setting for an overpowered strategy 2026-04-16 20:01:52 +02:00
5d096d5f58
Add G.calckeys and G.calccontext debugging tools 2026-04-09 20:04:27 +02:00
e74f4d44c9
Add new joker 2026-04-04 19:10:59 +02:00
21952a529b
Adjust deck atlas 2026-04-04 16:32:45 +02:00
4ac13e249c
Move Pass GO from deck to challenge 2026-04-04 16:25:28 +02:00
8d8574bf86
Fix crash and localization 2026-04-04 15:48:54 +02:00
2bd613b458
Optimize SMODS.get_enhancements patch 2026-03-21 22:04:12 +01:00
dd71145698
Use SMODS.get_enhancements 2026-03-21 21:53:03 +01:00
51ad870d8c
Fix Frozen on various jokers 2026-03-21 21:42:04 +01:00
f77dbe3ac8
Bump version 2026-03-19 19:42:23 +01:00
d219e79c89
Add dedicated Estate support 2026-03-19 19:41:49 +01:00
11120727f6
Add dev-art 2026-03-19 12:47:16 +01:00
59e9d43703
Buff new joker 2026-03-19 12:46:08 +01:00
78136a494c
Add more joker art 2026-03-19 01:38:42 +01:00
1869e60d91
Add new joker 2026-03-19 01:34:19 +01:00
286d1c6dc7
Add more joker art 2026-03-19 01:05:23 +01:00
455e08bbf7
Remove artifacts 2026-03-19 00:21:25 +01:00
87be46c360
Add more joker art 2026-03-19 00:21:06 +01:00
1bf6ac6a91
Merge branch 'main' into frozen-jokers 2026-03-18 02:38:49 +01:00
9595a18864
Add new joker art 2026-03-18 02:38:04 +01:00
b21f20b127
Add 2 new jokers 2026-03-17 23:02:37 +01:00
bea00f493b
Prevent warning 2026-03-15 03:47:31 +01:00
5c76ee60d2
Fix Frozen Estate edgecase 2026-03-14 12:39:49 +01:00
3f05ac6238
Fix Frozen Turtle Bean edgecase 2026-03-12 02:40:36 +01:00
8533e4c146
Apply to editioned cards 2026-03-09 13:32:42 +01:00
4bf3ef046e
Add support for thread and userdata 2026-03-09 11:33:20 +01:00
5466b2dc35
Add blind dev-art 2026-03-07 19:51:03 +01:00
9a5c531a02
Add sound for clicking the card 2026-03-05 14:25:07 +01:00
a04ac56169
Add new edition and spectral card 2026-03-05 01:49:54 +01:00
2fb6606748
Overhaul harsh ante scaling 2026-03-01 20:13:38 +01:00
6585e1fcc4
Bump version 2026-02-28 21:45:02 +01:00
2a865afc3a
Nerf early harsh ante scaling 2026-02-28 21:44:41 +01:00
8531bde452
Allow Venerable Visage to be bypassed 2026-02-28 21:44:03 +01:00
38 changed files with 1105 additions and 204 deletions

3
.gitignore vendored
View file

@ -38,3 +38,6 @@ luac.out
*.i*86
*.x86_64
*.hex
# Syncthing
.stfolder

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 904 B

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 29 KiB

BIN
assets/1x/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 70 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: 22 KiB

After

Width:  |  Height:  |  Size: 41 KiB

BIN
assets/2x/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

199
assets/shaders/frozen.fs Normal file
View file

@ -0,0 +1,199 @@
#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

BIN
assets/sounds/frozen1.ogg Normal file

Binary file not shown.

BIN
assets/sounds/frozen2.ogg Normal file

Binary file not shown.

BIN
assets/sounds/frozen3.ogg Normal file

Binary file not shown.

BIN
assets/sounds/frozen4.ogg Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/sounds/nilly.ogg Normal file

Binary file not shown.

View file

@ -3,6 +3,8 @@ return {
fusable_escapey = false,
harsh_ante_scaling = true,
illusion_seal = true,
import_funky = false,
no_highlight_limit = true,
no_wild_debuff = true,
scribable_basket = false,
}

View file

@ -9,14 +9,6 @@ return {
"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 = {
name = "Swapper Deck",
text = {
@ -37,7 +29,7 @@ return {
BakeryCharm_Roland_fat = {
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)",
},
@ -60,6 +52,10 @@ return {
},
},
Blind = {
bl_Roland_blizzard = {
name = "The Blizzard",
text = {"All cards", "are Frozen"},
},
bl_Roland_divide = {
name = "The Great Divide",
text = {"Half of the deck", "is discarded"},
@ -109,6 +105,12 @@ return {
},
},
},
Edition = {
e_Roland_frozen = {
name = "Frozen",
text = {"Values on", "this card do", "not change"},
},
},
Enhanced = {
m_wild = {
name = "Wild Card",
@ -116,6 +118,18 @@ return {
},
},
Joker = {
j_Roland_arctic = {
name = "Arctic Circle",
text = {"Retrigger all", "{C:dark_edition}Frozen {}cards"},
},
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 = {
@ -159,6 +173,14 @@ return {
},
},
},
j_Roland_hardboiled = {
name = "Hard-Boiled",
text = {
"Sell this Joker to",
"turn all cards in",
"hand {C:dark_edition}Frozen",
},
},
j_Roland_jokersr = {
name = "Joker Sr.",
text = {"{X:mult,C:white}X#1#{} Mult"},
@ -207,6 +229,14 @@ 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 = {
@ -228,6 +258,10 @@ return {
"least {C:attention}#2# suits",
},
},
j_Roland_suitable = {
name = "Suitable",
text = {"{V:1}#1# {}are {C:attention}Wild", "Suit changes", "every round"},
},
j_Roland_sunny = {
name = "Sunny Side Up",
text = {
@ -286,25 +320,10 @@ return {
name = "Efflorescent Sleeve",
text = {
"{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:dark_edition}({X:dark_edition,C:white}(ante * #2#){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 = {
name = "Swapper Sleeve",
text = {
@ -337,6 +356,14 @@ return {
"cards in {C:hands}hand",
},
},
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",
},
},
c_Roland_mirror = {
name = "Mirror",
text = {
@ -354,6 +381,12 @@ return {
},
},
},
Tag = {
tag_Roland_invisible = {
name = "Invisible Tag",
text = {"{C:attention}Duplicate {}a random Joker", "{C:inactive}(Must have room)"},
},
},
},
misc = {
challenge_names = {
@ -362,6 +395,7 @@ return {
c_Roland_Eternally_Crimson = "Eternally Crimson",
c_Roland_Eternally_Verdant = "Eternally Verdant",
c_Roland_Eternally_Violet = "Eternally Violet",
c_Roland_Go = "Pass GO",
c_Roland_Jokerful = "Jokerful",
c_Roland_Ornate = "Ornate",
c_Roland_Pastries = "Sweet Pastries",
@ -369,23 +403,28 @@ return {
},
v_dictionary = {
b_Roland_bye = "Bye!",
b_Roland_disabled = "Disabled",
b_Roland_enabled = "Enabled",
b_Roland_comma = ", ",
b_Roland_debuffed = "DEBUFFED",
b_Roland_entering_shop = "Entering shop!",
b_Roland_escape = "ESCAPE",
b_Roland_fuse = "FUSE",
b_Roland_debug_export = "Import funky.lua",
b_Roland_equinox_assist = "Assist: Only hide text (Equinox)",
b_Roland_fusable_escapey = "Fusable Escapey (overpowered)",
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_highlight_limit = "No consumable highlight limit",
b_Roland_no_wild_debuff = "No wild card debuffs",
b_Roland_na = "N/A",
b_Roland_of = " of ",
b_Roland_toggle = "TOGGLE",
},
labels = {
Roland_frozen = "Frozen",
roland_glass_seal = "Glass Seal",
},
v_text = {
@ -394,6 +433,7 @@ 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_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"},

View file

@ -6,7 +6,7 @@
"author": [
"Emik"
],
"version": "2.0.7",
"version": "2.2.12",
"badge_colour": "8BE9FD",
"main_file": "src/main.lua",
"badge_text_colour": "44475A",
@ -15,8 +15,8 @@
"provides": [],
"conflicts": [],
"dependencies": [
"Steamodded (>=1.*)",
"Steamodded (>=1.0~*)",
"Lovely (>=0.6)",
"Bakery (>=1.3.2~*)"
"Bakery (>=0.1.26~*)"
]
}

View file

@ -74,8 +74,9 @@ back {
key = "blossom",
pronouns = "any_all",
config = {extra = {times = 2}},
attributes = {"boss_blind"},
loc_vars = function(self, _, _)
return {vars = {self.config.extra.times}}
return {vars = {self.config.extra.times, self.config.extra.alt_times}}
end,
apply = function(_, _)
G.GAME.modifiers.Roland_blossom_deck = true
@ -91,9 +92,11 @@ back {
return
end
local count = self:is_alt() and G.GAME.round_resets.ante or self.config.extra.times
local count = self:is_alt() and
G.GAME.round_resets.ante * self.config.extra.alt_times or
self.config.extra.times
for _ = 1, count do
f(count):each(function()
G.GAME.blind:disable()
q {
@ -104,59 +107,20 @@ back {
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 = function() end,
calculate = f().noop,
}
local swapper = {Spectral = "Tarot", Tarot = "Spectral"}

View file

@ -61,6 +61,26 @@ local function has_enhancement(card)
return not not (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)
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)
end
@ -210,6 +230,30 @@ blind {
end,
}
blind {
key = "blizzard",
boss = {min = 3},
boss_colour = HEX "102a41ff",
pronouns = "it_its",
defeat = function(self)
self.cards():each(set_freeze())
G.GAME.blind.disabled = true
end,
disable = function(self)
self:defeat()
end,
calculate = function(self, b)
return not b.disabled and self.cards():where(function(v)
return not v.Roland_blizzard and v.facing == "front"
end):each(set_freeze(true)) or nil
end,
cards = function()
return f(G):where(function(v)
return type(v) == "table" and type(v.cards) == "table"
end):flatmap("cards", ipairs)
end,
}
blind {
key = "tranquilizer",
boss = {min = 6},
@ -267,7 +311,7 @@ blind {
blind {
key = "improbable",
boss = {min = 4},
boss = {min = 3},
boss_colour = HEX "009966ff",
pronouns = "it_its",
mult = 2,
@ -282,7 +326,6 @@ 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
@ -294,6 +337,8 @@ function SMODS.current_mod:calculate(context)
modifiers.Roland_martingale_seed = (modifiers.Roland_martingale_seed or 0) + 1
end
local _ = type(G.calccontext) == "function" and G.calccontext(context)
local _ = type(G.calckeys) == "function" and G.calckeys(f(context):keys())
local _ = type(G.calc) == "function" and G.calc(f(context):keys():string())
local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities
@ -351,7 +396,6 @@ 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
@ -365,7 +409,6 @@ 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
@ -381,6 +424,7 @@ 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
@ -393,7 +437,10 @@ local venerable_visage = blind {
end)
end
if b.Roland_vitriol or not context.end_of_round or not next(G.deck.cards) or not next(G.hand.cards) then
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
return
end
@ -472,7 +519,6 @@ local venerable_visage = blind {
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

@ -52,6 +52,29 @@ end
local jokerful, vanillas, pastries, amber, cerulean, crimson, verdant, violet, final
= bans(), bans(), bans(), bans(), bans(), bans(), bans(), bans(), bans()
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,
}
SMODS.Challenge {
key = "Jokerful",
rules = {custom = {{id = "Roland_Jokerful"}}},

View file

@ -79,6 +79,7 @@ end
charm {
key = "wii",
pronouns = "they_them",
attributes = {"skip"},
calculate = function(_, card, context)
if not context.skip_blind then
return
@ -116,16 +117,17 @@ end
charm {
key = "fat",
pronouns = "he_they",
-- 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,
attributes = {"passive"},
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
@ -160,6 +162,7 @@ end
charm {
key = "cocacola",
pronouns = "he_they",
attributes = {"passive", "tarot", "spectral"},
equip = function()
f(G.consumeables.cards):each(add_to_consumable_ability_by(1))
end,
@ -184,6 +187,7 @@ 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}}

312
src/edition.lua Normal file
View file

@ -0,0 +1,312 @@
local f, q = unpack(... or require "lib.shared")
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_sounds = f(4):map(frozen_sound):map("key"):table()
local needs_chip_mult_override = {
Bull = true,
Erosion = true,
Misprint = true,
TierList = true,
Bootstraps = 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):where(function(v, k)
return k ~= "Roland_frozen_proxy" and (k ~= "nine_tally" or v ~= 0)
end):table() or x
end
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
local ability, ret = card.ability, card.Roland_frozen_ability
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
SMODS.Edition {
key = "frozen",
shader = "frozen",
sound = {sound = "Roland_frozen", per = 1, vol = 0.8},
attributes = {"passive", "scaling"},
weight = 8,
extra_cost = 4,
in_shop = true,
apply_to_float = false,
calculate = function(_, card)
freeze(card)
end,
on_remove = function(card)
card.Roland_frozen_proxy = nil
card.Roland_frozen_estate = nil
card.Roland_frozen_ability = 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 not ret[1] then
return unpack(ret)
end
local ability = freeze(self)
if not needs_chip_mult_override[ability.name] then
return unpack(ret)
end
ability.Roland_frozen_mult_mod = ability.Roland_frozen_mult_mod or ret[1].mult_mod
ability.Roland_frozen_chip_mod = ability.Roland_frozen_chip_mod or ret[1].chip_mod
ability.Roland_frozen_xmult = ability.Roland_frozen_xmult or ret[1].xmult
return {
chip_mod = ability.Roland_frozen_chip_mod,
mult_mod = ability.Roland_frozen_mult_mod,
xmult = ability.Roland_frozen_xmult,
message = localize {
type = "variable",
key = "a_mult",
vars = {
ability.Roland_frozen_mult_mod or
ability.Roland_frozen_chip_mod or
ability.Roland_frozen_xmult,
},
},
}
end
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
q(function()
local estate = G.P_CENTERS.j_Bakery_Estate
if not estate then
return
end
---@param card Card|{ Roland_frozen_estate: integer }
local function estate_pos(card)
if card.area ~= G.jokers and card.area.config.type ~= "title" then
return 1
end
card.Roland_frozen_estate = card.Roland_frozen_estate and
card.Roland_frozen_estate or
(f(card.area.cards):swap():any(function(_, k)
return k == card
end) or 1)
return card.Roland_frozen_estate
end
local orig_calculate = estate.calculate
function estate:calculate(card, context, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_calculate(self, card, context, ...)
end
if not context.joker_main then
return
end
local joker_count = estate_pos(card)
local extra = card.ability.extra or {}
return {chips = extra.chips * joker_count, mult = extra.mult * joker_count}
end
local orig_loc_vars = estate.loc_vars
function estate:loc_vars(info_queue, card, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_loc_vars(self, info_queue, card, ...)
end
local joker_count = estate_pos(card)
local extra = card.ability.extra or {}
return {vars = {extra.chips * joker_count, extra.mult * joker_count}}
end
end)
q(function()
local proxy = G.P_CENTERS.j_Bakery_Proxy
if not proxy then
return
end
---@param card Card|{ Roland_frozen_proxy: number }
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).unique_val
---@param v Card
local function eq(v)
return v.unique_val == card.Roland_frozen_proxy
end
return f(G.jokers.cards):any(eq) ---@type Card?
end
local orig_calculate = proxy.calculate
function proxy:calculate(card, context, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_calculate(self, card, context, ...)
end
return SMODS.blueprint_effect(card, get_proxied_joker(card), context)
end
local orig_loc_vars = proxy.loc_vars
function proxy:loc_vars(info_queue, card, ...)
if not card or not card.edition or not card.edition.Roland_frozen then
return orig_loc_vars(self, info_queue, card, ...)
end
local other = get_proxied_joker(card)
local var = (other and other ~= card) and localize {
type = "name_text",
set = other.config.center.set,
key = other.config.center.key,
} or localize "k_none"
return {vars = {var}}
end
end)
q(function()
local orig_flip_double_sided = (Bakery_API or {}).flip_double_sided
if not orig_flip_double_sided then
return
end
function Bakery_API.flip_double_sided(card, ...)
if not card.edition or not card.edition.Roland_frozen then
return orig_flip_double_sided(card, ...)
end
end
end)

View file

@ -12,13 +12,19 @@ local joker = (function()
return ret
end
---@param tbl SMODS.Joker|{artist?: string, sinis?: boolean|{x: number, y: number}, soul_pos?: boolean|{x: number, y: number}}
---@param tbl SMODS.Joker|{artist?: string, sinis?: boolean|{x: number, y: number}, soul_pos?: boolean|{x: number, y: number}, attributes?: Attributes[]}
return function(tbl)
tbl.pos = inc()
tbl.atlas = "joker"
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
tbl.soul_pos = tbl.soul_pos and inc() or nil
tbl.sinis = tbl.sinis and inc() or nil
if ((tbl.config or {}).extra or {}).flipped ~= nil then
tbl.config.extra.front_pos = tbl.pos
tbl.config.extra.back_pos = inc()
end
local joker = SMODS.Joker(tbl)
q(function()
@ -31,10 +37,16 @@ local function destructible(card)
return not card.highlighted and not (card.ability or {}).eternal
end
---@param card Card
local function is_carbon(card)
return card.edition and card.edition.key == "e_Bakery_Carbon"
end
---@param card Card
local function is_frozen(card)
return card.edition and card.edition.key == "e_Roland_frozen"
end
local function is_mergeable_with(x)
return function(y)
return x.rank ~= y.rank and
@ -83,10 +95,16 @@ SMODS.Atlas {
py = 95,
}
SMODS.Sound {
key = "nilly",
path = "nilly.ogg",
}
joker {
key = "escapey",
pronouns = "they_them",
config = {extra = {hands = 2}},
attributes = {"destroy_card", "tarot", "planet", "spectral", "tag"},
cost = 4,
rarity = 2,
sinis = true,
@ -117,6 +135,9 @@ joker {
},
}
end,
calculate = function(self, card, context)
return context.forcetrigger and self.proc(card) or nil
end,
Bakery_can_use = function(self, card)
return not card.debuff and u() and (self.exchangable() or self.fusable(card))
end,
@ -128,9 +149,6 @@ joker {
"b_Roland_fuse" or "b_Roland_escape"),
}
end,
calculate = function(self, card, context)
return context.forcetrigger and self.proc(card) or nil
end,
Bakery_use_joker = function(self, card)
if card.debuff then
return
@ -230,6 +248,7 @@ joker {
blueprint_compat = true,
perishable_compat = true,
config = {extra = {chips = 30}},
attributes = {"chips"},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.chips}}
end,
@ -242,7 +261,9 @@ joker {
joker {
key = "mrsbones",
pronouns = "she_her",
artist = "char",
config = {extra = {xmult = 4, requirement = 4}},
attributes = {"xmult"},
cost = G.P_CENTERS.j_mr_bones.cost - 1,
rarity = 2,
eternal_compat = false,
@ -277,6 +298,7 @@ joker {
key = "phytoestrogens",
pronouns = "she_her",
config = {extra = {xmult = 0.25}},
attributes = {"mult", "xmult"},
cost = 8,
rarity = 3,
eternal_compat = true,
@ -300,6 +322,7 @@ joker {
pronouns = "it_its",
artist = "char",
config = {extra = {price = 4, hand_name = "Four of a Kind"}},
attributes = {"sell_value", "scaling", "economy", "hand_type"},
cost = 4,
rarity = 1,
eternal_compat = true,
@ -326,7 +349,9 @@ joker {
joker {
key = "domino",
pronouns = "it_its",
artist = "char",
config = {extra = {mult_gain = 4, hand_name = "Four of a Kind", mult = 0}},
attributes = {"mult", "scaling", "hand_type"},
cost = 4,
rarity = 2,
eternal_compat = true,
@ -355,7 +380,9 @@ joker {
joker {
key = "trimino",
pronouns = "it_its",
artist = "char",
config = {extra = {times = 4, hand_name = "Four of a Kind"}},
attributes = {"generation", "hand_type"},
cost = 8,
rarity = 3,
eternal_compat = false,
@ -383,7 +410,7 @@ joker {
return
end
for _ = 1, extra.times do
f(extra.times):each(function()
G.playing_card = (G.playing_card or 0) + 1
G.deck.config.card_limit = G.deck.config.card_limit + 1
local chosen = pseudorandom_element(scored_cards, pseudoseed "Roland_trimino")
@ -393,7 +420,7 @@ joker {
copy:start_materialize()
table.insert(copied, copy)
table.insert(G.playing_cards, copy)
end
end)
SMODS.calculate_effect({{message = localize "k_copied_ex", message_card = card}}, card)
playing_card_joker_effects(copied)
@ -406,8 +433,10 @@ joker {
}
joker {
key = "sunny", -- Cracked egg
key = "sunny",
pronouns = "they_them",
artist = "char",
attributes = {"food", "on_sell"},
cost = 2,
rarity = 1,
eternal_compat = false,
@ -464,9 +493,60 @@ joker {
end,
}
joker {
key = "hardboiled",
pronouns = "they_them",
attributes = {"food", "editions", "modify_card", "on_sell"},
cost = 5,
rarity = 2,
eternal_compat = false,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
end,
calculate = function(_, _, context)
return (context.selling_self or context.forcetrigger) and f(G.hand.cards):each(function(v)
q {
delay = 0.1,
func = function()
v:set_edition {Roland_frozen = true}
end,
}
end) or nil
end,
}
joker {
key = "basket",
pronouns = "they_them",
attributes = {"food", "generation", "on_sell"},
cost = 8,
rarity = 3,
eternal_compat = false,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, info_queue)
table.insert(info_queue, negative)
end,
calculate = function(_, _, context)
return (context.selling_self or context.forcetrigger) and f(G.consumeables.cards):each(function(v)
q {
delay = 1,
func = function()
play_sound "timpani"
SMODS.add_card {edition = "e_negative", key = v.config.center.key}
end,
}
end) or nil
end,
}
joker {
key = "sapling",
pronouns = "they_them",
artist = "char",
attributes = {"mult", "suit"},
cost = 4,
rarity = 1,
config = {extra = {mult = 15, suits = 3}},
@ -500,8 +580,9 @@ joker {
key = "yard",
pronouns = "he_him",
config = {extra = {money = 2}},
cost = 6,
rarity = 2,
attributes = {"economy"},
cost = 4,
rarity = 1,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
@ -514,9 +595,63 @@ joker {
end,
}
joker {
key = "suitable",
pronouns = "she_they",
attributes = {"suit", "passive", "hearts", "diamonds", "spades", "clubs"},
cost = 4,
rarity = 2,
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_CENTERS.m_wild)
local suit = (G.GAME.current_round.Roland_suitable or {}).suit or "Spades"
return {vars = {localize(suit, "suits_plural"), colours = {G.C.SUITS[suit]}}}
end,
add_to_deck = function()
G.GAME.modifiers.Roland_suitable = (G.GAME.modifiers.Roland_suitable or 0) + 1
end,
remove_from_deck = function()
G.GAME.modifiers.Roland_suitable = (G.GAME.modifiers.Roland_suitable or 0) - 1
end,
}
function SMODS.current_mod.reset_game_globals()
local immutable = f(SMODS.find_card "j_Roland_suitable"):any(is_frozen)
local suitable = {suit = "Spades"}
G.GAME.current_round.Roland_suitable = immutable and
G.GAME.current_round.Roland_suitable or suitable
if immutable then
return
end
local suits = f(G.playing_cards):where(SMODS.has_no_suit, false):table()
local card = pseudorandom_element(suits, "Roland_suitable" .. G.GAME.round_resets.ante)
suitable.suit = card and card.base.suit or suitable.suit
end
local orig_get_enhancements = SMODS.get_enhancements
function SMODS.get_enhancements(card, ...)
if (G.GAME.modifiers.Roland_suitable or 0) <= 0 or
card.base.suit ~= G.GAME.current_round.Roland_suitable.suit or
(card.area ~= G.hand and card.area ~= G.play) then
return orig_get_enhancements(card, ...)
end
local ret = orig_get_enhancements(card, ...) or {}
ret.m_wild = true
return ret
end
joker {
key = "misfortune",
pronouns = "she_they",
artist = "char",
attributes = {"discard", "generation", "seals"},
cost = 6,
rarity = 2,
eternal_compat = true,
@ -536,7 +671,8 @@ joker {
joker {
key = "temple",
pronouns = "any_all",
config = {extra = {xmult = 1.5}},
config = {extra = {xmult = 2}},
attributes = {"xmult", "enhancements"},
cost = 6,
rarity = 2,
eternal_compat = true,
@ -563,6 +699,7 @@ joker {
key = "jokersr",
pronouns = "he_him",
config = {extra = {xmult = 1.25}},
attributes = {"xmult"},
cost = 2,
rarity = 2,
eternal_compat = true,
@ -580,16 +717,27 @@ joker {
joker {
key = "bulldozer",
pronouns = "it_its",
attributes = {"xmult"},
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(self, _, _)
return {vars = {self.xmult()}}
loc_vars = function(self, _, card)
return {vars = {self:xmult_frozen(card)}}
end,
calculate = function(self, card, context)
return (context.joker_main or context.forcetrigger) and {card = card, xmult = self.xmult()} or nil
return (context.joker_main or context.forcetrigger) and {card = card, xmult = self:xmult_frozen(card)} or nil
end,
xmult_frozen = function(self, card)
if not is_frozen(card) then
return self.xmult()
end
local ability = card.Roland_frozen_ability or {}
card.Roland_frozen_ability = ability
ability.Roland_frozen_xmult = ability.Roland_frozen_xmult or self.xmult()
return ability.Roland_frozen_xmult
end,
xmult = function()
local function mult(id)
@ -608,6 +756,7 @@ joker {
key = "martingale",
pronouns = "he_him",
config = {extra = {odds = 2}},
attributes = {"xmult", "chance"},
cost = 8,
rarity = 2,
eternal_compat = true,
@ -656,6 +805,7 @@ joker {
cost = 7,
rarity = 3,
config = {extra = {probability = 1, probability_mult = 2, reset = 1}},
attributes = {"hands", "mod_chance", "scaling"},
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
@ -685,3 +835,75 @@ joker {
end
end,
}
joker {
key = "nilly",
pronouns = "any_all",
config = {extra = {flipped = false}},
attributes = {"xmult"},
cost = 0,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {
vars = {localize {
type = "variable",
key = card.ability.extra.flipped and "b_Roland_enabled" or "b_Roland_disabled",
}},
}
end,
inject = function(...)
SMODS.Joker.inject(...)
Bakery_API.double_sided_jokers.j_Roland_nilly = true
end,
calculate = function(_, card, context)
if (not context.joker_main or not card.ability.extra.flipped) and not context.forcetrigger then
return
end
mult = mod_mult(0)
update_hand_text({delay = 0}, {chips = hand_chips, mult = mult})
return {sound = "Roland_nilly", message = "0X " .. localize "k_mult", colour = G.C.MULT, card = card}
end,
Bakery_can_use = function(_, card)
return not card.debuff
end,
Bakery_use_button_text = function(_, card)
return localize {
type = "variable",
key = card.debuff and "b_Roland_debuffed" or "b_Roland_toggle",
}
end,
Bakery_use_joker = function(_, card)
local _ = card.debuff or Bakery_API.flip_double_sided(card)
end,
}
joker {
key = "arctic",
pronouns = "it_its",
cost = 10,
rarity = 3,
config = {extra = {repetitions = 1}},
attributes = {"retrigger", "editions"},
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, info_queue)
table.insert(info_queue, G.P_CENTERS.e_Roland_frozen)
end,
calculate = function(_, card, context)
return (context.repetition and is_frozen(context.other_card)) and {repetitions = 1} or SMODS.merge_effects(
f(G.jokers.cards):where(is_frozen):map(function(v)
return SMODS.blueprint_effect(card, v, context) or true
end):where(function(v)
return type(v) == "table"
end):map(function(v)
v.colour = G.C.BLACK
return v
end):table()
)
end,
}

View file

@ -103,32 +103,33 @@ end
--- Creates an enumeration.
---@generic K, V
---@param tbl table<K, V>
---@param iter table<K, V>
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K?, V?)
---@param step? nil
---@return F | { [K]: V }
---@overload fun(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
---@overload fun(iter: number, fpairs?: number, step?: number): F | { [number]: number }
---@overload fun(iter: string): (fun(table: { [string]: V }): V)
---@overload fun(iter: fun(): K, V): F | { [K]: V }
---@overload fun(iter: false): fun(): false
---@overload fun(iter: true): fun(): true
---@overload fun(iter: nil): F
function f.from(iter, fpairs, step)
if iter == true then
return f.tru
elseif tbl == false then
elseif iter == false then
return f.fals
elseif tbl == nil then
elseif iter == nil then
return none
end
local tbl_type = type(tbl)
local t = type(iter)
if tbl_type == "string" then
return f.index(tbl)
elseif tbl_type == "number" then
local ik, is, start = 0, step or 1, fpairs and tbl or 1
if t == "string" then
return f.index(iter)
elseif t == "number" then
local ik, is, start = 0, step or 1, fpairs and iter or 1
local stop = not fpairs and tbl or
local stop = not fpairs and iter 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
@ -145,16 +146,24 @@ function f.from(tbl, fpairs, step)
return ik, iv
end
end)
elseif tbl_type ~= "table" then
error("Invalid argument type for 'tbl': " .. type(tbl))
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
end
end)
else
local next, context, k, v = autopairs(iter, fpairs)
return f.new(function()
k, v = next(context, k)
return k, v
end)
end
local next, context, k, v = autopairs(tbl, fpairs)
return f.new(function()
k, v = next(context, k)
return k, v
end)
end
---@generic K, V
@ -220,7 +229,7 @@ end
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@overload fun(self: F|{ [K]: V }, func: string, fpairs?: fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)): F|{ [K]: U }
function f:flatmap(func, fpairs)
-- local i = 0
local vt, vk, vv, vp
@ -267,10 +276,11 @@ end
---@generic K, V
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): boolean
---@param is? any
---@return F|{ [K]: V }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: V }
function f:where(func)
---@overload fun(self: F|{ [K]: V }, func: string, is?: any): F|{ [K]: V }
function f:where(func, is)
func = type(func) == "string" and f.index(func) or func
return f.new(function()
@ -283,7 +293,15 @@ function f:where(func)
return
end
if func(v, k) then
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
@ -324,6 +342,20 @@ 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
@ -409,7 +441,7 @@ end
---@param func F|fun(v: V, k: K): boolean
---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
function f:any(func)
func = type(func) == "string" and f.index(func) or func
@ -427,7 +459,7 @@ end
---@param func F|fun(v: V, k: K): boolean
---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
function f:all(func)
func = type(func) == "string" and f.index(func) or func

View file

@ -23,6 +23,7 @@ local function protect_ev(fun)
return fun
end
---@alias Attributes "mult"|"chips"|"xmult"|"xchips"|"score"|"xscore"|"blindsize"|"xblindsize"|"balance"|"swap"|"retrigger"|"scaling"|"reset"|"suit"|"diamonds"|"hearts"|"spades"|"clubs"|"hand_type"|"rank"|"ace"|"two"|"three"|"four"|"five"|"six"|"seven"|"eight"|"nine"|"ten"|"jack"|"queen"|"king"|"face"|"economy"|"generation"|"destroy_card"|"hands"|"discard"|"hand_size"|"chance"|"joker_slot"|"mod_chance"|"copying"|"full_deck"|"passive"|"joker"|"tarot"|"planet"|"spectral"|"enhancements"|"seals"|"editions"|"tag"|"skip"|"modify_card"|"perma_bonus"|"prevents_death"|"boss_blind"|"reroll"|"on_sell"|"sell_value"|"food"|"space"
if false then
-- This allows for better type inference.
SMODS.Mods.Roland.config = require "config"

View file

@ -1,11 +1,11 @@
local qol = assert(SMODS.load_file "src/lib/shared.lua")() or require "lib.shared"
local f, q = unpack(qol)
f {"challenge", "spectral", "tweaks", "blind", "charm", "joker", "back", "seal"}:each(function(v)
f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "back", "seal", "tag"}:each(function(v)
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
end)
if Balatest then
if _G["Balatest"] then
f {"joker", "blind", "spectral"}:each(function(v)
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
end)
@ -13,7 +13,7 @@ end
q(function()
---@type table<string, {name: string, fg: table, bg: table}>
local contributors = Bakery_API.contributors
local contributors, mods = Bakery_API.contributors, SMODS.Mods
contributors.Roland_aster = {
name = "asterSSH",
@ -26,6 +26,10 @@ q(function()
fg = HEX "f8f8f2ff",
bg = HEX "ff79c6ff",
}
if mods.DebugPlus and mods.Roland.config.import_funky then
_G.f, _G.q, _G.u = unpack(qol)
end
end, true)
local function toggle(id)
@ -48,41 +52,23 @@ 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},
nodes = {
{
n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR},
nodes = {
toggle "illusion_seal",
toggle "no_wild_debuff",
toggle "no_highlight_limit",
_G["Talisman"] and toggle "harsh_ante_scaling",
toggle "fusable_escapey",
toggle "equinox_assist",
},
nodes = {{
n = G.UIT.C,
config = {minw = 1, minh = 1, align = "tl", padding = 0.1, colour = G.C.CLEAR},
nodes = {
toggle "illusion_seal",
toggle "no_wild_debuff",
toggle "no_highlight_limit",
_G["Talisman"] and toggle "harsh_ante_scaling",
toggle "fusable_escapey",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
toggle "equinox_assist",
SMODS.Mods.DebugPlus and toggle "import_funky",
},
SMODS.Mods.DebugPlus and {
n = G.UIT.C,
config = {minw = 2, minh = 2, align = "tl", padding = 0.2, colour = G.C.CLEAR},
nodes = {
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,
},
},
}},
}},
}
end
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

@ -11,6 +11,7 @@ SMODS.Seal {
key = "glass",
atlas = "seal",
pos = {x = 0, y = 0},
attributes = {"destroy_card", "tag"},
badge_colour = HEX "a6a6a6ff",
pronouns = "he_him",
calculate = function(_, card, context)

View file

@ -1,7 +1,5 @@
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
@ -40,9 +38,10 @@ spectral {
key = "afterimage",
pronouns = "he_they",
artist = "aster",
attributes = {"editions", "modify_card", "hand_size", "spectral"},
config = {extra = {amount = 1, hand = -2}},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, negative)
table.insert(info_queue, {key = "e_negative_playing_card", set = "Edition", config = {extra = 1}})
return {vars = {card.ability.extra.amount, card.ability.extra.hand}}
end,
can_use = function(_, card)
@ -54,7 +53,6 @@ spectral {
delay = 0.1,
func = function()
v:set_edition {negative = true}
v:juice_up(0.5, 0.5)
end,
}
end)
@ -69,10 +67,36 @@ spectral {
end,
}
spectral {
key = "coolheaded",
config = {extra = {amount = 1}},
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)
return u() and f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):count(function(v)
return not v.edition
end) == card.ability.extra.amount
end,
use = function()
f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):each(function(v)
q {
delay = 0.1,
func = function()
v:set_edition {Roland_frozen = true}
end,
}
end)
end,
}
spectral {
key = "dual",
pronouns = "they_them",
config = {extra = {amount = 2}},
attributes = {"seal", "modify_card", "spectral"},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.amount}}
end,
@ -86,7 +110,7 @@ spectral {
f(cards):take(card.ability.extra.amount):each(function(v)
local seal
for _ = 1, 31 do
for _ = 1, 64 do
seal = SMODS.poll_seal {guaranteed = true}
if seal ~= "Roland_glass" then
@ -103,6 +127,7 @@ spectral {
key = "mirror",
pronouns = "he_him",
config = {extra = {amount = 1}},
attributes = {"seal", "modify_card", "spectral"},
loc_vars = function(_, info_queue, card)
table.insert(info_queue, G.P_SEALS.Roland_glass)
return {vars = {card.ability.extra.amount}}
@ -124,8 +149,9 @@ local void = spectral {
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, negative)
table.insert(info_queue, {key = "e_negative_consumable", set = "Edition", config = {extra = 1}})
table.insert(info_queue, G.P_CENTERS.c_cryptid)
return {vars = {card.ability.extra.amount}}
end,
@ -156,12 +182,12 @@ local void = spectral {
f(cards):each(destroy)
f(G.jokers.cards):each(calculate_joker)
for _ = 1, card.ability.extra.amount do
f(card.ability.extra.amount):each(function()
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)

34
src/tag.lua Normal file
View file

@ -0,0 +1,34 @@
SMODS.Atlas {
px = 34,
py = 34,
key = "tag",
path = "tag.png",
}
SMODS.Tag {
key = "invisible",
atlas = "tag",
min_ante = 2,
pos = {x = 0, y = 0},
apply = function(_, tag)
local modifiers = G.GAME.modifiers
if tag.triggered or
not next(G.jokers.cards) 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)
G.jokers:emplace(copy)
copy:add_to_deck()
modifiers.Roland_invisible = (modifiers.Roland_invisible or 0) - 1
return true
end)
end,
}

View file

@ -17,9 +17,7 @@ function CardArea:can_highlight(...)
end
function Card:set_debuff(...)
if self.config and
self.config.center_key == "m_wild" and
SMODS.Mods.Roland.config.no_wild_debuff then
if SMODS.get_enhancements(self).m_wild and SMODS.Mods.Roland.config.no_wild_debuff then
self.debuff = false
else
orig_set_debuff(self, ...)
@ -113,41 +111,49 @@ function create_card_for_shop(...)
return ret
end
local hundred, orig_get_blind_amount = f().const(100), get_blind_amount
local orig_get_blind_amount = get_blind_amount
---@type fun(x: table|number): number
local function blind(offset)
return (_G["to_number"] or f().id)(orig_get_blind_amount(offset))
---@param ante number
---@return table|number
local function blind(ante)
return ante == 39 and 1e294 or (_G["to_number"] or f().id)(orig_get_blind_amount(ante))
end
local function no_harsh_ante_scaling()
return not _G["Talisman"] or not SMODS.Mods.Roland.config.harsh_ante_scaling
end
function get_blind_amount(ante, ...)
if ante <= 38 or not _G["Talisman"] or not SMODS.Mods.Roland.config.harsh_ante_scaling then
local loop = 39
if ante < loop or no_harsh_ante_scaling() then
return orig_get_blind_amount(ante, ...)
end
if ante >= 100 then
if (ante - 9) / 15 >= loop then
return 1 / 0
end
--- @type { new: fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table }
local big = _G["Big"]
--- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) }
local big, rem = _G["Big"], tonumber(blind((ante % loop) + 1))
if ante >= 91 then
local offset = ante - 91
local table = f(blind(offset)):map(hundred):table()
return big:new(table)
end
if ante >= 63 then
local offset = ante - 63
local table = f((offset / 4) + 1):map(hundred):concat {blind((offset % 4) * 8)}:table()
return big:new(table)
end
if ante >= 42 then
local offset = ante - 42
return big:new {blind((offset % 3) * 8), math.floor(offset / 3) + 2}
end
return big:new {ante - 36, 2}
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
q(function()
if not G.P_CENTERS.c_Bakery_Scribe then
return
end
local orig_can_use = G.P_CENTERS.c_Bakery_Scribe.can_use
function G.P_CENTERS.c_Bakery_Scribe.can_use(...)
return orig_can_use(...) and
(SMODS.Mods.Roland.config.scribable_basket or f(G.jokers.highlighted):all(function(v)
return v.config.center.key ~= "j_Roland_basket"
end))
end
end)