#💻・modding-dev
1 messages · Page 681 of 1

might just do that for the idea
because this joker is supposed to have a degenerate gambling idea behind it
so maybe having to spend your rerolls is more fun
or rather accurate to the idealogy
G.GAME.interest_amount = 1?
To The Moon adds 1 on obtaining, and subtract 1 on removing
hard-setting the value will interrupt the process
idk just tried it
seems fine to me
interrupting doesn't mean not working
okay but then theres no issue right?
let's say you obtain this joker, then To The Moon
To The Moon recovers interest by adding 1 to 0
you then remove this joker
hard-setting the value to 1 nullifies To The Moon
just tried it
it doesnt
gave the intended value
the only issue that i might see is that on green deck you now gain interest all of a sudden
okay just tried that
that just works properly
life when your code works as inteded

i assume vanillaremade shows this for to the moon, but actual vanilla hardcodes it somewhere else
that said i would still recommend not hard setting the interest amount in case any other modded content interacts with it
No, it does add and subtract from G.GAME.interest_amount
funnily enough to the moon gives interest again with this joker(if you have both not when sold)
but
thats a feature now
consider rephrasing the effect from:
Earn no interest
to:
Earn $1 less of interest every $5 you have
to prevent ambiguity
yeah
i probably should
if context.main_scoring and context.cardarea == G.play then
assert(SMODS.modify_rank(1))
end
again probably something very small,. this is a seal that is supposed to increase the rank of the card by 1 when played however currently i get this error
300: attempt to index local 'card' (a number value)
what does context.other_card do?
this also sadly still crashes the game with the same error
it should actually just be SMODS.modify_rank(card, 1)
you need to pass a card to the function for it to work, and card in the calculate function of a seal is the card that it's on
context.other_card refers to a card object in some contexts, but it doesn't exist during context.main_scoring
oh yeah thanks
is it possible to get your current balatro playtime and use that value in a joker?
Hey folks, I've got a global table with data, but the data itself seems to restore to its default values in between runs. Is there a way to save this data so it doesn't reset while still being able to reference it as the original table? Would save a bit of work
For saving data to a profile rather than a run you should store it in G.PROFILES[G.SETTINGS.profile]
G.GAME is for storing data to runs
If its just a global table with nothing needing to be saved it can just directly be stored as a global rather than in another table
Alright, thank you!
how would i go about making an edition that retriggers a joker
also how do i go about cchanging joker values with other jokers or editions
Spectrallib provides that
Or you can just do it with your own copied function but when it eventually breaks and you have to figure it out yourself thats probably not going to go well
it's probably bad practice, but if you want to save mod-specific info to the profile, what i do in my mod is i just save it to a profile-index-indexed table in my mod config 
.
that's why i qualify with mod-specific info, so the profile isn't bricked if the player decides to uninstall the mod. bad practice, but the profile stays safe
i shall look into this thank you
I dont think adding arbitrary data to the profile is going to do anything besides increase the size of the save
Which was probably happening anyway if you were saving mod data
i guess
thanking you
is there a func i should call when i change data in G.PROFILES[G.SETTINGS.profile] or does it just auto save whenever it's changed? which i'm pretty sure is not the case
G:save_progress()
ive managed to get it to edit the description of the joker but instead of aapplying that to teh joker it just gives 1.5 xmult
Code?
I'm back gang, I'm wondering what I'm doing wrong here? METER_DATA seems to not be saving
local start_run_hook = Game.start_run
function Game:start_run(args)
start_run_hook(self, args)
G.GAME.METER_DATA = {}
G.GAME.METER_DATA.meter = 0
G.GAME.METER_DATA.meter_active = true
end```
SMODS.Shader({ key = 'gold', path = 'gold.fs' })
SMODS.Edition {
key = 'golden',
shader = 'gold',
config = {
extra = {
modifytbl = {
["*"] = 1.5
}
}
},
loc_txt = {
name = 'Golden',
text = {
[1] = 'This Joker has its values',
[2] = 'increased by {C:attention}50%{}'
}
},
calculate = function(self, card, context)
if context.pre_joker then
card.ability.extra = card.ability.extra or {}
if not card.ability.extra.golden_applied then
BAGGUTRO.modify_joker_values(card, self.config.extra.modifytbl)
card.ability.extra.golden_applied = true
end
end
end
}
Yes, you need to use it like this BAGGUTRO.modify_joker_values(card, {['*'] = 1.5}, {x_mult = 1, x_chips = 1, h_size = 0, extra_value = true, cry_prob = true, d_size = 0, card_limit = true, extra_slots_used = true})
any way to make it static instead of every time its triggered?
Call it in on_apply and on_remove
chat i cooked up something
and how would i go about doing that? sorry im really bad at this
i think i did it wrong lol
i got it
YAY
ok new problem is that i may need to redo all my jokers to not be static values because those dont scale but vanilla ones do
I have a sticker that makes it to where if one is sold or destroyed all others with that sticker are too, but it hasn't been working
local oldcardremove = Card.remove
function Card:remove()
local g = oldcardremove(self)
local destroyed_cards = {}
if self.ability.modprefix_stickerkey then
for k, v in pairs(G.jokers.cards) do
if v ~= self and v.ability.modprefix_stickerkey then
if G.CONTROLLER.locks.selling_card then
G.FUNCS.sell_card({config = {ref_table = v}})
else
table.insert(destroyed_cards, v)
end
end
end
end
if next(destroyed_cards) then
SMODS.destroy_cards(destroyed_cards)
end
return g
end
o oke
Bump, sorry to bother you
You should be hooking Game:init_game_object
I get this message sometimes but I don't know what it means
Would this be with or without start_run?
local igo_hook = Game.init_game_object
function Game:init_game_object()
local g = igo_hook(self)
g.METER_DATA = {
meter = 0,
depth = 0,
meter_active = true,
}
return g
end``` I realize now g is G.GAME
how do I have a joker increase the hand size after two cards of a specific enhancement are scored'
if context.individual and context.cardarea == G.play then
local cards = {}
for k, v in pairs(context.scoring_hand) do
if SMODS.has_enhancement(v, 'm_modprefix_key') or SMODS.has_enhancement(v, 'm_modprefix_key2') then
table.insert(cards, v)
end
if v == context.other_card then break end
end
if #cards == 2 then
card.ability.extra.hand_size = card.ability.extra.hand_size+1
G.hand:change_size(1)
return {message = localize('k_upgrade_ex'), message_card = card}
end
end
cheers
any idea how to fix this?
It worked if I use draw_to_hand, so I tried copying the code the moves the card and it does this instead
Whenever the following code triggers, my game crashes
if pseudorandom('charming') < G.GAME.probabilities.normal / card.ability.extra.odds then
-- Thanks to nh6574 and bepisfever in the Balatro Discord for the help with this next line!
SMODS.add_card{ set = "Joker", edition = "e_negative", key = "charming"}
return {
card = card,
message = "Wow!",
}
end
end```
code since I'll be eeping
UI wizards please do your thing I'll be eternally grateful
It works now, thanks!
i have a bit of an issue here - i have a joker that adds +1 mult per enhancement/edition/seal to all played cards, but for some reason the red seal on unscored cards is retriggering the effect.
how do i prevent that?
instead of nvm I thought this was for an enhancementcontext.individual try using context.main_scoring instead. You should replace mentions of context.other_card with just card too iirc
context.individual gets retriggered by stuff
here's code for a card I have witha similar effect
-- Forte
SMODS.Consumable{
key = "vb_forte",
set = "Voicebank",
atlas = "voicebanks",
pos = {x=0,y=4},
calculate = function(self, card, context)
if card.ability.immutable._active then
if context.before then
for _, _card in ipairs(context.full_hand) do
if not SMODS.in_scoring(_card, context.scoring_hand) then
SMODS.scale_card(card, {
ref_table = _card.ability,
ref_value = "perma_bonus",
scalar_table = {_card:get_id()},
scalar_value = 1
})
end
end
end
end
end
}
ignore the _active thing thats not important
Use context.before and iterate over context.full_hand instead.
how do i use the initial_deck parameter? i want to create a deck of only Kings of Hearts, but i can't seem to get it to work properly
also im using apply with an event to add steel and red seals to every card, which creates a massive delay when starting a run. is there a workaround for that?
SMODS.Deck{... initial_deck = { Ranks = { 'King' }, Suits = { 'Hearts' } }, ...}
what are your event parameters? trigger, blocking, blockable, etc.
i had trigger after with a delay of 0 and blocking false
i did this, but only got 1 king of hearts, how do i get more than that?
you need to add more in your event in apply:
SMODS.add_card{
area = G.deck,
set = 'Base',
rank = 'King',
suit = 'Hearts',
seal = 'Red',
no_edition = true
}```
and before adding them, you can sneak in code that will add the red seal you want to the one king that you start with
G.E_MANAGER:add_event(Event({
func = function()
for _, c in ipairs(G.playing_cards or {}) do
if c.set_ability then c:set_ability("m_steel") end
if c.set_seal then c:set_seal("Red") end
end
return true
end
}))
end,
this is what i was doing originally
i removed the parameters i had before bc i only have one card at this point
so i do this outside of the event?
mb
inside?
try:
apply = function(self, back)
G.E_MANAGER:add_event(Event({
trigger = 'immediate',
blocking = false,
blockable = false,
func = function()
if G.deck then
G.deck.cards[1]:set_seal('Red', nil, true)
for _ = 1, 51 do
SMODS.add_card{
area = G.deck,
set = 'Base',
rank = 'King',
suit = 'Hearts',
seal = 'Red',
no_edition = true
}
end
return true
end
return false
end
}))
end,
if i add steel to them under the set_seal, do i need to also make them steel in the add_card?
also it still has a 2sec ish delay at the start while it populates the deck
i'm assuming you want all of them to be steel in that case, so you'd want to add enhancement = 'm_steel' to add_card
weird, i recently wrote a deck that does something similar with those exact event params and it's instant for me
also am i adding steel = m_steel to the add_card? or do i need a G.deck.cards[1]:set_ability or something
omg i'm dumb
well you're smarter than i am
i've been banging my head at the wall for like 3 hours trying to make a single deck
of one card
yeah, G.deck.cards[1]:set_ability('m_steel') below that first seal set will give you the steel on the first card
surely there's a better way to do decks than to manually create 51 extra copies of the same card :(
also why do i always forget the damn comma
well this functions, now i just need to figure out why its taking so long, and if i can make it all happen at once
ty
apparently there may be changes to SMODS.Deck at some point to allow you to more finely adjust the deck, but atm this is how you do it. that's the best way atm
i mean i guess ive never played with a modded deck that is like this, but i swear they always load instantly
even CCD in cryptid which makes them all consumables doesnt take this long i swear
but im sure its a lot more thought out in general
does anyone know if it's possible to mess with the cards in played hand during context.press_play?
they're not actually in the played hand yet, so they're still in G.hand.highlighted
ahh. i figured out a better solution to my problem, anyway
if you highlight cards in context.press_play i think you can add them to the played hand forcefully its really funny
hihi ophie
do you mind letting me know what you think about the stake art in #⚙・modding-general :333
a small issue, all the cards dont have the king of hearts sprite in my hand
...huh?
they have the correct sprite in the deck, but in my hand they are just blank steel cards with red seals
smods issue, will be fixed in the next update
unfortunately, sprite issues are beyond me ðŸ˜
i think at least
Adding skip_materialize = true to your add_card might help. It might be the repeated materializing animations slowing you down.
unfortunately not, it also makes a sound everytime it's doing whatever it is doing, perhaps that is making it take that long?
how do i do thousands separator in string.format?
Alternate idea then, let the deck create the typical 52 card deck as a basis, then in your event, loop through each card in G.playing_cards and:
SMODS.change_base(card, 'Hearts', 'King')
card:set_ability('m_steel')
card:set_seal('Red', true, true)
Notably, SMODS.add_card calls card:set_seal without the added true, true that prevents the animation and sound of adding a seal to a card. This method should let you skip that.
that worked, but now i dont draw cards to my hand
what
maybe i did something wrong
What's your code for the deck?
apply = function(self, back)
G.E_MANAGER:add_event(Event({
trigger = 'immediate',
blocking = false,
blockable = false,
skip_materialize = true,
func = function()
for _, card in ipairs(G.playing_cards) do
SMODS.change_base(card, 'Hearts', 'King')
card:set_ability('m_steel')
card:set_seal('Red', true, true)
end
end
}))
end,
this is my apply function
You need to return true in the event.
after the for loop?
Yeah, immediately after the loop.
Also you can remove the skip_materialize = true,
can i set the sprite of an enhancement dynamically so all cards with that enhancement get it instead of changing the sprites per card?
No, you can't.
dang
it's "possible" with a custom drawstep rendering the sprite instead of the enhancement itself, but that's certainly not simple
above center but below front
yep, possible but not simple
im reading up on how to make certain consumables appear in the shop
G.GAME.spectral_rate = self.config.spectral_rate(this value is 2)
this is how ghost deck does it but how would you do it for a custom consumable type you added?
i guess search for spectral_rate
You would replace spectral with the SMODS.ConsumableType key.
you can just do that?
so like SMODS.Modprefix_ConsumableType key?
No, it would just be the key without the mod prefix.
oh hype
is there a way to change the sprite of a joker's center in such a way that it affects all of them instead of setting the sprite for each individual copy of that joker?
No, you can only do that with seals and stickers.
understood. thanks
is there a way to check for specific jokers to beat a blind
like say i wanted it to select from an pool (objectType) that has to be in hand or the blind is not beatable
bump
nvm got it
had tow rap in an event for some reason
can i just shove a bunch of code in a joker's context return?
there's func and pre_func returns
oh right. the returned func value gets executed at the appropriate context return timing, iirc
func gets run after other effects, pre_func prior. if you need more control use extra tables
my sprite sheet is in order, my jokers are using correct sprites, but they arent appearing inn theh right order in the collection
i havnt changed any of that is what confuses me
shot in the dark, are you iterating over a table with pairs() as part of the load process?
how would one check this
well uh
did you on purpose type something like for k,v in pairs(files)
i do type that accidentally some times
you should check your main file
turns out the load order changed itself
i didnt touch it yet somehow it had been altered
anyway new problem
pretty sure its because sell value doesnt exist yet but i dont know how to go around that
what is the code
if g.jokers doesnt exist that function will return nil
card.ability.extra.thisjokersellvalue + ((function() for _, joker in ipairs(G.jokers and (G.jokers and G.jokers.cards or {}) or {}) do if joker == card then return joker.sell_cost end end end)()) * 2
why do you do this instead of just using card.sell_cost?
also yeah lol
it is also not good practice to in line functions like this it makes it less readable
alas joker forge as a base
it will definitely crash if the card isn't in G.jokers for some reason, such as the collection
dont edit jokerforge code, either use it or code it from scratch imo
.
oh right yeah thanks
game stopped crashing but now it doesnt actually modify sell fvalue
maybe start again by copying Egg but setting the value to 1 is harder
you would need to hook set_cost
can someone help me how to fix "engine/ui.lua:698: attempt to index field 'colour' (a nil value)"?
ok thanks:))
to any future travelers looking up how to change the "saved by mr. bones" text for a death saving thing, just change saved = true, to saved = "string"
that's in the wiki, isn't it?
-# for the %98
ye but there are way too many ppl that dont know it
and a few others
unsurprised, after all for most, the docs don't exist for the purpose of being read
you can also return a localization key and it works
woa how did you turn off pinging in replies
oh i figured it out
UI extension I'm working on https://discord.com/channels/1116389027176787968/1397732693101383841
how do you increase boosterpack slots?
SMODS.change_booster_limit(amount)
I think
Its something like that
Autocomplete should get it after the first bit
autocomplete isn't available for every mod dev
the documentation is
Oops! The game crashed:
engine/ui.lua:729: attempt to index field 'colour' (a nil value)
I keep having these crash i don't know how to handle it
how do I add a Credit tag under the badge of a card
I dont remember which mod add that
Is there a function or something that gets called when a booster pack is over?
3xCredits or i think its called creditslib theres two different ones but the implementation is the same
3x credit is an infoqueue ?
No, you just add this to your object at the end
credit = {
art = "Artist Name",
code = "Coder Name",
concept = "Concepter Name"
},
it would be very useful i need to hook something
context.ending_booster is called iirc
which is in uh
G.FUNCS.end_consumeable = function(e, delayfac)
what are you trying to do
call a function at the end of a booster pack, glorified context
its for a custom object
hey please don't dump your crash logs like this
Ok sr
you can always patch under the context call and run a function there
Maybe i'll ask later:))
you're issue is probably related to something here #⚙・modding-general message
does G.GAME.modifiers.booster_size_mod exist? i got an error that it was nil
or is it nil until its set to something
1 sec
Checked the code and yes, it's nil until it's set, and assumed to be 0 when it is nil.
i see
okay so. i am now just realizing that setting this variable does NOT work while still inside a booster pack
is there a way to make it work?
like draw an extra card?
probably emplace it into G.pack_cards?
emplace what though
just. get the card type of the pack and then create a card of that type?
Yeah
hm alright
the params are self (prototype), card (booster object), i (which card in the pack it is) and it returns either a Card object or a table that can be passed into SMODS.create_card
mm
okay so well new development i didny need to do any of that, there was miscommunication
what's the variable that tells how many choices you have left (e.g. 2 in a Mega pack)
im guessing G.booster_pack.something
G.GAME.pack_choices
yippee
wait ofc its not permanent
new suit coming to 0.0.3a
when i finish the code im gonna do the crossmod i need to do (for example pwx with uno cards)
each pack resets it to their own value im guess ing
how do you properly replace blinds in a custom deck?
Yeah that one gets reset with each pack. The permanent version is G.GAME.modifiers.booster_choice_mod and works the same way as the size_mod
this is what I'm trying, there's something I'm not doing properly:
calculate = function(self, context)
if context.setting_blind and context.blind and context.blind.boss then
if G.GAME.round_resets and (G.GAME.round_resets.ante % 8 == 0) then
G.GAME.round_resets.blind = 'bl_plant'
end
end
end,
yeah thanks
dw i WANT it to be temporary it makes my life easier
The Spike
2x Base
Debuff cards of the Icon Suit
local get_old_boss = get_new_boss
function get_new_boss()
if G.GAME.selected_back.effect.center.key == "b_modprefix_yourdeckkey" and (G.GAME.round_resets.ante % G.GAME.win_ante) == 0 then
return "bl_plant"
else
return get_old_boss()
end
end
This is what worked for me.
yo whats this
no i mean the thing youre using to show it
pwx
?
polterworx
do you want exactly the win ante, or do you want every multiple of the win ante?
for that matter - do you want the win ante, or every ante that gives a showdown blind?
does G.GAME.win_ante apply for every showdown boss?
it does not, it's only the one that wins you the game
and might not even be that if mods mess around with things
I forgot your original code wanted it every 8th ante, I adjusted my example
so if i use G.GAME.round_resets.ante % 8 == 0 it will do all showdowns?
it'll do every 8th ante
for compatibility with other mods that change the win ante, it should be G.GAME.round_resets.ante % G.GAME.win_ante == 0
if a mod changes how often showdown blinds appear, or what the win ante is, those won't correlate
and yea there's a number of mods out there that add e.g. a stake that makes showdowns appear twice as often
if you want every showdown, put your hook code after the original function and return the plant if the original result would've been a showdown blind
probably edit used blinds in the process to avoid side effects
im doing this rn, how can i properly edit the used blinds in this function?
local get_old_boss = get_new_boss or function() return "bl_big" end
function get_new_boss()
if G.GAME
and G.GAME.selected_back
and G.GAME.selected_back.effect
and G.GAME.selected_back.effect.center
and G.GAME.selected_back.effect.center.key == "b_steeldeck_all_kh"
and G.GAME.round_resets
and (G.GAME.round_resets.ante % G.GAME.win_ante == 0) then
return "bl_plant"
else
return get_old_boss()
end
end```
in that function, you don't
G.GAME.bosses_used.bl_plant = G.GAME.bosses_used.bl_plant + 1
...Also I promise you, you don't need that many in-case-of-nil checks, like at all. The vanilla get_new_boss function doens't check for any of that existing since it's all initialized beforehand.
atp use pcall for nil checks
How could I change what cards you start with in a deck
I can't use challenge decks
apply
i've seen what you're working on, i think the best route for you is to use initial_deck to start with a totally empty deck and then the apply function to add all the cards
initial_deck is an api function or soemthign else?
Cat
yeah just checked it
can I just supply nothing and it'll be nothign
initial_deck = {}
or do I need at least 1 card
you need to set it to exclude them i think
tried this and it gave me the full deck
try initial_deck = { Suits = { 'D', 'H', 'C', 'S' }, Ranks = { 'A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3', '2' }, exclude = true }
i would love more granular control over the starting deck
when are the changes
i mean
you have it
initial_deck gives you control over the ranks and suits in the deck, and the apply function lets you do basically everything else to that deck
maybe a remove initial deck flag would be cool
so you dont have to specify all the suits and ranks which might not be loaded yet
oh i just saw this nvm
how could I add the cards
it has to be in an event
also just use add_card it does all the stuff at the bottom
ah
wait I didn't provide a return
So I have a deck in my mod. For me, it appears last in the list (as intended) but someone's reporting it's appearing 2nd, right after red deck. Would anyone happen to know why?
I assume it's a difference in either steamodded or lovely version, or both?
How do I get the last played poker hand?
from the 1501a changelog https://github.com/Steamodded/smods/wiki/1501a
Is there a way to get the hands played from this round?
not specifically in smods, i'd probably use the mod calculate to save details about played hands to a table and then wipe it clean during end_of_round
Mod calculate?
function SMODS.current_mod.calculate(self, context)
...
end
That's a thing??
since 0827
🥹
it's very handy
Well, thanks!
how do i actualyl code in a joker retrigger for like an edition
like it retriggers teh card or joker thats used
ive got the optional feature turned on
i just dont know how it works
Question - can you add weights to consumables? Is that a thing?
it'll be easier when eremel's new PR is merged
idk how you'd approach that atm though
Maybe I'll just wait?
- why does this not work
- how do i get it to work on a joker
Okay, I have no idea what I'm doing
(This is for something else; effectively rerolling a booster pack)
g.booster_pack is a uibox i think
I have now realized that G.B- yeah
where is this code
If you're going to suggest a context, that's not applicable here.
Though, I could throw something together and create a variable that checks the key.
i mean if i suggest a context you could always use mod calculate
but i wasnt going to
checking something in the code give me a sec
local _card_to_spawn = SMODS.OPENED_BOOSTER.config.center:create_card(SMODS.OPENED_BOOSTER, #G.pack_cards)
local card
if type((_card_to_spawn or {}).is) == 'function' and _card_to_spawn:is(Card) then
card = _card_to_spawn
else
card = SMODS.create_card(_card_to_spawn)
end
if card then G.pack_cards:emplace(card) end
maybe that works?
yes
What if I want to create two? Just, put this in a for loop?
Here actually before I test that
Let me just try this as is
yeah it should work
the loop i mean, idk if it actually works properly in the first place
Works flawlessly. Nice.
Now let me try it in a loop.
it should be #G.pack_cards+1
but i dont think it matters that much
local num = #G.pack_cards
SMODS.destroy_cards(G.pack_cards.cards)
for i=1,num do
local _card_to_spawn = SMODS.OPENED_BOOSTER.config.center:create_card(SMODS.OPENED_BOOSTER, #G.pack_cards+1)
local card
if type((_card_to_spawn or {}).is) == 'function' and _card_to_spawn:is(Card) then
card = _card_to_spawn
else
card = SMODS.create_card(_card_to_spawn)
end
if card then G.pack_cards:emplace(card) end
end
Didn't work. User error?
It worked before I added the for loop, for the record.
Yeah, it's my fault (added a print statement and it didn't print anything), but I'm not sure what I did wrong
local num = #G.pack_cards.cards
oops yeah that
it's handled differently from regular stickers
card.sticker is set to just the key of the stake without a "stake" prefix, e.g. white or modprefix_key for modded stakes
like im trying to take ownership on them
you can't do that, they're not stickers gameplay-wise
oh so I cant edit the texture ?
no you absolutely can do that, the relevant properties are on the stake
sticker_atlas and sticker_pos
there just don't exist sticker objects for these because they don't act like stickers, they're just sticker sprites
create_badge(localize(v, "labels"), get_badge_colour(v), SMODS.get_badge_text_colour(v))
is it possible to make an invisible background color for a badge
G.C.CLEAR instead of get_badge_colour?
[manifest]
version = '1.0.1'
dump_lua = true
priority = 0
[[patches]]
[patches.pattern]
target = 'functions/UI_definitions.lua'
match_indent = true
position = 'before'
pattern = '''
if AUT.card_type ~= 'Locked' and AUT.card_type ~= 'Undiscovered' then
'''
payload = '''
if obj and obj.art_credit then
badges[#badges + 1] = create_badge(localize('taw_credit', "labels") + obj.art_credit, get_badge_colour(G.C.CLEAR), SMODS.get_badge_text_colour(G.C.GREY))
end
'''```
I probably did something wrong but idk
replace the whole get_badge_colour function call with just G.C.CLEAR
ok
how do i change the background color to a custom color when in a booster i made
https://github.com/Steamodded/smods/wiki/SMODS.Booster check vanillaremade's boosters for how it works
Before I do this manually, I just want a quick sanity check.
Is there a variable that keeps track of the last used consumable?
not in general, only one for consumables that The Fool can create
Alright, I'll just do it manually.
i want to change the animated sprite of a joker, i managed to do it for soul but not the card sprite itself
nvm
Sanity check. Am I doing this right?
SMODS.create_card({ key = [the key in that table] })
you can also add edition = key in create_card
or use add_card instead if you want it to be emplaced in consumables automatically
Oh!
sanity check: self.ability.rarity? or self.rarity? or self.config.rarity? or self.config.center.rarity? on Card, in highlight()
self.config.center.rarity
also there's self:is_rarity(key)
Is there a way to select more than 1 joker for the sake of a consumable
it's a bit janky, G.jokers.config.highlighted_limit = 2 will make it so you can highlight 2 but it's a bit hard to take into account other mods that might want to change it before/during/after
Maybe I should change the consumable effects
local oldHighlightLimit = G.jokers.config.highlighted_limit
G.jokers.config.highlighted_limit = 2
...
G.jokers.config.highlighted_limit = oldHighlightLimit```?
what if the other mod changes the value in between those
hmm
The middle one switches between the vanilla version and its counterpart in my mod, meanwhile the ones on the sides are to switch 2 vanilla jokers into the counterparts and vice versa
or what if another mod requires the highlighted limit to be 99 or 0 or something
usually if you want to have a consumable affect two jokers, it's in the form of "select this joker and affect the joker to the right of it"
weren't there helper functions to adjust highlight limits?
Maybe I have them only be used up after 2 uses
Is this possible to do or will it always disappear after using
if G.jokers.config.highlighted_limit < 2 then
G.jokers.config.highlighted_limit = G.jokers.config.highlighted_limit + 1
end
```?
maybe the docs have the answer
Where is the link again
Do seals come with only one badge or are they able to have many like other stuff
I'm asking because my coder wasn't able to put the mod badge in the sticker counterparts
And I wanna know if it's the same for seals
how can i fix this?
Your SMODS.calculate_context hook is set up incorrectly.
The full function is SMODS.calculate_context(context, return_table, no_resolve) and when you catch its return, you should use local ret = smods_calc_ref(context, return_table, no_resolve) or return_table
You should be using SMODS.current_mod.calculate instead of hooking SMODS.calculate_context
I want to make skill tree that give you more chips and mult
i'd like to make a deck that destroys enhanced cards when discarded, but decks don't naturally have a way to check for discarded cards i don't believe
i tried making a hook, but it still doesn't work; any help?
-- soapy deck effect
local original_discard_cards = G.FUNCS.discard_cards_from_highlighted
G.FUNCS.discard_cards_from_highlighted = function(e, hook)
local result = original_discard_cards(e, hook)
if G.GAME.back and G.GAME.back.key == "b_bof_soapy" and not hook then
for i = #G.discard.cards, 1, -1 do
local card = G.discard.cards[i]
if next(SMODS.get_enhancements(card)) ~= nil then
if SMODS.shatters(card) then
card:shatter()
else
card:start_dissolve()
end
table.remove(G.discard.cards, i)
end
end
end
return result
end
No, decks do get calculated with context.discard
if G.GAME.selected_back.effect.center.key == "b_bof_soapy" and not hook then
this didn't work though
calculate = function(self, context)
if context.discard then
if next(SMODS.get_enhancements(context.other_card)) ~= nil then
return {
remove = true
}
end
end
end
Yes, it should be self, back, context
why doesn't this work how i would like it to (win on red deck with blue stake or win on blue deck with red stake)
check_for_unlock = function(self, args)
if args.type == 'win_deck' and args.deck then
local deck_key = args.deck.key
local stake_level = args.stake_level or 1
return (deck_key == 'b_blue' and stake_level == 2) or (deck_key == 'b_red' and stake_level == 5)
end
return false
end
additionally, why doesn't this colour appear in-game?
G.C.plasma = { 0.8, 0.45, 0.85, 1 }
...
"hands or discards by",
"{C:plasma}+#1#{} for the next {C:attention}Ante"
},
...
G.ARGS.LOC_COLOURS.plasma = { 0.8, 0.45, 0.45, 1 }
Is there a way to flip cards drawn?
I was trying to search messages for context for on card drawn, but, doesn't look like that exists
But, I know card:flip() is a thing, and I know one of the boss blinds in the game flips card on drawn
Is there any way to do this on a joker?
awesome tysm
you're missing self in the arguments
i don't think the crash is related to this code tho, it looks like a ui code crash
Can you juice the back?
G.deck.cards[1]:juice_up()
What if no cards are left and a message(and juice) still needs to be displayed there?
G.deck:juice_up()?
smods handles this automatically for individual objects
juice the topmost card in G.deck, or G.deck itself in case it's empty
Whoaa G.deck works
is there a way for a voucher/joker/whatevr to make all jokers within an ObjectType be more likely to appear?
not yet at least
we need whatever stored inside a SMODS.ObjectType object to be copied over to G.GAME, plus various changes to shift the focus there in order for it to work
that's just the tip of the iceberg tho ðŸ˜
i think it will be easy with the new weight stuff eremel is working on
I see
ok well i guess uhh can i make a single joker more common and just manually do that for each joker
you can, by modifying the return values of get_current_pool, but the work would be tedious
and it breaks seeding as well, due to different pool size after modification
How can I convert a rank id into its key?
preferably without iterating though anything
SMODS.Ranks[key].id
otherway
Could I get a hand with this edition?
calculate = function(self, card, context)
if context.post_joker or (context.main_scoring and context.cardarea == G.play) then
G.GAME.blind.chips = math.floor(G.GAME.blind.chips - G.GAME.blind.chips * 0.05)
G.GAME.blind.chip_text = number_format(G.GAME.blind.chips)
return {
message = 'Depleted!',
colour = G.C.MULT,
card = card
}
end
end
Function-wise its working but visuals are a bit odd, the score updates all at once at the start instead of score by score. I tried using context.individual but that seemed to break it.
I have the id, but I need the key
ah i misread
then probably iterating
also why do you have the id but not the key
the deck view code uses obj_buffer
which stores the ids
wait actually im stupid I have actual card objects
I can just store the key
yeah
you need an event
for the chips_text
it's in the docs
instead of card:get_id(), why not card.base.value?
Cheers, sorry for silly question lol
i dont think it's silly, the docs don't really explain that problem
vanillaremade does however
On Vanilla Remade now :)
wait how do I get the rank key from a card
I can find it anywhere
card.base.value
thx
I think I did it wrong, the event is making it change text when it triggers instead of right away, but does the entire update on first trigger
calculate = function(self, card, context)
if context.post_joker or (context.main_scoring and context.cardarea == G.play) then
G.GAME.blind.chips = math.floor(G.GAME.blind.chips - G.GAME.blind.chips * 0.05)
return {
message = 'Depleted!',
colour = G.C.MULT,
card = card,
},
G.E_MANAGER:add_event(Event({
func = function()
G.GAME.blind.chip_text = number_format(G.GAME.blind.chips)
return true
end
}))
end
end
Is it as simple as event in wrong spot?
if i have a consumable that gives a negative joker it doesnt work if joker slots are full, how would i fix that?
boo ump
put that inside loc_colour hook
okie dokie
you need to save G.GAME.blind.chips to another local variable so it doesnt update in between, also dont put the event in the return
you need to manually check if the joker slots are full, so just delete whatever code you have that is doing that
but then if i had mmore jokers than joker slots wouldnt it still make a negative?
wdym
ok how much of a nightmare would it be to make a joker that adds like. a joker slot to the voucher area that refills once per ante
souper bump
tried with a different technique but it still didn't work
check_for_unlock = function(self, args)
return (
(args.type == "win_stake" and get_deck_win_stake() == 5) and
(args.type == "win_deck" and get_deck_win_stake("b_red"))
) or (
(args.type == "win_stake" and get_deck_win_stake() == 2) and
(args.type == "win_deck" and get_deck_win_stake("b_blue"))
)
end
How i am i supposed to set a custom badge color without needing to set another rarity again?
i forgot
custom colour where? the default rarity badge or a custom badge you added?
custom badge i added
it should be one of the create_badge options
like if i had 6 jokers and only 5 slots it still wouldnt work
or i mean itwould give it to me
also i have consumables that destroy a joker and make a joker but they also dont work when slots are full but then if it destroys a negative and the bypass is on it would overflow
hmm i still don't fully understand what you want to do
how could I prevent a card area from being destroyed when you start a run?
how do you destroy a card area
whenever it's gets undrawn its destroyed
which destroys all the cards in it
and apparently starting a run destroys the offscreen card areas i drew in the menu
they way i did it for one of my mechanics was to have the card not be in the uibox at all and just drawn over it
another way is to just copy_card the cards
You ca also save them and reload them
I tried removing the card from the offscreen card area when the gui is exited, but now they wont got back into the new card area
code?
had no internet ;-;
found the issue though
For some reason the cards got removed
I think I've fixed it though
i wanna unparent a card from an area and have it bounce around the screen like the dvd logo 🤔
what would i have to use beside context.setting_blind to ensure G.hand.cards is set up
if G.hand.cards and #G.hand.cards >= G.hand.card_limit then?
ill give it a shot but idk if it will work since it prints nothing within the table when context.setting_blind runs
you could put it in an event, inside the event, return true at the end, outside return false
and that way the event will do the thing you are trying to do when the hand is set up?
but tbh a better way might be
context.first_hand_drawn?
actually that could work since all i really need is the cards to show up
also yea sry if my questions are stupid like that since i am not that much of an expert
G.hand.cards always exists in all contexts
or the context wouldn't be called because it's outside a run
then i might just be dumb lol
wait i misunderstood
first_hand_drawn should work
setting_blind is before cards are drawn
yea it did work, im a little curious how i can automatically play a hand but ill figure it out eventually
yea that was my original plan but...
cards* not hand
yea
also fyi
calculate = function(self, card, context)
if context.first_hand_drawn then
G.E_MANAGER:add_event(Event({
trigger = "after",
func = function()
local cards = CalcLib.get_random_items(G.hand.cards, 5, "fatesreward")
for _, card in pairs(cards) do
card:highlight()
end
G.FUNCS.play_cards_from_highlighted()
return true
end
}))
end
end```
its allegedly this part thats breaking
is it possible to make the idea text smaller
badges[#badges + 1] = create_badge(localize('giga_art_credit', "labels")..obj.giga_data.art_credit, G.C.CLEAR, SMODS.get_badge_text_colour(G.C.GREY))
im using this
i think it's one of the arguments for create_badge let me check
ok nice
yes it's the next argument
like a number between 0 and 1 ig
this is in the docs btw : )
yeah but my lazy ass thought of modding-dev before 🤣 ðŸ˜
can I get the link to the doc
like I want to remove the padding under
you can't with the api
well maybe with generate_ui but that's barely user friendly
you would need to modify the box
well im bad with ui
arent we all
i understand nothing in that
i wish there were more UI helper functions ðŸ˜
i'd write some, but it requires enough understanding of UI structure to make something functional
tbh, it could at least result in cleaner and more readable code
anything to make the ui structure easier to understand would be a blessing
time for me to shill tf out of JTML
Actually I wanna revise it first ignore my shilling
balatro is such a great game to mod
By easier to understand you mean this? #1397732693101383841 message
yea prob, the framework looks nice
What if we take generate_card_ui
Scrap it entirely
And rebuild card (center, tag, blind) info UI in general by using new contexts similar to calculate context
🤷
go on
Depending on where you wanna do this that seems a bit overkill
I.e. SMODS? Might be BetterCalc: The Empire Strikes Back
-# i have no idea how to make this stupid joker effect run in series
Actually im curious to hear
wdym by this
basically im making this joker that automatically plays a hand without consuming it and in essence it just breaks with multiple copies
since it runs its effect in parallel for whatever reason and highlights already playing cards
cus i dont think this should have 235 events ðŸ˜
the only thing i can think of is that instead of writing your joker effect in the joker calculate, do it in the mod calculate instead. and have it check for next(SMODS.find_card('your full joker key'))
i could try but should i show the code just in case
sure
SMODS.Joker {
key = "TBOJ_Fatesreward",
atlas = "Placeholders",
pos = {
x = 0,
y = 0
},
loc_vars = function(self, info_queue, card)
return {
key = "j_TBOJ_Fatesreward"
}
end,
rarity = 2,
cost = 7,
blueprint_compat = false,
calculate = function(self, card, context)
if context.first_hand_drawn then
local cards = CalcLib.get_random_items(G.hand.cards, 5, "fatesreward") -- shuffles cards
G.E_MANAGER:add_event(Event({
trigger = "after",
func = function()
for _, selectedcard in pairs(cards) do
highlight_card(selectedcard, 0.5, "up")
table.insert(G.hand.highlighted, selectedcard)
end
card:juice_up(0.5, 0.2)
CalcLib.smart_play_cards(cards, 0) -- modified function to support deduction of 0 hands when played
return true
end
}))
return {}
end
end
}
how do you want multiple instances of the joker to be handled? only ever does it once, or does it for as many hands as there are copies of the joker?
bump again maybe it’ll make a Splash
try checking G.GAME.selected_back_key instead
like from left to right each joker plays their hand and spends 0 hands on it, but they just all do it in parallel for some reason
for one copy it works fine, just not with multiple
-# i think
that's how events work
basicly this is whats happening
for this i would probably stop any copies while another one is calculating, and then do it again after
if the hand didn't win
joker1 pushes event
joker2 pushes event
...
joker1 event occurs
joker1 event pushes more events
joker2 event occurs
joker2 event pushes more events
...
more joker1 events
these pushes more
etc.
question is how would i stop the rest without the whole game freezing
blocking = true, blockable = true?
make the first set a global the others check
ah
i wouldn't manage this through events only, it would be a nightmare
wait is there a way to block specific events
that might work
i tried 😔
you can set a condition in the event so it doesn't happen until all the other ones finish but that seems really convoluted
also it would cause problems if a hand wins
in short it's possible but not worth it, dont do that
yea i can probably just check whether the round score surpasses the requirement and skip all other copies
i would either have some logic so each joker goes after the previous OR set up some other place (like mod's calculate or hooks) that handles each individually in one place
yeah, i would still say to do so in the mod calculate like i said, but also instead of checking next(SMODS.find_card('j_TBOJ_Fatesreward')), put SMODS.find_card('j_TBOJ_Fatesreward') into a local variable and iterate through that
ill give it a shot i guess
the issue is that you still need logic to do the effects sequentially so only a loop probably wouldn't work
don't really know how to explain it better without coding it myself
there's also no smods context for selecting hand after a hand is played
what you're trying to do
im thinking since the function im using to remove the hand consumption is not part of the source code, i can just add another parameter to block it when one is already running but it would probably end up horribly
well forcing the hand to be played ends up calling more calculate events, right? so you could use other contexts to drive your loop iteration rather than just a simple for loop
and i thought this joker was gonna be a cakewalk ðŸ˜
although in that case instead of a local var, you make the SMODS.find_card('j_TBOJ_Fatesreward') go into a global var in your mod table
I mean what you need to do is when first hand drawn context called, set global variable and event in event in event.
Check for variable to prevent duplicate behavious, event in event will play hand when it will became available
I see no problem here
wouldn't that be janky if anything has another event in an event
how would i pause the event exactly
context calculating is single-layer queue so 3 layers in most cases will be enough
also some cards might expect for some context to be present after a hand is played
only returns tho not anything custom
Or, you can setup non-blocking non-blockable event which checks for game state and, if it's selecting hand, play it instantly, if it's round end, discard, otherwise keep itself in queue
then you'll not miss that for sure
this is what i would do yeah
shouldnt be that difficult imo
oh, that would work
like ```lua
if G.STATE == G.STATES.SELECTING_HAND then
...
return true
end
return false
although a little more sophisticated than that, maybe
you check for selecting hand as state where you need play hand, transition to round_eval to exit a loop when round win
And respect G.GAME.STOP_USE and G.CONTROLLER.locked
yes
im clearly stupid, i dont get it
i dont see where i would leave an event in queue bec its still all doing it in parallel
are you doing this
the state probably doesnt change immediately then
i think it would be better to make a variable in G.GAME or something and set stuff there
if not, im lowk lost
yeah, have a G.GAME var to halt additional event instances
although those additional instances should still be checking G.GAME.STOP_USE and G.CONTROLLER.locked, as well as G.STATES == G.ROUND_EVAL specifically to terminate if one of the hands wins
that's exactly what you need to do
closer to
if
not (G.GAME.FRTriggering
or G.GAME.stop_use
or G.CONTROLLER.locked)
then
G.GAME.FRTriggering = true
...
return true
elseif G.STATE == G.STATES.ROUND_EVAL
return true
end
return false
``` but yes, that is the general idea
actually, that's still gonna be queued if you start it, go to the main menu and do smth like start another run. even just reloading the saved run would make it screwy. would need to check against something like G.STAGE == G.STAGES.MAIN_MENU to kill the event too
close but not like this
if context.first_hand_drawn and not G.GAME.FRTTriggered then
G.GAME.FRTTriggered = true
G.E_MANAGER:add_event(Event({
blocking = false,
func = function()
if G.STATE == G.STATES.ROUND_EVAL then
G.GAME.FRTTriggered = false
return true
elseif G.STATE == G.STATES.SELECTING_HAND and (G.GAME.STOP_USE or 0) < 1 and not G.CONTROLLER.locked then
-- play your hand
return false
end
return false
end
}))
end
that's it
i'd say also
if G.STATE == G.STATES.ROUND_EVAL or G.STAGE == G.STAGES.MAIN_MENU then
G.GAME.FRTTriggered = false
return true
...
``` to prevent save reload/new run tomfoolery
no needs, event queue is fully cleared when you exit a run
oh right
and of course you need setup a check on game:load or somewhere and check is joker which starts thing is present and if so start loop again
othervise save-scum breaks the loop
is joker?
variable is not reliable method because it will be nil on first hand
im pretty sure theres something wrong with the code i have since it just seems to ignore all other conditions
it just prints ELSE
yea G.GAME.STOP_USE is at 1 apparently
@primal robin
blocking = false
for one thing it works but it does it indefinitely
you also need to put not G.GAME.FRTriggering in the middle condition.
i'm not sure about turning it back off within that same condition, either. you can try it, but i suspect you'll still have the same issue
yea the blocking got it to work
but i gotta make it know it has already played its hand
try changing the return false in the second condition to true instead
is there a way i can juice_card_until juice more aggressively? like increase the rate at which the animation is played
probably make own one
eligible_card:set_edition({ polychrome = true })
eligible_card:add_sticker({ eternal = true })
how do i correctly apply the eternal sticker to a joker?
card:add_sticker('eternal', true)
im trying to make a function for a consumeable with a charge rate
basically, you use the consumeable once and it stays in play but you cant use it again until it recharges, but im struggling with how to make it
i want it to gain 1 charge after each round
can anyone help?
is there a way to make the scaling work with joker retriggers?
context.retrigger i think
Looks good to me
is there a context check for a seal being played for jokers?
return nil, true
this acts as doing nothing return wise but will still calculate joker retriggers and post_trigger
if it scales after\
what if it was after the if card.ability.extra.x_mult > 0 check
if context.individual and context.cardarea == G.play and context.other_card:get_seal()
would you insert they key for the seal like this
get_seal(red_seal)
No, it would be context.other_card:get_seal() == 'Red'
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and context.other_card:get_seal() == "Everyone" then
card.ability.extra.mult = card.ability.extra.mult + card.ability.extra.mult_mod
return {
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.mult_mod * card.ability.extra.mult } },
}
end
if context.joker_main then
return {
mult = card.ability.extra.mult
}
end
end
ive been bashing my head against the wall with this for a little bit and cant seem to figure out why it doesnt work
can i see your seal definition
i think the first contexxt check doesnt work
bcs i have a #1# on mult that doesnt increase at all
💀
okay here's what id try
seperate the first 2 conditions in the if
print the result of get seal
then check the seal
it has to be on the first one
otherwise #1# would go up
first 2 conditions as in context.individual and contex.cardarea == G.play
mod prefix
tried that
for the seal...
im thinking
I get a strange error when using Room No. 2024
After a few antes, it may sometimes reset by subtracting a bit too much, leading to weird situations where I get -2 hand size
I don't really know how to explain it, I would need to show the code
-- Room No. 2024
SMODS.Joker({
key = "room2024",
config = { extra = { h_size = 0, h_mod = 1 } },
loc_txt = {
["name"] = "Room No. 2024",
["text"] = {
{
"If played hand contains a {C:attention}#2#{}",
"add {C:attention}+1{} hand size, resets when",
"{C:attention}Boss Blind{} is defeated"
}
},
},
pos = { x = 7, y = 7 },
cost = 9,
rarity = 3,
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.h_size, localize('Full House', 'poker_hands', card.ability.extra.h_mod) } }
end,
calculate = function(self, card, context)
if context.before and not context.blueprint and (next(context.poker_hands['Flush House']) or next(context.poker_hands['Full House'])) then
-- See note about SMODS Scaling Manipulation on the wiki
card.ability.extra.h_size = card.ability.extra.h_size + card.ability.extra.h_mod
G.hand:change_size(card.ability.extra.h_mod)
return {
message = localize('k_upgrade_ex'),
}
end
if context.end_of_round and context.game_over == false and context.main_eval and not context.blueprint then
if context.beat_boss and card.ability.extra.h_size > 1 then
G.hand:change_size(-card.ability.extra.h_size)
return {
message = localize('k_reset'),
}
end
end
end,
remove_from_deck = function(self, card, from_debuff)
G.hand:change_size(-card.ability.extra.h_size)
end -- I mean it kinda works? Kinda? I dont know, I got a weird error where I got a handsize of 4 once. No idea how to reproduce that.
})
maybe i inport it in a different way and then call it over there?
im honestly so lost at how to do this
who knew calling a seal in jokers was so annoying
you are not setting h_size to 0 after substracting it
you should do that in both end_of_round and remove_from_deck
okay no i figured it out
o how i do that
no need to be a dick abt it
anyways
-- Demented Face
SMODS.Joker({
key = "dementedface",
config = { extra = { xmult = 1.5, odds = 4 } },
loc_txt = {
["name"] = "Demented Face",
["text"] = {
{
"{C:white,X:mult}X#3#{} Mult",
"for played {C:attention}face{} cards",
"{C:green}#1# in #2#{} chance to",
"{C:attention}destroy{} played cards"
}
},
},
pos = { x = 0, y = 8 },
cost = 9,
rarity = 3,
blueprint_compat = false,
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_dementedface')
return { vars = { numerator, denominator, card.ability.extra.xmult } }
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and context.other_card:is_face() and SMODS.pseudorandom_probability(card, 'j_hatch_dementedface', 1, card.ability.extra.odds) then
SMODS.destroy_cards(context.full_hand, nil, nil, true)
end
if context.individual and context.cardarea == G.play and context.other_card:is_face() then
return {
xmult = card.ability.extra.xmult
}
end
end
})
what context should i run for SMODS.destroy_cards?
bc im guessing context.full_hand just... fucks things up
you shouldn't be destroying cards in calculate that way, give me a second to give an example how you should do that because i don't really know how to explain
i tried remove but it didnt rly work ðŸ˜
it just juiced and then did nothing
im guessing i need context.destroy_card
yes. you should generally be using context.destroy_card for things like this. see:
if
context.individual
and context.cardarea == G.play
and context.other_card:is_face()
and not context.other_card.getting_sliced
and SMODS.pseudorandom_probability(card, 'j_hatch_dementedface', 1, card.ability.extra.odds)
then
for _, pCard in ipairs(context.full_hand) do
pCard.getting_sliced = true
end
end
if context.destroy_card and context.destroy_card.getting_sliced then
return { remove = true }
end
...
ah
loc_vars = function(self, info_queue, card)
local numerator, denominator = SMODS.get_probability_vars(card, 1, card.ability.extra.odds,
'j_hatch_dementedface')
return { vars = { numerator, denominator, card.ability.extra.xmult } }
end,
calculate = function(self, card, context)
if context.individual and context.destroy_card and context.cardarea == G.play and context.other_card:is_face() and SMODS.pseudorandom_probability(card, 'j_hatch_dementedface', 1, card.ability.extra.odds) then
return {
remove = true
}
end
if context.individual and context.cardarea == G.play and context.other_card:is_face() then
return {
xmult = card.ability.extra.xmult
}
end
end
})
this is how i did it
although
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and context.other_card:is_face() and SMODS.pseudorandom_probability(card, 'j_hatch_dementedface', 1, card.ability.extra.odds) then
if context.destroy_card then
return {
remove = true
}
end
end
this may be cleaner
you shouldn't do a context.destroy_card check inside a context.individual check
they're separate contexts
but you should also generally not be returning remove = true in any context that isn't context.destroy_card
if you're concerned about the cards being parsed by other things, that's what setting getting_sliced helps with. it also makes the card fail can_calculate()
o
you can use SMODS.destroy_cards
oh?
it was made to handle this
ahhh 
there's really no issue with them destroying the entire hand in context.individual?
then idk what to do ðŸ˜
do i keep the SMODS.destroy_cards(context.full_hand, nil, nil, true) ?
no
it happens after scoring
yes. based on what N said, i was wrong and what you were doing with SMODS.destroy_cards in context.individual is fine
i see
i just tested my original code and its being a little wacky
the idea is this
every face card thats played has a 1 in 4 chance to destroy itself
and a 3 in 4 chance to give x1.5 mult
how do i make my code reflect that
ohhhhhh
yeah, bc i have no idea how to make the code do that
here's some pseudocode to get you started
during context.individual
if context.other_card is a face card
1 in 4 chance to SMODS.destroy_cards(context.other_card)
3 in 4 chance to return { xmult = 1.5 }
end
end
(is the 3 in 4 chance independent from the 1 in 4 chance?)
no, it's the same probability
you make that hinge on whether the pseudorandom check suceeds (destroy) or fails (xmult)
i would implement that like:
if
context.individual
and context.cardarea == G.play
and context.other_card:is_face()
and not context.other_card.getting_sliced
then
if SMODS.pseudorandom_probability(card, 'j_hatch_dementedface', 1, card.ability.extra.odds) then
context.other_card.getting_sliced = true
else
return { xmult = card.ability.extra.xmult, message_card = context.other_card }
end
end
if context.destroy_card and context.destroy_card.getting_sliced then
return { remove = true }
end
ok yea it'd just be if 1 in 4 chance, destroy. else, xmult
apparently its lazy code but idk
who told you that?? blundering them with my mind rn
no, elses in general are fine, what the problem is with them is making giant if else blocks. see the vanilla calculate_joker code 🥴
ah so thats what sliced means
yea a regular if/else statement is perfectly fine, if you're doing if ... elseif ... elseif ... elseif ... elseif ... else you might want to consider restructuring your code lol
thats the stuff i see in my old jokerforge code
i had to go back and redo certain jokers of mine to make it better
Oh fuck my fundamentals are lazy code ðŸ˜
example being risky revolver, which has been redone bc i didnt like how it worked
-- Demented Face
SMODS.Joker({
key = "dementedface",
config = { extra = { xmult = 1.5, odds = 4 } },
loc_txt = {
["name"] = "Demented Face",
["text"] = {
{
"{C:white,X:mult}X#3#{} Mult",
"for played {C:attention}face{} cards",
"{C:green}#1# in #2#{} chance to",
"{C:attention}destroy{} played cards"
}
},
},
pos = { x = 1, y = 8 },
cost = 9,
rarity = 3,
blueprint_compat = false,
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_dementedface')
return { vars = { numerator, denominator, card.ability.extra.xmult } }
end,
-- credit to NopeTooFast and Meta and N' for help
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and context.other_card:is_face() and not context.other_card.getting_sliced then
if SMODS.pseudorandom_probability(card, 'j_hatch_dementedface', 1, card.ability.extra.odds) then
context.other_card.getting_sliced = true
else
return {
xmult = card.ability.extra.xmult
}
end
end
if context.destroy_card and context.destroy_card.getting_sliced then
return { remove = true }
end
end
})```
heres what i got
time to test this bitch
ok it works
it's a lot simpler nowadays
in the atlas definition, set atlas_table = "ANIMATION_ATLAS" and frames = (number of frames), and then you just have to put all the frames in a single row in the image and the game will animate it on loop for you
it defaults to 10 fps, but you can set fps = something to change it
oo yay
charlie charlie kirky
can i slow down the speed messages play? ex: the "again!" on retriggers
yes, add delay = number to the return
is there a method or context i can use for when a card with a specific sticker is added to the deck? tried using add_to_deck in the sticker definition but it doesn't seem to have that
stickers have apply
N, do you know if steamodded has anywhere where it declares any G.GAME vars? thinking of iterating my dynamic (un)ban system some more now that i have a vague idea of how i'm going to tackle it
i need to use add to deck instead because the sticker debuffs the card so i want that to happen when its gained not when its created in the shop
try using card:is_rarity() instead
i tried that before and had no luck
what is the full code of that? and your custom rarity, too
if you want it when you apply it yourself then doesn't that mean you already have the function where you can apply the effect?
i dont remember any at the start of the run but there probably is one somewhere
do you have the code for the rarity
i can check revos repo i guess
hmm i dont see why that wouldn't work
SMODS.Rarity({
key = "p",
badge_colour = G.C.RARITY[3],
pools = {
["Joker"] = {
rate = 0.01,
},
},
default_weight = 0.01,
})
could you show us more of the surrounding code?
and it's just that one condition that doesn't work?
yeah only the printer doesnt work
found what i was looking for: context.card_added
and you're sure it's the rarity check that's not working? you've tried without and it enters?
if i try without its just going to apply everywhere
if you're concerned about that when testing it, you could comment out the add sticker function and add a print
everyWHERE EXCEPT THE PRINTER CARD???
@dreamy thunder
i'm commenting out the printer totem
while u try to see why this happens
what was it you commented out to test for this result, can you show
i just removed the rarity check
ah
so it applies to every card if the totem head is printer (which was the case)
how would i change the rarity badge text for a specific joker
for a specific joker added by your mod, or for a vanilla joker
joker added by my mod
define a set_card_type_badge function
https://github.com/Steamodded/smods/wiki/SMODS.Joker
(it's not so much "editing the rarity badge" as it is "skipping the rarity badge and letting you make your own")
yeah
(for your own joker, just skip the taking-ownership part)
hey hi hello im a Little Bit new here (joined like 50 minutes ago lol) and i come here in search of some help. only recently bought balatro on steam, though ive had an idea floating around in my head a bit. i did a very silly and Bad mod as a quick test and got some of the basics down, though im unsure of the specific syntax(?) used for stuff i had thought of and searching on my own yielded nothing.
Xmult scaling is the first thing ive not figured out yet. i Assume it’s just Xmult_scale similar to an example i saw using chip_scale but i still wanted to corroborate
resetting the scaling after a certain condition similar to Ride the Bus is another part that’s been stumping me (more on the specific condition later)
splash. just in general. i tried to look for what makes it count all cards in scoring in the source code but i couldn’t find anything and left feeling the fool myself
now, for any of this to work, the joker needs to check something which i gaslit myself into thinking was already in the game? but it isn’t, which complicates things quite a bit. i need to somehow check the sum of the base chip value for the played cards. ive no idea how to go about this At All. the gist of my idea was that, if the base value of the played cards was equal to the specified number, it would play out the splash effect and scale the Xmult. if the total went over, however, it would Not do that and reset back to 1x instead (the number is 21 it’s blackjack it’s just a blackjack joker)
but anyway, if anyone has any form of helping out that would be cool thamks. also my apologies for the big ass wall of text i just wanted to make sure i put out all the information at once skfjdkfjfj
the source code isn't very helpful for learning jokers and stuff
use vremade and the smods wiki instead
also looking at other people's code helps
There's also #1349064230825103441 and #1486847115127947374
man I wonder who made those
I did not make the starter pack smh
yeah but the other one
any idea why this doesn't work? been asking for about a day now
check_for_unlock = function(self, args)
return (
(args.type == "win_stake" and get_deck_win_stake() == 5) and
(args.type == "win_deck" and get_deck_win_stake("b_red"))
) or (
(args.type == "win_stake" and get_deck_win_stake() == 2) and
(args.type == "win_deck" and get_deck_win_stake("b_blue"))
)
end
on a deck btw
Because of how you've laid out your ands and ors, part of your condition effectively includes the statement args.type == "win_stake" and args.type == "win_deck". This is impossible, a variable cannot be two separate values at the same time like that.
In words, what exactly do you want your unlock condition to be?
this is my unlock condition
Try this:
return args.type == "win_stake" and (get_deck_win_stake("b_red") >= 5 or get_deck_win_stake("b_blue") >= 2)
it worked!
thank you
You're welcome.
why does I get an extra box
Was testing out tables, Does anyone know why this happens like a million times at round end?
Code:
if context.end_of_round then
if card.ability.extra.count < 5 then
card.ability.extra.count = card.ability.extra.count + 1
print(Count[card.ability.extra.count])
print(card.ability.extra.count)
else if card.ability.extra.count == 5 then
card.ability.extra.count = 1
print(Count[card.ability.extra.count])
print(card.ability.extra.count)
end
end
end
context.end_of_roundis called during both context.main_eval and context.individual for each card in your deck
how would I read the a config value from a sticker if the location I want to read it to isn't inside the sticker code? For instance, I know that I can read default extra values from a joker by using G.P_CENTERS.j_modprefix_key.config.extra, but I don't know how to do the same with a sticker.
Is there a way to disable vanilla tarot cards, spectral cards, and tags from all showing up?
I know theres a mod out there that lets you manually set that up, but, it'd be cool if I could have it automatically do that for my mod
Nvm, figured out that I can use SMODS.Sticker.obj_table.modprefix_stickerkey.config.extra
for k, v in pairs(G.P_CENTERS) do
if (v.set == 'Tarot' or v.set == 'Spectral') and not v.original_mod then
SMODS.Consumable:take_ownership(v.key, {
in_pool = function() return false end,
}, true)
end
end
for k, v in pairs(G.P_TAGS) do
if not v.original_mod then
SMODS.Tag:take_ownership(v.key, {
in_pool = function() return false end,
}, true)
end
end
Hell yeah awesome, ty
What function do I put this in? I haven't done anything aboutside of custom jokers just yet. Is it just in the main.lua somewhere?
how do I change the color of the background of a joker description
in your main file is fine
how do people usually make a certain type of consumable in a consumable set appear less, something akin to soul
hidden = true
Wait four hours and do it really easily
eh? cant I have a parameter set for how often it appears?
soul_rate
In the next release you can just do weight = 0.1 for example which will be 100 times rarer
how timely convenient
how do you make a sticker incompatible with others like eternal/perishable
what API method am I supposed to use to use for the consumable to be updated in a booster pack?
and why is it returning high card and not 4oak since thats supposed to be the default
it's kinda hard to actually do due to how stickers are checked, it's really hard to make them actually incompatible with vanilla stickers too
loc_vars?
what's the code
SMODS.Consumable{
key = 'nptunicorndream',
set = 'creamerycons',
atlas = 'creameryatlas',
nptrarity = 'rarecreamery',
hidden = true,
soul_set = 'creamerycons',
can_repeat_soul = true,
soul_rate = 0.0,
pos = {x = 0, y = 2},
soul_pos = {x = 1, y = 2},
config = { extra = {base=3, scaleou = 1, scale = 3, mphand = tje.mphand(), max = 50} },
loc_vars = function(self, info_queue, card)
if card.ability.extra.base + (math.floor(G.GAME.hands[card.ability.extra.mphand].level/card.ability.extra.scale)) > card.ability.extra.max then
return {vars = {card.ability.extra.base, card.ability.extra.scaleou, card.ability.extra.scale, card.ability.extra.max or card.ablity.extra.base, card.ability.extra.mphand, card.ability.extra.max}}
else
return {vars = {card.ability.extra.base, card.ability.extra.scaleou, card.ability.extra.scale, card.ability.extra.base + (math.floor(G.GAME.hands[card.ability.extra.mphand].level/card.ability.extra.scale)) or card.ablity.extra.base, card.ability.extra.mphand, card.ability.extra.max}}
end
end,
update = function(self, card, dt)
card.ability.extra.mphand = tje.mphand()
end,
can_use = function()
return true
end,
calculate = function(self,card, context)
card.ability.extra.mphand = tje.mphand()
end,
use = function(self, card)
if (math.floor(G.GAME.hands[card.ability.extra.mphand].level/card.ability.extra.scale)) > card.ability.extra.max then
print((math.floor(G.GAME.hands[card.ability.extra.mphand].level/card.ability.extra.scale)) ..'is more than'.. card.ability.extra.max)
udnpttargetlevel = card.ability.extra.base + card.ability.extra.max
else
print((math.floor(G.GAME.hands[card.ability.extra.mphand].level/card.ability.extra.scale)) ..'is less than'.. card.ability.extra.max)
udnpttargetlevel = card.ability.extra.base + (math.floor(G.GAME.hands[card.ability.extra.mphand].level/card.ability.extra.scale))
end
update_hand_text({sound = 'button', volume = 0.7, pitch = 1.1, delay = 0}, { mult = G.GAME.hands[card.ability.extra.mphand].mult, chips = G.GAME.hands[card.ability.extra.mphand].chips, handname = card.ability.extra.mphand, level = ''})
level_up_hand(card, card.ability.extra.mphand, nil, udnpttargetlevel)
update_hand_text({sound = 'button', volume = 0.7, pitch = 1.1, delay = 0}, {mult = 0, chips = 0, handname = '', level = ''})
end
}
made this like very long ago I think
so apologies if it seems really REALLY annoying to read
yeah if you set a value in config it's only set when you boot up the game
ahh
instead of saving it to config just replace the return in loc_vars for the mphand with that function
zeebee
you also don't need to update it in update, just use the function whenever you need the value
it uhhh still returns high card ðŸ˜
are you updating it in loc_vars?
whar
are you updating the value in your loc_vars function?
ahh found out why, tnx anyways!!
Bump
do you have a loc_vars function for this? what does it look like if so
yes
this is the only thing you get an extra multibox for?
yeah
are you doing any other description-related stuff, like patching vanilla code somewhere, or
hmmm
after updating the code, did you start a new run and get a new item?
yes, check a fresh instance
ah
could i see the full joker code, then? i don't see how you could possibly have an extra random multibox
SMODS.Joker:take_ownership("j_joker", { --Ritten
atlas = 'Jokers',
pos = {x = 0, y = 0},
taw_data = {
grow_time = 2,
mult_add = 1,
mult = 0
},
loc_vars = function(self,info_queue,center)
return{vars = {center.ability.mult + center.config.center.taw_data.mult, center.config.center.taw_data.grow_time}}
end,
calculate = function(self,card,context)
if context.joker_main then
return {
mult = card.ability.mult + card.config.center.taw_data.mult
}
end
if context.end_of_round and context.main_eval then
card.config.center.taw_data.grow_time = card.config.center.taw_data.grow_time - 1
if card.config.center.taw_data.grow_time <= 0 then
Taw.grow(card, 'j_taw_rat')
end
end
end
}, false)```
im just taking ownership