#💻・modding-dev
1 messages · Page 58 of 1
What's your code at now, and what stage of scoring does it lock up on? I'd say figuring out why it's locking up first is more important than figuring out the functionality, or did you mean removing a for statement fixed it?
I would think removing the for i,v in ipairs fixed it, game loads properly
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.blueprint then
if pseudorandom('minihakkero') < G.GAME.probabilities.normal/self.config.extra.odds then
card.ability.extra.money = card.ability.extra.money + card.ability.extra.money_mod
card_eval_status_text(card, 'extra', nil, nil, nil, {message = localize('k_upgrade_ex')})
return {calculated = true}
end
local destroyed_cards = {}
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.2,
func = function()
for i=#G.play, 1, -1 do
destroyed_cards[#destroyed_cards+1] = G.play[i]
local card = G.play[i]
if card.ability.name == 'Glass Card' then
card:shatter()
else
card:start_dissolve(nil, i == #G.play)
end
end
end
}))
end
end
What's happening now is that, when i play a hand, the game just doesn't score the hand and softlocks there
you forgot the part where the event needs to return true
Is there a thorough explanation of the event manager anywhere, or has no one made one yet?
oh lol, hold on let me change that
no events are too borked for anyone to bother
okay, so it does score now, (just added return true to the end of the event manager)
but it still doesnt destroy the cards
wait hold on this is wrong in the first place
oh this might be more tricky than i thought
is there any way i can make it calculate based on just the intial scoring and not based on retriggers?
not context.repetition, probably works fine.
2ho
Is this meant to be a joker or a new object type? If a joker, upon the creation event, use G.jokers:emplace(card), assuming local card is what you have create_joker or whatever you used as.
oh thank you yeah its great
Is there a way to force a joker to always show up in shops? i want to try and create a new joker but if it only shows up randomly i dont know how to test it
theres this mod called debugplus, allows you to spawn in a joker from your collection
also can change suits, enhancements, the whole kahuna
ah i see do you have a link to it by any chance?
legend thanks
That is the better way to accomplish what you want, but in general an easy way to forcefully spawn a card is via hooking create_card
is there any way i can add text to a row column?
i made a new cardarea lol
i always add G.(cardarea):emplace(card)
anyways chat am i cooked
i got no joker slots
Yeah like tooltips do
Put a text UI object inside
You can create a text node inside a row node
i dont want to
why not
because then it creates a new row node
That’s not how it works
that is
ive got a row node with 2 columns, i want text overlayed on the columns
if i add a text node
It’s either misaligning (compared to what you expect) or you’re doing something wrong
its a new row node
ive looked at just about every card destroying joker and consumable in the game but i still havent found something that works. its way more difficult to remove a random card than i thought
There’s probably some nested combination of nodes that work
What do you want it to look like?
What’s the difficulty?
@hushed cradle
i dont know its just never destroying the card. everything else works fine except for that
What do you have
Oh I don’t think that game supports that. I think you’d either need to edit UI code or try to cheat it in some way. Maybe using root nodes, but Idk how they work
Maybe you could cheat it with a shadow?
Like make the second color a shadow that’s translated to the side?
Might be able to frakenstein it by having the text split into 2 nodes, left being aligned to tr/cr of the left column, and right being aligned to tl/cl of the right column, and some padding shenanigans.
That’s also a possibility, but I remember text inside of columns being weird. Although
That might’ve just been the X control for tooltips
Because it’s hardcoded to behave in a certain way
Like destroying a random card shouldn’t be hard so I want to know what you have already
i was preoccupied with something hold on
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.blueprint and not context.repetition then
if pseudorandom('minihakkero') < G.GAME.probabilities.normal/self.config.extra.odds then
card.ability.extra.money = card.ability.extra.money + card.ability.extra.money_mod
card_eval_status_text(card, 'extra', nil, nil, nil, {message = localize('k_upgrade_ex')})
return {calculated = true}
end
local destroyed_cards = {}
for i,v in ipairs(context.full_hand) do
destroyed_cards[#destroyed_cards+1] = G.play[i]
end
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.2,
func = function()
for i=#G.play, 1, -1 do
local card = G.play[i]
if card.ability.name == 'Glass Card' then
card:shatter()
else
card:start_dissolve(nil, i == #G.play)
end
end
return true end}))
end
end
I'd be C > R that's tr, > T that's tr/tl, then out to the other column, C > R that's tl > T that's tl/tr, and then you can maybe mess with giving the rows or text elements negative padding.
I think you’re accessing the wrong object. G.play is a CardArea and as such it stores its cards in G.play.cards
Also I’m pretty sure here it’s the same as context.full_hand
Does negative padding work?
Eremel the Archwizard of UI himself taught me about it, it's finicky, but it does work.
Well not inside the Event, but I’d try to put most of the destruction logic inside the Event anyways
hold on i made another mistake
You need to think in terms of Event handling to try and decide what’s better inside or outside
Boolean checks are often better inside in case the object ceases to exist before the Event is handled
For example with multiple copies of the same effect
Although you can check if the object still exists inside the Event
hmmmm. yeah theres still an issue
because now it destroys the entire hand irrelevant to which cards activate
I mean that’s what the code you wrote does
I think using a new root node resets the position somewhat, I haven’t played around with it fully but I was experiencing stacking tooltips when getting galdur tooltips working properly
also the trigger seems to not work the cards dissolve before scoring, but thats probably easy enough to fix
so im wondering, would i need to use a pseudoseed in order to make it only have a chance to destroy a played card?
If you want to destroy them after scoring maybe you can reuse the Glass card functionality. I’m not sure you can but still
Otherwise you either need to inject there or find a context afterwards. Maybe context.after but I think that can break things still
No just do what you did at the start of the Joker
theyre not gonna always be equal sizes sadly
You could also just have the text node in a row below these colours and play with negative padding
That is wise
IT WORKS!!!!!!!!
and the trigger is fixed too!
uh
huh
it worked and then some really weird stuff started happening
Now try it with something that uses the cards that were destroyed
oh i did, used splash
Did you just make the delay really big?
i dont think thats it?
Now Splash works before
though that would work
I’m 99% sure that’s it that’s why I said not to do it
Ah alright
also it seems to be very weird right now
what’s it like anyways
okay wait i see why its being weird
how tf have i never noticed the white border almost every joker has
how do i get both the money increase and the card destruction in the same if statement?
I mean you can put both inside the Event
GOOD NEWS, It's stopped crashing, however, now the message isn't working (the mult still isn't working from before either but after some searching I think I might know why that is)
this is where the effect is at currently
I haven't heard of Emult_mod. Is that a thing?
I believe it's from talisman, same thing cryptid uses for jokers like exponentia I believe
ah makes sense
I recall hearing that individual context does this differently but I'm not entirely sure how, something to do with changing mult_mod to just mult? but my attempt at doing that made it crash, lol, so I think I'm still missing something
I should try looking in the files again
ok, I'm really close here, I got it to work with +mult, now I just need to get it to work with emult
YES
it works, omg, I did it 🫠
there are more things I wanna test out and learn using this joker but for now I think I'll play with it for a bit, I wanna have a quick run with this working joker before turning it into something new
i think mult is for giving mult when a card scores, like even steven for example, and mult mod is for jokers that just give mult during the joker scoring part, like default jimbo etc
Does anyone know how Smeared Joker causes bosses like The Window to debuff Hearts if you generate it during the Blind?
I implemented this Joker which only applies its effects while there are cards in G.play, so it dodges those debuffs, but that seems unintuitive, but I'm not sure how I'd fix it
Ah whenever a card is added to the deck it calls Blind.set_blind
Smeared joker has a line directly in the ``is_suit()` function, which is why it allows it to count as multiple things.
That's an answer to a different question
I just assumed that it's because the blind stuff is called automatically, and calls is_suit(), but, I haven't looked at blinds that much myself.
That answer is so generic that it's true, but it's so generic it's not helpful
Anyways, while Blind.debuff_card is called in a handful of places, as I said adding a card to the deck calls Blind.set_blind which in turn calls Blind.debuff_card
Which with Blinds like the Window calls Card.is_suit
So maybe I can hook the play function to add a call of Blind.set_blind right at the start? That might not work depending on when cards move to the play CardArea
It seems to have worked
This is probably very impossible, but is there a way to have a card be considered two ranks when scoring?
I had an idea for a binary joker, whose gimmick is that 10s are also considered 2s and vice versa, but I have no clue where to start when implementing this.
Someone made an X card that can assume a few different ranks but I'm not sure what was involved in that process
What mod was this?
It's 100% possible :D
Where do I start?
well, without putting too much thought into it, theres a calculate_joker function that gets passed arguments and calculates the joker effects.
oh wait you can just take a look at something like smeared joker
that does what you want, but with suits
Suits are different though, right? They use the is_suit(suit) function, while ranks use the get_id() function.
yes they're different, but you can see how smeared joker works, and apply it to suits
I'm not sure if I can. The reason Smeared Joker works is that is_suit() returns one value, true or false, based on the required suit. get_id() returns the number corresponding to the id. In theory, if I wanted to have a card count as multiple ranks, I would need to completely rewrite this function and every card that uses it. Unless there's another way, that is.
you can hook get_id()
^
But then wouldn't I need to still rewrite all jokers that use it?
Actually I just reworked a Joker so I wouldn't have to implement that
Because the way to make it work as desired would require rewriting every vanilla Joker, and wouldn't work with modded Jokers
That's what I thought
I did briefly consider an alternative where I make get_id return a custom object which has special comparison operators, but Lua doesn't quite allow it to be used as desired
Because IIRC it would only use the custom comparison if both terms were the same custom class
yeah the comparisons only work if they have the same exact metatable
Maybe you could overwrite the default Lua number class with a class that's almost exactly the same except it has the extra value
But that doesn't sound possible
that's probably bad
Yes
tl;dr: It's not possible in a reasonable/good way
Actually the solution is to ask thunk to rewrite Balatro in a way that would make it work while also breaking every mod that doesn't update to the new standard, thus forcing modders to update their mods to work with your multirank modded Joker effect
Cool thanks, will do that /s



Conclusion: the easy solution is
I'm also amused by the people who said "you totally can just look at Smeared" when ranks work completely different :^)
local new_moon = Ceres.SETTINGS.blinds.enabled and Ceres.SETTINGS.blinds.base_blinds.enabled and SMODS.Blind{
key = 'new_moon',
boss_colour = Ceres.C.new_moon,
dollars = 10, -- default is 5, default showdown is 8
mult = 2,
boss = {
showdown = true
},
pos = {
x = 0,
y = 0,
},
atlas = 'base_blinds',
discoverd = false or Ceres.discover_all,
debuff = {
suit = 'Spades',
value = 'Ace',
},
}```
this is my code for the blind
Ceres.discover_all is true so im not sure why its still not discovered
unless it just works differently for blinds
im stupid
cant spell
hate myself
Im trying to recreate the visual/sound effects that Ramen makes when you discard cards and the xmult decreases. I'm looking in the game code at Ramen but im not sure where in the code these effects are implemented.
card:calculate joker
context.discard or smth similar
i think
elseif context.discard then
if self.ability.name == 'Ramen' and not context.blueprint then
if self.ability.x_mult - self.ability.extra <= 1 then
G.E_MANAGER:add_event(Event({
func = function()
play_sound('tarot1')
self.T.r = -0.2
self:juice_up(0.3, 0.4)
self.states.drag.is = true
self.children.center.pinch.x = true
G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.3, blockable = false,
func = function()
G.jokers:remove_card(self)
self:remove()
self = nil
return true; end}))
return true
end
}))
return {
message = localize('k_eaten_ex'),
colour = G.C.FILTER
}
else
self.ability.x_mult = self.ability.x_mult - self.ability.extra
return {
delay = 0.2,
message = localize{type='variable',key='a_xmult_minus',vars={self.ability.extra}},
colour = G.C.RED
}
end
end```
I just meant the actual effects, where they exists within this code you have shown here
return {
delay = 0.2,
message = localize{type='variable',key='a_xmult_minus',vars={self.ability.extra}},
colour = G.C.RED
}```
this bit here
if you have a joker
then in its calculate function you return a message and a colour
itll pop up with that message
and a little square with the colour
as for sounds you can do play_sound()
Ok, I see! The thing is, I have this in my Jokers code but its not displaying the effects ?
what does your code look like?
you can also do card_eval_status_text() to just skip having to return a message
because thats what the game does with your message anyway i think
I get effects for played 8s but unfortunately no effects for cards played that arent 8s.
hm
I mean, it worked fine when I tested it. Is the value not subtracting or what?
The value is subtracting correctly but theres no effects displaying when cards that aren't 8 are played
what do you mean by effects?
I'd assume a message that the xmult went down, right?
Yes, I was trying to recreate the Ramen discard effect, theres also a card modded joker named Nachos that has it i was looking at
that uses it
@wary crescent Notably Ramen also plays a separate sound
In the line that reads play_sound
But that's when it's eaten
I still don't know which part isn't working
Theres no visual cue that your xmult is being decreased when a card that isnt an 8 is being played.
scored
dont you need a card = card in your return?
Oh wait
That's because your elseif is always false
also, for the Event in the first part, I think you can replace it with card_eval_status_text
Why would it be? Also, he mentioned that the decrease effect was working, just that it wasn't showing anything when it activated.
Ah the Xmult can be increased in the first if, so not always false
This would explain it, and, my explanation taken from the example joker mod is as follows:
-- The return value, "card", is set to the variable "card", which is the joker.
-- Basically, this tells the return value what it's affecting, which if it's the joker itself, it's usually card.
-- It can be things like card = context.other_card in some cases, so specifying card (return value) = card (variable from function) is required.
it doesn't know where to put the message without the card value iirc
I'd assume a SMODS thing, since we use card for a lot of stuff while the base game uses self a lot of the time.
I don't know why Ramen doesn't have it
but it's a vanilla thing
oh, does the ramen message go on the scoring card?
ramen bypasses the card parameter

context.discard automatically handles card_eval_status_text. This is also the case for other context calls.
The contexts that have messages possible on both playing cards and jokers needs the specification.
Yeah that makes sense from a vanilla point of view. They should probably be patched to use the card parameter though
for example, you might want to display a message on a discarded card from a joker effect
although I suppose you can just call card_eval_status_text yourself
You can do that through manually calling card_eval_status_text
This is how Faceless Joker does it.
Although I don't think it's needed for it to

This is the main reason I don't think recommending new modders to read the source code is a good idea 🙃
it's so jank and inconsistent within itself
how do i add the lucky info thing to a joker ive got?
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Enhanced', key = 'm_lucky'}
end,```
this is not how
G.P_CENTERS['m_lucky'] should do it
is that instead of the = {set thing or the key?
the set thing
cool thanks
yep it works
neat little joker i think
art could do with a bit of work tho
Art's like 10x better than anything that I could make lmao
I actually had a joker idea that was very similar to this
beat you to it
im just not keen on the bit at the bottom with the spin buttons
i didnt really know what to do there
I really need to learn how to make passable art
an easy way to do art is take a ref image and just scale it down to the size of a joker lol
then try go over the outline if you can make it out
might be easier in 2x
Cool art!
calculate = function(self, card, context)
if context.cardarea == G.jokers and context.before and not context.blueprint then
local odds = {}
for k, v in ipairs(context.scoring_hand) do
if (v:get_id() <= 10 and
v:get_id() >= 0 and
v:get_id()%2 == 1) or
v:get_id() == 14
then
odds[#odds+1] = v
v:set_ability(G.P_CENTERS.m_lucky, nil, true)
G.E_MANAGER:add_event(Event({
func = function()
v:juice_up()
return true
end
}))
end
end
if #odds > 0 then
return {
message = 'Lucky',
colour = G.C.RED,
card = card,
}
end
end
end,```
ty
Also obligatory slot machine triple 7s Joker
Can Judgement and Soul create cards with editions?
Yes, iirc
is there an obvious error here as to why my joker message isnt working
yeah
I remember getting get Holo Legendary once
I'll hope that changing Editions won't break anything then
I had card = self instead of card = card in my version
ill try that but im pretty sure card is the joker itself
nah didnt change anything
It's just the message not working, the cards are turning lucky, right?
yeah
I don't think you need the context.cardarea == G.jokers condition, but I don't know if that'll change anything. Try it anyway though.
ill try that
I don't think every context automatically handles messages
Although I think context.before should
the code is literally ripped from the games code for midas mask lol
nah no luck
like here
localize modifies the table in-place
I think
the border is missing
or at least the white part of it
i havent got room for it with the joker writing sadly
the graey bit is there
i plan on redoing the art tho
yay!
or touching it up a bit
that would also work
i didnt even know most jokers had white borders until earlier
even after 900 hours
All of them except the ones that aren't cards
Thunk was inconsistent about it then I think they redid them
during development
I'm trying to make a joker that shuffles all jokers before the spin hand, but this effect, for some reason, only applies after the spin hand.
G.E_MANAGER:add_event(Event({ trigger = 'before', delay = 0.2, func = function()
G.E_MANAGER:add_event(Event({ func = function() G.jokers:shuffle('aajk'); play_sound('cardSlide1', 0.85);return true end }))
delay(0.15)
G.E_MANAGER:add_event(Event({ func = function() G.jokers:shuffle('aajk'); play_sound('cardSlide1', 1.15);return true end }))
delay(0.15)
G.E_MANAGER:add_event(Event({ func = function() G.jokers:shuffle('aajk'); play_sound('cardSlide1', 1);return true end }))
delay(0.5)
return true end }))
end```
might be because its an event
Technically, Credit Card, Red Card, etc. also don't, but I get what you mean, just phrased a bit wrong.
It’s setting events inside the event
Idk, I just ripped the code from Amber Acorn. If I take the events out, would it work?
before what spin
Shoot, I meant hand
but yes it's 99% event managing
some events dont get done until after the hand is played and stuff
I'm too used to Luck Be a Landlord modding lol
i dont know how any of it works really
Yeah I don’t think you can use events for this
From my brief understanding of events
Events mostly happen in the order they're created when they're handled, so by putting this in an Event it delays the execution until Events are handled
And by nesting events you actually throw them to the end of the event queue
still doesnt work
also
smth weird
it feels like the card scores really quickly after it gets turned lucky
like the chips start counting before it finishes juicing
well midas mask isnt displaying a message either so i think i broke smth
are you in the main SMODS branch?
am i what
Okay, so unnesting the events made it so that the Joker shuffle animation plays properly, but the Jokers still trigger in their original order. I'm thinking I might need to hook the eval_play function to add another Joker check after, if that's even possible and I'm not just talking nonsense
Steamodded
wdym am i in the main branch
git
im not in it no
Did you remove all the events?
Try updating the main branch and checking out to it
im pretty sure i have the latest version
It's this now
G.E_MANAGER:add_event(Event({ func = function() G.jokers:shuffle('aajk'); play_sound('cardSlide1', 1.15);return true end }))
end```
Aren't you using the Retrigger API pull request?
You need to remove all the events
ive got a bunch of patches that mess with the eval play function so its smth about that probably
yea
That's very outdated
No
damn
I don’t think that’s the problem though
^^
ill go digging around
Wait, that worked?
What does making an event even do?
That’s more likely
This is how far behind you are
I'm so confused why this exists
It throws whatever is inside to the end of the queue, so it’ll shuffle them after it’s done all its calculations
im gonna be honest that means nothing to me
Events allow controlling the timing of how things happen
its a whole bunch of letters and numbers
This looks a lot worse because a load of prs have been merged lately
I think in an ideal world everything in a game would be handled by an Event manager
why cant the versions just count up
Rather than being hardcoded
Those are commit ids
And the Event manager would look at the game state and manager Events
yeah but i just donwloaded "1.0.0~ALPHA-0813a-STEAMODDED"
oh nvm i cant count for shit
Basically, doing an action can create one or more Events, each of which can create one or more Events, and they need to occur at the right time
The versions are date strings btw, so you can tell easily how old your version is
Balatro doesn't do this, but a robust Event handling system would also support listeners
Well, I guess maybe the way Balatro does it is good enough
pretty sure there arent 13 months
But you need to hardcode calls to things that can be listening
And you need to iterate over everything
Rather than only the things listening for a specific event
Month/day
ik its just thats the stupidest way to do it
I think good approximations of Event systems in games are MTG's Stack, Hearthstone's Queue, and Marvel Snap's Nested Queue
That’s the proper way to write dates so they increase though
okay thats actually a good point
Y/M/D date format is clearly superior
But keep in mind an Event system for these games would include everything else outside of those
I think you can actually make listeners with balatros event manager
But I’d have to look into it more
I think D/M/Y is good for human use, while Y/M/D is good for machine use
I mean there's only one queue, for the most part, and the Events don't have any identifying properties
You can make your own queues
Like you can't look at an Event object and deduce what contexts it touches
But vanilla doesn't support them
To be clearer, vanilla Jokers don't use them, etc.
Vanilla doesn’t do a lot of things it’s able to tbf
Can anyone point me in the right direction with a good example of code to add a new voucher, i've looked through the source code and some other mods and i'm a little lost right now
Thanks I'll give them a look
Does anyone know how to add Perishable/Rental tooltips to Jokers?
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {key = 'e_negative', set = 'Edition', config = {extra = 1}}
info_queue[#info_queue+1] = {key = 'perishable', set = 'Other'}
info_queue[#info_queue+1] = {key = 'rental', set = 'Other'}
end
didn't work
I'd assume it's a similar thing to this issue?
The actual error is this (well, it would read "Perishable" instead of "Rental" but for this in particular I commented out the line with Perishable)
Okay I think I found how to do it but I'm not sure I understand why
What did you use?
I haven't tested but this is how vanilla has them
Compare 'negative' to 'perishable'
Weird. Hope it works, since I was looking through the init_item_prototypes() and I don't see anything about a stickers.
It works but Perishable shows the number of rounds remaining
Not sure I can natively disable it or if I'd need a custom tooltip
Probably a custom tooltip, why would it need to be disabled?
Because the Joker itself isn't Perishable so it shouldn't display the number of remaining rounds
Makes sense, and a neat idea. I think the best way to go about it would be to make it custom, because I don't think there's a way to modify it without also modifying it for every other case.
Oh I got it wrong originally because I can't read
Anyways, yeah, I guess it'd need to be an original one
Although I could have a fake tooltip and copy the original tooltip sans the last line
Assuming the structure is the same across all languages
maybe
Stickers have no "center" or any place where they're actually stored.
Speaking of which, I'll probably need to make a custom pool to select Jokers from
That's been a suggestion for a while but tbh they'll prob be baked into the labels idea which isn't currently planned as a SMODS 1.0 feature.
I think querying a database of objects for a pool is a good generic feature
And this is what it looks like when the counter advances
This is the effect, by the way. The text needs some work though
It's probably simpler to just make it hit the Joker to the right (if it's non-Negative)
I've figured out the way for custom tooltips, if you're still stuck on the info_queue stuff.
Fair enough, I haven't before, so I'm only just now learning, since it does seem useful.
Maybe you have a better method
The Credits are very hacky
I basically have a fake tooltip which I overwrite into the tooltip I want
It's literally just throwing in a new line into localization, and then calling that as an info_queue. No idea how you changed the background color of the credits at all, and I am curious as to how.
It’s just specifying different colors than the normal tooltip.
iirc
Actually I custom made it but if there's a vanilla way to do it I'm all ears
🌽
Also I learned today how thunk checks if a card is in the collection or not, and it's much better than what I was doing (O(1) instead of O(n))
although I haven't yet updated anything to use their method
So I give a name to the tooltip which is a code that is unlikely to show up otherwise, then I replace that with the actual name localize('k_vic_credits'). The rest is taking the name and the description nodes and putting them inside an appropriate number of row UIElement objects that look like I want
It looks something like
- Row
- Row
- Text
- Row
- Description nodes
Impressive, and, unfortunately complex for a simple gray background, but, yeah, figuring out that method could definitely let you do some other fun stuff.
So I've managed to create a new voucher which want to have as the next in line to Telescope - Observatory.
the voucher is created and purchasable and i think dependant on Observatory being purchased.
However I cannot figure out how to use the Card:Calculate_joker correctly to get it to use the new mult variable i've declared
any assistance greatly appreciated
It's not too complex, it's just that otherwise the original colors are hardcoded. You could likely make it scalable in some way
also check out Stheno for the "fun stuff"
I don't think, and I could be wrong, that Vouchers are stored anywhere after purchase, so you might need to set a global variable and have calculate access the correct number
The code is taken directly from observatory, I'm looking through it myself to try to figure out why it wouldn't.
I mean I thought it did but their issue was having to hardcode the values
Also, I don't think it's using the prefix. The key of the voucher should actually be v_prefix_jameswebb, rather than just jameswebb, prefix is the first four letters of your mod ID in lowercase if you haven't specified a prefix, I believe, might be the mod name instead.
So, it'd be, if G.GAME.used_vouchers.v_prefix_jameswebb, and Xmult_mod should be G.P_CENTERS.v_prefix_jameswebb.config.extra, I think.
any good example mods for adding enhancements?
Cryptid or Lobcorp
thanks
L Corp has Enhancements?
you sir are a legend, that has fixed it for me and it's working as intended now
Sorry I thought you said achievements
Then cryptid and ortalab (I think they have those?)
@hushed cradle
thanks i was really confused, looking through cryptid lol
do i have to
hook
to smth to calculate it
probably not for basic effects
its just theres no documentation so idk any of the methods
with cryptids echo it looks like you used a hook to calculate when it goes off
We did
You’d have to look at the game_object code but I think some basic effects work without hooks
alright thanks
just realised why all my jokers were broken too
thank god
do you see how the makima joker goes again again again before the card scores
then hanging chad gets retriggered all at once
for the smods enhancement calc function is it similar to calc joker?
with context.other_card for example
What do you think?
looks pretty cool
not too broken either
does anyone know how i can make an event trigger after each card in the hand has been scored
Create the event after each card in hand has been scored
its for an enhancement tho
atm im creating it in the calc function
just gonna patch instead
art is gonna stay like that its just so i could easily tell them apart
anyone know how i fix the name thing
for some reason this UI ended up looking way better than I thought it would
a UI guide somewhere would be amazing
i actually used one to help me, lemme pull it up
idk why but the transparent buttons end up looking really nice with how the rest of the UI is set up
pretty much out of the box
yw
it still took me an hour or two to figure out enough how to use it
but it works now i guess
I also somehow made this part before that guide, but this part is much easier
that is beautiful
i still have to code the replay info tab later, i'll probably take inspiration from the win screen for what I put on there
also the code for it is really ugly
like probably much worse than thunk
but honestly in this case it's good to be like that, makes it harder for cheaters to mess with it
is this not a worse glass card 90% of the time
glass but with less risk
rarely do i agree with things being worse but this is a case where it's objective.
it was 1 in 5
do you know how i fix the name thing tho lol
Hi! Been having a couple issues getting my Joker's data (scaling mult) to save between opening and closing the game, I've added the code here I'd really appreciate any help 🙏 (for context this is just the code relevant to the Joker, not the entire mod)
instead of self.config you want to use card.ability
in the calculate function
imagine the smods.joker is like a blueprint for the joker
so everytime you find it in game
or its created in game
its basically a copy of that template
at the moment youre modifying the template
so any of the same joker you get in the future will also be scaled up already
by modifying card.ability youre changing that one joker specifically
instead of the template for the joker
and when you exit a run the game saves each cards ability
so itll then save the scaled mult
SMODS.Joker{
key = "five_leaf_clover",
name = "Five-Leaf Clover",
rarity = 1,
discovered = true,
pos = {x = 0, y = 0},
cost = 5,
blueprint_compat = true,
config = {name = "Five-Leaf Clover", extra = {mult_growth = 5, plus_mult = 0}},
loc_txt = {
name = "Five-Leaf Clover",
text = {
"This Joker gains {C:red}+#1#{} Mult every",
"time a {C:attention}Lucky{} card {C:green}successfully{}",
"triggers",
"{C:inactive}(Currently {C:red}+#2# {C:inactive}Mult)"
}
},
atlas = "five_leaf_clover",
loc_vars = function(self, info_queue, center)
return {vars = {card.ability.extra.mult_growth, card.ability.extra.plus_mult}}
end,
calculate = function(self, card, context)
if context.individual and context.other_card.lucky_trigger and not context.blueprint then
card.ability.extra.plus_mult = card.ability.extra.plus_mult + card.ability.extra.mult_growth
return {
extra = {focus = card, message = localize('k_upgrade_ex'), colour = G.C.MULT},
card = card
}
elseif context.cardarea == G.jokers and not context.before and not context.after then
return {
message = localize{type='variable',key='a_mult',vars={card.ability.extra.plus_mult}},
mult_mod = card.ability.extra.plus_mult
}
end
end```
that should work
also had to change it card.ability in loc vars because otherwise itll say currently +0 mult
as the template is at +0
no not really
I think it’s a smods issue that’s fixed in a pr somewhere
Thank you! That makes a lot of sense, I'd seen different examples use the two and didn't know the different until now, I appreciate the explanation!
ill find it at some point then maybe
how do i get this event to only destroy one card and not the full hand? or do i need to use a different function entirely
G.E_MANAGER:add_event(Event({
trigger = 'before',
delay = 0.2,
func = function()
for i=#G.play.cards, 1, -1 do
local card = G.play.cards[i]
if card.ability.name == 'Glass Card' then
card:shatter()
else
card:start_dissolve(nil, i == #G.play.cards)
end
end
return true end}))
Is it supposed to just destroy the first card?
i can post the whole thing, its supposed to have a 1/8 chance of destroying a random card, and while it does activate 1/8 times when it does it destroys the whole hand
SMODS.Joker { -- Mini Hakkero
key = 'minihakkero',
loc_txt = {
name = 'Mini Hakkero',
text = {'{C:green}#1# in #2#{} chance to {C:attention}destroy{}',
'a random scored card.',
'Permanently increase payout by {C:money}$#3#{}',
'when card is destroyed.',
'{C:inactive}(Currently earn{} {C:money}$#4#{} {C:inactive}at end of round.){}' }
},
config = {extra = {odds = 8, money_mod = 1, money = 1}},
rarity = 3,
pos = {x = 2,y = 0},
atlas = 'jokeratlas',
cost = 9,
unlocked = true,
discovered = true,
blueprint_compat = false,
loc_vars = function(self, info_queue, center)
return {vars = {G.GAME.probabilities.normal, center.ability.extra.odds, center.ability.extra.money_mod, center.ability.extra.money}}
end,
calculate = function(self, card, context)
if context.individual and pseudorandom('minihakkero') < G.GAME.probabilities.normal/self.config.extra.odds and context.cardarea == G.play and not context.blueprint and not context.repetition then
card.ability.extra.money = card.ability.extra.money + card.ability.extra.money_mod
card_eval_status_text(card, 'extra', nil, nil, nil, {message = localize('k_upgrade_ex')})
G.E_MANAGER:add_event(Event({
trigger = 'before',
delay = 0.2,
func = function()
for i=#G.play.cards, 1, -1 do
local card = G.play.cards[i]
if card.ability.name == 'Glass Card' then
card:shatter()
else
card:start_dissolve(nil, i == #G.play.cards)
end
end
return true end}))
end
end,
calc_dollar_bonus = function(self, card)
if card.ability.extra.money > 0 then
return card.ability.extra.money
end
end
}
That’s because you loop through the entire hand and destroy them all
You’d need to choose a random card from the hand instead of using a loop
for that i would need to use pseudorandom_element?
What do you mean?
it does destroy a random card in hand, but that card is irrelevant to whichever card upgrades the return on the joker
it does function perfectly fine though, or at least doesnt break
Oh is it supposed to only upgrade on destroy?
yep
destroys a card and then upgrades
probably i can nest it in an if statement and itll work?
hmm?
I think it’s context.other_card
why is there name in the config
so instead of context.individual use context.other_card
now it destroys the card to the right of it whenever it activates
That’s a timing error then I think
not my code, youd have to ask the person whose code it is
yeah, got it fixed, somewhat
its not entirely how i want it to end up looking, but as far as i can tell it works as intended
If you can video it and tell me what it should be like I can probably help further
maybe some other time, it does work and thats whats most important right now
No no no aesthetics are always the most important
i guess we can disagree on that lol
probably some other time, i want to start work on the joker art
Who cares if mechanics work as long as it’s pretty
local reversed_fool = Ceres.SETTINGS.consumables.enabled and Ceres.SETTINGS.consumables.reversed_tarots.enabled and SMODS.Consumable{
key = 'reversed_fool',
set = 'reversed_tarot',
config = {},
atlas = 'reversed_tarots',
pos = {
x = 0,
y = 0,
},
discovered = false or Ceres.SETTINGS.misc.discover_all.enabled,
loc_vars = function(self, info_queue, card)
local fool_c = G.GAME.last_reversed_tarot and G.P_CENTERS[G.GAME.last_reversed_tarot] or nil
local last_reversed_tarot = fool_c and localize{type = 'name_text', key = fool_c.key, set = fool_c.set} or localize('k_none')
local colour = (not fool_c or fool_c.name == 'The Fool') and G.C.RED or G.C.GREEN
local main_end = {
{n=G.UIT.C, config={align = "bm", padding = 0.02}, nodes={
{n=G.UIT.C, config={align = "m", colour = colour, r = 0.05, padding = 0.05}, nodes={
{n=G.UIT.T, config={text = ' '..last_reversed_tarot..' ', colour = G.C.UI.TEXT_LIGHT, scale = 0.3, shadow = true}},
}}
}}
}
if not (not fool_c or fool_c.name == 'The Fool Reversal') then
info_queue[#info_queue+1] = fool_c
end
return {main_end = main_end}
end,
can_use = function(self, card)
if (#G.consumeables.cards < G.consumeables.config.card_limit or card.area == G.consumeables)
and G.GAME.last_reversed_tarot and G.GAME.last_reversed_tarot ~= 'c_cere_reversed_fool' then
return true
else
return false
end
end,
use = function(self, card, area, copier)
G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.4, func = function()
if G.consumeables.config.card_limit > #G.consumeables.cards then
play_sound('timpani')
local card = create_card('reversed_tarot', G.consumeables, nil, nil, nil, nil, G.GAME.last_reversed_tarot, 'reversed_fool')
card:add_to_deck()
G.consumeables:emplace(card)
card:juice_up(0.3, 0.5)
end
return true end }))
delay(0.6)
end
}```
i have this code for a consumable
similar to the fool
but for this new consumable type i added
it works perfectly
even saves when you restart the game and everything
but for some reason when i hover over the card in the collection mid run the game just closes down
doesnt even give me an error or anything
just closes straight up
its kinda unsettling
okay it seems to just work now
not sure what was happening
i didnt even change anything
local cobalt = Ceres.SETTINGS.enhancements.enabled and Ceres.SETTINGS.enhancements.illusion.enabled and SMODS.Enhancement{
key = 'cobalt',
pos = {
x = 1,
y = 0,
},
atlas = 'enhancement_atlas',
config = {
extra = 2,
},
loc_vars = function(self, info_queue)
return {vars = {self.config.extra}}
end,
calculate = function(self, context, effect)
if context.area == G.hand then
return {
message = 'X' .. self.config.extra,
x_chips = self.config.extra,
colour = G.C.CHIPS,
}
end
end
}```
tryna get this enhancement to give x2 chips when held in hand
still not really sure how the calculate works for enhancements
it does
I don't know why people think ti doesnt
it has more robust calculation than editions
~I'm pretty sure they were merged with it 🤷♂️
it's not complete, but it is there
do you have an x_chips module?
yeah
for jokers its just
return {
message = 'X' .. self.config.extra,
x_chips = self.config.extra,
colour = G.C.CHIPS,
}```
Maybe the module doesn’t add the functionality to cards
but playing cards dont hit the same evaluationa s jokers
try xchips_mod
or x_chips_mod
havent tried it admittedly
Xchip_mod doesnt, ill try x_mult
x_mult doesnt work
or Xmult_mod
is it actually hitting that block?
I don't remember quite how the calculate was patched in, and I don't have the code infront of me rn
nws, ill figure it out eventually i think
take a look at your lovely dumps, you want state_events and common_events iirc
for evaluate_play and eval_card
will do
if center.set == 'Enhanced' and center.calculate and type(center.calculate) == 'function' then
center:calculate(context, ret)
enhancement_calculated = true
end```
not really sure how im supposed to do anything tho
it doesnt take anything that i return it looks like
oh, try adding it to the effect table instead of returning
calculate = function(self, context, effect)
if context.area == G.hand then
effect.x_chips = self.config.extra
effect.message = 'X' .. self.config.extra
effect.colour = G.C.CHIPS
effect.card = context
return effect
end
end```
did that but still nothing
so do like, lua effect.x_chips = { bla bla bla}
unless thats formatted wrong?
ah
ill try that
for some reason i still struggle wrapping my head round editing tables like that
do the other bits in here assign the calculate return to something?
not on the enhanccement ones, just the standard ones
yeah
local h_mult = card:get_chip_h_mult()
if h_mult > 0 then
ret.h_mult = h_mult
end```
for example
yeah, so I think the effect.x_chips thing should work
i got bad news
calculate = function(self, context, effect)
if context.area == G.hand then
effect.x_chips = {
message = 'X' .. self.config.extra,
x_chips = self.config.extra,
colour = G.C.CHIPS,
card = context,
}
end
end```
unless im dumb
wait
maybe
actually i dont even know anymore
calculate = function(self, context, effect)
if context.area == G.hand then
effect.x_chips = {self.config.extra}
end
end```
not that either
def hitting that block
does x_mult work?
in your calc
that's not tprint?
what is tprint?
it prints the table properly 😆
theres a function to print the table properly?
I think it's a thunk function
it's very useful
i usually do for k,v in pairs do print(k.."="..v) (with the tostrings and stuff)
my world absolutely blown
can you do a context one too out of interest?
sure
becuase that looks an awful lot like the context table 💀
sorry had to take a phone call
uh lemme print the other thing
oh
uh
isnt self supposed to be a darker blue?
or am i being dumb
lemme print the thing sorry
this is as far up as it goes apparently
okay yeah I thought that might be it
change it to calculate = function(_, self, context, effect)
okay lol
Redoing my 8s joker i had issues with yesterday, here is the Joker text: "This Joker Gains {X:mult,C:white}X#1#{} Mult",
"for each scored or discarded {C:attention}8{}.",
"{C:inactive}(Currently {X:mult,C:white}X#2#{C:inactive} Mult)",
so this didnt work, but i mightve just realised why none of its working
and im very dumb
oh?
im using mathisfun's retrigger api thing and it overwrites the eval card func lol
oh
yea
it fully rewrites it?
yea
🙃
yeah sorry for putting you through this
When y'all get a sec 🙂 how is scored and discarded written in the return ,vars = section? Bc it is connected to the same Xmult bonus number? "This Joker Gains {X:mult,C:white}X#1#{} Mult",
"for each scored or discarded {C:attention}8{}.",
"{C:inactive}(Currently {X:mult,C:white}X#2#{C:inactive} Mult)"
are you sure it overwrites it?
I don't see anything in the PR that changes eval_card
what do you mean?
yeah
mine might be outdated tbf
i downloaded it as soon as it got put on git lol
oh yeah that doesnt look like the one thats as a PR
Under loc vars, i am trying to set it up to give the same Xmult bonus for both scoring OR discarding a specific card rank
This Joker Gains {X:mult,C:white}X#1#{} Mult",
"for each scored or discarded {C:attention}8{}.",
loc_vars = function()
return {vars = {card.ability.increase or decrease or whatever you want}}
end```
So this is what im learning from: "loc_vars gives your loc_text variables to work with, in the format of #n#, n being the variable in order.
-- #1# is the first variable in vars, #2# the second, #3# the third, and so on." My #1# has two possible conditions and i wasnt sure how to go about putting it in the return
yeah #1# and #2# etc are variables
you pass them into the description by returning {vars = {}}
where each variable is at the index or the corresponding number like you said
in your loc txt you can also do
'text blah blah #1#, and also #1#'
What do you mean by it has two possible conditions?
then whats the problem?
I wasnt sure if/how you could put multiple descriptions or functions under the same variable. Usually each var only has 1 ,
^^
oh I see, yeah you can use them as much as you like
ik ty ty 🙂
im done with this card
does it work now?
I'll grab my dev machine and look at some of the ones I've got working
i would greatly appreciate that 🙏
is there a thread or a link to documentation of how to format uibox stuff properly
your best bet is probably start praying
i've already dabbled in ui stuff before, but it was through trial and error and i'd like to have more understanding of what im doing
a bit late response from me but thank you
god this enhancement calc is kinda scuffed
it's only working because it gets caught by the joker calculation 🙃
I think I fixed it
🎉
there we go, thrown it in a PR for aure to look at later on 🙂
what code creates a specific joker again
How would you perform a card id check for a specific rank discarded. Like Hit the Road
do you want me to send the code for hit the road?
yes plz
function Card:calculate_joker(context)
...
elseif context.discard then
if self.ability.name == 'Hit the Road' and not context.other_card.debuff and context.other_card:get_id() == 11 and not context.blueprint then
self.ability.x_mult = self.ability.x_mult + self.ability.extra
return {
message = localize{type='variable',key='a_xmult',vars={self.ability.x_mult}},
colour = G.C.RED,
delay = 0.45,
card = self
}
end
...
end
end
I find it much easier to understand modded jokers than vanilla

formatted it for you, incase that helps at all.
ignore that part
ok
if it says self. in the vanilla code then it should be card. in the equivalent smods function
ok I understand
but also anything that references the name is irrelevant in modded objects
Hey can anyone point me in the direction of a good tutorial on how to integrate GitHub into Visual Studio code, I'm a bit of a GitHub novice so not 100% how it all works either
vscode has git integration built in
oh ok, still no idea how to use it though. I'll do some a-googlin and see if i cant figure it out.
are you not using github at all or just not the integration with VSCode? be interested to get an idea of other peoples workflow
I'm sure there'll be some good tutorials for it somewhere
I use vscode simply for the text editor
IMO just use cli for git
where ever possible try to optimize your work flow to not use a mouse haha
you can use git directly in a terminal from vsc, or you could open up your own git bash console specifically for git stuff
git lense is a good add on for vsc, esp if ur collaborating with others
ultimately the best workflow for you is whatever you are used to, and works best for you
In that case it will be a mess of files in about a hundred different places, no incremental filenaming or verisioning and just hoping for the best. 🤡
I'm 47 now so learning new stuff is frankly a bit of a challenge
In that case it will be a mess of files in about a hundred different places, no incremental filenaming or verisioning and just hoping for the best. 🤡
More relatable than you think haha
Ok, let me dm you my personal style guide, and some a git guide i put together for contributors for another of my projects
awesome, ill have to have a look at that in a bit
ik this wasnt done specifically for me but thank you
Would someone mind looking at my Joker again? Ive completely redesigned its effects and it is functioning as intended except im not getting a display in game for mult being upgraded OR a visual for when mult it is applied to my score.
The score one is because you have two messages in your return, I assume you get chips one?
Yes
You’ll need to call the mult message yourself
How do i do that
Use card_eval_status_text
Yea there can only be one message variable inside a return
ok i see
I’m not sure why the other message doesn’t work right now
But it’s probably something silly like the place it returns to doesn’t handle a message for you
So you could just throw your own in there instead too
card_eval_status_text(card, eval_type, amt, percent, dir, extra)
you can enter nil instead of some of those to make it use the default option
extra is a table {colour = G.C.RED} and other stuff (lookin at the function rn)
eval_type can be 'debuff' 'chips' 'mult' 'x_mult' 'dollars' 'extra'
and a bunch of others
to change the text you need to make sure extra has message
card_eval_status_text is at functions/common_events.lua if you want to look at it
ty sm
im currently losing my mind bcos im doing ui code and it actually works??
archipelagooooooo
btw is there any way to make the button on top left to use the display name instead of full name?
adding proper config screen with a bunch of clientside overrides
How to apply modded sticker?
Ooh how is archipelago going?
Honestly archipelago sounds fun to program
stable rn but still missing challenges
it is but i only do the clientside, the apworld code is burndi so idk how that goes
guh that actually is what it should be using
that's an artifact from before I added that smh
Does archipelago have some kind of in game chat?
Well anyways I was thinking I could make an aoi for mods to open the DebugPlus console in a special mode where they have full control of what happens when commands are run
So you could make it a chat box
Or I could make an evsk console
Trying to apply a sticker, maybe there's a function that I'm not aware of?
atm no
all of the data the player needs is given to them either via ui or the love.print function
with love.print functions being phased out probably this update because they only use the default font
is there a thread for archipelago?? dont know what it is but sounds cool lol
there is but its inactive because most of the activity is on the archipelago after dark server (balatro cant be on the main server because pegi)
i thought pegi went back on its mature rating for balatro?
lol, looks like theyre going back and forth on it
the most unenthusiastic of battles. rating battles
@wintry solar finally got it working
if context.repetition_only then
if card.ability.set == 'Enhanced' and center.calculate and type(center.calculate) == 'function' then
center:calculate(context, ret)
enhancement_calculated = true
end
local seals = card:calculate_seal(context) or {repetitions = 0}
if seals then
ret.seals = seals
end
return ret
end```
had to tweak the eval_card a bit tho
apparently if it was calculating repetitions only then it was returning nothing because there are no seals
no that's the wrong block
and then in eval play it tries a for loop for effects.seals.repetitions
so its not working?
it calculates repetition only in eval play anyway i think
i might be wrong tho
if reps[j] == 1 then
--Check for hand doubling
--From Red seal
local eval = eval_card(G.hand.cards[i], {repetition_only = true,cardarea = G.hand, full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, repetition = true, card_effects = effects})
if next(eval) and (next(effects[1]) or #effects > 1) then
for h = 1, eval.seals.repetitions do
reps[#reps+1] = eval
end
end```
when it was doing this
evals.seals was nil
so it tried indexing that for the h=1 loop thing
and it was crashing
so i put that or {repetitions = 0} so if theres no seals it doesnt crash
my card isnt actually calculated in that bit
im fairly sure
calculate = function(self, context, effect)
if context.cardarea == G.hand then
effect.x_chips = self.config.extra
end
end```
do not context.repetition
ah
thank you
didnt think i would have to for some reason
yep that works
ty
gonna blame my nap for me not thinking straight
thank you for all the help
slowly getting there
What version of lua balatro use?
LuaJIT is a Just-In-Time (JIT) compiler for the Lua language.
hey when i try to put my mod into the game I'm getting an error, is there an error dictionary or something to find what's wrong with it
I mean, the crash screen itself is probably as close to an error dictionary that you're going to get, what error are you getting?
That helps quite little, since it varies both on code that's been injected, either from your mod or a different one, what file it's in, what kind of error it is, several things. Just specifying a line gives very little information. Can you take a screenshot of the error to start with?
I also copyed the the error if that's better
Oh you should send this to the dev of the mod here https://discord.com/channels/1116389027176787968/1249801316616765560
No, this appears to be his own mod, it's not by Cerlo.
This one is a simple grammar error in your code, I believe you forgot a comma on line 13 most likely, maybe using a period instead, but, for future reference, I'll go ahead and break down how you can read it and how you can parse the information.
yes please
this is my own thing, i'm just doing this as my first coding project to see if I like it.
Oh, I suppose you're doing this on top of someone's code?
First thing, is that the top line is usually the most important piece of information. Where it happened is located in steamodded's loader.lua file, specifically, a function there failed. But, it gives more information right after that - notice the P5 Hero Joker.lua that comes right after the initial part, it's showing where the actual error is located.
It's showing loader first, because, the error happened while trying to load in the file.
After that, it gives a line - 14, a } is expected, and specifically to close line 12 This means that something is wrong with that list of items between two{}, and the first { starts at line 12, so work your way from there.
Commonly, it's an issue of not having commas, or closing brackets incorrectly. Without commas, rather than trying to say, a = 1, then b = 2, it's trying to do them in the exact same instance, and doesn't know what to do because they aren't separated, so it crashes.
Where it says near '.', which is a period, it's saying, there's no comma after the . to separate objects in the list, so it should've ended in the line after, at 14, instead of finding another object, which is probably the one located at line 14.
For the stuff under, that's traceback - it shows what went wrong in the events leading up to the crash. A lot of the time, that much simpler line at the top gives you all the information you need, but when it doesn't provide enough information or context about what happened, that's when you go down to Traceback.
You don't have to completely parse everything, if you notice some key words, that can be enough to give you an idea of where to look for stuff.
Most commonly, I'll notice assert and xpcall errors there - Those are simple, stupid mistakes - usually if it mentions assert, that means I did something wrong grammar wise. If it's only xpcall, that often means something is wrong with files - double check file names, folder names, make sure they're referring to the right stuff in code, all that stuff.
If you want a look at an easy example, pay attention to the next time someone inevitably reports a crash for Cryptid when installing it.
You'll often see xpcall mentioned - that's usually because the folders for Cryptid have to be named Cryptid and Talisman, due to their specific custom way of injecting objects into the game. Since, if they aren't named that exactly, then, they can't find the files that are supposed to be loading in, hence the xpcall stuff in those tracebacks.
Apologies for the quite long wall of text, but, it is important information, so I hope it clears things up and makes modding easier in the long run.
No, thank you for helping, hopefully now I won't need to write in here with every little issue I have with my code
I'm finally getting to the point of making my own mod instead of just editing pre-existing jokers, I found that with this joker for some reason the vars in description aren't showing up in game (numbers being replaced by nil), how would I fix this?
Do you have any loc_vars?
I don't think so? this thing is pretty small
by "vars" I mean the extra config btw, in case those are something different
Recommending my example mod since it has comments that explain stuff, you can find the loc_vars at line 66, https://github.com/Steamopollys/Steamodded/blob/main/example_mods/Mods/ExampleJokersMod/ModdedVanilla.lua
But to break it down, loc_vars means localization variables, and it tells the loc_txt, localization text, what to replace those #1#s with, but since it doesn't have anything to replace them with, it's just saying that they're nil.
I will check it out! thank u! :>
Hi, I just got started with modding by just making a simple joker, is there a way to manually add it to my jokers or to the shop so I don't have to keep rerolling to find it whenever I wanna test it?
if you put _RELEASE_MODE = false in your mod then it enables debug mode
during a run if you go to the collection
hold tab to bring up the debug menu
then press 3 while hovering over any card to spawn it
Ty!
np
How would I put a card area inside a tab?
I currently have this code
return (create_UIBox_generic_options({
back_func = 'exit_mods',
contents = {
{
n = G.UIT.R,
config = {
padding = 0,
align = "cm",
colour = G.C.CLEAR,
},
nodes = {
create_tabs({
snap_to_nav = true,
colour = G.C.BOOSTER,
tabs = {
{
label = 'Joker Order',
chosen = true,
tab_definition_function = function()
return {
config = {
align = "cm",
padding = 0.05,
colour = G.C.CLEAR,
},
nodes = {
object = config_joker_list
}
}
end
}
}
})
}
}
}
}))
might want to look at what to collection does
i think i have some code ill try find
function s_create_card_display(args)
args = args or {}
args._type = args._type or 'Joker'
args.col = args.col or 5
args.row = args.row or 2
args.specific_center = args.specific_center or nil
local deck_tables = {}
local cards_per_page = args.col*args.row
local current_center = 0
S.card_display = {}
for i = 1, args.row do
S.card_display[i] = CardArea(
G.ROOM.T.x + 0.2*G.ROOM.T.w/args.row,G.ROOM.T.h,
args.col*G.CARD_W,
0.95*G.CARD_H,
{card_limit = args.col, type = 'title', highlight_limit = 0, collection = false})
table.insert(deck_tables,
{n=G.UIT.R, config={align = "cm", padding = 0.07, no_fill = true}, nodes={
{n=G.UIT.O, config={colour = G.C.CLEAR, object = S.card_display[i]}}
}}
)
end
if args.specific_center then
local center = args.specific_center
local card = Card(S.card_display[1].T.x + S.card_display[1].T.w/args.row, S.card_display[1].T.y, G.CARD_W, G.CARD_H, nil, center)
if args._type == 'Joker' then
card.sticker = get_joker_win_sticker(center)
end
S.card_display[1]:emplace(card)
return {
{n=G.UIT.R, config={align = "cm", r = 0.1, colour = G.C.CLEAR, emboss = 0.05}, nodes=deck_tables},
}
end
for i = 1, args.row do
for j = 1, args.col do
current_center = current_center + 1
local center = G.P_CENTER_POOLS[args._type][current_center + (S.current_page*(args.row*args.col))]
if not center then break end
local card = Card(S.card_display[i].T.x + S.card_display[i].T.w/args.row, S.card_display[i].T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, center)
card.s_stats = true
if args._type == 'Joker' then
card.sticker = get_joker_win_sticker(center)
end
S.card_display[i]:emplace(card)
end
end
local t = {
{n=G.UIT.R, config={align = "cm", r = 0.1, colour = G.C.CLEAR, emboss = 0.05}, nodes=deck_tables},
{n=G.UIT.R, config={align = "cm",}, nodes = s_create_page_cycle({_type = args._type, colour = G.C.SECONDARY_SET.Planet, _type = args._type, row = args.row, col = args.col})}
}
return t
end```
this is really similar to what the game does for the collection i think
ty
nws
youll have to change S.card_display and S.current_page and s_create_page_cycle tho
i think the game has a built in page cycle thing
Wandering if its possible to create a Joker like Hallucination, with the 1 in 2 chance to create a card on Booster Pack open, BUT it selects randomly between specific a list of tarot/spectral cards?
Hey, I tried adapting your code but the card area still appears outside the menus instead of inside the tab
try having a root not in the tab def function, then create a ui box with the definition of the card display, and in the config you can set the parent to the root node in the tab
you could have a list of each type of card and use pseudoelement
G.FUNCS.cere_change_page = function(e)
local page_config, back = Ceres.PAGE_FUNCS[e.config.ref_page]()
local option_box = G.OVERLAY_MENU:get_UIE_by_ID('tab_contents')
option_box.config.object:remove()
option_box.config.object = UIBox{
definition = {
n = G.UIT.ROOT,
config = {
emboss = 0.05,
r = 0.1,
minh = 8,
minw = 7.3,
align = 'cm',
padding = 0.2,
colour = G.C.BLACK,
},
nodes = Ceres.FUNCS.create_buttons(page_config, back),
},
config = {
offset = {
x = 0,
y = 0,
},
parent = option_box,
}
}
option_box.UIBox:recalculate()
end
Ceres.PAGE_FUNCS = {}
Ceres.PAGE_FUNCS.main = function()
local ref_table = Ceres.SETTINGS
local _buttons = {
{label = 'Jokers', toggle_ref = ref_table.jokers, button_ref = 'cere_change_page', ref_page = 'jokers_rarities'},
{label = 'Consumables', toggle_ref = ref_table.consumables, button_ref = 'cere_change_page', ref_page = 'consumables'},
{label = 'Enhancements', toggle_ref = ref_table.enhancements, button_ref = 'cere_change_page', ref_page = 'enhancements'},
{label = 'Blinds', toggle_ref = ref_table.blinds, button_ref = 'cere_change_page', ref_page = 'blinds'},
{label = 'Suits', toggle_ref = ref_table.suits, button_ref = 'cere_change_page', ref_page = 'suits'},
{label = 'Editions', toggle_ref = ref_table.editions, button_ref = 'cere_change_page', ref_page = 'editions'},
{label = 'Miscellaneous', button_ref = 'cere_change_page', ref_page = 'misc', remove_enable = true,},
}
return _buttons, false
end```
might be helpful
nws
itll def need some adapting
most imporant bits are local option_box = G.OVERLAY_MENU:get_UIE_by_ID('tab_contents')
and config = { offset = { x = 0, y = 0, }, parent = option_box, }
probably
Is this for 0.9.8 or 1.0.0?
1.0.0
Is there a way for a Joker to apply an effect multiple times on a card by itself? Like, let's say (hypothetically) I wanted a Joker to make a card have a 1/2 chance to give x1.5 Mult three times in a row (three rolls of 1/2, each of them having giving a seperate XMult). Is that possible?
you could do events for eval messages maybe?
not really sure how youd make it give the x mult tho
Okay different question, is there a way to get a Joker to retrigger another Joker? I know they can retrigger cards, but idk about Jokers.
mathisfun wrote some api for that
i think you can find it in a pull request on the github
Getting a bug where when after I call go_to_menu all of the decks in the new run carousel turn into red decks. I've seen there's a bug reported for Steamodded that causes this, does anybody know why?
seems to have something to do with remove_all(G.STAGE_OBJECTS[G.STAGE]), but i dont know enough about the source code to understand why it only allows for the red deck to be selected
Sorry if this is a dumb question or asked many times before, but how would you set up a Joker that destroys all other Jokers except for themselves at start of blind? Been trying to do this as an experiment to learn lua but I absolutely think I'm missing something simple lol
I'm used to C#, lua is moon runes to me
you can do if v ~= card then btw
if this is in the calculate function for your joker
then you have to use card
because self refers to the smods joker object thing
but card refers to the actual card in game
is the problem atm that its destroying every joker?
also after dissolving them youll want to do some other stuff to make sure theyre dead
ill see if i can find an example from the source code
nvm start_dissolve might be enough apparently
Yeah I'd appreciate it! I looked through the source code for stuff like Madness and Dagger but since they had other functions related to randomness or placement it just overwhelmed me
it just didn't do anything
Okay yeah changing it from Self to Card actually fixed the problem!
Nah don't worry this was all I needed help with atm lol
but thank ya, again, lua is new to me
dont worry about it, when i started with balatro the most id done is a bit of python lol
so i had no clue what i was doing
I've looked through the Retrigger Joker API, but it seems I can't get a Joker effect to retrigger on a certain card, which is what I need. Shame, I'll have to try something else
yeah its just general retriggering i think
its pretty useful for a lot of stuff
shame it wasnt in this case tho
good luck
I might just have to do the most blatant hard-coding in my life to get this effect to work.
But to do that, I'd need to go back to this idea, and I haven't figured that out yet
maybe make the joker create its own invisible jokers that trigger on the card lol
It took me a second to realize you meant Jokers that are invisible and not Invisible Jokers lol
Wouldn't that cause visual weirdness though with joker positioning and slot number?
dont add them to deck or emplace them to G.jokers
and then they can just sit invisbly in the middle of the screen or wherever lol
You lost me there, I don't really know how to use emplace. Plus, wouldn't I then need to make an invisible variant of every Joker I want to retrigger?
emplace basically just puts the joker in a card area, like your hand or the joker area, or your deck
yeah you probably would have to actually
maybe not the best idea then
youll never guess what the card next to it does
Does it create a Green Save?
doesn't balatro use "destroy" instead of "burn"?
also tbh having them copy the other cards in hand could be a rlly fun effect
it does but i thought burn sounded cooler lol
that was my plan and then i got sidetracked and ended up w this
think i still wanna add an enhancement that does smth similar tho
what the flip