--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 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 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 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 function cry_edition_to_table(edition) -- look mom i figured it out (this does NOT need to be a function) if edition then return { [edition] = true } end end -- simple plural s function for localisation function cry_pls(str, vars) if string.sub(str, 1, 1) == "p" or string.sub(str, 1, 1) == "s" or string.sub(str, 1, 1) == "y" then num = vars[tonumber(string.sub(str, 2, -1))] if num then if math.abs(to_big(num) - 1) > to_big(0.001) then return string.sub(str, 1, 1) == "y" and "ies" or "s" else return string.sub(str, 1, 1) == "y" and "y" or "" end end end return false -- idk it doesn't really matter end -- generate a random edition (e.g. Antimatter Deck) function cry_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 get_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 center_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" then return true end if self.edition and self.edition.key == "e_cry_m" then return true end return false end function cry_with_deck_effects(card, func) if not card.added_to_deck then return func(card) else card:remove_from_deck(true) local ret = func(card) card:add_to_deck(true) return ret end end function cry_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[cry_deep_copy(k, s)] = cry_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 get_m_jokers() local mcount = 0 if G.jokers then for i = 1, #G.jokers.cards do if safe_get(G.jokers.cards[i], "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 center_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 center_no(center, "no_" .. m, key, true) end --todo: move to respective stake file --[from pre-refactor] make this always active to prevent crashes function cry_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 true then --Cryptid.enabled["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 cry_bonusvouchermod(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 cry_misprintize(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 Cryptid.big_num_whitelist = { j_ride_the_bus = true, j_egg = true, j_runner = true, j_ice_cream = true, j_constellation = true, j_green_joker = true, j_red_card = true, j_madness = true, j_square = true, j_vampire = true, j_hologram = true, j_obelisk = true, j_turtle_bean = true, j_lucky_cat = true, j_flash = true, j_popcorn = true, j_trousers = true, j_ramen = true, j_castle = true, j_campfire = true, j_throwback = true, j_glass = true, j_wee = true, j_hit_the_road = true, j_caino = true, j_yorick = true, -- Once all Cryptid Jokers get support for this, these can be removed j_cry_dropshot = true, j_cry_wee_fib = true, j_cry_whip = true, j_cry_pickle = true, j_cry_chili_pepper = true, j_cry_cursor = true, j_cry_jimball = true, j_cry_eternalflame = true, j_cry_fspinner = true, j_cry_krustytheclown = true, j_cry_antennastoheaven = true, j_cry_mondrian = true, j_cry_spaceglobe = true, j_cry_m = true, j_cry_exponentia = true, j_cry_crustulum = true, j_cry_primus = true, j_cry_stella_mortis = true, j_cry_hugem = true, j_cry_mprime = true, } function is_card_big(joker) local center = joker.config and joker.config.center if not center then return false end return Cryptid.big_num_whitelist[center.key or "Nope!"] --[[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 function safe_get(t, ...) local current = t for _, k in ipairs({ ... }) do if 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_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 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 cry_get_enchanced_deck_info(deck) --only accounts for vanilla stuff at the moment (WIP) local edition, enhancement, sticker, suit, seal = "e_" .. (safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_edition") or "foil"), safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_enhancement") or "m_bonus", safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_sticker") or "eternal", safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_suit") or "Spades", safe_get(G.PROFILES, G.SETTINGS.profile, "cry_edeck_seal") or "Gold" -- Do Stuff edition = (safe_get(G.P_CENTERS, edition) and edition or "e_foil"):sub(3) enhancement = safe_get(G.P_CENTERS, enhancement) and enhancement or "m_bonus" sticker = safe_get(SMODS.Stickers, sticker) and sticker or "eternal" suit = safe_get(SMODS.Suits, suit) and suit or "Spades" seal = 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 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 = 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 reload_cryptid_localization() SMODS.handle_loc_file(Cryptid.path) return init_localization() end