balatro-mods/Cryptid/lib/misc.lua

1463 lines
39 KiB
Lua

--Localization colors
local lc = loc_colour
function loc_colour(_c, _default)
if not G.ARGS.LOC_COLOURS then
lc()
end
G.ARGS.LOC_COLOURS.cry_code = G.C.SET.Code
G.ARGS.LOC_COLOURS.heart = G.C.SUITS.Hearts
G.ARGS.LOC_COLOURS.diamond = G.C.SUITS.Diamonds
G.ARGS.LOC_COLOURS.spade = G.C.SUITS.Spades
G.ARGS.LOC_COLOURS.club = G.C.SUITS.Clubs
for k, v in pairs(G.C) do
if string.len(k) > 4 and string.sub(k, 1, 4) == "CRY_" then
G.ARGS.LOC_COLOURS[string.lower(k)] = v
end
end
return lc(_c, _default)
end
-- More advanced version of find joker for things that need to find very specific things
function Cryptid.advanced_find_joker(name, rarity, edition, ability, non_debuff, area)
local jokers = {}
if not G.jokers or not G.jokers.cards then
return {}
end
local filter = 0
if name then
filter = filter + 1
end
if edition then
filter = filter + 1
end
if type(rarity) ~= "table" then
if type(rarity) == "string" then
rarity = { rarity }
else
rarity = nil
end
end
if rarity then
filter = filter + 1
end
if type(ability) ~= "table" then
if type(ability) == "string" then
ability = { ability }
else
ability = nil
end
end
if ability then
filter = filter + 1
end
-- return nothing if function is called with no useful arguments
if filter == 0 then
return {}
end
if not area or area == "j" then
for k, v in pairs(G.jokers.cards) do
if v and type(v) == "table" and (non_debuff or not v.debuff) then
local check = 0
if name and v.ability.name == name then
check = check + 1
end
if
edition
and (v.edition and v.edition.key == edition) --[[ make this use Cryptid.safe_get later? if it's possible anyways]]
then
check = check + 1
end
if rarity then
--Passes as valid if rarity matches ANY of the values in the rarity table
for _, a in ipairs(rarity) do
if v.config.center.rarity == a then
check = check + 1
break
end
end
end
if ability then
--Only passes if the joker has everything in the ability table
local abilitycheck = true
for _, b in ipairs(ability) do
if not v.ability[b] then
abilitycheck = false
break
end
end
if abilitycheck then
check = check + 1
end
end
if check == filter then
table.insert(jokers, v)
end
end
end
end
if not area or area == "c" then
for k, v in pairs(G.consumeables.cards) do
if v and type(v) == "table" and (non_debuff or not v.debuff) then
local check = 0
if name and v.ability.name == name then
check = check + 1
end
if
edition
and (v.edition and v.edition.key == edition) --[[ make this use Cryptid.safe_get later? if it's possible anyways]]
then
check = check + 1
end
if ability then
--Only passes if the joker has everything in the ability table
local abilitycheck = true
for _, b in ipairs(ability) do
if not v.ability[b] then
abilitycheck = false
break
end
end
if abilitycheck then
check = check + 1
end
end
--Consumables don't have a rarity, so this should ignore it in that case (untested lmfao)
if check == filter then
table.insert(jokers, v)
end
end
end
end
return jokers
end
-- Midground sprites - used for Exotic Jokers and Gateway
-- don't really feel like explaining this deeply, it's based on code for The Soul and Legendary Jokers
local set_spritesref = Card.set_sprites
function Card:set_sprites(_center, _front)
set_spritesref(self, _center, _front)
if _center and _center.name == "cry-Gateway" then
self.children.floating_sprite = Sprite(
self.T.x,
self.T.y,
self.T.w,
self.T.h,
G.ASSET_ATLAS[_center.atlas or _center.set],
{ x = 2, y = 0 }
)
self.children.floating_sprite.role.draw_major = self
self.children.floating_sprite.states.hover.can = false
self.children.floating_sprite.states.click.can = false
self.children.floating_sprite2 = Sprite(
self.T.x,
self.T.y,
self.T.w,
self.T.h,
G.ASSET_ATLAS[_center.atlas or _center.set],
{ x = 1, y = 0 }
)
self.children.floating_sprite2.role.draw_major = self
self.children.floating_sprite2.states.hover.can = false
self.children.floating_sprite2.states.click.can = false
end
if _center and _center.soul_pos and _center.soul_pos.extra then
self.children.floating_sprite2 = Sprite(
self.T.x,
self.T.y,
self.T.w,
self.T.h,
G.ASSET_ATLAS[_center.atlas or _center.set],
_center.soul_pos.extra
)
self.children.floating_sprite2.role.draw_major = self
self.children.floating_sprite2.states.hover.can = false
self.children.floating_sprite2.states.click.can = false
end
end
-- simple plural s function for localisation
function Cryptid.pluralize(str, vars)
local inside = str:match("<(.-)>") -- finds args
local _table = {}
if inside then
for v in inside:gmatch("[^,]+") do -- adds args to array
table.insert(_table, v)
end
local num = vars[tonumber(string.match(str, ">(%d+)"))] -- gets reference variable
if type(num) == "string" then
num = (Big and to_number(to_big(num))) or num
end
if not num then
num = 1
end
local plural = _table[1] -- default
local checks = { [1] = "=" } -- checks 1 by default
local checks1mod = false -- tracks if 1 was modified
if #_table > 1 then
for i = 2, #_table do
local isnum = tonumber(_table[i])
if isnum then
if not checks1mod then
checks[1] = nil
end -- dumb stuff
checks[isnum] = "<" .. (_table[i + 1] or "") -- do less than for custom values
if isnum == 1 then
checks1mod = true
end
i = i + 1
elseif i == 2 then
checks[1] = "=" .. _table[i]
end
end
end
local function fch(str, c)
return string.sub(str, 1, 1) == c -- gets first char and returns boolean
end
local keys = {}
for k in pairs(checks) do
table.insert(keys, k)
end
table.sort(keys, function(a, b)
return a < b
end)
if not (tonumber(num) or is_number(num)) then
num = 1
end
for _, k in ipairs(keys) do
if fch(checks[k], "=") then
if to_big(math.abs(num - k)) < to_big(0.001) then
return string.sub(checks[k], 2, -1)
end
elseif fch(checks[k], "<") then
if to_big(num) < to_big(k - 0.001) then
return string.sub(checks[k], 2, -1)
end
end
end
return plural
end
end
-- generate a random edition (e.g. Antimatter Deck)
function Cryptid.poll_random_edition()
local random_edition = pseudorandom_element(G.P_CENTER_POOLS.Edition, pseudoseed("cry_ant_edition"))
while random_edition.key == "e_base" do
random_edition = pseudorandom_element(G.P_CENTER_POOLS.Edition, pseudoseed("cry_ant_edition"))
end
ed_table = { [random_edition.key:sub(3)] = true }
return ed_table
end
-- gets a random, valid consumeable (used for Hammerspace, CCD Deck, Blessing, etc.)
function Cryptid.random_consumable(seed, excluded_flags, banned_card, pool, no_undiscovered)
-- set up excluded flags - these are the kinds of consumables we DON'T want to have generating
excluded_flags = excluded_flags or { "hidden", "no_doe", "no_grc" }
local selection = "n/a"
local passes = 0
local tries = 500
while true do
tries = tries - 1
passes = 0
-- create a random consumable naively
local key = pseudorandom_element(pool or G.P_CENTER_POOLS.Consumeables, pseudoseed(seed or "grc")).key
selection = G.P_CENTERS[key]
-- check if it is valid
if selection.discovered or not no_undiscovered then
for k, v in pairs(excluded_flags) do
if not Cryptid.no(selection, v, key, true) then
--Makes the consumable invalid if it's a specific card unless it's set to
--I use this so cards don't create copies of themselves (eg potential inf Blessing chain, Hammerspace from Hammerspace...)
if not banned_card or (banned_card and banned_card ~= key) then
passes = passes + 1
end
end
end
end
-- use it if it's valid or we've run out of attempts
if passes >= #excluded_flags or tries <= 0 then
if tries <= 0 and no_undiscovered then
return G.P_CENTERS["c_strength"]
else
return selection
end
end
end
end
-- checks for Jolly Jokers or cards that are supposed to be treated as jolly jokers
function Card:is_jolly()
if self.ability.name == "Jolly Joker" or self.ability.name == "cry-jollysus Joker" then
return true
end
if self.edition and self.edition.key == "e_cry_m" then
return true
end
return false
end
function Cryptid.with_deck_effects(card, func)
if not card.added_to_deck then
return func(card)
else
card.from_quantum = true
card:remove_from_deck(true)
local ret = func(card)
card:add_to_deck(true)
card.from_quantum = nil
return ret
end
end
function Cryptid.deep_copy(obj, seen)
if type(obj) ~= "table" then
return obj
end
if seen and seen[obj] then
return seen[obj]
end
local s = seen or {}
local res = setmetatable({}, getmetatable(obj))
s[obj] = res
for k, v in pairs(obj) do
res[Cryptid.deep_copy(k, s)] = Cryptid.deep_copy(v, s)
end
return res
end
function SMODS.current_mod.reset_game_globals(run_start)
G.GAME.cry_ach_conditions = G.GAME.cry_ach_conditions or {}
end
--Used for m vouchers, perhaps this can have more applications in the future
function Cryptid.get_m_jokers()
local mcount = 0
if G.jokers then
for i = 1, #G.jokers.cards do
if Cryptid.safe_get(G.jokers.cards[i].config.center, "pools", "M") then
mcount = mcount + 1
end
if G.jokers.cards[i].ability.name == "cry-mprime" then
mcount = mcount + 1
end
end
end
return mcount
end
-- Check G.GAME as well as joker info for banned keys
function Card:no(m, no_no)
if no_no then
-- Infinifusion Compat
if self.infinifusion then
for i = 1, #self.infinifusion do
if
G.P_CENTERS[self.infinifusion[i].key][m]
or (G.GAME and G.GAME[m] and G.GAME[m][self.infinifusion[i].key])
then
return true
end
end
return false
end
if not self.config then
--assume this is from one component of infinifusion
return G.P_CENTERS[self.key][m] or (G.GAME and G.GAME[m] and G.GAME[m][self.key])
end
return self.config.center[m] or (G.GAME and G.GAME[m] and G.GAME[m][self.config.center_key]) or false
end
return Card.no(self, "no_" .. m, true)
end
function Cryptid.no(center, m, key, no_no)
if no_no then
return center[m] or (G.GAME and G.GAME[m] and G.GAME[m][key]) or false
end
return Cryptid.no(center, "no_" .. m, key, true)
end
--todo: move to respective stake file
--[from pre-refactor] make this always active to prevent crashes
function Cryptid.apply_ante_tax()
if G.GAME.modifiers.cry_ante_tax then
local tax = math.max(
0,
math.min(G.GAME.modifiers.cry_ante_tax_max, math.floor(G.GAME.modifiers.cry_ante_tax * G.GAME.dollars))
)
ease_dollars(-1 * tax)
return true
end
return false
end
--Changes main menu colors and stuff
--has to be modified with new enabling system
if Cryptid_config.menu then
local oldfunc = Game.main_menu
Game.main_menu = function(change_context)
local ret = oldfunc(change_context)
-- adds a Cryptid spectral to the main menu
local newcard = Card(
G.title_top.T.x,
G.title_top.T.y,
G.CARD_W,
G.CARD_H,
G.P_CARDS.empty,
G.P_CENTERS.c_cryptid,
{ bypass_discovery_center = true }
)
-- recenter the title
G.title_top.T.w = G.title_top.T.w * 1.7675
G.title_top.T.x = G.title_top.T.x - 0.8
G.title_top:emplace(newcard)
-- make the card look the same way as the title screen Ace of Spades
newcard.T.w = newcard.T.w * 1.1 * 1.2
newcard.T.h = newcard.T.h * 1.1 * 1.2
newcard.no_ui = true
newcard.states.visible = false
-- make the title screen use different background colors
G.SPLASH_BACK:define_draw_steps({
{
shader = "splash",
send = {
{ name = "time", ref_table = G.TIMERS, ref_value = "REAL_SHADER" },
{ name = "vort_speed", val = 0.4 },
{ name = "colour_1", ref_table = G.C, ref_value = "CRY_EXOTIC" },
{ name = "colour_2", ref_table = G.C, ref_value = "DARK_EDITION" },
},
},
})
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0,
blockable = false,
blocking = false,
func = function()
if change_context == "splash" then
newcard.states.visible = true
newcard:start_materialize({ G.C.WHITE, G.C.WHITE }, true, 2.5)
else
newcard.states.visible = true
newcard:start_materialize({ G.C.WHITE, G.C.WHITE }, nil, 1.2)
end
return true
end,
}))
return ret
end
end
-- just dumping this garbage here
-- this just ensures that extra voucher slots work as expected
function Cryptid.bonus_voucher_mod(mod)
if not G.GAME.shop then
return
end
G.GAME.cry_bonusvouchercount = G.GAME.cry_bonusvouchercount + mod
if G.shop_jokers and G.shop_jokers.cards then
G.shop:recalculate()
if mod > 0 then -- not doing minus mod because it'd be janky and who really cares
for i = 1, G.GAME.cry_bonusvouchercount + 1 - #G.shop_vouchers.cards do
local curr_bonus = G.GAME.current_round.cry_bonusvouchers
curr_bonus[#curr_bonus + 1] = get_next_voucher_key()
-- this could be a function but it's done like what... 3 times? it doesn't matter rn
local card = Card(
G.shop_vouchers.T.x + G.shop_vouchers.T.w / 2,
G.shop_vouchers.T.y,
G.CARD_W,
G.CARD_H,
G.P_CARDS.empty,
G.P_CENTERS[curr_bonus[#curr_bonus]],
{ bypass_discovery_center = true, bypass_discovery_ui = true }
)
card.shop_cry_bonusvoucher = #curr_bonus
Cryptid.manipulate(card)
if G.GAME.events.ev_cry_choco2 then
card.misprint_cost_fac = (card.misprint_cost_fac or 1) * 2
card:set_cost()
end
if
G.GAME.modifiers.cry_enable_flipped_in_shop
and pseudorandom("cry_flip_vouch" .. G.GAME.round_resets.ante) > 0.7
then
card.cry_flipped = true
end
create_shop_card_ui(card, "Voucher", G.shop_vouchers)
card:start_materialize()
if G.GAME.current_round.cry_voucher_edition then
card:set_edition(G.GAME.current_round.cry_voucher_edition, true, true)
end
G.shop_vouchers.config.card_limit = G.shop_vouchers.config.card_limit + 1
G.shop_vouchers:emplace(card)
end
end
end
end
function Cryptid.save()
local data = {
shinytags = {},
}
data.shinytags = copy_table(Cryptid.shinytagdata)
compress_and_save(G.SETTINGS.profile .. "/" .. "cryptidsave.jkr", STR_PACK(data))
end
local sppref = set_profile_progress
function set_profile_progress()
sppref()
if not Cryptid.shinytagdata then
Cryptid.shinytagdata = {}
end
if not Cryptid.shinytagdata.init then
for k, v in pairs(G.P_TAGS) do
if Cryptid.shinytagdata[k] == nil then
Cryptid.shinytagdata.init = true
Cryptid.shinytagdata[k] = false
end
end
end
end
Cryptid.big_num_blacklist = {
["j_cry_fractal"] = true,
["j_cry_wonka_bar"] = true,
["j_cry_oldcandy"] = true,
["j_cry_negative"] = true,
["c_magician"] = true,
["c_empress"] = true,
["c_heirophant"] = true,
["c_lovers"] = true,
["c_chariot"] = true,
["c_justice"] = true,
["c_strength"] = true,
["c_hanged_man"] = true,
["c_death"] = true,
["c_devil"] = true,
["c_tower"] = true,
["c_star"] = true,
["c_moon"] = true,
["c_sun"] = true,
["c_world"] = true,
["c_cry_eclipse"] = true,
["c_cry_seraph"] = true,
["c_cry_instability"] = true,
["v_cry_stickyhand"] = true,
["v_cry_grapplinghook"] = true,
["v_cry_hyperspacetether"] = true,
-- Add your Jokers here if you *don't* want to have it's numbers go into BigNum
-- FORMAT: <Joker Key ("j_cry_oil_lamp")> = true,
-- TARGET: BigNum Black List
}
Cryptid.mod_whitelist = {
Cryptid = true,
-- Add your ModName here if you want your mod to have it's jokers' values go into BigNum
-- FORMAT: <ModName> = true,
-- TARGET: BigNum Mod Whitelist
}
function Cryptid.is_card_big(joker)
local center = joker.config and joker.config.center
if not center then
return false
end
if center.immutable and center.immutable == true then
return false
end
if center.mod and not Cryptid.mod_whitelist[center.mod.name] then
return false
end
local in_blacklist = Cryptid.big_num_blacklist[center.key or "Nope!"] or false
return not in_blacklist --[[or
(center.mod and center.mod.id == "Cryptid" and not center.no_break_infinity) or center.break_infinity--]]
end
--Utility function to check things without erroring
---@param t table
---@param ... any
---@return table|false
function Cryptid.safe_get(t, ...)
local current = t
for _, k in ipairs({ ... }) do
if not current or current[k] == nil then
return false
end
current = current[k]
end
return current
end
--Functions used by boss blinds
function Blind:cry_ante_base_mod(dt)
if not self.disabled then
local obj = self.config.blind
if obj.cry_ante_base_mod and type(obj.cry_ante_base_mod) == "function" then
return obj:cry_ante_base_mod(dt)
end
end
return 0
end
function Blind:cry_round_base_mod(dt)
if not self.disabled then
local obj = self.config.blind
if obj.cry_round_base_mod and type(obj.cry_round_base_mod) == "function" then
return obj:cry_round_base_mod(dt)
end
end
return 1
end
function Blind:cry_cap_score(score)
if not self.disabled then
local obj = self.config.blind
if obj.cry_modify_score and type(obj.cry_modify_score) == "function" then
score = obj:cry_modify_score(score)
end
if obj.cry_cap_score and type(obj.cry_cap_score) == "function" then
return obj:cry_cap_score(score)
end
end
return score
end
function Blind:cry_after_play()
if not self.disabled then
local obj = self.config.blind
if obj.cry_after_play and type(obj.cry_after_play) == "function" then
return obj:cry_after_play()
end
end
end
function Blind:cry_before_play()
if not self.disabled then
local obj = self.config.blind
if obj.cry_before_play and type(obj.cry_before_play) == "function" then
return obj:cry_before_play()
end
end
end
--The decision's ability to show a booster pack
function Blind:cry_before_cash()
if not self.disabled then
local obj = self.config.blind
if obj.cry_before_cash and type(obj.cry_before_cash) == "function" then
return obj:cry_before_cash()
end
end
end
function Blind:cry_calc_ante_gain()
if G.GAME.modifiers.cry_spooky then --here is the best place to check when spooky should apply
local card
if pseudorandom(pseudoseed("cry_spooky_curse")) < G.GAME.modifiers.cry_curse_rate then
card = create_card("Joker", G.jokers, nil, "cry_cursed", nil, nil, nil, "cry_spooky")
else
card = create_card("Joker", G.jokers, nil, "cry_candy", nil, nil, nil, "cry_spooky")
end
card:add_to_deck()
card:start_materialize()
G.jokers:emplace(card)
end
if not self.disabled then
local obj = self.config.blind
if obj.cry_calc_ante_gain and type(obj.cry_calc_ante_gain) == "function" then
return obj:cry_calc_ante_gain()
end
end
return 1
end
function Cryptid.enhanced_deck_info(deck)
--only accounts for vanilla stuff at the moment (WIP)
local edition, enhancement, sticker, suit, seal =
"e_" .. (Cryptid.safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_edition") or "foil"),
Cryptid.safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_enhancement") or "m_bonus",
Cryptid.safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_sticker") or "eternal",
Cryptid.safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_suit") or "Spades",
Cryptid.safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_seal") or "Gold"
-- Do Stuff
edition = (Cryptid.safe_get(G.P_CENTERS, edition) and edition or "e_foil"):sub(3)
enhancement = Cryptid.safe_get(G.P_CENTERS, enhancement) and enhancement or "m_bonus"
sticker = Cryptid.safe_get(SMODS.Stickers, sticker) and sticker or "eternal"
suit = Cryptid.safe_get(SMODS.Suits, suit) and suit or "Spades"
seal = Cryptid.safe_get(G.P_SEALS, seal) and seal or "Gold"
local ret = {
edition = edition,
enhancement = enhancement,
sticker = sticker,
suit = suit,
seal = seal,
}
for k, _ in pairs(ret) do
if G.GAME.modifiers["cry_force_" .. k] and not G.GAME.viewed_back then
ret[k] = G.GAME.modifiers["cry_force_" .. k]
elseif Cryptid.safe_get(deck, "config", "cry_force_" .. k) then
ret[k] = deck.config["cry_force_" .. k]
end
end
return ret.edition, ret.enhancement, ret.sticker, ret.suit, ret.seal
end
function Cryptid.post_process(center)
if center.pools and center.pools.M then
local vc = center.calculate
center.calculate = function(self, card, context)
local ret, trig = vc(self, card, context)
if context.retrigger_joker_check and context.other_card == card then
local reps = Cryptid.get_m_retriggers(self, card, context)
if reps > 0 then
return {
message = localize("k_again_ex"),
repetitions = reps + (ret and ret.repetitions or 0),
card = card,
}
end
end
return ret, trig
end
end
end
-- Wrapper G.FUNCS function to reset localization
-- For resetting localization on the fly for family friendly toggle
function Cryptid.reload_localization()
SMODS.handle_loc_file(Cryptid.path)
Cryptid.handle_other_localizations()
return init_localization()
end
-- Purely for crossmod purposes
function Cryptid.handle_other_localizations() end
-- Checks if all jokers in shop will have editions (via Curate, Edition Decks, etc.)
-- Will cause edition tags to Nope!
function Cryptid.forced_edition()
return G.GAME.modifiers.cry_force_edition or G.GAME.used_vouchers.v_cry_curate
end
-- Add Ctrl+Space for Pointer UI in Debug Mode
local ckpu = Controller.key_press_update
function Controller:key_press_update(key, dt)
ckpu(self, key, dt)
if
key == "space"
and G.STAGE == G.STAGES.RUN
and not _RELEASE_MODE
and (self.held_keys["lctrl"] or self.held_keys["rctrl"] or self.held_keys["lgui"] or self.held_keys["rgui"])
and not G.GAME.USING_CODE
then
G.GAME.USING_CODE = true
G.GAME.USING_POINTER = true
G.DEBUG_POINTER = true
G.ENTERED_CARD = ""
G.CHOOSE_CARD = UIBox({
definition = create_UIBox_pointer(card),
config = {
align = "cm",
offset = { x = 0, y = 10 },
major = G.ROOM_ATTACH,
bond = "Weak",
instance_type = "POPUP",
},
})
G.CHOOSE_CARD.alignment.offset.y = 0
G.ROOM.jiggle = G.ROOM.jiggle + 1
G.CHOOSE_CARD:align_to_major()
end
end
function Cryptid.roll_shiny()
local prob = 1
if next(SMODS.find_card("j_lucky_cat")) then
prob = 3
end
if pseudorandom("cry_shiny") < prob / 4096 then
return "shiny"
end
return "normal"
end
function Cryptid.is_shiny()
if Cryptid.roll_shiny() == "shiny" then
return true
end
return false
end
--Abstracted cards
function Cryptid.cry_enhancement_has_specific_suit(card)
for k, _ in pairs(SMODS.get_enhancements(card)) do
if G.P_CENTERS[k].specific_suit then
return true
end
end
return false
end
function Cryptid.cry_enhancement_get_specific_suit(card)
for k, _ in pairs(SMODS.get_enhancements(card)) do
if G.P_CENTERS[k].specific_suit then
return G.P_CENTERS[k].specific_suit
end
end
return nil
end
function Cryptid.cry_enhancement_has_specific_rank(card)
for k, _ in pairs(SMODS.get_enhancements(card)) do
if G.P_CENTERS[k].specific_rank then
return true
end
end
return false
end
function Cryptid.cry_enhancement_get_specific_rank(card)
for k, _ in pairs(SMODS.get_enhancements(card)) do
if G.P_CENTERS[k].specific_rank then
return G.P_CENTERS[k].specific_rank
end
end
return nil
end
--For better durability (at the expense of performance), this finds the rank ID of a custom rank (such as abstract).
function Cryptid.cry_rankname_to_id(rankname)
for i, v in pairs(SMODS.Rank.obj_buffer) do
if rankname == v then
return i
end
end
return nil
end
-- for buttercup
function G.FUNCS.can_store_card(e)
-- get shop highlighted
-- only from the jokers spot
local highlighted_shop_cards = {}
local areas_to_check = {
shop_jokers = G.shop_jokers,
shop_vouchers = G.shop_vouchers,
shop_booster = G.shop_booster,
}
local jok = e.config.ref_table
for key, value in pairs(areas_to_check) do
if value == nil then
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
return
elseif #value.highlighted == 1 and #highlighted_shop_cards == 0 then
highlighted_shop_cards[1] = value.highlighted[1]
end
end
if #highlighted_shop_cards == 1 and jok:can_use_storage() then
e.config.colour = G.C.BLUE
e.config.button = "store_card"
else
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
end
end
function G.FUNCS.store_card(e)
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.1,
func = function()
local areas_to_check = {
shop_jokers = G.shop_jokers,
shop_vouchers = G.shop_vouchers,
shop_booster = G.shop_booster,
}
local this_card = e.config.ref_table
-- This doesn't take into account the possibility that multiple cards might be selected in different areas
-- but can_store_card already does that for us, so who cares tbh
for shop_name, shop_area in pairs(areas_to_check) do
if #shop_area.highlighted == 1 then
local new_card = shop_area.highlighted[1]
new_card.T.orig = { w = new_card.T.w, h = new_card.T.h }
new_card.T.w = new_card.T.w * 0.5
new_card.T.h = new_card.T.h * 0.5
new_card.cry_from_shop = shop_name
if new_card.children.price then
new_card.children.price:remove()
end
new_card.children.price = nil
if new_card.children.buy_button then
new_card.children.buy_button:remove()
end
new_card.children.buy_button = nil
shop_area:remove_card(new_card)
this_card.cry_storage:emplace(new_card)
end
end
return true
end,
}))
end
function Card:can_use_storage()
if self.cry_storage ~= nil then
return #self.cry_storage.cards < self.ability.extra.slots
elseif self.config.center.key == "j_cry_buttercup" then -- "where did my fucking storage go"
sendInfoMessage("creating missing card area")
self.cry_storage = CardArea(0.5, 0.5, 1, 1, storage_area_config)
end
return false
end
function Cryptid.reset_to_none()
update_hand_text({ delay = 0 }, {
mult = Cryptid.ascend(G.GAME.hands["cry_None"].mult),
chips = Cryptid.ascend(G.GAME.hands["cry_None"].chips),
level = G.GAME.hands["cry_None"].level,
handname = localize("cry_None", "poker_hands"),
})
end
function Card:is_food()
--you cant really check if vanilla jokers are in a pool because its hardcoded
--so i have to hardcode it here too for the starfruit unlock
local food = {
j_gros_michel = true,
j_egg = true,
j_ice_cream = true,
j_cavendish = true,
j_turtle_bean = true,
j_diet_cola = true,
j_popcorn = true,
j_ramen = true,
j_selzer = true,
}
if food[self.config.center.key] or Cryptid.safe_get(self.config.center, "pools", "Food") then
return true
end
end
function Cryptid.get_highlighted_cards(areas, ignore, min, max, blacklist, seed)
ignore.checked = true
blacklist = blacklist or function()
return true
end
local cards = {}
for i, area in pairs(areas) do
if area.cards then
for i2, card in pairs(area.cards) do
if
card ~= ignore
and blacklist(card)
and (card.highlighted or G.cry_force_use)
and not card.checked
then
cards[#cards + 1] = card
card.checked = true
end
end
end
end
for i, v in ipairs(cards) do
v.checked = nil
end
if (#cards >= min and #cards <= max) or not G.cry_force_use then
ignore.checked = nil
return cards
else
for i, v in pairs(cards) do
v.f_use_order = i
end
pseudoshuffle(cards, pseudoseed("forcehighlight" or seed))
local actual = {}
for i = 1, max do
if cards[i] and not cards[i].checked and actual ~= ignore then
actual[#actual + 1] = cards[i]
end
end
table.sort(actual, function(a, b)
return a.f_use_order < b.f_use_order
end)
for i, v in pairs(cards) do
v.f_use_order = nil
end
ignore.checked = nil
return actual
end
return {}
end
function Cryptid.table_merge(...)
local tbl = {}
for _, t in ipairs({ ... }) do
if type(t) == "table" then
for _, v in pairs(t) do
tbl[#tbl + 1] = v
end
end
end
return tbl
end
function Cryptid.get_circus_description()
local desc = {}
local ind = 1
local extra_rarities = {}
if not Cryptid.circus_rarities then
Cryptid.circus_rarities = {}
end
for i, v in pairs(Cryptid.circus_rarities) do
if not v.hidden then
extra_rarities[#extra_rarities + 1] = v
end
end
table.sort(extra_rarities, function(a, b)
return a.order < b.order
end)
for i, v in pairs(extra_rarities) do
local rarity = v.rarity
rarity = localize(({
[1] = "k_common",
[2] = "k_uncommon",
[3] = "k_rare",
[4] = "k_legendary",
})[rarity] or "k_" .. rarity)
local orig = localize("cry_circus_generic")
orig = string.gsub(orig, "#1#", ind)
orig = string.gsub(orig, "#2#", rarity)
orig = string.gsub(orig, "#3#", "#" .. tostring(ind) .. "#")
desc[#desc + 1] = orig
ind = ind + 1
end
return desc
end
function Cryptid.add_circus_rarity(rarity, dontreload)
Cryptid.circus_rarities[rarity.rarity] = rarity
if not dontreload then
Cryptid.reload_localization()
end
end
function Cryptid.get_paved_joker()
if G.hand then
local total = 0
for i, v in pairs(SMODS.find_card("j_cry_paved_joker")) do
total = total + v.ability.extra
end
local stones = 0
for i, v in pairs(G.hand.highlighted) do
if v.config.center.key == "m_stone" then
stones = stones + 1
end
end
for i, v in pairs(G.play.cards) do
if v.config.center.key == "m_stone" then
stones = stones + 1
end
end
total = math.min(stones, total)
return total
end
return 0
end
function Card:has_stickers()
for i, v in pairs(SMODS.Sticker.obj_table) do
if self.ability[i] then
return true
end
end
end
function Card:remove_random_sticker(seed)
local s = {}
for i, v in pairs(SMODS.Sticker.obj_table) do
if not v.hidden and i ~= "cry_absolute" and self.ability[i] then
s[#s + 1] = i
end
end
if #s > 0 then
local sticker = pseudorandom_element(s, pseudoseed(seed))
self.ability[sticker] = nil
if sticker == "perishable" then
self.ability.perish_tally = nil
end
end
end
function create_UIBox_class()
return SMODS.card_collection_UIBox(G.P_CENTER_POOLS.Enhanced, { 4, 4 }, {
no_materialize = true,
snap_back = true,
h_mod = 1.03,
--infotip = localize('ml_edition_seal_enhancement_explanation'),
hide_single_page = true,
back_func = "exit_overlay_menu_code",
})
end
function create_UIBox_variable_code()
local cards = {}
local ranks = {}
for i, v in pairs(SMODS.Ranks) do
cards[#cards + 1] = G.P_CENTERS.c_base
ranks[#ranks + 1] = i
end
table.sort(ranks, function(a, b)
return SMODS.Ranks[a].id < SMODS.Ranks[b].id
end)
return SMODS.card_collection_UIBox(cards, { 5, 5, 5 }, {
no_materialize = true,
snap_back = true,
h_mod = 1.03,
--infotip = localize('ml_edition_seal_enhancement_explanation'),
hide_single_page = true,
back_func = "exit_overlay_menu_code",
modify_card = function(card, center, i, j)
SMODS.change_base(card, "Spades", ranks[(j - 1) * 5 + i])
end,
})
end
function create_UIBox_exploit()
local cards = {}
local ranks = {}
for i, v in pairs(G.P_CENTER_POOLS.Planet) do
if v.config.handname then
cards[#cards + 1] = v
end
end
table.sort(ranks, function(a, b)
return G.GAME.hands[a.config.handname].order < G.GAME.hands[b.config.handname]
end)
return SMODS.card_collection_UIBox(cards, { 5, 5, 5 }, {
no_materialize = true,
snap_back = true,
h_mod = 1.03,
--infotip = localize('ml_edition_seal_enhancement_explanation'),
hide_single_page = true,
back_func = "exit_overlay_menu_code",
})
end
G.FUNCS.exit_overlay_menu_code = function(e)
G.FUNCS.exit_overlay_menu(e)
G.GAME.USING_CLASS = nil
G.GAME.USING_CODE = nil
G.GAME.USING_VARIABLE = nil
G.GAME.USING_EXPLOIT_HAND = nil
G.GAME.USING_EXPLOIT = nil
G.GAME.USING_POINTER = nil
G.GAME.POINTER_SUBMENU = nil
G.GAME.POINTER_PLAYING = nil
G.GAME.POINTER_COLLECTION = nil
if
G.GAME.CODE_DESTROY_CARD
and G.GAME.CODE_DESTROY_CARD.ability
and G.GAME.CODE_DESTROY_CARD.ability.cry_multiuse
then
G.GAME.CODE_DESTROY_CARD.ability.cry_multiuse = G.GAME.CODE_DESTROY_CARD.ability.cry_multiuse - 1
elseif G.GAME.CODE_DESTROY_CARD then
G.GAME.CODE_DESTROY_CARD:start_dissolve()
G.GAME.CODE_DESTROY_CARD = nil
end
G.GAME.CODE_DESTROY_CARD = nil
end
function G.UIDEF.exploit_menu()
return create_UIBox_generic_options({
contents = {
create_tabs({
tabs = {
{
label = localize("b_poker_hands"),
chosen = true,
tab_definition_function = create_UIBox_current_hands_exploit,
},
},
tab_h = 8,
snap_to_nav = true,
}),
},
})
end
function create_UIBox_current_hands_exploit(simple)
local ref = create_UIBox_current_hand_row
local ret = create_UIBox_current_hands(simple)
create_UIBox_current_hand_row = ref
return ret
end
local htref = create_UIBox_hand_tip
function create_UIBox_hand_tip(handname)
if G.GAME.USING_EXPLOIT then
G.GAME.USING_EXPLOIT_HAND = handname
end
return htref(handname)
end
local lcpref = Controller.L_cursor_press
function Controller:L_cursor_press(x, y)
lcpref(self, x, y)
if G and G.GAME and G.GAME.hands and G.GAME.USING_EXPLOIT_HAND then
if
G.CONTROLLER.cursor_hover
and G.CONTROLLER.cursor_hover.target
and G.CONTROLLER.cursor_hover.target.config
and G.CONTROLLER.cursor_hover.target.config.on_demand_tooltip
and G.CONTROLLER.cursor_hover.target.config.on_demand_tooltip.filler
and G.CONTROLLER.cursor_hover.target.config.on_demand_tooltip.filler.args
and G.GAME.hands[G.CONTROLLER.cursor_hover.target.config.on_demand_tooltip.filler.args]
then
-- Re-use the Exploit card
if G.GAME.ACTIVE_CODE_CARD then
if
not G.GAME.ACTIVE_CODE_CARD.ability.cry_multiuse
or to_big(G.GAME.ACTIVE_CODE_CARD.ability.cry_multiuse) <= to_big(1)
then
G.GAME.ACTIVE_CODE_CARD:start_dissolve()
else
G.GAME.ACTIVE_CODE_CARD.ability.cry_multiuse =
lenient_bignum(to_big(G.GAME.ACTIVE_CODE_CARD.ability.cry_multiuse) - to_big(1))
end
end
G.GAME.ACTIVE_CODE_CARD = nil
G.GAME.cry_exploit_override = G.GAME.USING_EXPLOIT_HAND
G.FUNCS.exit_overlay_menu_code()
end
end
end
function create_UIBox_pointer_rank()
G.GAME.POINTER_SUBMENU = "Rank"
G.GAME.POINTER_PLAYING = {}
local cards = {}
local ranks = {}
for i, v in pairs(SMODS.Ranks) do
cards[#cards + 1] = G.P_CENTERS.c_base
ranks[#ranks + 1] = i
end
table.sort(ranks, function(a, b)
return SMODS.Ranks[a].id < SMODS.Ranks[b].id
end)
return SMODS.card_collection_UIBox(cards, { 5, 5, 5 }, {
no_materialize = true,
snap_back = true,
h_mod = 1.03,
--infotip = localize('ml_edition_seal_enhancement_explanation'),
hide_single_page = true,
back_func = "your_collection",
modify_card = function(card, center, i, j)
SMODS.change_base(card, "Spades", ranks[(j - 1) * 5 + i])
if
center.hidden
or center.no_noe
or center.no_pointer
or center.no_code
or center.no_variable
or center.no_class
then
card.deuff = true
end
end,
})
end
function create_UIBox_pointer_suit()
G.GAME.POINTER_SUBMENU = "Suit"
local cards = {}
local suits = {}
for i, v in pairs(SMODS.Suits) do
cards[#cards + 1] = G.P_CENTERS.c_base
suits[#suits + 1] = i
end
table.sort(suits, function(a, b)
return SMODS.Suits[a].suit_nominal < SMODS.Suits[b].suit_nominal
end)
return SMODS.card_collection_UIBox(cards, { 4, 4, 4 }, {
no_materialize = true,
snap_back = true,
h_mod = 1.03,
--infotip = localize('ml_edition_seal_enhancement_explanation'),
hide_single_page = true,
back_func = "your_collection",
modify_card = function(card, center, i, j)
SMODS.change_base(card, suits[(j - 1) * 4 + i], G.GAME.POINTER_PLAYING.rank)
if
center.hidden
or center.no_noe
or center.no_pointer
or center.no_code
or center.no_variable
or center.no_class
then
card.deuff = true
end
end,
})
end
function create_UIBox_pointer_enhancement()
G.GAME.POINTER_SUBMENU = "Enhancement"
return create_UIBox_your_collection_enhancements_pointer()
end
function create_UIBox_pointer_edition()
G.GAME.POINTER_SUBMENU = "Edition"
return create_UIBox_your_collection_editions_pointer()
end
function create_UIBox_pointer_seal()
G.GAME.POINTER_SUBMENU = "Seal"
return create_UIBox_your_collection_seals_pointer()
end
G.FUNCS.your_collection_create_card_rank = function(e)
G.SETTINGS.paused = true
G.FUNCS.overlay_menu({
definition = create_UIBox_pointer_rank(),
})
end
create_UIBox_your_collection_enhancements_pointer = function()
local cards = {
G.P_CENTERS.c_base,
}
for i, v in pairs(G.P_CENTER_POOLS.Enhanced) do
cards[#cards + 1] = v
end
return SMODS.card_collection_UIBox(cards, { 4, 4 }, {
no_materialize = true,
snap_back = true,
h_mod = 1.03,
hide_single_page = true,
modify_card = function(card, center)
SMODS.change_base(card, G.GAME.POINTER_PLAYING.suit, G.GAME.POINTER_PLAYING.rank)
if
center.hidden
or center.no_noe
or center.no_pointer
or center.no_code
or center.no_variable
or center.no_class
then
card.deuff = true
end
end,
})
end
create_UIBox_your_collection_editions_pointer = function()
return SMODS.card_collection_UIBox(G.P_CENTER_POOLS.Edition, { 5, 5 }, {
snap_back = true,
h_mod = 1.03,
hide_single_page = true,
collapse_single_page = true,
modify_card = function(card, center)
if center.discovered then
card:set_edition(center.key, true, true)
SMODS.change_base(card, G.GAME.POINTER_PLAYING.suit, G.GAME.POINTER_PLAYING.rank)
card:set_ability(G.P_CENTERS[G.GAME.POINTER_PLAYING.center])
if
center.hidden
or center.no_noe
or center.no_pointer
or center.no_code
or center.no_variable
or center.no_class
then
card.deuff = true
end
end
end,
})
end
create_UIBox_your_collection_seals_pointer = function()
local cards = {
{ key = nil },
}
for i, v in pairs(G.P_CENTER_POOLS.Seal) do
cards[#cards + 1] = v
end
return SMODS.card_collection_UIBox(cards, { 5, 5 }, {
snap_back = true,
hide_single_page = true,
collapse_single_page = true,
center = "c_base",
h_mod = 1.03,
modify_card = function(card, center)
card:set_seal(center.key, true)
SMODS.change_base(card, G.GAME.POINTER_PLAYING.suit, G.GAME.POINTER_PLAYING.rank)
card:set_ability(G.P_CENTERS[G.GAME.POINTER_PLAYING.center])
card:set_edition(G.GAME.POINTER_PLAYING.edition, true, true)
if
center.hidden
or center.no_noe
or center.no_pointer
or center.no_code
or center.no_variable
or center.no_class
then
card.deuff = true
end
end,
})
end
function Cryptid.get_next_tag(override)
if next(SMODS.find_card("j_cry_kittyprinter")) then
return "tag_cry_cat"
end
end
-- for Cryptid.isNonRollProbabilityContext
local probability_contexts = {
"mod_probability",
"fix_probability",
}
-- Checks if a context table is a probability context called outside of a roll
function Cryptid.isNonRollProbabilityContext(context)
for _, ctx in ipairs(probability_contexts) do
if context[ctx] then
return context.from_roll
end
end
return true
end
function Cryptid.nuke_decimals(number, surviving_decimals, round)
surviving_decimals = surviving_decimals or 0
--Set round to 0.5 to round or 0 to floor
round = round or 0
local aaa = 10 ^ surviving_decimals
return math.floor(number * aaa + round) / aaa
end
-- "log base (x) of (y)". Pre-Calculus courses recommended
function Cryptid.funny_log(x, y)
return math.log(y) / math.log(x)
end
local say_stuff_ref = Card_Character.say_stuff
function Card_Character:say_stuff(n, not_first, quip_key)
local quip = SMODS.JimboQuips[quip_key]
if quip then
return say_stuff_ref(self, n, not_first, quip_key)
end
end