#💻・modding-dev
1 messages · Page 660 of 1
other_joker doesn't exist in the first if
ah okay
vanlla remade is slightly different than the version i am using
what is different?
the way everything iis alligned when i put it into my code
name = "Jokerless",
id = 'c_jokerless_1',
rules = {
custom = {
{id = 'no_shop_jokers'},
},
modifiers = {
}
},
jokers = {
{id = 'j_canio'},
{id = 'j_triboulet'},
{id = 'j_yorick'},
{id = 'j_chicot'},
{id = 'j_perkeo'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
},
banned_tags = {
},
banned_other = {
}
} ```
the error wo't copy
change canio into caino
oh ii thought i rechanged it
oh i copied the wrong code
name = "Jokerless",
id = 'c_jokerless_1',
rules = {
custom = {
{id = 'no_shop_jokers'},
},
modifiers = {
}
},
jokers = {
{id = 'j_caino'},
{id = 'j_triboulet'},
{id = 'j_yorick'},
{id = 'j_chicot'},
{id = 'j_perkeo'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
},
banned_tags = {
},
banned_other = {
}
}
}
} ```
I dont like how the code starts with "{"
Is there any code before this
before what is sent on the message
yes the other challenges are berfore it
can you screenshot the top off the code
for the jokerless
I just wanna be extra sure on something
yes
what code do you have to replace the code of the existing challenge?
SMODS.Challenge:take_ownership('jokerless', {
{
name = "Jokerless",
id = 'c_jokerless_1',
rules = {
custom = {
{id = 'no_shop_jokers'},
},
modifiers = {
}
},
jokers = {
{id = 'j_caino'},
{id = 'j_triboulet'},
{id = 'j_yorick'},
{id = 'j_chicot'},
{id = 'j_perkeo'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
},
banned_tags = {
},
banned_other = {
}
}
}
}, true)
I wanna like look at this
at the top it says it will replace the existing jokerless challenge
Should i put this n and try it
hold on let me just restart this mod because itcnat open no matter what i do
ok i have a normal balatro now
what would be the if statement to check if a card already has a seal(any kind) on it?
like if a joker wants to apply a seal, i dont want that seal replacing the one that's already there
One last thing does anyone know how to add new pages to the challenge menu
they are automatically added when you add more challenges
i'd suggest you read through https://github.com/Steamodded/smods/wiki/SMODS.Challenge to make sure you get it right
put the drawstep stuff into soul_pos.draw but replace G.shared_soul with card.children.floating_sprite
And remove the draw_major thing
And then remove the drawstep itself ofc
"put the drawstep stuff into soul_pos.draw"
wha
sorry my brain isnt braining right now
soul_pos.draw
soul_pos = {x = 0, y = 0, draw = function(card, scale_mod, rotate_mod) end}
ah
yay it works!
aw man
It dosent work when I make it trans
this is what I mean
when I use the malverk texture pack
I swear I thought you were just using a modding term
it dosent do the bouncy thing anymore
I wanted to make sure I specified
as funny as it would be, that is sadly not a term anyone would use lol
😭 It sounds kinda wrong without the message below
SADLY?
WHAT DO YOU MEAN "SADLY" ? 😭
idk tbh, i just say shit sometimes ¯_(ツ)_/¯
ok 😭
what part of the code would tarot card effects be in
malverk doesn't have custom draw functions for textures
aw shucks
if card.seal, but you should use card:get_seal() if you want to get the seal or check if it has a seal but not for the reason of not replacing it by adding another seal.
SMODS.Joker({
key = "sciencelab",
config = { extra = { odds = 4, retriggers = 2 } },
loc_txt = {
["name"] = "Science Lab",
["text"] = {
{
[1] = "Played hand has a {C:green}#1# in #2#{}",
[2] = "chance to retrigger cards twice.",
[3] = "If successful, {C:attention}destroy{} this Joker",
}
},
},
pos = { x = 9, y = 5 },
cost = 5,
rarity = 2,
blueprint_compat = true,
eternal_compat = true,
perishable_compat = true,
unlocked = true,
discovered = false,
atlas = "CustomJokers",
loc_vars = function(self, info_queue, card)
local numerator, denominator = SMODS.get_probability_vars(card, 1, card.ability.extra.odds, 'j_hatch_sciencelab')
return { vars = { numerator, denominator, card.ability.extra.repetitions } }
end,
calculate = function(self, card, context)
if context.repetition and card.should_retrigger then
return { repetitions = card.ability.extra.repetitions }
end
if context.main_scoring and context.cardarea == G.play then
card.should_retrigger = false
if SMODS.pseudorandom_probability(card, 'j_hatch_Sciencelab', 1, card.ability.extra.odds, 'j_hatch_sciencelab') then
card.should_retrigger = true
card.ability.extra.repetitions = 2
end
G.E_MANAGER:add_event(Event({
func = function()
card:start_dissolve()
return true
end
}))
end
end
})
idk what im doing wrong
but its not doing anything
Jokers don't have context.main_scoring
context.before and remove the context.cardarea check.
ok i did that
it still doesnt do anything
it should be retriggering all played cards twice and then killing itself
nvm i think i figured it out
ok i fixed it
i decided to frankenstein some code together
well at least it works
if you ever want to, you can come back and make it cleaner later
SMODS.Joker({
key = "sciencelab",
config = { extra = { odds = 4, hands_left = 1 } },
loc_txt = {
["name"] = "Science Lab",
["text"] = {
{
[1] = "Played hand has a {C:green}#1# in #2#{}",
[2] = "chance to retrigger cards twice.",
[3] = "If successful, {C:attention}destroy{} this Joker",
}
},
},
pos = { x = 9, y = 5 },
cost = 5,
rarity = 2,
blueprint_compat = true,
eternal_compat = true,
perishable_compat = true,
unlocked = true,
discovered = false,
atlas = "CustomJokers",
loc_vars = function(self, info_queue, card)
local numerator, denominator = SMODS.get_probability_vars(card, 1, card.ability.extra.odds, card.ability.extra.hands_left, 'j_hatch_sciencelab')
return { vars = { numerator, denominator, card.ability.extra.repetitions } }
end,
calculate = function(self, card, context)
if context.repetition and context.cardarea == G.play and SMODS.pseudorandom_probability(card, 'j_hatch_sciencelab', 1, card.ability.extra.odds) then
return {
repetitions = 2
}
end
if context.after and SMODS.pseudorandom_probability(card, 'j_hatch_sciencelab', 1, card.ability.extra.odds) then
SMODS.destroy_cards(card, nil, nil, true)
return {
message = "Destroyed!"
}
end
end
})```
the current code
not too shabby if i do say so myself c:
that doesn't do what you want it to do
this gives each scored card a separate 1 in 4 chance to be retriggered twice, and then the joker a separate 1 in 4 chance to be destroyed
question is, do they want it to go through the whole hand before self destruct, or self destruct the moment even a single card retriggers?
either way, you'd need to remove the second pseudorandom_probability
concepting another joker
what do you think
trying to make some jokers that can help defend from chat commands
is your mod on github
is it going to destroy itself after it's out of protections?
probably not
i'd reccomend it does tbh, just for it to be clear to the player that they're vulnerable again lol
other than that, it's neat :3
how would i generate a random joker that is compatible with a certain sticker (perishable, in this case?) i don't need the joker to actually have said sticker, just not generate jokers like ride the bus, square, etc
it is but just a private repo right now until I feel like it's playable enough for ppl to come accross and download
understandable
gulp
how do i fix it
All I'm curious is how you connect to twitch via socket
im also currently trying to figure out how to have a joker create a random tag
I think they changed it to use a login token
from twitch
cause when u open balatro and click connect opens authorization page to link your twitch account
context.before check your chance and set an internal flag
context.repetition check for flag and send repetitions
context.after check for flag and destroy
simply remove the second pseudorandom_probability above the joker destruction
if context.after then
SMODS.destroy_cards(card, nil, nil, true)
return {
message = "Destroyed!"
}
end
end
})
I see, that's more complicated but understandable way
Originally it was based off ur twitch blind mod
I dunno what they changed
since they are a lot smarter than me when it comes to this i'm just concepting jokers and making some commands lol
will it trigger even if i dont roll the 1 in 4 chance
also how do i have a joker make a random tag
I see, makes sense
local tag_pool = get_current_pool('Tag')
local selected_tag = pseudorandom_element(tag_pool, 'modprefix_seed')
local it = 1
while selected_tag == 'UNAVAILABLE' do
it = it + 1
selected_tag = pseudorandom_element(tag_pool, 'modprefix_seed_resample'..it)
end
?
ok idk why it did that
add_tag({key = get_next_tag_key(self.key)})
oke
that function will break seeding fyi
No, append is added to all the seeds, so it shouldn't.
oh true I am blind nvm
bump
In what sense?
Oh i see
Im pretty sure card.sort_id is what youre looking for
Since that is unique per card and increases per card created
hell yeah
thank ya kindly
how do I add something to a table that is already constructed?
im assuming you mean appending to a number indexed table?
Which would be table[#table+1] = x
They should be able to hook SMODS.restart_game. I originally planned to make a system to aid in shutting down threads but I had to reduce scope due to time constraints. It likely will come later. Also sorry I haven't had time to look at handy, how's it going
or use table.insert
I hooked game restart to kill a thread so for now it's fine
About archives is low priority so no progress for now
(jokerforge) what in the world is my joker doing
I have a construct method making my jokers, and I have one joker that needs a soul_pos value, but none of the others do, so I'm trying to construct the joker and then add on a soul_pos after it gets constructed
joker.soul_pos = ...
suite
joker being whatever table neds that
would that be like j_modID_jokerName or something
huh
oh nvm, it looks like the key is all it needs
what are you actually doing
I figured it out
You could also
why does your joker contructor use positional arguments..
let that function do soul_pos
wtf is this code
And also that looks like torture coding
I could, but then every other joker would have a nil value
wdym
What
What do you mean
nil is the absence of a value
no other joker would need a soul_pos
that just means it doesnt exist
yes
I'd have to write nil 20 times, versus writing soul_pos once
I do believe this guy is python dev
oh boy
am I doing something very bad by lua standards
I'm used to Python, Java, and c#
Why are you using a wierd function to begin with
ez
why is it function(key, set, pos, rarity, config, info_queue, calculate) this looks like a nightmare
that's how my other jokers are written
but why
that' actually crazy
damn 😔
we stopped writing like this after 0.9.8 because it was silly
python has named arguments
idk, I think I got the order from the smods wiki or something and just never changed it
the order isn't the problem
oh
why do you use a constructor function that requires you fill out arguments in a specific order (that doesn't even include all the options) to generate a table to feed into SMODS.Joker instead of writing the table yourself?
what am I doing wrong? I'm not used to lua 😅
ohhh
return { vars = { card.ability.extra.mult, card.ability.extra.dollars, card.ability.extra.mult_gain } }
end,
calculate = function(self, card, context)
if context.end_of_round and context.game_over == false and not context.blueprint then
return {
card.ability.extra.mult == card.ability.extra.mult + card.ability.extra.mult_gain,
dollars = card.ability.extra.dollars * -1
}
end
if context.joker_main then
return {
mult = card.ability.extra.mult
}
end
end
})```
these can be in any order or be missing
I don't think the mult gain should be in a return
well- ok, I know that's an option, but making a constructor function saves me a decent amount of time having to rewrite all the variable names
oo
apart from it being hyper restrictive
I mean, global replace
alright fine, I'll go back to how it was before 😔
I mean you can do it however you like
it's just the way you have chosen makes it harder to do imo
also if you have lsp you can use autocomplete to save time or vscode snippets or copypasting
oke i made it work
would like some help to know if im overcomplicating this
the pitch is an enhancement that, when held in hand, retriggers a random scoring card
i tried experimenting a bit but couldn't seem to make it work from within the smods section? i think because of when the held in hand cards are usually checked
is there an easier way to tell a card it needs to retrigger when the source is a card held in hand than messing with eval_card manually?
Yes, you need to use the mod calculate.
how do you mean? adding it into the enhancements calculate? it didnt seem to retrigger it when i tried that, im assuming because the held in hand cards arent usually checked then
will tinker with it again and come back if it doesnt work lmao
No, SMODS.current_mod.calculate
ooooh didnt know that existed, will try messing w that
got this working! but only for the first card causing the effect
i know i could just cause it to retrigger as many times as it should, but i'd really like to make each mirror card do the "again!" pop up individually, is there a good way to do that?
(note the first variable used to say sources before i was tinkering more so thats not the cause lol)
your for loop doesn't loop because of the return
i imagined that was the case but not sure how to make it work
you need to use SMODS.merge_effects
is there a good place to find out how to use that? not sure where i'd look on the wiki
oh wait maybe found it in utility
thank you! 
Quick question, I'm trying to make an unlock condition for my cards where you have to discover a certain amount of a custom consumable. I checked to see if it tracks automatically, and I can just call over, and it doesn't seem like it. Do I have to create a custom one on the code? and how would I do that?
check G.DISCOVER_TALLIES
Does anyone know how to make every card in a deck debuffed as a challenge deck
ok, this is really nice haha. I'm glad I shifted it back
before, it wouldn't collapse correctly
How could I modify the amount of xmult an individual glass card gives?
I figured out how to change the display but it doesn't seem to change the amount given
did you use take_ownership?
no I did this
--- Lost Media
SMODS.Joker{
key = "lostmedia",
atlas = "jokers",
pos = {x=6,y=0},
rarity = 2,
cost = 6,
pools = {["Song"] = true, ["MonochroMenace"] = true},
config = {
extra = {
inc = 1
}
},
loc_vars = function(self, info_queue, card)
info_queue[#info_queue + 1] = G.P_CENTERS.m_glass
return {vars = {card.ability.extra.inc}}
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and context.other_card then
if SMODS.has_enhancement(context.other_card, "m_glass") then
SMODS.scale_card(context.other_card, {
ref_table = context.other_card.ability,
ref_value = "foobar_numerator",
scalar_table = card.ability.extra,
scalar_value = "inc",
no_message = true,
operation = function(ref_table, ref_value, initial, change)
ref_table[ref_value] = (initial or 1) + change
end
})
SMODS.scale_card(context.other_card, {
ref_table = context.other_card.ability,
ref_value = "Xmult",
scalar_table = card.ability.extra,
scalar_value = "inc",
scaling_message = {
message = "Tap the glass..."
}
})
SMODS.scale_card(context.other_card, {
ref_table = context.other_card.ability,
ref_value = "extra",
scalar_table = card.ability.extra,
scalar_value = "inc",
no_message = true
})
end
end
end
}
oic, it's in a joker
card.ability.Xmult = number; card.ability.x_mult = number
thats what im doing
You should be checking if context.other_card.config.center.key == 'm_glass', because if the card had glass as a quantum enhancement you wouldn't be able to change the xmult
so do that instead of has enhnacement?
ah
that still doens't explain why its not working for normal glass cards though
it gave x2 instead of the x3 it shoudl've given
code?
its right above
.
it displays properly but is only giving x2
did you edit this code since learning you need to use the context somethingcom pointed out?
not yet, but that wouldn't make a difference for why it isn't workign
since this is a regular glass card
the conditional is triggering to increase the stats
but the stats don't seem to be actually increasing
display visually showing as such
glass cards have two different xmult values
I'm not seeing any returns in this calculate function
oh, you're using scale_card. I see
that seems like not the best option
oh whats the other one
theres nothing to return
card.ability.x_mult and card.ability.Xmult
is x_mult the calculated and Xmult visual?
either that or the reverse, yes
ight thx
Yes, but do I gonna have to go and make a whole new G.DISCOVER_TALLIES and patch it or is it automatic
it's automatic
ok so I just make a var or something
i've read this page but i didnt get it at all
where should the UI code be written?
i can see that there are ways to make a table but i dont get where to send it
I want to change strength's effect to lower rank of 2 cards how would I do that
probably just look how strength does it and just reverse it
I cant find tarot card effects in the code
well in whatever function that runs when you want the UIBox to pop up
i recommend checking the mod object wiki for an example with the mod config or other mods
line 676
strength
i can't find tarot.lua iin my actual balatro copy
?
bc there is no tarot.lua??
vanillaremade is vanilla content remade with smods functions
you're expected to reference the repo, not try and find the vanilla counterpart in the source code
Is there an action in a function to skip to the end of round payout after selecting a blind?
end_round() will either win or lose depending on whether the blind requirements are met
nice, how do I turn down the blind requirements to -1?
you could just set your score to the blind requirements
either or, I just dunno how to do either lol-
I am modding the mac os apple arcade version so there is nothing to help with making mods
here's a code snippet from debugplus for you
G.GAME.chips = G.GAME.blind.chips
G.STATE = G.STATES.HAND_PLAYED
G.STATE_COMPLETE = true
end_round()```
Thank you very much!
It didn't work
locked_loc_vars = function(self, info_queue, card)
return { vars = { 4 } }
end,
check_for_unlock = function(self, args) -- equivalent to `unlock_condition = { type = 'discover_amount', tarot_count = 22 }`
return args.type == 'discover_amount' and #G.P_CENTER_POOLS.fnaf_item <= G.DISCOVER_TALLIES.fnaf_item.tally
end
So I have an issue afterward because I want my joker to beat the boss blind when it's selected, but it keeps the 8 cards in my hand
well, i see that there are ways to modify mod's credit page, but what about spawning new menus during gameplay? or at least how to set up that menu and show it when the gameplay requires it
that's what the state stuff should've fixed
you would call the function that creates the uibox at the appropriate moment
try putting it in an event
i dont have many good examples but i have this for when you press a button on a joker (the button is defined elsewhere but it has button = "joy_perform_summon")
https://github.com/nh6574/JoyousSpring/blob/16557e58b2db9315a2d767524877e380edf3c2a2/src/summon.lua#L1106
so something like this?
calculate = function(self,card,context)
if context.setting_blind and (G.GAME.blind:get_type() == 'Boss') then
G.E_MANAGER:add_event(Event({
func = function()
G.GAME.chips = G.GAME.blind.chips
G.STATE = G.STATES.HAND_PLAYED
G.STATE_COMPLETE = true
end_round()
SMODS.destroy_cards(card, nil, nil, true)
return true
end
}))
end
end
maybe put the destroy_cards before
probably timing issue, make sure the tables exist first
tried to make a function file to see if it works but it didn't
function fnaf_set_discover_tallies()
G.DISCOVER_TALLIES = G.DISCOVER_TALLIES or {
fnaf_item = {tally = 0, of = 0},
}
for _, v in pairs(G.DISCOVER_TALLIES) do
v.tally = 0
v.of = 0
end
for _, v in pairs(G.P_CENTERS) do
if not v.omit then
if v.set and v.set == 'fnaf_item' then
G.DISCOVER_TALLIES.fnaf_item.of = G.DISCOVER_TALLIES.fnaf_item.of+1
if v.discovered then
G.DISCOVER_TALLIES.fnaf_item.tally = G.DISCOVER_TALLIES.fnaf_item.tally+1
end
end
end
end
if check_for_unlock then check_for_unlock({type = 'discover_amount',
fnaf_item_count = G.DISCOVER_TALLIES.fnaf_item.tally})
end
end
you don't need to set them yourself
still ending up with a hand during shop phase. If it helps any, the destroy_cards only destroys the joker because I want it to be a one time use
ah ok
still, i dunno
when are you calling it
wait i'm blind
i see
yea i dunno, sorry
No worries!
change to context.hand_drawn
Ok I tried another thing but it didn't work as well
check_for_unlock = function(self, args) -- equivalent to `unlock_condition = { type = 'discover_amount', tarot_count = 22 }`
return args.type == 'discover_amount' and #G.P_CENTER_POOLS.fnaf_item <= 4
end```
still drew another hand
Is it possible to put a drawn hand back into the deck?
Probably
if context.hand_drawn and G.GAME.blind:get_type() == 'Boss' then
G.E_MANAGER:add_event(Event({
func = function()
G.GAME.chips = G.GAME.blind.chips
G.STATE = G.STATES.HAND_PLAYED
G.STATE_COMPLETE = true
end_round()
return true
end
}))
end
this 100% works
is there away to use a trigger to increase the spawn rate of a boosterpack
Yes, the get_weight function.
And it crashes, anyone knows how to tally discovered custom consumables?, I trying to make a unlock condition
SMODS.Joker {
key = "cassie",
atlas = 'Joker',
pos = { x = 3, y = 2 },
blueprint_compat = true,
unlocked = false,
rarity = 2,
cost = 4,
config = { extra = { odds = 4 } },
loc_vars = function(self, info_queue, card)
return { vars = { G.GAME.probabilities.normal or 1, card.ability.extra.odds } }
end,
calculate = function(self, card, context)
if context.using_consumeable and not context.blueprint and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit
and context.consumeable.ability.set == 'fnaf_item' and (pseudorandom('fnaf_cassie') < G.GAME.probabilities.normal / card.ability.extra.odds) then
G.GAME.consumeable_buffer = G.GAME.consumeable_buffer + 1
G.E_MANAGER:add_event(Event({
trigger = 'before',
delay = 0.0,
func = (function()
SMODS.add_card {
set = 'fnaf_item',
key_append = 'fnaf_cassie_item'
}
G.GAME.consumeable_buffer = 0
return true
end)
}))
return {
message = "+1 Item",
colour = G.C.GREEN,
}
end
end,
locked_loc_vars = function(self, info_queue, card)
return { vars = { 4 } }
end,
check_for_unlock = function(self, args) -- equivalent to `unlock_condition = { type = 'discover_amount', tarot_count = 22 }`
return args.type == 'discover_amount' and #G.P_CENTER_POOLS.fnaf_item <= G.DISCOVER_TALLIES.fnaf_item.tally
end
}```
is there an easyish way to apply a full screen shader?
Yes, SMODS.ScreenShader, but only on the dev version of SMODS
any idea as to when it'll be moved from the dev version to a regular version?
or is there another alternative
the alternative would be porting that system but I'll recommend waiting
it's probably in one or two weeks hopefully
oh okay that'll work then
you can use the dev version to work on it
where can I find it at?
do I just clone the current commit of smods instead of using releases?
big green code button, yes
thx
I see how it works but how can I get it to change the weight of a specific booster pack
oh rip
what would I need to do to apply a shader whenever you have a specific joker then?
should_apply = function(self)
return next(SMODS.find_card(key))
end,
``` in your screenshader def
shader def would be
SMODS.ScreenShader{
key = 'menaceshader',
atlas = 'shaders',
should_apply ...
}
or something else?
not atlas?
return SMODS.Boosters.twitch_pack_1 * self.weight
end,```
for screenshader there are two ways to specify what shader it pulls from
would this be right
either you define the shader earlier like:
SMODS.Shader {
key = "a",
path = "a.fs",
}
SMODS.ScreenShader {
key = "a",
shader = "modprefix_a",
}
or you give it the path to the shader like:
SMODS.ScreenShader {
key = "a",
path = "a.fs",
}
ah thx
No, SMODS.Boosters doesn't exist.
how would I do this to get the boosters weight
I see how to do overrall boosters
but not just specific booster pack
or is it just as simple as putting the key in there
is there a way to have ui transition into view instead of instantly spawn in an overlay?
shaders are pieces of code which run on the GPU used for quickly modifying the visuals of something
fahlksdfoa8 whiekf ahuifesd
time to learn another lanaugae yay
whats the script type?
its GLSL* but the file extension is .fs for some reason
-# *not really, but it's very very similar
thx
there are plenty of guides to shaders you can find on the smods wiki
does anyone know a good way for a joker to check for multiple specific hands, including hidden ones?
the joker im tryna make rn is basically just a better Droll joker
I can't get the weight of the custom booster pack
in the get_weight function to work
is it not supported for doing a specific pack
do I need the disolve mask code if my shader isn't for cards?
droll joker already does this. it's specifically worded as "if played hand contains a Flush", which means it works on those four hands and any modded hands that would also contain a flush
oohhhhh i did not notice that
this is how droll joker works (card.ability.extra.type = "Flush" in the joker)
i kinda assumed that was looking for specifically just flush
but it does make my life easier
yea no all the handtype jokers are "contains" stuff. so the pair jokers work with 5oaks and full houses and even a flush if that flush happens to also contain a pair
no
you must have the vertex function tho, otherwise itll crash
neat, thx :3
get_weight = function(self)
return mod.Booster.twitch_pack_1.weight * self.weight + 8
end,
probably doing this wrong but I just wanna see if i'm on the right track
thx
wiat whats that
all I see is effect
this is what I got rn and its crashing
like this ```
#ifdef VERTEX
vec4 position(mat4 transform_projection,vec4 vertex_position)
{
return transform_projection*vertex_position;
}
#endif
make sure you have that function at the end
ah thx
still crashed ;-;
same as before
bad argument to apply patches
wait i
wait i'm stupid
the shaders folder is in the 1x fodler
buh it still crashed
I can get the rates for everything but booster
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define PRECISION highp
#else
#define PRECISION mediump
#endif
extern PRECISION vec2 menace
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
vec4 pixel_color = Texel(texture, texture_coords);
vec3 purple_tint = vec3(0.6 + (meance.y * 0.000001), 0.2, 0.8 + (meance.x * 0.000001));
pixel_color.rgb = mix(pixel_color.rgb, purple_tint, 0.2);
return pixel_color * color;
}
#ifdef VERTEX
vec4 position(mat4 transform_projection,vec4 vertex_position)
{
return transform_projection*vertex_position;
}
#endif
oh wait ik why
more stupids
typos
you say meance in the purple_tint vector
okay it actually read the file this time
thx
yw
that would do it lol

ooo are you messing around with screenshader
si
someone in the teto server suggested making menace tint your screen purple
so we did a bit of a sidequest
i really wanna come up with a use for screenshader in one of my mods at some point
and now I know a little of GLSL
its great for animations
never touching this again 🔥 ( I hope)
hm, i've got something that could probably be flashy enough to have a subtle screenshader whenever you interact with it
honestly
i had an idea for something that could go aloongside screenshader
which would distort your effective mouse position to the game
but that does not feel like an smods api thing
and its not like i need it for anyhting
is it possible to use a joker pool with :set_ability?
wow is that another lily
as in - i want to set a joker's ability to a random rare joker, for instance
hi yeah im lily
you can determine the card first and then set_ability with it
let me see
yeah thats true. i guess what im asking is like. how do i pull a random joker from the pool
ideally one that has been discovered
or. unlocked?
you may have to manually find a card within the pool
is there like. a way to get an array that is equivalent to the cards in the pool lol
its probably in the smods documentation
get_current_pool(set, rarity)
in this case get_current_pool("Joker", "Rare") would work
oh nice
note that this will not be a list of all available ones
but just all ones, with ones that arent available being "UNAVAILABLE" rather than their joker key
alright. good to know
so from there you'll probably have a loop that uses pseudorandom_element to get a random element from that pool, and repeating until the result isn't "UNAVAILABLE"
I dunno what to try i've tried adding configs to booster and all that but i can't figure out how to get the weight table into the get_weight function on the custopm deck
you usually do that in order for parity with vanilla but if youre just picking a random rare joker outside of the shop from another joker then its fine if you do a non loop approach
true you could just go over that table and remove all the unavailables first
unless i need it to be identical to vanilla shop order style i usually would remove unavailables first because frankly
yeah that's my approach i'll just filter unavailable elements
i do not feel comfortable with while loops that have no consistent runtime
local _pool = get_current_pool("Joker", "Rare")
local _sanitized_pool = {}
for index, value in ipairs(_pool) do
if(_pool[index] ~= 'UNAVAILABLE') then
_sanitized_pool[#_sanitized_pool+1] = value
end
end
local _new_joker = pseudorandom_element(_sanitized_pool, 'frozen')
card:set_ability(_new_joker)
like this
looks good
bump
how would i make this have a 1/2 chance to fail like how wheel of fortune has the chance to fail? i looked at the code for wheel of fortune but i dont really understand it
I guess i'll make a skip for creating a twitch booster pack
because I can't get the get_weight to work
what do you have in the use function?
you can't if you're using the config, you would need to make a use function
when i take ownership of the planet consumable type it makes 2 planet tabs in the collection and I don't know how to fix that
whats the thingy to look at the current hand type being played? G.GAME. something I'm guessing?
i dont think smods can take ownership of planets
what are you trying to do
where
not the if statement, trying to set a variable to the hand currently played
yes
context.scoring_name
thank you thank you
im trying to make special planets from my mod be more rare than normal planets
SMODS.ConsumableType:take_ownership("Planet", {
rarities = {
{
key = "nfall_planet_common",
rate = 0.8,
},
{
key = "nfall_planet_rare",
rate = 0.2,
},
},
rarity = "nfall_planet_common",
})```
ah hmm
idk
smods would need to implement the vanilla consumable types for it to work i think
a shitty workaround to do it without rarities is just to have a pseudorandom check in the special planets' in_pool function
well, it's the other part that's getting weird now
How would I go about leveling up the final hand played if it wins a blind? I've gotten to about here
if context.press_play then
card.ability.extra.hand_type = context.scoring_name
end
if context.after and G.GAME.chips >= G.GAME.blind.chips then
return { level_up_hand = card.ability.extra.hand_type }
smods has this but im not sure why it still causes issues https://github.com/Steamodded/smods/blob/151989e5f170f75057a0d707117577c366a1dd63/src/game_object.lua#L1122
maybe needs level_up = 1 in the return table too, i'm not super familiar with the functionality
i see, i thought it didnt because a lot of places seem hardcoded still
its probably that
💔
i forg
tried something like
if context.after and G.GAME.chips >= G.GAME.blind.chips then
return { level_up_hand = card.ability.extra.hand_type, level_up = 1 }
end
end
but didn't get a level at the end
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.0,
func = function()
SMODS.calculate_effect({message = 'Oh a cactus !', instant = true, colour = HEX('50ca96')}, card)
delay(0.9375)
card.children.center:set_sprite_pos({ x = 0, y = 1 })
SMODS.destroy_cards({card}, nil, nil, true)
anhilated = true
play_sound("mii_vsauced")
SMODS.calculate_effect({message = 'message', colour = HEX('50ca96')}, card)
return true
end
}))
delay(0.9375)
end}
```l
hey so i'm trying to make to make the message have a delay before it does the rest (changing sprite, destroying, etc) but it stillchange the sprite almost near instantly, how do i fix it ?
G.GAME.chips doesn't get updated until long after the main scoring calculation loop finishes, you should check G.GAME.chips + SMODS.calculate_round_score() >= G.GAME.blind.chips to account for that
(the calculate_round_score function, contrary to its name, only calculates the score for the current hand being played)
och, gotcha
Works like a charm, thanks!
Is G.STATES.DRAW_TO_HAND used only for drawing new cards at the beginning of a Blind, or is it for whenever cards are drawn after Play/Discard?
Looked some more through game.lua; looks like it is for generic card-drawin'.
even happens when cards are drawn in booster packs iirc
how do you share the code like that? its for not share the code with a screenshot
you do:
```lua
insert code here
```
print("Like this!")
config = {
max_highlighted = 2,
mod_conv = 'm_quemado'
},
loc_vars = function(self, info_queue, card)
info_queue[#info_queue + 1] = G.P_CENTERS[card.ability.mod_conv]
return {
vars = {
card.ability.max_highlighted,
localize {
type = 'name_text',
set = 'Enhanced',
key = card.ability.mod_conv
}
}
}
end,
What's your mod prefix, buddy?
Probably oughtta be m_<mod_prefix>_quemado instead of just m_quemado.
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.0,
func = function()
anhilated = true
SMODS.destroy_cards({card}, nil, nil, true)
delay (0.3375)
-- should be done at the same time the card start to disapear
card.children.center:set_sprite_pos({ x = 0, y = 1 })
SMODS.calculate_effect({message = 'message', colour = HEX('50ca96'), play_sound("mii_vsauced")}, card)
return true
end
}))
Alright i'm not sure what i should do, i'm try to make so when the card disapear all the effects plays at the same time but the sound and the set sprite arrive too early how do i fix that ?
The message is the only one that is correctly timed tho
its key = consumeables (idk if that its right im kinda new at this)
Come again?
your mod prefix is in the json file of your mod folder
"mod.json"
also i tried making another G.E_Manager for it but doesn't help so i'm back to the start wit this
my mod prefix is cocosmod
Try m_cocosmod_quemado. And I apologize, @pale holly, that I know not how to help you.
heh that's fine, but idk it's kinda infurating, normally following G.E_Manger logic, the sprite change and sound play should happen AFTER the destroy cards but it somehow does just before
i could try making another G.E_manager again
destroy cards has an event nested inside another event in its code
i tried replacing the " 'm_quemado' " with the one you send me but the error is the same as before
the immediate argument only gets rid of one
Oh, so like it's not about my code itself but more of how SMODS destroy cards works from what i get ?
i haven't read the entire thing but probably
i guess the only work around is making separated event manager with delays 🤔
i think immediate is the 3rd argument
i suppose you can't even do a play_sound instead the smods destroy cards ?
try setting that to true
What is your error message?
so SMODS.destroy_cards({card}, nil, immediate, true)?
so like this then
yesh
i'll give it a try
lmao
i send it on the #⚙・modding-support recently
tbh i don't think i've seen it explained before with the vanilla remade wiki, well the destroyed cards effect but not the things after "nil", etc
vanillaremade is not smods documentation
if it's explained anywhere it should be there
seems to be outdated
Looks like that was from back when you were using m_quemado. Has the error message changed since then?
no. its the same problem
well it's alright better, i do notice that when it's the end of the round, the destroying card effect will have a delay unlike when the shop is ending, but otherwise the rest works a bit better at least
Well, it looks like this only gives the loc_vars of the card. Could you, in this channel, show what the rest of the card's code looks like?
SMODS.Atlas{
key = "atlas_consumeables",
path = 'consumeables.png',
px = 71,
py = 96,
}
SMODS.Consumable({
key = "consumeables",
set = "Tarot",
pos = {x=0, y= 0},
order = 99,
atlas = "atlas_consumeables",
unlocked = true,
cost = 4,
hidden = false,
object_type = "Consumeables",
loc_txt = {
name = "el piromano",
text={
"transforma {C:attention}2{} cartas ",
"en {C:attention}cartas quemadas",
},
},
config = {
max_highlighted = 2,
mod_conv = 'm_cocosmod_quemado'
},
loc_vars = function(self, info_queue, card)
info_queue[#info_queue + 1] = G.P_CENTERS[card.ability.mod_conv]
return {
vars = {
card.ability.max_highlighted,
localize {
type = 'name_text',
set = 'Enhanced',
key = card.ability.mod_conv
}
}
}
end,
use = function(self, card, area, copier)
for i = 1, #G.hand.highlighted do
G.hand.highlighted[i]:set_ability(card.ability.mod_conv)
end
G.hand:unhighlight_all()
end,
can_use = function(self, card)
return G.hand
and #G.hand.highlighted > 0
and #G.hand.highlighted <= card.ability.max_highlighted
end
})
Replace the use() function with this and tell me what came of it.
use = function(self, card, area, copier)
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.4,
func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true
end
}))
for i = 1, #G.hand.highlighted do
local percent = 1.15 - (i - 0.999) / (#G.hand.highlighted - 0.998) * 0.3
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.15,
func = function()
G.hand.highlighted[i]:flip()
play_sound('card1', percent)
G.hand.highlighted[i]:juice_up(0.3, 0.3)
return true
end
}))
end
delay(0.2)
for i = 1, #G.hand.highlighted do
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.1,
func = function()
G.hand.highlighted[i]:set_ability(card.ability.mod_conv)
return true
end
}))
end
for i = 1, #G.hand.highlighted do
local percent = 0.85 + (i - 0.999) / (#G.hand.highlighted - 0.998) * 0.3
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.15,
func = function()
G.hand.highlighted[i]:flip()
play_sound('tarot2', percent, 0.6)
G.hand.highlighted[i]:juice_up(0.3, 0.3)
return true
end
}))
end
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.2,
func = function()
G.hand:unhighlight_all()
return true
end
}))
delay(0.5)
end,
i replaced it and now the its says '}' expected (to close '{' but maybe its because now the SMODS. consumable part the "({" is now red should i add a ")}" ?
Yeah, definitely, An unclosed bracket can stop your code in its tracks.
oh wait now its the loc vars part because now the error says theres an unexpected simbol near the '=' in the loc vars part
Near which =? The error screen tells you which line it's on.
at line 883 the [SMODS cocosmod "main.lua"]:986: '}' expected (to close '{' at line 883) near 'SMODS'
i know the line part but i see the code and i dont see something in the loc vars part that is a problem
ok nevermind i just fix it thanks btw
So, it's all running smoothly?
kinda, now the game don't crash if i open the game an but when i use the tarot card the cards now flip but its crashes saying attempt to index local 'center' (a nil value)
What's on the offending line?
sorry whats is a offending line? do you mean on the crash or on the code?
By "offending line" I mean the line that the crash report points to.
ooooh
no theres not its just. card.lua:280: attempt to index local 'center' (a nil value)
So what's on line 280?
its the code for another joker that is just "end," but i dont think that is related to the problem because its already a finished and working joker
Maybe it's some bracket that wasn't properly closed. But since I don't know the full contents of the file, I don't know exactly what's gone awry.
And does this line 280 come before or after El Piromano's code?
card.lua should be the vanilla file
before its actually a code thats its closed with brackets and with the ends so idk why that would be a related problem.
Try going over every bracket in El Piromano's code and see if any of them turn red. And just to be on the safe side, check the brackets of the Joker whose code's above it.
i saw the brackets in the piromano code and all of them and the jokers above are the yellow or blue or purple as usual ,no reds
Could you send the code for your enhancement
yeah sure let me send it
SMODS.Atlas{
key = 'enchanters',
path = 'quemado.png',
px = 71,
py = 96,
}
SMODS.Enhancement {
key = 'quemado',
atlas = 'enchanters',
pos = { x = 0, y = 0 },
loc_txt = {
name = 'Carta quemada',
text = {
"se que hace pero me da lata poner la descripcion"
}
},
config = { mult = 4 },
loc_vars = function(self, info_queue, card)
return { vars = { card.ability.mult } }
end,
}
i was just trying to just give +4 mult for then make the rest for the code
hmm, when you hover over your consumable, does it properly show the description of the enhancement
its just shows that nothing on the left or right
is there a context for when a probability event occurs
i cant find one in the documentation
I think your mod prefix is wrong in the code, can you double check your json file
I settled for putting in a patch directly after SMODS.post_prob[#SMODS.post_prob+1] = {pseudorandom_result = true, result = result, trigger_obj = trigger_obj, numerator = numerator, denominator = denominator, identifier = identifier or seed}:
if result then
--
end
Isn't it context.pseudorandom_result then
[[patches]]
[patches.pattern]
target = '=[SMODS _ "src/utils.lua"]'
pattern = 'SMODS.post_prob[#SMODS.post_prob+1] = {pseudorandom_result = true, result = result, trigger_obj = trigger_obj, numerator = numerator, denominator = denominator, identifier = identifier or seed}'
position = 'after'
match_indent = true
payload = '''
if result then
--
end
'''
i double check the json file and its say in the json file and the mod prefix its still cocosmod
Couldn't find that in state_events.lua.
should i put that also in the main.lua?
Does your json file have "prefix": "cocosmod"
i can't make something occur in this, this is just for setting probabilities
yes
That description is wrong then, that's the context for it
legendaries of the 2,3,4oak jokers
You don't have a steammodded header right, and can you send the json file
{
"id":"cocosmod",
"name":"cocosmod",
"display_name":"cocosmod",
"author" : [
"Coquetenges"
],
"description":"mi mod jaja lol",
"prefix": "cocosmod",
"main_file": "main.lua",
"priority": 0,
"badge_colour": "B62E2E",
"badge_text_colour": "FFFFFF",
"version": "1.0",
"dependencies": [
"Lovely (>=0.7)",
"Balatro (>=1.0.1o)"
],
"conflicts": [
]
}
is that the page for the install of another mods? because if it that the case is not i dont have it
i can't tell what's wrong tbh
But m_cocosmod_quemado is somehow not the right key
A steammodded header is an outdated feature that you shouldn't use, it is some commented text at the top of lua files
If you have that remove it, otherwise try making sure you're looking at the right mod folder for the json
If it's there you should see the words "STEAMODDED HEADER" at the top of one of your lua files
Wait, how many lua files do you have in your mod
idk if this is bad but all the code of the mod is in one file and that in the main.lua but i tried putting the code of the jokers and the consumeables and etc in diferent places but the game just dont read the others just the main.lua
bump
you'd have to load the other files yourself within the main file, but it's fine for now, you can experiment with that once we find out what the issue is
Do you see your enhancement in the collection
And do you have the debugplus mod installed
Okay can you go into collection for your enhancement, put your mouse over it, open debugplus console and type eval dp.hovered.config.center_key
And see what the output is
its say m_cocosmod_quemado
its say the same thing with a new run
Does the consumable still not have the description of the enhancement to the side when hovered?
Did you forget to save the main lua file
yes theres only the text below
nope but i will double check
Check that you're editing the right file too
yeah its still the same and im editing in the main.lua
But like, 100% sure it's the main.lua in your Balatro mods folder
you're not editing a copy that's somewhere else or anything like that
oooooh
hey, question, is there a way to safely debuff and un-debuff a joker without worrying about un-debuffing perishables or during crimson heart?
yeah i use a example mod that only have 1 joker and i editing putting a lot of stuff that the reason?
should i make copy and paste my code into a folder with nothing in it? and start from zero with the folders?
I believeee there's a mod function for handling debuff, should be on the wiki iirc
ah, thank you
Just make sure you're editing Balatro/Mods/your mod name/main.lua
And not any other file
oh yeah im pretty sure im editing on the main.lua
Arghh okay
Go ingame and hover over your consumable and do eval dp.hovered.ability.mod_conv
its says m_cocosmod_quemado
I am actually gonna go insane, everything points to it working but it doesn't
Make sure that in your mods folder there is only your mod and debugplus, nothing else
For now if you have any other mods try either disabling them or moving them from the folder
To a different one
i have anothers mod and i disabled it i do the same thing with the debug?
Keep debugplus enabled
Your only mods in the balatro mod menu should be your mod and debugplus enabled, if there is any other it should be disabled
i already did it now what
Check again that your enhancement appears in collection, that the center_key is correct and same with the consumable ingame with mod_conv
Using the debugplus console like before
now with the enhancement appears the messege "nil"
i will try now with the consumeable
the consumeables appears the m_cocosmod_quemado
that's not right, were u hovering the right card and typed the command properly?
yeah i did it i doubled check even and its still says nil
no but that makes no sense, your enhancement is there in the collection right and you're running eval dp.hovered.config.center_key
While your mouse is on top of your enhancement
now its says m_cocosmod_quemado
can you send the full current code for your consumable
last thing
either I figure out what's wrong here or I give up and I accept defeat
ok
SMODS.Consumable({
key = "consumeables",
set = "Tarot",
pos = {x=0, y= 0},
order = 99,
atlas = "atlas_consumeables",
unlocked = true,
cost = 4,
hidden = false,
object_type = "Consumeables",
loc_txt = {
name = "el piromano",
text={
"transforma {C:attention}2{} cartas ",
"en {C:attention}cartas quemadas",
},
},
config = {
max_highlighted = 2,
mod_conv = 'm_cocosmod_quemado'
},
loc_vars = function(self, info_queue, card)
info_queue[#info_queue + 1] = G.P_CENTERS[card.ability.mod_conv]
return {
vars = {
card.ability.max_highlighted,
localize {
type = 'name_text',
set = 'Enhanced',
key = card.ability.mod_conv
}
}
}
end,
})
use = function(self, card, area, copier)
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.4,
func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true
end
}))
for i = 1, #G.hand.highlighted do
local percent = 1.15 - (i - 0.999) / (#G.hand.highlighted - 0.998) * 0.3
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.15,
func = function()
G.hand.highlighted[i]:flip()
play_sound('card1', percent)
G.hand.highlighted[i]:juice_up(0.3, 0.3)
return true
end
}))
end
delay(0.2)
for i = 1, #G.hand.highlighted do
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.1,
func = function()
G.hand.highlighted[i]:set_ability(card.ability.mod_conv)
return true
end
}))
end
its not all the code
i dont have nitro
i will send the other part
"
delay(0.2)
for i = 1, #G.hand.highlighted do
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.1,
func = function()
G.hand.highlighted[i]:set_ability(card.ability.mod_conv)
return true
end
}))
end
for i = 1, #G.hand.highlighted do
local percent = 0.85 + (i - 0.999) / (#G.hand.highlighted - 0.998) * 0.3
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.15,
func = function()
G.hand.highlighted[i]:flip()
play_sound('tarot2', percent, 0.6)
G.hand.highlighted[i]:juice_up(0.3, 0.3)
return true
end
}))
end
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.2,
func = function()
G.hand:unhighlight_all()
return true
end
}))
delay(0.5)
end,
thats all
does eval G.P_CENTERS.m_cocosmod_quemado say anything that's not nil
no it's fine
Everything seems to be correct yet nothing seems to work
I'm just gonna assume you're cursed and wish you luck
🙏
you are restarting the run to test, right
I love Spanish speaking people talking to each other in English
I want to make a mod that does the following:
At the end of every blind, create a random negative eternal Cryptid joker (and you cant skip blinds) and i want the shop to just be the vanilla shop, so nothing from Cryptid.
is there any recorces on how to make a config for a mod? i dont see it anywhere on the smod wiki :/
you might be able to make a patch to filter the shop pool? also hi yeet lol
What do discover and bypass_discovery_center parameters for SMODS.create_card actually do?
Because whatever I do with them created card DOES get discovered
At this point it feels like I should just do the Card thing instead of trying to use these
It's not a great example due it being specific to keybind features, but this example mod is pretty small and shows how a config is set up
https://github.com/Steamodded/examples/tree/master/Mods/KeyboardController
A more practical example is maybe this one. It's my small config (it has 3 options) for Blockbuster: Value Manipulation. https://github.com/icyethics/Blockbuster-ValueManipulation/blob/main/src/config_menu.lua
The config.lua file defines the parameters that are configurable. The config_menu.lua file turns that into a proper config page
mmmmm i see ok thx ill give them a look :3
A guide would be nice, though, for sure! If I have time today, I might set up an example mod that just has some config options 🤔
Also, feel free to ping me if you still have questions. There's way more you can do for config pages than I could explain, but I'm sure I'd be able to explain the basics, if there's something unclear, haha
oki will do. ive doen next to nothing with ui and the bit i have done is buggy as fuck lmao. but my previse mod was buggy af so people didnt notice lol
that would be sick. goals like you make modding accessible.
UI isn't difficult, but it's annoying and finnicky, haha. I'm still not comfortable with it. Frustration while working on UI is normal 😛
hi turtle
Have you messed around with DebugPlus? It has the ability to watch files, which allows you to reload UI immediately when you save a change. It makes it way easier to work on UI stuff!
ive used the console ya but tend not to have it on as it made my game crash often :/ ill give it a try again tho
wait cant i autobanish every cryptid thing
because banishing is literally filtering it out from the shop
Yeah, just cycle through centers and check for source mod
then just add to banned keys
Brrrrrrr bad timing
Where are you creating the card?
Oh does the cardarea matter? I wasn't actually emplacing it lmao
Sooo does bypassing discovery in SMODS.create_card work for specific areas or am I misreading that
I don’t have access to my dumps right now 😭
wait how would i go about implementing that
depends on what you wanna do
i want to banish everything from Cryptid using the DELETE code card functionality and create a random negative eternal Cryptid joker at the end of every blind
(and keep the created joker banished from the shop)
maybe the DELETE code card functionality isnt enough since i also dont want you to be able to make them via Judgement for example although i think that doesnt matter that much
Ah, that's slightly more complicated. The way deleting stuff works is adding them to the banned card lists, which bans them from being pulled from random pools. So you'd need to make a separate pool. My understanding is that DELETE works like that, removing a card from all random pools
yeah banning works too
and i specifically want a cryptid joker to be made at the end of the round
But the way you'd do it is to go over all jokers in the joker pool, or the general centers pool, depending on what you'd want to be banned. If it's the jokers pool, all of 'm will count as jokers, but if it's G.P_CENTERS, check for [var].ability.set == "Joker" and [var].original_mod.id == "cryptid" (do double check cryptid's mod ID, I don't know it by heart
how do i make a button? i wanna make a button that likes out to the twitch auth page. the code is still basicly identical to yours lol rn i have this
local create_menu_button = function (parent, config_button, callback)
parent.nodes[#parent.nodes + 1] = create_button_binding_pip({
label = localize(config_button.label),
callback = callback,
ref_table = EngagementBait.config,
ref_value = config_button.ref_value,
})
-- if config_button.tooltip then
-- parent.nodes[#parent.nodes].config.detailed_tooltip = localize(config_button.tooltip)
-- end
end
create_button_binding_pip was the only create button func my auto complete could see but it doesnt seem to be what i want (or im seting it up wrong)
config_button:
local config_buttons = {
{ref_value = "link_account", label = "eb_config_link_account" }, --, tooltip = "eb_config_link_account_tooltip"},
}
also whenever i add the tooltips it crashes :/
also how do one do this? got it installed but i dont see a way to set it up >.<
You mean just a clickable button? Instead of a toggleable pip?
idk what a toggleable pip even is x.x but ya i want a button that can be liked that i can use to link out to a website.
I haven't had to use it in a few months, but I'll refresh on how once I'm back at my PC and let you know, haha
Oooh
lol oki
So, I'm not at my PC, so I can't link an example, but buttons are pretty simple. If you go to the Balatro Goes Kino repo, you can look for where I use 'Discord', as that should show you a button that does exactly that!
There are better examples, but it's the one I can give without checking an actual repo, haha
i love when my mod crashes the game when starting a new run
that just how it be lol
so much worse is when it works for you then DJ uses it and it crashes 😭
https://github.com/icyethics/Kino/blob/main/src/kino_ui.lua#L411
I hope this helps! There's a premade function to get a button. You link its functionality by setting up a function. In this case, that's a the bottom of the file!
https://github.com/WilsontheWolf/DebugPlus/blob/master/docs/dev.md#watch
Also, getting work related pings so I probably don't have the time to write out a more detailed explanation, but it's this functionality
................. one of these crashes.............................. i fucking hate this code base so much..........
the fact that it isnt consistent with the other functoins like create_slider or create_text_input or create_toggle is soooooo annoying
you have to put {} around the label in UIBox_button but not in the other ones
Oooh, yeah, yeah
and yeah, UI specifically is hard to work with. It's made to do exactly what vanilla needs, so a lot of the other stuff is added by SMODS or needs to be made yourself. Makes working with it pretty awkward. For me, a lot of UI code is just copying pasting existing code I wrote, specifically to avoid running into issues where I use brackets where they weren't needed, etc.
Part of why there aren't really many resources on making modded UI. Most people working with it don't like doing it or are very comfortable working with it, haha
lol
is there a way to have the button not point to a global function?
local create_menu_button = function (parent, config_button, callback)
parent.nodes[#parent.nodes + 1] = UIBox_button({
label = {localize(config_button.label)},
-- button = callback,
button = "LinkAccount",
ref_table = EngagementBait.config,
ref_value = config_button.ref_value,
})
-- if config_button.tooltip then
-- parent.nodes[#parent.nodes].config.detailed_tooltip = localize(config_button.tooltip)
-- end
end
"linkAccount" works but callback doesnt :/
I'm not sure. Is there a reason you'd want to avoid making a function for it in 'G.FUNCS'?
jsut general neatness. "EngagementBait.LinkTwitch" is more clear that is from my mod then "G.FUNCS.LinkTwitch"
no, you should just add the prefix to that
For clarity there, you can still add your mod's key in your functions name
ya ik. if ya cant ill just use globals, just wanted to ask if you can use non globals
anyone know how to fix this?
That means you're returning something in context.repetition or context.retrigger_joker_check without repetitions or repetitions is less than or equal to 0.
Are you returning outside of a context check?
no
calculate = function(self, card, context)
-- Start Retriggering
if context.before then
local hasAce = false
local has4 = false
for _, v in pairs(context.full_hand) do
has4 = has4 or v:get_id() == 4
hasAce = hasAce or v:get_id() == 14
end
if has4 and hasAce then
card.ability.extra.trigger = true
card.ability.extra.upgrade.count = card.ability.extra.upgrade.count + 1
G.E_MANAGER:add_event(Event({func = function() card.ability.extra.spr = true return true end }))
return { message = localize("elle_41_activate"), colour = G.C.BLUE }
end
end
-- Retrigger
if card.ability.extra.trigger then
-- Stop Retriggering
if context.after then
card.ability.extra.trigger = false
G.E_MANAGER:add_event(Event({func = function() card.ability.extra.spr = false return true end }))
return
end
local card_pos = 0
for i = 1, #G.jokers.cards do
if G.jokers.cards[i] == card then card_pos = i break end
end
ret = {}
local retl = SMODS.blueprint_effect(card, G.jokers.cards[card_pos-1], context)
local retr = SMODS.blueprint_effect(card, G.jokers.cards[card_pos+1], context)
if retl then
retl.colour = G.C.BLUE
ret = SMODS.merge_effects(retl, ret)
end
if retr then
retr.colour = G.C.BLUE
ret = SMODS.merge_effects(retr, ret)
end
return ret
end
end```
No, you are.
oh 😭
yeah i was gonna say merge effects adds .extra
so it will always count as returning something
a
you should check if the joker returns anything before merging
No, it's because you're setting ret to {} by default.
yeah do like if not retl and not retr then return nil end
it only happens if it does retrigger tho
wait
i get it yea
ty
it works now
nope
now it's not retriggering
^
does anyone have an idea why would the tooltip go away after 1 frame?
i am trying to assign a tooltip to a button
...i just replaced my whole retrigger code with this
local ret = {}
ret[#ret+1] = SMODS.blueprint_effect(card, G.jokers.cards[card_pos-1], context)
ret[#ret+1] = SMODS.blueprint_effect(card, G.jokers.cards[card_pos+1], context)
return SMODS.merge_effects(ret)```
why doesnt this code work (with help from chatgpt because i dont want to learn lua):
local cryptid_mod_id = "Cryptid"
local function is_from_cryptid(center)
return center
and center.mod
and center.mod.id == cryptid_mod_id
end
local original_get_current_pool = get_current_pool
function get_current_pool(_type, rarity, legendary, key_append)
local pool = original_get_current_pool(_type, rarity, legendary, key_append)
local filtered = {}
for _, center in ipairs(pool) do
if is_from_cryptid(center) then
table.insert(filtered, center)
end
end
return filtered
end
local function get_random_cryptid_joker()
Cryptid_jokers = {}
for k, center in pairs(G.P_CENTERS) do
if center.set == 'Joker' and center.mod and center.mod.id == cryptid_mod_id then
table.insert(Cryptid_jokers, k)
end
end
if #Cryptid_jokers == 0 then return nil end
return pseudorandom_element(Cryptid_jokers)
end
function create_negative_eternal_cryptid()
local key = get_random_cryptid_joker()
if not key then return end
local card = create_card('Joker', G.jokers, nil, nil, nil, nil, nil, key)
if card then
card:set_edition('negative', true)
card:set_eternal(true)
G.jokers:emplace(card)
end
end
local original_defeat = end_round
function end_round()
original_defeat()
create_negative_eternal_cryptid()
end
I'm all for helping, but I'm sorry, don't think I'll commit time to helping someone fix code that was made by chatgpt because they don't wanna learn lua
If you really wanna become more than a modding novice, you're gonna have to learn Lua.
i think its more that i dont know how the create_card function works exactly (i do have the balatro source code but its very unreadable code)
is there a way to refersh the cofig ui to update the text from withing the gloable func?
make the text take ref_table and ref_value
thx
Hear of VanillaRemade? It's the vanilla code adapted into Steamodded, create_card() is in there somewhere if memory serves.
i do see a strange thing that the create_card() call here does not only pick from the Cryptid pool
Also, this might help.
https://www.lua.org/pil/contents.html
not the vanilla one
pool is a list of keys
i am aware
then why do you think that's strange
But the code makes a list of keys for Cryptid jokers but i dont think the code chooses from that pool
Wait so how do i create a random card from the list of centers (one of the previous versions of my code did use centers)
what's the goal here
use SMODS.create_card instead
it's just a wrapper for the vanilla one, but it makes it much more comprehensible
There's something about this joker that is causing the following crash, and I'm not sure what it is
--Ajani
SMODS.Joker {
object_type = "Joker",
name = "mtg-ajani",
key = "ajani",
pos = { x = 0, y = 7 },
config = {
extra = {
planeswalker = {
has_activated_loyalty = true,
loyalty = 3,
loyalty_abilities = {
{
cost=1,
use=function(self, card)
print("activate!!!")
end
},{
cost=-3,
use=function(self, card)
print("activate!!!!!")
end
}
},
},
},
},
order = 5,
rarity = 2,
cost = 8,
atlas = "mtg_atlas",
loc_vars = function(self, info_queue, center)
return { vars = { center.ability.extra.planeswalker.loyalty } }
end,
calculate = function(self, card, context)
if context.setting_blind and not (context.blueprint_card or card).getting_sliced then
card.ability.extra.planeswalker.has_activated_loyalty = false
end
end
}```
In the middle of the blind this crash will occur, even if the player isn't doing anything
Is there a way that I can have a function associated with a card?
I'm calling the function externally
G.FUNCS.use_loyalty = function(e)
local card = e.config.ref_table
local loyalty_ability = card:get_loyalty_ability(e.config.index)
card.ability.extra.planeswalker.loyalty = card.ability.extra.planeswalker.loyalty + loyalty_ability["cost"]
card.ability.extra.planeswalker.has_activated_loyalty = true
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.1,
func = function()
loyalty_ability["use"](card)
return true
end,
}))
G.jokers:unhighlight_all()
if card.ability.extra.planeswalker.loyalty <= 0 then
card:start_dissolve(nil, true)
end
end```
if it's exclusive to that card save it to the center (with a unique name including your mod prefix) and then do card.config.center.modpefix_function()
if it can be shared save it to a global table like MTJ.function and then save the name to the card
save it to the center means have it at the same level as calculate
This is the goal
ah idk how delete works but you can just add stuff to G.GAME.banned_keys
That makes sense
if i call SMODS.create_card and use the stickers field, will it only generate cards compatible with said stickers? i.e. if i do SMODS.create_card({set = "Joker", key_append = "modid_append", stickers = ["eternal"]}), would it automatically filter out cards like gros michel, etc?
iirc it will just not apply eternal to cards that aren’t compatible
i went to check and it's not adding eternal regardless to any joker, probably cus should_apply is false always
Is there a way to change color in description text from loc_vars?
bump
the game calls check_for_unlock within set_ability if the card is in the jokers or consumables area
oh wait I get what you mean
but it's actively not unlocking despite that
still, the game calls discover_card in Card:added_to_deck
well i have a consumable that runs set_ability on a joker and the result doesn't get unlocked in collections, so something's wrong
just tested it and it seems to work for me
could you send the code you're testing with?
This is causing strange behavior when I have multiples of the same joker (spawned with debugplus), any idea what might be causing it?
local updateref = Card.update
function Card:update(dt)
updateref(self, dt)
if G.STAGE == G.STAGES.RUN then
if not self:is_planeswalker() then return end
local can_activate = self:can_activate_any_loyalty_ability()
if can_activate and not self.config.center.planeswalker.jiggle then
local eval = function(card) return card:can_activate_any_loyalty_ability() end
juice_card_until(self, eval, true)
self.config.center.planeswalker.jiggle = true
end
if (not can_activate) and self.config.center.planeswalker.jiggle then
self.config.center.planeswalker.jiggle = false
end
end
end
You're applying it to the "prototype" object.
Did you mean something akin to self.ability.planeswalker.jiggle?
what's the G.P_CENTERS. equivalent of custom stuff like G.P_CENTERS.p_standard_mega_1 how would I point it to a custom booster
how can i make it so youre unable to sell something until you have ten dollars?
Hook Card:can_sell_card
discovered = true,```
does't seem to work in tags
custom tag is still locked
nvm I found the issue I had a tag with same key from testing stuff lol
would you add a condition at the beginning of the function that just checks for a specific condition and returns true to allow selling?
Yes.
I figured out everything on tags but how their text is displayed
I tried the local_text but doesn't seem to work
For context some jokers of mine are to only trigger when the poker hand is a combination of other poker hands (two pair, full house, straight flush, etc.) however my coder made it to where if it contains those hands it triggers, making them triggerable in flushes with 2 pairs and is having a hard time setting it to what's intended
Is there some lines of code I can send them that can help with that
so you want to replace "contains" for "is"?
anyone knows how to tally discovered custom consumables?, I trying to make a unlock condition but it seems to keep crashing
SMODS.Joker {
key = "cassie",
atlas = 'Joker',
pos = { x = 3, y = 2 },
blueprint_compat = true,
unlocked = false,
rarity = 2,
cost = 4,
config = { extra = { odds = 4 } },
loc_vars = function(self, info_queue, card)
return { vars = { G.GAME.probabilities.normal or 1, card.ability.extra.odds } }
end,
calculate = function(self, card, context)
if context.using_consumeable and not context.blueprint and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit
and context.consumeable.ability.set == 'fnaf_item' and (pseudorandom('fnaf_cassie') < G.GAME.probabilities.normal / card.ability.extra.odds) then
G.GAME.consumeable_buffer = G.GAME.consumeable_buffer + 1
G.E_MANAGER:add_event(Event({
trigger = 'before',
delay = 0.0,
func = (function()
SMODS.add_card {
set = 'fnaf_item',
key_append = 'fnaf_cassie_item'
}
G.GAME.consumeable_buffer = 0
return true
end)
}))
return {
message = "+1 Item",
colour = G.C.GREEN,
}
end
end,
locked_loc_vars = function(self, info_queue, card)
return { vars = { 4 } }
end,
check_for_unlock = function(self, args) -- equivalent to `unlock_condition = { type = 'discover_amount', tarot_count = 22 }`
return args.type == 'discover_amount' and #G.P_CENTER_POOLS.fnaf_item <= G.DISCOVER_TALLIES.fnaf_item.tally
end
}```
if ({['Two Pair']=1,['Full House']=1,['Straight Flush']=1})[context.scoring_name]

Cutting-edge technology I'm working on
Ben starr 
how do you modify a playing card's base chips
like not adding or subtracting permanent bonus chips like hiker
but like actually modifying their base chips
card.base.nominal
i genuinely can't tell if im stupid or not.
can someone explain how the ipairs stuff works in crayon eating terms? i'm still so lost on how to detect certain variables (in this case, joker(s))
the joker im trying to create wants to detect certain jokers in order to scale and do a hidden effect once both jokers are obtained
i keep being told to do ipairs and if statements, but i'm so lost.
use SMODS.find_card
like
calculate = function(self, carry, context)
if context.main_joker and not context.blueprint then
if next(SMODS.find_card("j_modid_jokerkey")) then
--- scale
end
end
end
🔥
How may I pass an SMODS.calculate_context() call as a boolean value? Outside of The Tuba, the Discard button is grayed out after each Hand played, and while playing The Tuba, the Discard button remains enabled no matter what.
[[patches]]
[patches.pattern]
target = 'functions/button_callbacks.lua'
match_indent = true
position = 'after'
pattern = '''
e.config.button = 'discard_cards_from_highlighted'
'''
payload = '''
if SMODS.calculate_context({
LAPSEMS_should_the_discard_button_be_blocked = true,
full_hand = G.hand.highlighted
}) then
if (G.GAME.current_round.hands_left > G.GAME.current_round.discards_left) then
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
end
end
'''
calculate = function(self, blind, context)
if not blind.disabled then
if context.LAPSEMS_should_the_discard_button_be_blocked then
for _, card in pairs(context.full_hand) do
if card:is_suit(self.config.extra.suit) then
return true
end
end
return false
end
end
end,
ipairs takes a table as an input
it then starts at index 1 and checks if it's value is not nil
then index 2, and 3, and so on until its nil
itll then give something rhat you can iterate through in a loop
for index, value in ipairs({"a", "b", "c", not_accessed = true}) do
print("index: " .. index .. ", value: " .. value)
end
> index: 1, value: a
> index: 2, value: b
> index: 3, value: c
so, for confirmation (and to see if im being stupid or not...)
loc_vars = function(self,local_queue,card)
return {vars = card.ability.extra.mult,card.ability.extra.scaling}
if SMODS.find_card("TBC_jokerhere) or SMODS.find_card then
(insert rest of code)
end,
return will cut the function short
so return would have to be later in the code? or do i have to cut it out?
-# also, how are you guys getting color in your text?
and pass the result of SMODS.find_card into next()
```lua
lua code
```
it should be the very last thing
ah, for confirmation:
loc_vars = function(self,local_queue,card)
if SMODS.find_card("TBC_jokerhere") or SMODS.find_card("TBC_jokerhere2") then
(insert code)
next()
(return code)
?
I've done something similar for another Blind and it's worked with no fuss.
[[patches]]
[patches.pattern]
target = 'functions/button_callbacks.lua'
match_indent = true
position = 'after'
pattern = '''
e.config.button = 'discard_cards_from_highlighted'
'''
payload = '''
-- This is utilized by The Horn.
if (G.GAME.blind.config.blind.key == 'bl_lapsems_horn') and not G.GAME.blind.disabled then
for i = 1, #G.hand.highlighted do
if LAPSEMS.is_one_of_these_ranks(G.hand.highlighted[i], G.GAME.blind.config.blind.config.extra.ranks_to_anti_discard) then
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
break
end
end
end
'''
loc_vars = function(self,local_queue,card)
if next(SMODS.find_card("j_TBC_jokerhere")) or next(SMODS.find_card("j_TBC_jokerhere2")) then
(insert code)
end
(return code)
end
ignore my horrible tabbing im on phone
@errant arrow
the reason next is used is because find_card always returns a table even if empty and in lua empty tables are truthy
next returns the next value in a table or nil which is considered false
i dont think you can, you can pass a table to it as a second argument iirc and the returns will be there
The returns will be where?
I see, thank you!
Or may I simply add a LAPSEMS.block_discard_button() function call instead of a return true?
local effects = {}
SMODS.calculate_context(context, effects)
effects.jokers or something will have the returns iirc
i dont bother with any of that and just make my jokers set a global variable
its not like i care about other mods using my contexts
I think I'm just gon' go with a LAPSEMS.block_discard_button() call.
You can also assign the result of the function to something
smods does this itself
Well, I meant to assign the result to true or false/nil, but that didn't work for me.
useful if you only need boolean flags
Thats not
Not what i mean
Nor does that sentence make sense
dont you need to add it to the return tables for that
these
Uh not sure
based on code I wrote 17 years ago, effects would just be an array of stuff that might be tables or not
I'm afraid I don't follow either.
I thought I could just patch in an SMODS.calculate_context() statement and assign the result to a boolean value.
I was previously adding one patch per Boss Blind, and that worked A-OK.
Is this the correct way to hook this function? We're trying to make it so that selling consumables with negative sell value don't let you go under the minimum amount of money but it’s not working
just use a global variable
N recommending bad practices 🥀 never thought i'd see the day /silly
What'd that look like?
Im on mobile
But like just check if the given boss is active
And loop over G.hand.highlighted to check the cards
And see if its valid to discard
And if not then you e.config.colour = G.C.UI.INACTIVE; e.config.button = nil
Or whatever i forgot what the colour is called
Just look at the original function
in all seriousness it's a fair enough implementation if you don't expect others to interact with the context, especially since you put the globals in a table for the mod. just feels a little bit evil lol
yeah
[I've done just that before](#💻・modding-dev message), but I wanted something more modular.
Surely, if I've already implemented SMODS.calculate_context({LAPSEMS_before_jokers_score = true}), then this should be a piece of cake.
calculate context is not designed to return a boolean
and you probably shouldnt mess with it to do so
true as a return is basically reserved for destruction so for example merge_effects will fail
Is it capable of returning anything?
it is designed to return things in a table
Could I check, then, if #<return_value> > 0?
no but you can probably do next(table)
but anyway i dont know if the returns will actually be passed
because smods looks for specific keys
i think return_table does get all the returns
but i might be wrong i cant test rn
bump
I do custom context calls for https://github.com/TheOneGoofAli/TOGAPackBalatro/blob/c7bb50413c13aea4581341b2e85f340a01991991/togastuff.lua#L700-L759
so return_table ends up being an array of 112 tables, 111 of which are empty, and one of them look like this
that's with j_joker only in joker_main
lol
Trying to make a blind that needs to be one-shot in order to beat. I believe the effect is working, just the text isn't being updated. Am I missing something to make the score show 0?
calculate = function(self, blind, context)
if not blind.disabled then
if context.after then
if not SMODS.last_hand_oneshot then
G.GAME.chips = 0
end
end
end
end,
I believe I had something like that as a Joker before.
ah G.GAME.chips updates after the context is ran
so you would need to use events i think
-# If that helps any.
I'll take a look, thank you!
local cards_discovered = 0
local your_consumable_name_here
for _, card in pairs(G.P_CENTER_POOLS[your_consumable_name_here]) do
if card.discovered then
cards_discovered = cards_discovered + 1
end
end
print(cards_discovered)
put this on a separate function or straight to the joker?
Dunno. That's just a way to know how many cards out of G.P_CENTER_POOLS[your_consumable_name_here] have been discovered.
ok thank you
wait so i implemented this idea but it sometimes creates vanilla jokers and sometimes doesnt apply eternal
but always negative
i did:
card:set_edition({negative = true}, true)
card:set_eternal(true)
card:add_to_deck()
G.jokers:emplace(card)```
You should be able to do this in an existing context depending on what you mean by after playing cards
Between cards scored/held and Jokers scoring. And it already works perfectly, I was only demonstrating that I already know the basics of adding custom contexts.
Oh I misinterpreted your message
yall im new to modding do you delete the zip file after extracting
The delete button in file explorer
The trash can looking one
While you have the zip selected
no do you or do you not
so yes
oh lol ur fine
how do i get a card's position?
like in the cardarea?
card.T.x and card.T.y iirc
But its in balatro units which are insanely janky so if youre doing math with them be prepared
Okay
Working as intended! TYTY
SMODS.Joker({
key = "abacus",
config = { extra = { mult = 0, plusmult = 1, minusmult = 1 } },
loc_txt = {
["name"] = "Abacus",
["text"] = {
{
[1] = "{C:mult}+#2#{} Mult for each played {C:attention}2{}, {C:attention}4{}, {C:attention}6{} and {C:attention}8{}",
[2] = "{C:mult}-#3#{} Mult for each played {C:attention}3{}, {C:attention}5{}, {C:attention}7{} and {C:attention}9{}",
[3] = "{C:inactive}(Currently {C:mult}+#1#{}{C:inactive} Mult){}"
}
},
},
pos = { x = 4, y = 6 },
cost = 7,
rarity = 2,
blueprint_compat = true,
eternal_compat = true,
perishable_compat = true,
unlocked = true,
discovered = false,
atlas = "CustomJokers",
loc_vars = function(self, info_queue, card)
return { vars = { card.ability.extra.mult, card.ability.extra.plusmult, card.ability.extra.minusmult } }
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card:get_id() == 3 or
context.other_card:get_id() == 5 or
context.other_card:get_id() == 7 or
context.other_card:get_id() == 9 then
card.ability.extra.mult = card.ability.extra.mult - card.ability.extra.minusmult
return {
message = localize { type = 'variable', key = 'a_mult_minus', vars = { card.ability.extra.minusmult } },
colour = G.C.RED
}
end
if context.individual and context.cardarea == G.play then
if context.other_card:get_id() == 2 or
context.other_card:get_id() == 4 or
context.other_card:get_id() == 6 or
context.other_card:get_id() == 8 then
card.ability.extra.mult = math.max(0, card.ability.extra.mult - card.ability.extra.minusmult)
return {
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.plusmult } },
colour = G.C.RED
}
end
end
if context.joker_main and context.cardarea == G.jokers then
return {
mult = card.ability.extra.mult
}
end
end
end
})```
my code is being super janky
the joker doesn't gain mult or lose mult when i play these cards
Lily made smth to convert balaunits to pixels iirc
Probably ask her
i'm trying to make a soul sprite with different dimensions to the card it's on, but i'm having trouble with fixing its rotation pivot
that's why
Ruby did something like that with a janky solution for entropy i think
yea
oo icic
i know