--- STEAMODDED HEADER --- MOD_NAME: Cryptid --- MOD_ID: Cryptid --- PREFIX: cry --- MOD_AUTHOR: [MathIsFun_, Cryptid and Balatro Discords] --- MOD_DESCRIPTION: Adds unbalanced ideas to Balatro. --- BADGE_COLOUR: 708b91 --- DEPENDENCIES: [Talisman>=2.0.0-beta8<=2.0.9, Steamodded>=1.0.0~ALPHA-1225a<=1.0.0~ALPHA-1304a] --- VERSION: 0.5.3c --- PRIORITY: 2147483647 ---------------------------------------------- ------------MOD CODE ------------------------- -- Currently there's no rhyme or reason to how the contents of this file are organized. It's kind of just an "anything goes" sort of file. -- If you're learning about Cryptid's codebase, the files in the Items folder are generally much more organized. -- Enables debug features (I think this is currently useless.) --Cryptid.debug = true -- Save the mod path permanently. local mod_path = "" .. SMODS.current_mod.path -- Load Options Cryptid_config = SMODS.current_mod.config -- This will save the current state even when settings are modified Cryptid.enabled = copy_table(Cryptid_config) --backwards compat moment cry_enable_jokers = Cryptid.enabled["Misc. Jokers"] cry_enable_epics = Cryptid.enabled["Epic Jokers"] cry_enable_exotics = Cryptid.enabled["Exotic Jokers"] cry_minvasion = Cryptid.enabled["M Jokers"] -- Gradient isn't included since other logic seems to also handle it SMODS.Rarity{ key = "exotic", loc_txt = {}, badge_colour = HEX('708b91'), } SMODS.Rarity{ key = "epic", loc_txt = {}, badge_colour = HEX('ef0098'), default_weight = 0.003, pools = {["Joker"] = true}, get_weight = function(self, weight, object_type) -- The game shouldn't try generating Epic Jokers when they are disabled if Cryptid_config["Epic Jokers"] then return 0.003 else return 0 end end, } SMODS.Rarity{ key = "candy", loc_txt = {}, badge_colour = HEX("e275e6"), } SMODS.Rarity{ key = "cursed", loc_txt = {}, badge_colour = HEX("474931"), } --Add Event type - used for events in e.g. Chocolate Dice SMODS.Events = {} SMODS.Event = SMODS.GameObject:extend{ obj_table = SMODS.Events, obj_buffer = {}, required_params = { "key" }, inject = function() end, set = "Event", class_prefix = "ev", -- This should be called to start an event. start = function(self) G.GAME.events[self.key] = true end, -- This should be called to finish an event. finish = function(self) G.GAME.events[self.key] = nil end, -- Runs once before and after jokers, as well as a few special cases calculate = function(self, context) end, -- used for Chocolate Die tooltips, can maybe be repurposed later loc_vars = function(self, info_queue, center) info_queue[#info_queue + 1] = { set = "Other", key = self.key } end, } --Calculate events on cash out local gfco = G.FUNCS.cash_out G.FUNCS.cash_out = function(e) local ret = gfco(e) for k, v in pairs(SMODS.Events) do if G.GAME.events[k] then v:calculate({cash_out = true}) end end return ret end -- Calculate events on start of shop local guis = G.UIDEF.shop G.UIDEF.shop = function(e) local ret = guis(e) for k, v in pairs(SMODS.Events) do if G.GAME.events[k] then v:calculate({start_shop = true}) end end return ret end -- Calculations for Please Take One. Incredibly scuffed and should get moved to Spooky file later local gure = Game.update_round_eval function Game:update_round_eval(dt) if G.GAME.events.ev_cry_choco6 and not pack_opened and not G.STATE_COMPLETE then G.STATE_COMPLETE = true for k, v in pairs(SMODS.Events) do if G.GAME.events[k] then v:calculate({pre_cash = true}) end end return end if G.GAME.events.ev_cry_choco6 and pack_opened and G.STATE_COMPLETE and not G.round_eval then G.STATE_COMPLETE = false; return end gure(self, dt) end --Add Unique consumable set - used for unique consumables that aren't normally obtained (e.g. Potion) SMODS.ConsumableType{ key = "Unique", primary_colour = G.C.MONEY, secondary_colour = G.C.MONEY, collection_rows = { 4, 4 }, shop_rate = 0.0, loc_txt = {}, default = "c_cry_potion", can_stack = false, can_divide = false, } -- Create G.GAME.events when starting a run, so there's no errors local gigo = Game.init_game_object function Game:init_game_object() local g = gigo(self) g.events = {} return g end --Changes main menu colors and stuff if 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 = create_card('Spectral',G.title_top, nil, nil, nil, nil, 'c_cryptid', 'elial1') -- 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 -- 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'}, }}}) return ret end end --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 -- 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 --this is where the code starts to get really scuffed... I'd recommend closing your eyes --anyway this function basically hardcodes unredeeming a voucher function cry_debuff_voucher(center) -- sorry for all the mess here... local new_center = G.GAME.cry_voucher_centers[center] local center_table = { name = new_center and new_center.name, extra = new_center and new_center.config.extra, } if center_table.name == "Overstock" or center_table.name == "Overstock Plus" then G.E_MANAGER:add_event(Event({ func = function() change_shop_size(-center_table.extra) return true end, })) end if center_table.name == "Tarot Merchant" or center_table.name == "Tarot Tycoon" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.tarot_rate = G.GAME.tarot_rate / center_table.extra return true end, })) end if center_table.name == "Planet Merchant" or center_table.name == "Planet Tycoon" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.planet_rate = G.GAME.planet_rate / center_table.extra return true end, })) end if center_table.name == "Hone" or center_table.name == "Glow Up" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.edition_rate = G.GAME.edition_rate / center_table.extra return true end, })) end if center_table.name == "Magic Trick" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.playing_card_rate = 0 return true end, })) end if center_table.name == "Crystal Ball" then G.E_MANAGER:add_event(Event({ func = function() G.consumeables.config.card_limit = G.consumeables.config.card_limit - center_table.extra return true end, })) end if center_table.name == "Clearance Sale" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.discount_percent = 0 for k, v in pairs(G.I.CARD) do if v.set_cost then v:set_cost() end end return true end, })) end if center_table.name == "Liquidation" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.discount_percent = 25 for k, v in pairs(G.I.CARD) do if v.set_cost then v:set_cost() end end return true end, })) end if center_table.name == "Reroll Surplus" or center_table.name == "Reroll Glut" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.round_resets.reroll_cost = G.GAME.round_resets.reroll_cost + center_table.extra G.GAME.current_round.reroll_cost = math.max(0, G.GAME.current_round.reroll_cost + center_table.extra) return true end, })) end if center_table.name == "Seed Money" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.interest_cap = 25 --note: does not account for potential deck effects return true end, })) end if center_table.name == "Money Tree" then G.E_MANAGER:add_event(Event({ func = function() G.GAME.interest_cap = G.P_CENTERS.v_seed_money.extra return true end, })) end if center_table.name == "Grabber" or center_table.name == "Nacho Tong" then G.GAME.round_resets.hands = G.GAME.round_resets.hands - center_table.extra ease_hands_played(-center_table.extra) end if center_table.name == "Paint Brush" or center_table.name == "Palette" then G.hand:change_size(-center_table.extra) end if center_table.name == "Wasteful" or center_table.name == "Recyclomancy" then G.GAME.round_resets.discards = G.GAME.round_resets.discards - center_table.extra ease_discard(-center_table.extra) end if center_table.name == "Antimatter" then G.E_MANAGER:add_event(Event({ func = function() if G.jokers then G.jokers.config.card_limit = G.jokers.config.card_limit - center_table.extra end return true end, })) end if center_table.name == "Hieroglyph" or center_table.name == "Petroglyph" then ease_ante(center_table.extra) G.GAME.round_resets.blind_ante = G.GAME.round_resets.blind_ante or G.GAME.round_resets.ante G.GAME.round_resets.blind_ante = G.GAME.round_resets.blind_ante + center_table.extra if center_table.name == "Hieroglyph" then G.GAME.round_resets.hands = G.GAME.round_resets.hands + center_table.extra ease_hands_played(center_table.extra) end if center_table.name == "Petroglyph" then G.GAME.round_resets.discards = G.GAME.round_resets.discards + center_table.extra ease_discard(center_table.extra) end 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 -- 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 -- check if Director's Cut or Retcon offers a cheaper reroll price function cry_cheapest_boss_reroll() local dcut = G.GAME.cry_voucher_centers["v_directors_cut"].config.extra or 1e308 local retc = G.GAME.cry_voucher_centers["v_retcon"].config.extra or 1e308 if dcut < retc then return dcut else return retc end 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 function cry_voucher_debuffed(name) -- simple function but idk if G.GAME.voucher_sticker_index and G.GAME.voucher_sticker_index.perishable[name] then if G.GAME.voucher_sticker_index.perishable[name] == 0 then return true end end return false end function cry_voucher_pinned(name) if G.GAME.voucher_sticker_index then if G.GAME.voucher_sticker_index.pinned[name] then return true end end return false 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 function cry_get_next_voucher_edition() -- currently only for editions + sticker decks, can be modified if voucher stickering/editioning becomes more important if G.GAME.modifiers.cry_force_edition then return cry_edition_to_table(G.GAME.modifiers.cry_force_edition) elseif G.GAME.modifiers.cry_force_random_edition then return cry_poll_random_edition() end end -- code to generate Stickers for Vouchers, based on that for Jokers function cry_get_next_voucher_stickers() local eternal_perishable_poll = pseudorandom("cry_vet" .. (key_append or "") .. G.GAME.round_resets.ante) local ret = { eternal = false, perishable = false, rental = false, pinned = false, banana = false } if (G.GAME.modifiers.cry_force_sticker == "eternal") or G.GAME.modifiers.cry_sticker_sheet_plus or ( G.GAME.modifiers.cry_any_stickers and (G.GAME.modifiers.enable_eternals_in_shop and eternal_perishable_poll > 0.8) ) then ret.eternal = true end if G.GAME.modifiers.enable_perishables_in_shop and G.GAME.modifiers.cry_any_stickers then -- bloated as shit if not G.GAME.modifiers.cry_eternal_perishable_compat and ((eternal_perishable_poll > 0.4) and (eternal_perishable_poll <= 0.7)) then ret.perishable = true end if G.GAME.modifiers.cry_eternal_perishable_compat and pseudorandom("cry_vper" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7 then ret.perishable = true end end if (G.GAME.modifiers.cry_force_sticker == "perishable") or G.GAME.modifiers.cry_sticker_sheet_plus then ret.perishable = true end if G.GAME.modifiers.cry_force_sticker == "rental" or G.GAME.modifiers.cry_sticker_sheet_plus or ( G.GAME.modifiers.cry_any_stickers and ( G.GAME.modifiers.enable_rentals_in_shop and pseudorandom("cry_vssjr" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7 ) ) then ret.rental = true end if G.GAME.modifiers.cry_force_sticker == "pinned" or G.GAME.modifiers.cry_sticker_sheet_plus or ( G.GAME.modifiers.cry_any_stickers and ( G.GAME.modifiers.cry_enable_pinned_in_shop and pseudorandom("cry_vpin" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7 ) ) then ret.pinned = true end if G.GAME.modifiers.cry_force_sticker == "banana" or G.GAME.modifiers.cry_sticker_sheet_plus then ret.banana = true end if not G.GAME.modifiers.cry_eternal_perishable_compat and G.GAME.modifiers.enable_banana and G.GAME.modifiers.cry_any_stickers and (pseudorandom("cry_bpbanana" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7) and (eternal_perishable_poll <= 0.7) then ret.banana = true end if G.GAME.modifiers.cry_eternal_perishable_compat and G.GAME.modifiers.enable_banana and G.GAME.modifiers.cry_any_stickers and (pseudorandom("cry_bpbanana" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7) then ret.banana = true end return ret end -- Calculates Rental sticker for Consumables function Card:cry_calculate_consumeable_rental() if self.ability.rental then ease_dollars(-G.GAME.cry_consumeable_rental_rate) card_eval_status_text(self, "dollars", -G.GAME.cry_consumeable_rental_rate) end end -- Calculates Perishable sticker for Consumables function Card:cry_calculate_consumeable_perishable() if not self.ability.perish_tally then self.ability.perish_tally = 1 end if self.ability.perishable and self.ability.perish_tally > 0 then self.ability.perish_tally = 0 card_eval_status_text( self, "extra", nil, nil, nil, { message = localize("k_disabled_ex"), colour = G.C.FILTER, delay = 0.45 } ) self:set_debuff() end end -- Update the Cryptid member count using HTTPS function update_cry_member_count() if Cryptid.enabled["HTTPS Module"] == true then if not GLOBAL_cry_member_update_thread then -- start up the HTTPS thread if needed local file_data = assert(NFS.newFileData(mod_path .. "https/thread.lua")) GLOBAL_cry_member_update_thread = love.thread.newThread(file_data) GLOBAL_cry_member_update_thread:start() end local old = GLOBAL_cry_member_count or 8830 -- get the HTTPS thread's value for Cryptid members local ret = love.thread.getChannel("member_count"):pop() if ret then GLOBAL_cry_member_count = string.match(ret, '"approximate_member_count"%s*:%s*(%d+)') -- string matching a json is odd but should be fine? end if not GLOBAL_cry_member_count then GLOBAL_cry_member_count = old -- Something failed, print the error local error = love.thread.getChannel("member_error"):pop() if error then sendDebugMessage(error) end end else -- Use a fallback value if HTTPS is disabled (you all are awesome) GLOBAL_cry_member_count = 8830 end end -- deal with Rigged and Fragile when scoring a playing card local ec = eval_card function eval_card(card, context) if not card or card.will_shatter then return end -- Store old probability for later reference local ggpn = G.GAME.probabilities.normal if card.ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local ret = ec(card, context) if card.ability.cry_rigged then G.GAME.probabilities.normal = ggpn end return ret end -- deal wirh Rigged on Consumables local uc = Card.use_consumeable function Card:use_consumeable(area, copier) local ggpn = G.GAME.probabilities.normal if self.ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local ret = uc(self, area, copier) if self.ability.cry_rigged then G.GAME.probabilities.normal = ggpn end return ret end --some functions to minimize the load on calculate_joker itself function Card:cry_copy_ability() local orig_ability = {} if self.ability then for i, j in pairs(self.ability) do if (type(j) == "table") and is_number(j) then orig_ability[i] = to_big(j) elseif type(j) == "table" then orig_ability[i] = {} for i2, j2 in pairs(j) do orig_ability[i][i2] = j2 end else orig_ability[i] = j end end end return orig_ability end local cj = Card.calculate_joker function Card:cry_double_scale_calc(orig_ability, in_context_scaling) if self.ability.name ~= "cry-happyhouse" and self.ability.name ~= "Acrobat" and self.ability.name ~= "cry-sapling" and self.ability.name ~= "cry-mstack" and self.ability.name ~= "cry-notebook" and self.ability.name ~= "Invisible Joker" and self.ability.name ~= "cry-Old Invisible Joker" then local jkr = self if jkr.ability and type(jkr.ability) == "table" then if not G.GAME.cry_double_scale[jkr.sort_id] or not G.GAME.cry_double_scale[jkr.sort_id].ability then if not G.GAME.cry_double_scale[jkr.sort_id] then G.GAME.cry_double_scale[jkr.sort_id] = { ability = { double_scale = true } } end for k, v in pairs(jkr.ability) do if type(jkr.ability[k]) ~= "table" then G.GAME.cry_double_scale[jkr.sort_id].ability[k] = v else G.GAME.cry_double_scale[jkr.sort_id].ability[k] = {} for _k, _v in pairs(jkr.ability[k]) do G.GAME.cry_double_scale[jkr.sort_id].ability[k][_k] = _v end end end end if G.GAME.cry_double_scale[jkr.sort_id] and not G.GAME.cry_double_scale[jkr.sort_id].scaler then local dbl_info = G.GAME.cry_double_scale[jkr.sort_id] if jkr.ability.name == "cry-Number Blocks" then dbl_info.base = { "extra", "money" } dbl_info.scaler = { "extra", "money_mod" } dbl_info.scaler_base = jkr.ability.extra.money_mod dbl_info.offset = 1 end if jkr.ability.name == "cry-Exponentia" then dbl_info.base = { "extra", "Emult" } dbl_info.scaler = { "extra", "Emult_mod" } dbl_info.scaler_base = jkr.ability.extra.Emult_mod dbl_info.offset = 1 end if jkr.ability.name == "cry-Redeo" then dbl_info.base = { "extra", "money_req" } dbl_info.scaler = { "extra", "money_mod" } dbl_info.scaler_base = jkr.ability.extra.money_mod dbl_info.offset = 1 end if jkr.ability.name == "cry-Chili Pepper" then dbl_info.base = { "extra", "Xmult" } dbl_info.scaler = { "extra", "Xmult_mod" } dbl_info.scaler_base = jkr.ability.extra.Xmult_mod dbl_info.offset = 1 end if jkr.ability.name == "cry-Scalae" then dbl_info.base = { "extra", "shadow_scale" } dbl_info.scaler = { "extra", "shadow_scale_mod" } dbl_info.scaler_base = jkr.ability.extra.scale_mod dbl_info.offset = 1 end if jkr.ability.name == "cry-mprime" then dbl_info.base = { "extra", "mult" } dbl_info.scaler = { "extra", "bonus" } dbl_info.scaler_base = jkr.ability.extra.bonus dbl_info.offset = 1 end if jkr.ability.name == "Yorick" then dbl_info.base = { "x_mult" } dbl_info.scaler = { "extra", "xmult" } --not kidding dbl_info.scaler_base = 1 dbl_info.offset = 1 end if jkr.ability.name == "Hologram" then dbl_info.base = { "x_mult" } dbl_info.scaler = { "extra" } dbl_info.scaler_base = jkr.ability.extra dbl_info.offset = 1 end if jkr.ability.name == "Gift Card" then dbl_info.base = { "extra_value" } dbl_info.scaler = { "extra" } dbl_info.scaler_base = jkr.ability.extra dbl_info.offset = 1 end if jkr.ability.name == "Throwback" then dbl_info.base = { "x_mult" } dbl_info.scaler = { "extra" } dbl_info.scaler_base = jkr.ability.x_mult or 1 dbl_info.offset = 1 end if jkr.ability.name == "Egg" then dbl_info.base = { "extra_value" } dbl_info.scaler = { "extra" } dbl_info.scaler_base = jkr.ability.extra dbl_info.offset = 1 end local default_modifiers = { mult = 0, h_mult = 0, h_x_mult = 0, h_dollars = 0, p_dollars = 0, t_mult = 0, t_chips = 0, x_mult = 1, h_size = 0, d_size = 0, } for k, v in pairs(jkr.ability) do --extra_value is ignored because it can be scaled by Gift Card if k ~= "extra_value" and dbl_info.ability[k] ~= v and is_number(v) and is_number(dbl_info.ability[k]) then dbl_info.base = { k } local predicted_mod = math.abs(to_number(to_big(v)) - to_number(to_big(dbl_info.ability[k]))) local best_key = { "" } local best_coeff = 10 ^ 100 for l, u in pairs(jkr.ability) do if not (default_modifiers[l] and default_modifiers[l] == u) then if l ~= k and is_number(u) then if to_number(to_big(predicted_mod / u)) >= 0.999 and to_number(to_big(predicted_mod / u)) < to_number(to_big(best_coeff)) then best_coeff = to_number(to_big(predicted_mod / u)) best_key = { l } end end if type(jkr.ability[l]) == "table" then for _l, _u in pairs(jkr.ability[l]) do if is_number(_u) and to_number(to_big(predicted_mod / _u)) >= 0.999 and to_number(to_big(predicted_mod / _u)) < to_number(to_big(best_coeff)) then best_coeff = to_number(to_big(predicted_mod / _u)) best_key = { l, _l } end end end end end dbl_info.scaler = best_key end if type(jkr.ability[k]) == "table" and type(dbl_info.ability) == "table" and type(dbl_info.ability[k]) == "table" then for _k, _v in pairs(jkr.ability[k]) do if dbl_info.ability[k][_k] ~= _v and is_number(_v) and is_number(dbl_info.ability[k][_k]) then dbl_info.base = { k, _k } local predicted_mod = math.abs(_v - dbl_info.ability[k][_k]) local best_key = { "" } local best_coeff = 10 ^ 100 for l, u in pairs(jkr.ability) do if is_number(u) and to_number(to_big(predicted_mod / u)) >= 0.999 then if to_number(to_big(predicted_mod / u)) < to_number(to_big(best_coeff)) then best_coeff = to_number(to_big(predicted_mod / u)) best_key = { l } end end if type(jkr.ability[l]) == "table" then for _l, _u in pairs(jkr.ability[l]) do if (l ~= k or _l ~= _k) and is_number(_u) and to_number(to_big(predicted_mod / _u)) >= 0.999 then if to_number(to_big(predicted_mod / _u)) < to_number(to_big(best_coeff)) then best_coeff = to_number(to_big(predicted_mod / _u)) best_key = { l, _l } end end end end end dbl_info.scaler = best_key end end end end if dbl_info.scaler then dbl_info.scaler_base = #dbl_info.scaler == 2 and orig_ability[dbl_info.scaler[1]][dbl_info.scaler[2]] or orig_ability[dbl_info.scaler[1]] dbl_info.offset = 1 end end end end local orig_scale_base = nil local orig_scale_scale = nil if G.GAME.cry_double_scale[self.sort_id] and G.GAME.cry_double_scale[self.sort_id].scaler then local jkr = self local dbl_info = G.GAME.cry_double_scale[self.sort_id] if #dbl_info.base == 2 then if not ( type(jkr.ability) ~= "table" or not orig_ability[dbl_info.base[1]] or type(orig_ability[dbl_info.base[1]]) ~= "table" or not orig_ability[dbl_info.base[1]][dbl_info.base[2]] ) then orig_scale_base = orig_ability[dbl_info.base[1]][dbl_info.base[2]] end else if jkr.ability[dbl_info.base[1]] then orig_scale_base = orig_ability[dbl_info.base[1]] end end if #dbl_info.scaler == 2 then if not (not orig_ability[dbl_info.scaler[1]] or not orig_ability[dbl_info.scaler[1]][dbl_info.scaler[2]]) then orig_scale_scale = orig_ability[dbl_info.scaler[1]][dbl_info.scaler[2]] end else if orig_ability[dbl_info.scaler[1]] then orig_scale_scale = orig_ability[dbl_info.scaler[1]] end end end if orig_scale_base and orig_scale_scale then local new_scale_base = nil local true_base = nil local jkr = self local dbl_info = G.GAME.cry_double_scale[self.sort_id] if #dbl_info.base == 2 then if not ( type(jkr.ability) ~= "table" or not jkr.ability[dbl_info.base[1]] or type(jkr.ability[dbl_info.base[1]]) ~= "table" or not jkr.ability[dbl_info.base[1]][dbl_info.base[2]] ) then new_scale_base = jkr.ability[dbl_info.base[1]][dbl_info.base[2]] end else if jkr.ability[dbl_info.base[1]] then new_scale_base = jkr.ability[dbl_info.base[1]] end end true_base = dbl_info.scaler_base if new_scale_base and ((to_big(math.abs(new_scale_base - orig_scale_base)) > to_big(0)) or in_context_scaling) then for i = 1, #G.jokers.cards do local obj = G.jokers.cards[i].config.center if obj.cry_scale_mod and type(obj.cry_scale_mod) == "function" then local ggpn = G.GAME.probabilities.normal if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local o = obj:cry_scale_mod( G.jokers.cards[i], jkr, orig_scale_scale, true_base, orig_scale_base, new_scale_base ) if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if o then if #dbl_info.scaler == 2 then if not ( not jkr.ability[dbl_info.scaler[1]] or not jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] ) then jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] = o orig_scale_scale = o end else if jkr.ability[dbl_info.scaler[1]] then jkr.ability[dbl_info.scaler[1]] = o orig_scale_scale = o end end card_eval_status_text( G.jokers.cards[i], "extra", nil, nil, nil, { message = localize("k_upgrade_ex") } ) end local reps = {} for i2 = 1, #G.jokers.cards do local _card = G.jokers.cards[i2] local ggpn = G.GAME.probabilities.normal if _card.ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local check = cj(G.jokers.cards[i2], { retrigger_joker_check = true, other_card = G.jokers.cards[i] }) if _card.ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if type(check) == "table" then reps[i2] = check and check.repetitions and check or 0 else reps[i2] = 0 end if G.jokers.cards[i2] == G.jokers.cards[i] and G.jokers.cards[i].edition and G.jokers.cards[i].edition.retriggers then local old_repetitions = reps[i] ~= 0 and reps[i].repetitions or 0 local check = false --G.jokers.cards[i]:calculate_retriggers() if check and check.repetitions then check.repetitions = check.repetitions + old_repetitions reps[i] = check end end end for i0, j in ipairs(reps) do if (type(j) == "table") and j.repetitions and (j.repetitions > 0) then for r = 1, j.repetitions do card_eval_status_text(j.card, "jokers", nil, nil, nil, j) local ggpn = G.GAME.probabilities.normal if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local o = obj:cry_scale_mod( G.jokers.cards[i], jkr, orig_scale_scale, true_base, orig_scale_base, new_scale_base ) if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if o then if #dbl_info.scaler == 2 then if not ( not jkr.ability[dbl_info.scaler[1]] or not jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] ) then jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] = o orig_scale_scale = o end else if jkr.ability[dbl_info.scaler[1]] then jkr.ability[dbl_info.scaler[1]] = o orig_scale_scale = o end end card_eval_status_text( G.jokers.cards[i], "extra", nil, nil, nil, { message = localize("k_upgrade_ex") } ) end end end end end end end end end function Card:calculate_joker(context) --Calculate events if self == G.jokers.cards[1] then for k, v in pairs(SMODS.Events) do if G.GAME.events[k] then context.pre_jokers = true v:calculate(context) context.pre_jokers = nil end end end local active_side = self if next(find_joker("cry-Flip Side")) and not context.dbl_side and self.edition and self.edition.cry_double_sided then self:init_dbl_side() active_side = self.dbl_side if context.callback then local m = context.callback context.callback = function(card,a,b) m(self,a,b) end context.dbl_side = true end end if active_side.will_shatter then return end local ggpn = G.GAME.probabilities.normal if not G.GAME.cry_double_scale then G.GAME.cry_double_scale = { double_scale = true } --doesn't really matter what's in here as long as there's something end if active_side.ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local orig_ability = active_side:cry_copy_ability() local in_context_scaling = false local callback = context.callback if active_side.ability.cry_possessed then if not ((context.individual and not context.repetition) or (context.joker_main) or (context.other_joker and not context.post_trigger)) then return end context.callback = nil end local ret, trig = cj(active_side, context) if active_side.ability.cry_possessed and ret then if ret.mult_mod then ret.mult_mod = ret.mult_mod * -1 end if ret.Xmult_mod then ret.Xmult_mod = ret.Xmult_mod ^ -1 end if ret.mult then ret.mult = ret.mult * -1 end if ret.x_mult then ret.x_mult = ret.x_mult ^ -1 end ret.e_mult = nil ret.ee_mult = nil ret.eee_mult = nil ret.hyper_mult = nil ret.Emult_mod = nil ret.EEmult_mod = nil ret.EEEmult_mod = nil ret.hypermult_mod = nil if ret.chip_mod then ret.chip_mod = ret.chip_mod * -1 end if ret.Xchip_mod then ret.Xchip_mod = ret.Xchip_mod ^ -1 end if ret.chips then ret.chips = ret.chips * -1 end if ret.x_chips then ret.x_chips = ret.x_chips ^ -1 end ret.e_chips = nil ret.ee_chips = nil ret.eee_chips = nil ret.hyper_chips = nil ret.Echip_mod = nil ret.EEchip_mod = nil ret.EEEchip_mod = nil ret.hyperchip_mod = nil if ret.message then -- TODO - this is a hacky way to do this, but it works for now if type(ret.message) == "table" then ret.message = ret.message[1] end if ret.message:sub(1,1) == "+" then ret.message = "-" .. ret.message:sub(2) elseif ret.message:sub(1,1) == "X" then ret.message = "/" .. ret.message:sub(2) else ret.message = ret.message .. "?" end end callback(context.blueprint_card or self, ret, context.retrigger_joker) end if not context.blueprint and (active_side.ability.set == "Joker") and not active_side.debuff then if ret or trig then in_context_scaling = true end end if active_side.ability.cry_rigged then G.GAME.probabilities.normal = ggpn end active_side:cry_double_scale_calc(orig_ability, in_context_scaling) --Calculate events if self == G.jokers.cards[#G.jokers.cards] then for k, v in pairs(SMODS.Events) do if G.GAME.events[k] then context.post_jokers = true v:calculate(context) context.post_jokers = nil end end end return ret, trig end function exponentia_scale_mod(self, orig_scale_scale, orig_scale_base, new_scale_base) local jkr = self local dbl_info = G.GAME.cry_double_scale[jkr.sort_id] if jkr.ability and type(jkr.ability) == "table" then if not G.GAME.cry_double_scale[jkr.sort_id] or not G.GAME.cry_double_scale[jkr.sort_id].ability then if not G.GAME.cry_double_scale[jkr.sort_id] then G.GAME.cry_double_scale[jkr.sort_id] = { ability = { double_scale = true } } end for k, v in pairs(jkr.ability) do if type(jkr.ability[k]) ~= "table" then G.GAME.cry_double_scale[jkr.sort_id].ability[k] = v else G.GAME.cry_double_scale[jkr.sort_id].ability[k] = {} for _k, _v in pairs(jkr.ability[k]) do G.GAME.cry_double_scale[jkr.sort_id].ability[k][_k] = _v end end end end if G.GAME.cry_double_scale[jkr.sort_id] and not G.GAME.cry_double_scale[jkr.sort_id].scaler then dbl_info.base = { "extra", "Emult" } dbl_info.scaler = { "extra", "Emult_mod" } dbl_info.scaler_base = jkr.ability.extra.Emult_mod dbl_info.offset = 1 end end local true_base = dbl_info.scaler_base if true_base then for i = 1, #G.jokers.cards do local obj = G.jokers.cards[i].config.center if obj.cry_scale_mod and type(obj.cry_scale_mod) == "function" then local ggpn = G.GAME.probabilities.normal if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local o = obj:cry_scale_mod( G.jokers.cards[i], jkr, orig_scale_scale, true_base, orig_scale_base, new_scale_base ) if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if o then if #dbl_info.scaler == 2 then if not ( not jkr.ability[dbl_info.scaler[1]] or not jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] ) then jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] = o orig_scale_scale = o end else if jkr.ability[dbl_info.scaler[1]] then jkr.ability[dbl_info.scaler[1]] = o orig_scale_scale = o end end card_eval_status_text( G.jokers.cards[i], "extra", nil, nil, nil, { message = localize("k_upgrade_ex") } ) end local reps = {} for i2 = 1, #G.jokers.cards do local _card = G.jokers.cards[i2] local ggpn = G.GAME.probabilities.normal if _card.ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local check = cj(G.jokers.cards[i2], { retrigger_joker_check = true, other_card = G.jokers.cards[i] }) if _card.ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if type(check) == "table" then reps[i2] = check and check.repetitions and check or 0 else reps[i2] = 0 end if G.jokers.cards[i2] == G.jokers.cards[i] and G.jokers.cards[i].edition and G.jokers.cards[i].edition.retriggers then local old_repetitions = reps[i] ~= 0 and reps[i].repetitions or 0 local check = false --G.jokers.cards[i]:calculate_retriggers() if check and check.repetitions then check.repetitions = check.repetitions + old_repetitions reps[i] = check end end end for i0, j in ipairs(reps) do if (type(j) == "table") and j.repetitions and (j.repetitions > 0) then for r = 1, j.repetitions do card_eval_status_text(j.card, "jokers", nil, nil, nil, j) local ggpn = G.GAME.probabilities.normal if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local o = obj:cry_scale_mod( G.jokers.cards[i], jkr, orig_scale_scale, true_base, orig_scale_base, new_scale_base ) if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if o then if #dbl_info.scaler == 2 then if not ( not jkr.ability[dbl_info.scaler[1]] or not jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] ) then jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] = o orig_scale_scale = o end else if jkr.ability[dbl_info.scaler[1]] then jkr.ability[dbl_info.scaler[1]] = o orig_scale_scale = o end end card_eval_status_text( G.jokers.cards[i], "extra", nil, nil, nil, { message = localize("k_upgrade_ex") } ) end end end end end end end end function compound_interest_scale_mod(self, orig_scale_scale, orig_scale_base, new_scale_base) local jkr = self local dbl_info = G.GAME.cry_double_scale[jkr.sort_id] if jkr.ability and type(jkr.ability) == "table" then if not G.GAME.cry_double_scale[jkr.sort_id] or not G.GAME.cry_double_scale[jkr.sort_id].ability then if not G.GAME.cry_double_scale[jkr.sort_id] then G.GAME.cry_double_scale[jkr.sort_id] = { ability = { double_scale = true } } end for k, v in pairs(jkr.ability) do if type(jkr.ability[k]) ~= "table" then G.GAME.cry_double_scale[jkr.sort_id].ability[k] = v else G.GAME.cry_double_scale[jkr.sort_id].ability[k] = {} for _k, _v in pairs(jkr.ability[k]) do G.GAME.cry_double_scale[jkr.sort_id].ability[k][_k] = _v end end end end if G.GAME.cry_double_scale[jkr.sort_id] and not G.GAME.cry_double_scale[jkr.sort_id].scaler then dbl_info.base = { "extra", "percent" } dbl_info.scaler = { "extra", "percent_mod" } dbl_info.scaler_base = jkr.ability.extra.percent_mod dbl_info.offset = 1 end end local true_base = dbl_info.scaler_base if true_base then for i = 1, #G.jokers.cards do local obj = G.jokers.cards[i].config.center if obj.cry_scale_mod and type(obj.cry_scale_mod) == "function" then local ggpn = G.GAME.probabilities.normal if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local o = obj:cry_scale_mod( G.jokers.cards[i], jkr, orig_scale_scale, true_base, orig_scale_base, new_scale_base ) if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if o then if #dbl_info.scaler == 2 then if not ( not jkr.ability[dbl_info.scaler[1]] or not jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] ) then jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] = o orig_scale_scale = o end else if jkr.ability[dbl_info.scaler[1]] then jkr.ability[dbl_info.scaler[1]] = o orig_scale_scale = o end end card_eval_status_text( G.jokers.cards[i], "extra", nil, nil, nil, { message = localize("k_upgrade_ex") } ) end local reps = {} for i2 = 1, #G.jokers.cards do local _card = G.jokers.cards[i2] local ggpn = G.GAME.probabilities.normal if _card.ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local check = cj(G.jokers.cards[i2], { retrigger_joker_check = true, other_card = G.jokers.cards[i] }) if _card.ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if type(check) == "table" then reps[i2] = check and check.repetitions and check or 0 else reps[i2] = 0 end if G.jokers.cards[i2] == G.jokers.cards[i] and G.jokers.cards[i].edition and G.jokers.cards[i].edition.retriggers then local old_repetitions = reps[i] ~= 0 and reps[i].repetitions or 0 local check = false --G.jokers.cards[i]:calculate_retriggers() if check and check.repetitions then check.repetitions = check.repetitions + old_repetitions reps[i] = check end end end for i0, j in ipairs(reps) do if (type(j) == "table") and j.repetitions and (j.repetitions > 0) then for r = 1, j.repetitions do card_eval_status_text(j.card, "jokers", nil, nil, nil, j) local ggpn = G.GAME.probabilities.normal if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = 1e9 end local o = obj:cry_scale_mod( G.jokers.cards[i], jkr, orig_scale_scale, true_base, orig_scale_base, new_scale_base ) if G.jokers.cards[i].ability.cry_rigged then G.GAME.probabilities.normal = ggpn end if o then if #dbl_info.scaler == 2 then if not ( not jkr.ability[dbl_info.scaler[1]] or not jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] ) then jkr.ability[dbl_info.scaler[1]][dbl_info.scaler[2]] = o orig_scale_scale = o end else if jkr.ability[dbl_info.scaler[1]] then jkr.ability[dbl_info.scaler[1]] = o orig_scale_scale = o end end card_eval_status_text( G.jokers.cards[i], "extra", nil, nil, nil, { message = localize("k_upgrade_ex") } ) end end end end end end end end function Card:is_jolly() local check = false if self.ability.name == "Jolly Joker" then check = true end if (self.edition and self.edition.key == "e_cry_m") then check = true end --[[ Some scenarios/ examples I used for testing this (These DO work as intended if not commented out) if next(find_joker("cry-mneon")) then check = true end if G.GAME.blind.boss then check = true end ]]-- return check 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 G.C.CRY_JOLLY = { 0, 0, 0, 0 } -- File loading based on Relic-Jokers local files = NFS.getDirectoryItems(mod_path .. "Items") Cryptid.obj_buffer = {} for _, file in ipairs(files) do print("Loading file " .. file) local f, err = SMODS.load_file("Items/" .. file) if err then print("Error loading file: " .. err) else local curr_obj = f() if curr_obj.name == "HTTPS Module" and Cryptid_config[curr_obj.name] == nil then Cryptid_config[curr_obj.name] = false end if Cryptid_config[curr_obj.name] == nil then Cryptid_config[curr_obj.name] = true Cryptid.enabled[curr_obj.name] = true end if Cryptid_config[curr_obj.name] then if curr_obj.init then curr_obj:init() end if not curr_obj.items then print("Warning: " .. file .. " has no items") else for _, item in ipairs(curr_obj.items) do if not item.order then item.order = 0 end if curr_obj.order then item.order = item.order + curr_obj.order end if SMODS[item.object_type] then if not Cryptid.obj_buffer[item.object_type] then Cryptid.obj_buffer[item.object_type] = {} end Cryptid.obj_buffer[item.object_type][#Cryptid.obj_buffer[item.object_type] + 1] = item else print("Error loading item " .. item.key .. " of unknown type " .. item.object_type) end end end end end end for set, objs in pairs(Cryptid.obj_buffer) do table.sort(objs, function(a, b) return a.order < b.order end) for i = 1, #objs do if objs[i].post_process and type(objs[i].post_process) == "function" then objs[i]:post_process() end SMODS[set](objs[i]) end end local cryptidTabs = function() return { { label = localize("cry_set_features"), chosen = true, tab_definition_function = function() cry_nodes = { { n = G.UIT.R, config = { align = "cm" }, nodes = { { n = G.UIT.O, config = { object = DynaText({ string = localize("cry_set_enable_features"), colours = { G.C.WHITE }, shadow = true, scale = 0.4, }), }, }, }, }, } left_settings = { n = G.UIT.C, config = { align = "tl", padding = 0.05 }, nodes = {} } right_settings = { n = G.UIT.C, config = { align = "tl", padding = 0.05 }, nodes = {} } --todo: completely redesign this, make it possible to enable/disable individual items local ordered_config = {} for k, _ in pairs(Cryptid_config) do if localize("cry_feat_"..string.lower(k)) ~= "ERROR" and k ~= "JokerDisplay" then ordered_config[#ordered_config+1] = k end end table.sort(ordered_config) for _, k in ipairs(ordered_config) do if #right_settings.nodes < #left_settings.nodes then right_settings.nodes[#right_settings.nodes + 1] = create_toggle({ label = localize("cry_feat_"..string.lower(k)), ref_table = Cryptid_config, ref_value = k }) else left_settings.nodes[#left_settings.nodes + 1] = create_toggle({ label = localize("cry_feat_"..string.lower(k)), ref_table = Cryptid_config, ref_value = k }) end end config = { n = G.UIT.R, config = { align = "tm", padding = 0 }, nodes = { left_settings, right_settings } } cry_nodes[#cry_nodes + 1] = config return { n = G.UIT.ROOT, config = { emboss = 0.05, minh = 6, r = 0.1, minw = 10, align = "cm", padding = 0.2, colour = G.C.BLACK, }, nodes = cry_nodes, } end, }, { label = localize("cry_set_music"), tab_definition_function = function() -- TODO: Add a button here to reset all Cryptid achievements. -- If you want to do that now, add this to the SMODS.InjectItems in Steamodded/loader/loader.lua --[[fetch_achievements() for k, v in pairs(SMODS.Achievements) do G.SETTINGS.ACHIEVEMENTS_EARNED[k] = nil G.ACHIEVEMENTS[k].earned = nil end fetch_achievements()]] cry_nodes = { { n = G.UIT.R, config = { align = "cm" }, nodes = { --{n=G.UIT.O, config={object = DynaText({string = "", colours = {G.C.WHITE}, shadow = true, scale = 0.4})}}, }, }, } settings = { n = G.UIT.C, config = { align = "tl", padding = 0.05 }, nodes = {} } settings.nodes[#settings.nodes + 1] = create_toggle({ label = localize("cry_mus_jimball"), ref_table = Cryptid_config.Cryptid, ref_value = "jimball_music", }) settings.nodes[#settings.nodes + 1] = create_toggle({ label = localize("cry_mus_code"), ref_table = Cryptid_config.Cryptid, ref_value = "code_music", }) settings.nodes[#settings.nodes + 1] = create_toggle({ label = localize("cry_mus_exotic"), ref_table = Cryptid_config.Cryptid, ref_value = "exotic_music", }) settings.nodes[#settings.nodes + 1] = create_toggle({ label = localize("cry_mus_high_score"), ref_table = Cryptid_config.Cryptid, ref_value = "big_music", }) config = { n = G.UIT.R, config = { align = "tm", padding = 0 }, nodes = { settings } } cry_nodes[#cry_nodes + 1] = config return { n = G.UIT.ROOT, config = { emboss = 0.05, minh = 6, r = 0.1, minw = 10, align = "cm", padding = 0.2, colour = G.C.BLACK, }, nodes = cry_nodes, } end, }, } end G.FUNCS.cryptidMenu = function(e) local tabs = create_tabs({ snap_to_nav = true, tabs = cryptidTabs(), }) G.FUNCS.overlay_menu({ definition = create_UIBox_generic_options({ back_func = "options", contents = { tabs }, }), config = { offset = { x = 0, y = 10 } }, }) end --[[SMODS.current_mod.config_tab = function() return { n = G.UIT.ROOT, config = { emboss = 0.05, minh = 6, r = 0.1, minw = 10, align = "cm", padding = 0.2, colour = G.C.BLACK }, nodes = {UIBox_button{ label = {"Open Cryptid Config"}, button = "cryptidMenu", colour = G.C.DARK_EDITION, minw = 5, minh = 0.7, scale = 0.6}} } end--]] SMODS.current_mod.extra_tabs = cryptidTabs -- Modify to display badges for credits local smcmb = SMODS.create_mod_badges function SMODS.create_mod_badges(obj, badges) smcmb(obj, badges) if obj and obj.cry_credits then local function calc_scale_fac(text) local size = 0.9 local font = G.LANG.font local max_text_width = 2 - 2 * 0.05 - 4 * 0.03 * size - 2 * 0.03 local calced_text_width = 0 -- Math reproduced from DynaText:update_text for _, c in utf8.chars(text) do local tx = font.FONT:getWidth(c) * (0.33 * size) * G.TILESCALE * font.FONTSCALE + 2.7 * 1 * G.TILESCALE * font.FONTSCALE calced_text_width = calced_text_width + tx / (G.TILESIZE * G.TILESCALE) end local scale_fac = calced_text_width > max_text_width and max_text_width / calced_text_width or 1 return scale_fac end if obj.cry_credits.art or obj.cry_credits.code or obj.cry_credits.idea then local scale_fac = {} local min_scale_fac = 1 local strings = {"Cryptid"} for _, v in ipairs({"idea", "art", "code"}) do if obj.cry_credits[v] then for i = 1, #obj.cry_credits[v] do strings[#strings+1] = localize{type='variable',key='cry_'..v,vars={obj.cry_credits[v][i]}}[1] end end end for i = 1, #strings do scale_fac[i] = calc_scale_fac(strings[i]) min_scale_fac = math.min(min_scale_fac, scale_fac[i]) end local ct = {} for i = 1, #strings do ct[i] = { string = strings[i], } end local cry_badge = { n = G.UIT.R, config = { align = "cm" }, nodes = { { n = G.UIT.R, config = { align = "cm", colour = G.C.CRY_EXOTIC, r = 0.1, minw = 2/min_scale_fac, minh = 0.36, emboss = 0.05, padding = 0.03 * 0.9, }, nodes = { { n = G.UIT.B, config = { h = 0.1, w = 0.03 } }, { n = G.UIT.O, config = { object = DynaText({ string = ct or "ERROR", colours = { obj.cry_credits and obj.cry_credits.text_colour or G.C.WHITE }, silent = true, float = true, shadow = true, offset_y = -0.03, spacing = 1, scale = 0.33 * 0.9, }), }, }, { n = G.UIT.B, config = { h = 0.1, w = 0.03 } }, }, }, }, } local function eq_col(x, y) for i = 1, 4 do if x[1] ~= y[1] then return false end end return true end for i = 1, #badges do if eq_col(badges[i].nodes[1].config.colour,HEX("708b91")) then badges[i].nodes[1].nodes[2].config.object:remove() badges[i] = cry_badge break end end end if obj.cry_credits.jolly then local scale_fac = {} local min_scale_fac = 1 for i = 1, #obj.cry_credits.jolly do scale_fac[i] = calc_scale_fac(obj.cry_credits.jolly[i]) min_scale_fac = math.min(min_scale_fac, scale_fac[i]) end local ct = {} for i = 1, #obj.cry_credits.jolly do ct[i] = { string = obj.cry_credits.jolly[i], } end badges[#badges + 1] = { n = G.UIT.R, config = { align = "cm" }, nodes = { { n = G.UIT.R, config = { align = "cm", colour = G.C.CRY_JOLLY, r = 0.1, minw = 2, minh = 0.36, emboss = 0.05, padding = 0.03 * 0.9, }, nodes = { { n = G.UIT.B, config = { h = 0.1, w = 0.03 } }, { n = G.UIT.O, config = { object = DynaText({ string = ct or "ERROR", colours = { obj.cry_credits and obj.cry_credits.text_colour_jolly or G.C.WHITE }, silent = true, float = true, shadow = true, offset_y = -0.03, spacing = 1, scale = 0.33 * 0.9, }), }, }, { n = G.UIT.B, config = { h = 0.1, w = 0.03 } }, }, }, }, } end end end -- This is short enough that I'm fine overriding it function calculate_reroll_cost(skip_increment) if G.GAME.current_round.free_rerolls < 0 then G.GAME.current_round.free_rerolls = 0 end if next(find_joker("cry-crustulum")) or G.GAME.current_round.free_rerolls > 0 then G.GAME.current_round.reroll_cost = 0 return end if next(find_joker("cry-candybuttons")) then G.GAME.current_round.reroll_cost = 1 return end if G.GAME.used_vouchers.v_cry_rerollexchange then G.GAME.current_round.reroll_cost = 2 return end G.GAME.current_round.reroll_cost_increase = G.GAME.current_round.reroll_cost_increase or 0 if not skip_increment then G.GAME.current_round.reroll_cost_increase = G.GAME.current_round.reroll_cost_increase + (G.GAME.modifiers.cry_reroll_scaling or 1) end G.GAME.current_round.reroll_cost = (G.GAME.round_resets.temp_reroll_cost or G.GAME.round_resets.reroll_cost) + G.GAME.current_round.reroll_cost_increase end --Top Gear from The World End with Jimbo has several conflicts with Cryptid items --Namely, It overrides the edition that edition jokers spawn with, and doesn't work correctly with edition decks --I'm taking ownership of this, overiding it, and making an implementaion that is compatible with Cryptid --Unrelated but kind of related side note: this prevents top gear from showing up in collection, not sure what's up with that --Is it due to how TWEWJ is Coded? Is it an issue with Steamodded itself? Might be worth looking into, just sayin --Ok it's definitely something with steamodded if (SMODS.Mods["TWEWY"] or {}).can_load then SMODS.Joker:take_ownership('twewy_topGear', { name = "Cry-topGear", --Stop Top Gear's Old code from working by overriding these add_to_deck = function(self, card, from_debuff) end, remove_from_deck = function(self, card, from_debuff) end, rarity = 3, loc_txt = { name = 'Top Gear', text = { "All {C:blue}Common{C:attention} Jokers{}", "are {C:dark_edition}Polychrome{}", } }, }) end -- We're modifying so much of this for Brown and Yellow Stake, Equilibrium Deck, etc. that it's fine to override... function create_card(_type, area, legendary, _rarity, skip_materialize, soulable, forced_key, key_append) local area = area or G.jokers local pseudo = function(x) return pseudorandom(pseudoseed(x)) end local ps = pseudoseed if area == "ERROR" then pseudo = function(x) return pseudorandom(predict_pseudoseed(x)) end ps = predict_pseudoseed end local center = G.P_CENTERS.b_red if (_type == "Joker") and G.GAME and G.GAME.modifiers and G.GAME.modifiers.all_rnj then forced_key = "j_cry_rnjoker" end local function aeqviable(center) return center.unlocked and not center_no(center, "doe") and not center_no(center, "aeq") and not (center.rarity == 6 or center.rarity == "cry_exotic") end if _type == "Joker" and not _rarity then if not G.GAME.aequilibriumkey then G.GAME.aequilibriumkey = 1 end local aeqactive = nil if next(find_joker('Ace Aequilibrium')) and not forced_key then while not aeqactive or not aeqviable(G.P_CENTER_POOLS.Joker[aeqactive]) do if math.ceil(G.GAME.aequilibriumkey) > #G.P_CENTER_POOLS["Joker"] then G.GAME.aequilibriumkey = 1 end aeqactive = math.ceil(G.GAME.aequilibriumkey) G.GAME.aequilibriumkey = math.ceil(G.GAME.aequilibriumkey + 1) end end if aeqactive then forced_key = G.P_CENTER_POOLS["Joker"][aeqactive].key end end --should pool be skipped with a forced key if not forced_key and soulable and not G.GAME.banned_keys["c_soul"] then for _, v in ipairs(SMODS.Consumable.legendaries) do if (_type == v.type.key or _type == v.soul_set) and not (G.GAME.used_jokers[v.key] and not next(find_joker("Showman")) and not v.can_repeat_soul) then if pseudo("soul_" .. v.key .. _type .. G.GAME.round_resets.ante) > (1 - v.soul_rate) then forced_key = v.key end end end if (_type == "Tarot" or _type == "Spectral" or _type == "Tarot_Planet") and not (G.GAME.used_jokers["c_soul"] and not next(find_joker("Showman"))) then if pseudo("soul_" .. _type .. G.GAME.round_resets.ante) > 0.997 then forced_key = "c_soul" end end if (_type == "Planet" or _type == "Spectral") and not (G.GAME.used_jokers["c_black_hole"] and not next(find_joker("Showman"))) then if pseudo("soul_" .. _type .. G.GAME.round_resets.ante) > 0.997 then forced_key = "c_black_hole" end end end if _type == "Base" then forced_key = "c_base" end if forced_key then --vanilla behavior change, mainly for M Joker reasons center = G.P_CENTERS[forced_key] _type = (center.set ~= "Default" and center.set or _type) else gcparea = area local _pool, _pool_key = get_current_pool(_type, _rarity, legendary, key_append) gcparea = nil center = pseudorandom_element(_pool, ps(_pool_key)) local it = 1 while center == "UNAVAILABLE" do it = it + 1 center = pseudorandom_element(_pool, ps(_pool_key .. "_resample" .. it)) end center = G.P_CENTERS[center] end -- handle banned keys for playing cards -- can cache this if it's too much of a performance hit local _cardlist = {} for k, v in pairs(G.P_CARDS) do local add = true if G.GAME and G.GAME.cry_banned_pcards and G.GAME.cry_banned_pcards[k] then add = false end if add then _cardlist[#_cardlist+1] = k end end if #_cardlist <= 0 then _cardlist[#_cardlist+1] = 'S_A' end local front = ( (_type == "Base" or _type == "Enhanced") and G.P_CARDS[pseudorandom_element(_cardlist, ps("front" .. (key_append or "") .. G.GAME.round_resets.ante))] ) or nil if area == "ERROR" then local ret = (front or center) if not ret.config then ret.config = {} end if not ret.config.center then ret.config.center = {} end if not ret.config.center.key then ret.config.center.key = "" end if not ret.ability then ret.ability = {} end return ret --the config.center.key stuff prevents a crash with Jen's Almanac hook end local card = Card( area and (area.T.x + area.T.w / 2) or 0, area and area.T.y or 0, G.CARD_W * (center and center.set == "Booster" and 1.27 or 1), G.CARD_H * (center and center.set == "Booster" and 1.27 or 1), front, center, { bypass_discovery_center = area == G.shop_jokers or area == G.pack_cards or area == G.shop_vouchers or (G.shop_demo and area == G.shop_demo) or area == G.jokers or area == G.consumeables, bypass_discovery_ui = area == G.shop_jokers or area == G.pack_cards or area == G.shop_vouchers or (G.shop_demo and area == G.shop_demo), discover = area == G.jokers or area == G.consumeables, bypass_back = G.GAME.selected_back.pos, } ) if front and G.GAME.modifiers.cry_force_suit then card:change_suit(G.GAME.modifiers.cry_force_suit) end if front and G.GAME.modifiers.cry_force_enhancement then card:set_ability(G.P_CENTERS[G.GAME.modifiers.cry_force_enhancement]) end if front and G.GAME.modifiers.cry_force_edition then card:set_edition({ [G.GAME.modifiers.cry_force_edition] = true }, true, true) end if front and G.GAME.modifiers.cry_force_seal then card:set_seal(G.GAME.modifiers.cry_force_seal) end if card.ability.consumeable and not skip_materialize then card:start_materialize() end for k, v in ipairs(SMODS.Sticker.obj_buffer) do local sticker = SMODS.Stickers[v] if sticker.should_apply and type(sticker.should_apply) == "function" and sticker:should_apply(card, center, area) then sticker:apply(card, true) end end if G.GAME.modifiers.cry_force_sticker == "eternal" or ( G.GAME.modifiers.cry_sticker_sheet_plus and not ( (_type == "Base" or _type == "Enhanced") and not ((area == G.shop_jokers) or (area == G.pack_cards)) ) ) then -- wow that is long card:set_eternal(true) card.ability.eternal = true end if G.GAME.modifiers.cry_force_sticker == "perishable" or ( G.GAME.modifiers.cry_sticker_sheet_plus and not ( (_type == "Base" or _type == "Enhanced") and not ((area == G.shop_jokers) or (area == G.pack_cards)) ) ) then card:set_perishable(true) card.ability.perish_tally = G.GAME.perishable_rounds -- set_perishable should be doing this? whatever card.ability.perishable = true end if G.GAME.modifiers.cry_force_sticker == "rental" or ( G.GAME.modifiers.cry_sticker_sheet_plus and not ( (_type == "Base" or _type == "Enhanced") and not ((area == G.shop_jokers) or (area == G.pack_cards)) ) ) then card:set_rental(true) card.ability.rental = true end if G.GAME.modifiers.cry_force_sticker == "pinned" or ( G.GAME.modifiers.cry_sticker_sheet_plus and not ( (_type == "Base" or _type == "Enhanced") and not ((area == G.shop_jokers) or (area == G.pack_cards)) ) ) then card.pinned = true end if G.GAME.modifiers.cry_force_sticker == "banana" or ( G.GAME.modifiers.cry_sticker_sheet_plus and not ( (_type == "Base" or _type == "Enhanced") and not ((area == G.shop_jokers) or (area == G.pack_cards)) ) ) then card.ability.banana = true end if G.GAME.modifiers.cry_sticker_sheet_plus and not (_type == "Base" or _type == "Enhanced") then for k, v in pairs(SMODS.Stickers) do if v.apply and not v.no_sticker_sheet then v:apply(card, true) end end end if card.ability.name == "cry-Cube" then card:set_eternal(true) end if _type == "Joker" or (G.GAME.modifiers.cry_any_stickers and not G.GAME.modifiers.cry_sticker_sheet) then if G.GAME.modifiers.all_eternal then card:set_eternal(true) end if G.GAME.modifiers.cry_all_perishable then card:set_perishable(true) end if G.GAME.modifiers.cry_all_rental then card:set_rental(true) end if G.GAME.modifiers.cry_all_pinned then card.pinned = true end if G.GAME.modifiers.cry_all_banana then card.ability.banana = true end if (area == G.shop_jokers) or (area == G.pack_cards) then local eternal_perishable_poll = pseudorandom("cry_et" .. (key_append or "") .. G.GAME.round_resets.ante) if G.GAME.modifiers.enable_eternals_in_shop and eternal_perishable_poll > 0.7 then card:set_eternal(true) end if G.GAME.modifiers.enable_perishables_in_shop then if not G.GAME.modifiers.cry_eternal_perishable_compat and ((eternal_perishable_poll > 0.4) and (eternal_perishable_poll <= 0.7)) then card:set_perishable(true) end if G.GAME.modifiers.cry_eternal_perishable_compat and pseudorandom("cry_per" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7 then card:set_perishable(true) end end if G.GAME.modifiers.enable_rentals_in_shop and pseudorandom("cry_ssjr" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7 then card:set_rental(true) end if G.GAME.modifiers.cry_enable_pinned_in_shop and pseudorandom("cry_pin" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7 then card.pinned = true end if not G.GAME.modifiers.cry_eternal_perishable_compat and G.GAME.modifiers.enable_banana and (pseudorandom("cry_banana" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7) and (eternal_perishable_poll <= 0.7) then card.ability.banana = true end if G.GAME.modifiers.cry_eternal_perishable_compat and G.GAME.modifiers.enable_banana and (pseudorandom("cry_banana" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7) then card.ability.banana = true end if G.GAME.modifiers.cry_sticker_sheet then for k, v in pairs(SMODS.Stickers) do if v.apply and not v.no_sticker_sheet then v:apply(card, true) end end end if not card.ability.eternal and G.GAME.modifiers.cry_enable_flipped_in_shop and pseudorandom("cry_flip" .. (key_append or "") .. G.GAME.round_resets.ante) > 0.7 then card.cry_flipped = true end end if _type == "Joker" and not G.GAME.modifiers.cry_force_edition then local edition = poll_edition("edi" .. (key_append or "") .. G.GAME.round_resets.ante) card:set_edition(edition) check_for_unlock({ type = "have_edition" }) end end if (card.ability.set == "Code") and G.GAME.used_vouchers.v_cry_quantum_computing and pseudorandom("cry_quantum_computing") > 0.7 then card:set_edition({ negative = true }) end if G.GAME.modifiers.cry_force_edition and not G.GAME.modifiers.cry_force_random_edition and area ~= G.pack_cards then card:set_edition(nil, true) end if G.GAME.modifiers.cry_force_random_edition and area ~= G.pack_cards then local edition = cry_poll_random_edition() card:set_edition(edition, true) end if not (card.edition and (card.edition.cry_oversat or card.edition.cry_glitched)) then cry_misprintize(card) end if _type == "Joker" and G.GAME.modifiers.cry_common_value_quad then if card.config.center.rarity == 1 then cry_misprintize(card,{min = 4, max = 4}, nil, true) end end if card.ability.consumeable and card.pinned then -- counterpart is in Sticker.toml G.GAME.cry_pinned_consumeables = G.GAME.cry_pinned_consumeables + 0 end if next(find_joker("Cry-topGear")) and card.config.center.rarity == 1 then if card.ability.name ~= "cry-meteor" and card.ability.name ~= "cry-exoplanet" and card.ability.name ~= "cry-stardust" and card.ability.name ~= "cry-universe" then card:set_edition("e_polychrome", true, nil, true) end end if card.ability.name == "cry-meteor" then card:set_edition("e_foil", true, nil, true) end if card.ability.name == "cry-exoplanet" then card:set_edition("e_holo", true, nil, true) end if card.ability.name == "cry-stardust" then card:set_edition("e_polychrome", true, nil, true) end if card.ability.name == "cry-universe" then card:set_edition("e_cry_astral", true, nil, true) end -- Certain jokers such as Steel Joker and Driver's License depend on values set -- during the update function. Cryptid can create jokers mid-scoring, meaning -- those values will be unset during scoring unless update() is manually called. card:update(0.016) -- dt is unused in the base game, but we're providing a realistic value anyway --Debuff jokers if certain boss blinds are active if _type == "Joker" and G.GAME and G.GAME.blind and not G.GAME.blind.disabled then if G.GAME.blind.name == "cry-box" or (G.GAME.blind.name == "cry-Obsidian Orb" and G.GAME.defeated_blinds["bl_cry_box"] == true) then if card.config.center.rarity == 1 and not card.debuff then card.debuff = true card.debuffed_by_blind = true end end if G.GAME.blind.name == "cry-windmill" or (G.GAME.blind.name == "cry-Obsidian Orb" and G.GAME.defeated_blinds["bl_cry_windmill"] == true) then if card.config.center.rarity == 2 and not card.debuff then card.debuff = true card.debuffed_by_blind = true end end if G.GAME.blind.name == "cry-striker" or (G.GAME.blind.name == "cry-Obsidian Orb" and G.GAME.defeated_blinds["bl_cry_striker"] == true) then if card.config.center.rarity == 3 and not card.debuff then card.debuff = true card.debuffed_by_blind = true end end if G.GAME.blind.name == "cry-shackle" or (G.GAME.blind.name == "cry-Obsidian Orb" and G.GAME.defeated_blinds["bl_cry_shackle"] == true) then if (card.edition and card.edition.negative == true) and not card.debuff then card.debuff = true card.debuffed_by_blind = true end end if G.GAME.blind.name == "cry-pin" or (G.GAME.blind.name == "cry-Obsidian Orb" and G.GAME.defeated_blinds["bl_cry_pin"] == true) then if (card.config.center.rarity ~= 3 and card.config.center.rarity ~= 2 and card.config.center.rarity ~= 1 and card.config.center.rarity ~= 5) then card.debuff = true card.debuffed_by_blind = true end end end return card end -- Make tags fit if there's more than 13 of them local at = add_tag function add_tag(tag) at(tag) if #G.HUD_tags > 13 then for i = 2, #G.HUD_tags do G.HUD_tags[i].config.offset.y = 0.9 - 0.9 * 13 / #G.HUD_tags end end end --add calculation context and callback to tag function local at2 = add_tag function add_tag(tag, from_skip, no_copy) if no_copy then at2(tag) return end local added_tags = 1 for i = 1, #G.jokers.cards do local ret = G.jokers.cards[i]:calculate_joker({ cry_add_tag = true }) if ret and ret.tags then added_tags = added_tags + ret.tags end end if added_tags >= 1 then at2(tag) end for i = 2, added_tags do local tag_table = tag:save() local new_tag = Tag(tag.key) new_tag:load(tag_table) at2(new_tag) end end local tr = Tag.remove function Tag:remove() tr(self) if #G.HUD_tags >= 13 then for i = 2, #G.HUD_tags do G.HUD_tags[i].config.offset.y = 0.9 - 0.9 * 13 / #G.HUD_tags end end end local nr = new_round function new_round() G.hand:change_size(0) nr() end local gfcfbs = G.FUNCS.check_for_buy_space G.FUNCS.check_for_buy_space = function(card) if (card.ability.name == "cry-Negative Joker" and card.ability.extra >= 1) or (card.ability.name == "cry-soccer" and card.ability.extra.holygrail >= 1) or (card.ability.name == "cry-Tenebris" and card.ability.extra.slots >= 1) then return true end return gfcfbs(card) end local gfcsc = G.FUNCS.can_select_card G.FUNCS.can_select_card = function(e) if (e.config.ref_table.ability.name == "cry-Negative Joker" and e.config.ref_table.ability.extra >= 1) or (e.config.ref_table.ability.name == "cry-soccer" and e.config.ref_table.ability.extra.holygrail >= 1) or (e.config.ref_table.ability.name == "cry-Tenebris" and e.config.ref_table.ability.extra.slots >= 1) then e.config.colour = G.C.GREEN e.config.button = 'use_card' else gfcsc(e) end end --Redefine these here because they're always used Cryptid.base_values = {} function cry_misprintize_tbl(name, ref_tbl, ref_value, clear, override, stack) if name and ref_tbl and ref_value then tbl = cry_deep_copy(ref_tbl[ref_value]) for k, v in pairs(tbl) do if (type(tbl[k]) ~= "table") or is_number(tbl[k]) then if is_number(tbl[k]) and not (k == "id") and not (k == "perish_tally") and not (k == "colour") and not (k == "suit_nominal") and not (k == "base_nominal") and not (k == "face_nominal") and not (k == "qty") and not (k == "x_mult" and v == 1 and not tbl.override_x_mult_check) and not (k == "selected_d6_face") then --Temp fix, even if I did clamp the number to values that wouldn't crash the game, the fact that it did get randomized means that there's a higher chance for 1 or 6 than other values if not Cryptid.base_values[name] then Cryptid.base_values[name] = {} end if not Cryptid.base_values[name][k] then Cryptid.base_values[name][k] = tbl[k] end tbl[k] = cry_sanity_check( clear and Cryptid.base_values[name][k] or cry_format( (stack and tbl[k] or Cryptid.base_values[name][k]) * cry_log_random( pseudoseed("cry_misprint" .. G.GAME.round_resets.ante), override and override.min or G.GAME.modifiers.cry_misprint_min, override and override.max or G.GAME.modifiers.cry_misprint_max ), "%.2g" ) ) end else for _k, _v in pairs(tbl[k]) do if is_number(tbl[k][_k]) and not (_k == "id") and not (k == "perish_tally") and not (k == "colour") and not (_k == "suit_nominal") and not (_k == "base_nominal") and not (_k == "face_nominal") and not (_k == "qty") and not (k == "x_mult" and v == 1 and not tbl[k].override_x_mult_check) and not (_k == "selected_d6_face") then --Refer to above if not Cryptid.base_values[name] then Cryptid.base_values[name] = {} end if not Cryptid.base_values[name][k] then Cryptid.base_values[name][k] = {} end if not Cryptid.base_values[name][k][_k] then Cryptid.base_values[name][k][_k] = tbl[k][_k] end tbl[k][_k] = cry_sanity_check( clear and Cryptid.base_values[name][k][_k] or cry_format( (stack and tbl[k][_k] or Cryptid.base_values[name][k][_k]) * cry_log_random( pseudoseed("cry_misprint" .. G.GAME.round_resets.ante), override and override.min or G.GAME.modifiers.cry_misprint_min, override and override.max or G.GAME.modifiers.cry_misprint_max ), "%.2g" ) ) end end end end ref_tbl[ref_value] = tbl end end function cry_misprintize_val(val, override) if is_number(val) then val = cry_sanity_check( cry_format( val * cry_log_random( pseudoseed("cry_misprint" .. G.GAME.round_resets.ante), override and override.min or G.GAME.modifiers.cry_misprint_min, override and override.max or G.GAME.modifiers.cry_misprint_max ), "%.2g" ) ) end return val end function cry_sanity_check(val) if not val or type(val) == "number" and (val ~= val or val > 1e300 or val < -1e300) then return 1e300 end return val end function cry_misprintize(card, override, force_reset, stack) if Card.no(card, "immutable", true) then force_reset = true end --infinifusion compat if card.infinifusion then if card.config.center == card.infinifusion_center or card.config.center.key == 'j_infus_fused' then calculate_infinifusion(card, nil, function(i) cry_misprintize(card, override, force_reset, stack) end) end end if (not force_reset or G.GAME.modifiers.cry_jkr_misprint_mod) and (G.GAME.modifiers.cry_misprint_min or override or card.ability.set == "Joker") and not stack or not Card.no(card, "immutable", true) then if G.GAME.modifiers.cry_jkr_misprint_mod and card.ability.set == "Joker" then if not override then override = {} end override.min = override.min or G.GAME.modifiers.cry_misprint_min or 1 override.max = override.max or G.GAME.modifiers.cry_misprint_max or 1 override.min = override.min * G.GAME.modifiers.cry_jkr_misprint_mod override.max = override.max * G.GAME.modifiers.cry_jkr_misprint_mod end if G.GAME.modifiers.cry_misprint_min or override and override.min then cry_misprintize_tbl(card.config.center_key, card, "ability", nil, override, stack) if card.base then cry_misprintize_tbl(card.config.card_key, card, "base", nil, override, stack) end end if G.GAME.modifiers.cry_misprint_min then --card.cost = cry_format(card.cost / cry_log_random(pseudoseed('cry_misprint'..G.GAME.round_resets.ante),override and override.min or G.GAME.modifiers.cry_misprint_min,override and override.max or G.GAME.modifiers.cry_misprint_max),"%.2f") card.misprint_cost_fac = 1 / cry_log_random( pseudoseed("cry_misprint" .. G.GAME.round_resets.ante), override and override.min or G.GAME.modifiers.cry_misprint_min, override and override.max or G.GAME.modifiers.cry_misprint_max ) card:set_cost() end else cry_misprintize_tbl(card.config.center_key, card, "ability", true) end if card.ability.consumeable then for k, v in pairs(card.ability.consumeable) do card.ability.consumeable[k] = cry_deep_copy(card.ability[k]) end end end function cry_log_random(seed, min, max) math.randomseed(seed) local lmin = math.log(min, 2.718281828459045) local lmax = math.log(max, 2.718281828459045) local poll = math.random() * (lmax - lmin) + lmin return math.exp(poll) end function cry_format(number, str) if math.abs(to_big(number)) >= to_big(1e300) then return number end return tonumber(str:format((Big and to_number(to_big(number)) or number))) end --use ID to work with glitched/misprint function Card:get_nominal(mod) local mult = 1 local rank_mult = 1 if mod == "suit" then mult = 1000000 end if self.ability.effect == "Stone Card" or (self.config.center.no_suit and self.config.center.no_rank) then mult = -10000 elseif self.config.center.no_suit then mult = 0 elseif self.config.center.no_rank then rank_mult = 0 end return 10 * (self.base.id or 0.1) * rank_mult + self.base.suit_nominal * mult + (self.base.suit_nominal_original or 0) * 0.0001 * mult + 10 * self.base.face_nominal * rank_mult + 0.000001 * self.unique_val end --Cryptid (THE MOD) localization local function parse_loc_txt(center) center.text_parsed = {} if not center.text then else for _, line in ipairs(center.text) do center.text_parsed[#center.text_parsed+1] = loc_parse_string(line) end center.name_parsed = {} for _, line in ipairs(type(center.name) == 'table' and center.name or {center.name}) do center.name_parsed[#center.name_parsed+1] = loc_parse_string(line) end if center.unlock then center.unlock_parsed = {} for _, line in ipairs(center.unlock) do center.unlock_parsed[#center.unlock_parsed+1] = loc_parse_string(line) end end end end local il = init_localization function init_localization() il() if G.SETTINGS.language == "en-us" then G.localization.descriptions.Spectral.c_cryptid.text[2] = "{C:attention}#2#{} selected card" G.localization.descriptions.Spectral.c_talisman.text[2] = "to {C:attention}#1#{} selected" G.localization.descriptions.Spectral.c_trance.text[2] = "to {C:attention}#1#{} selected" G.localization.descriptions.Spectral.c_medium.text[2] = "to {C:attention}#1#{} selected" G.localization.descriptions.Spectral.c_deja_vu.text[2] = "to {C:attention}#1#{} selected" G.localization.descriptions.Spectral.c_deja_vu.text[2] = "to {C:attention}#1#{} selected" G.localization.descriptions.Spectral.c_deja_vu.text[2] = "to {C:attention}#1#{} selected" G.localization.descriptions.Voucher.v_antimatter.text[1] = "{C:dark_edition}+#1#{} Joker Slot" G.localization.descriptions.Voucher.v_overstock_norm.text[1] = "{C:attention}+#1#{} card slot" G.localization.descriptions.Voucher.v_overstock_plus.text[1] = "{C:attention}+#1#{} card slot" G.localization.descriptions.Voucher.v_crystal_ball.text[1] = "{C:attention}+#1#{} consumable slot" G.localization.descriptions.Joker.j_seance.text[1] = "If {C:attention}played hand{} contains a" -- damnit seance end if Cryptid.obj_buffer.Stake then for i = 1, #Cryptid.obj_buffer.Stake do local key = Cryptid.obj_buffer.Stake[i].key local color = G.localization.descriptions.Stake[key] and G.localization.descriptions.Stake[key].colour if color then local sticker_key = key:sub(7).."_sticker" if not G.localization.descriptions.Other[sticker_key] then G.localization.descriptions.Other[sticker_key] = { name = localize{type='variable',key='cry_sticker_name',vars={color}}[1], text = localize{type='variable',key='cry_sticker_desc',vars={color,"{C:attention}","{}"}}, } parse_loc_txt(G.localization.descriptions.Other[sticker_key]) end end end end end G.FUNCS.cry_asc_UI_set = function(e) end -- this is a hook to make funny "x of a kind"/"flush x" display text local pokerhandinforef = G.FUNCS.get_poker_hand_info function G.FUNCS.get_poker_hand_info(_cards) local text, loc_disp_text, poker_hands, scoring_hand, disp_text = pokerhandinforef(_cards) if G.SETTINGS.language == "en-us" then if #scoring_hand > 5 and (text == 'Flush Five' or text == 'Five of a Kind') then local rank_array = {} local county = 0 for i = 1, #scoring_hand do local val = scoring_hand[i]:get_id() rank_array[val] = (rank_array[val] or 0) + 1 if rank_array[val] > county then county = rank_array[val] end end local function create_num_chunk(int) -- maybe useful enough to not be local? but tbh this function is probably some common coding exercise if int >= 1000 then int = 999 end local ones = {["1"] = "One", ["2"] = "Two", ["3"] = "Three", ["4"] = "Four", ["5"] = "Five", ["6"] = "Six", ["7"] = "Seven", ["8"] = "Eight", ["9"] = "Nine"} local tens = {["1"] = "Ten", ["2"] = "Twenty", ["3"] = "Thirty", ["4"] = "Forty", ["5"] = "Fifty", ["6"] = "Sixty", ["7"] = "Seventy", ["8"] = "Eighty", ["9"] = "Ninety"} local str_int = string.reverse(int.."") -- ehhhh whatever local str_ret = "" for i = 1, string.len(str_int) do local place = str_int:sub(i, i) if place ~= "0" then if i == 1 then str_ret = ones[place] elseif i == 2 then if place == "1" and str_ret ~= "" then -- admittedly not my smartest moment, i dug myself into a hole here... if str_ret == "One" then str_ret = "Eleven" elseif str_ret == "Two" then str_ret = "Twelve" elseif str_ret == "Three" then str_ret = "Thirteen" elseif str_ret == "Five" then str_ret = "Fifteen" elseif str_ret == "Eight" then str_ret = "Eighteen" else str_ret = str_ret.."teen" end else str_ret = tens[place]..((string.len(str_ret) > 0 and " " or "")..str_ret) end elseif i == 3 then str_ret = ones[place]..(" Hundred"..((string.len(str_ret) > 0 and " and " or "")..str_ret)) end -- this line is wild end end return str_ret end -- text gets stupid small at 100+ anyway loc_disp_text = (text == 'Flush Five' and "Flush " or "")..((county < 1000 and create_num_chunk(county) or "Thousand")..(text == 'Five of a Kind' and " of a Kind" or "")) end end local hand_table = { ['High Card'] = G.GAME.used_vouchers.v_cry_hyperspacetether and 1 or nil, ['Pair'] = G.GAME.used_vouchers.v_cry_hyperspacetether and 2 or nil, ['Two Pair'] = 4, ['Three of a Kind'] = G.GAME.used_vouchers.v_cry_hyperspacetether and 3 or nil, ['Straight'] = next(SMODS.find_card('j_four_fingers')) and 4 or 5, ['Flush'] = next(SMODS.find_card('j_four_fingers')) and 4 or 5, ['Full House'] = 5, ['Four of a Kind'] = G.GAME.used_vouchers.v_cry_hyperspacetether and 4 or nil, ['Straight Flush'] = next(SMODS.find_card('j_four_fingers')) and 4 or 5, -- debatable ['cry_Bulwark'] = 5, ['Five of a Kind'] = 5, ['Flush House'] = 5, ['Flush Five'] = 5, ['cry_Clusterfuck'] = 8, ['cry_UltPair'] = 8, ['cry_WholeDeck'] = 52, } -- this is where all the logic for asc hands is. currently it's very simple but if you want more complex logic, here's the place to do it if hand_table[text] then G.GAME.current_round.current_hand.cry_asc_num = G.GAME.used_vouchers.v_cry_hyperspacetether and #_cards - hand_table[text] or #scoring_hand - hand_table[text] else G.GAME.current_round.current_hand.cry_asc_num = 0 end G.GAME.current_round.current_hand.cry_asc_num_text = (G.GAME.current_round.current_hand.cry_asc_num and G.GAME.current_round.current_hand.cry_asc_num > 0) and " (+"..G.GAME.current_round.current_hand.cry_asc_num..")" or "" return text, loc_disp_text, poker_hands, scoring_hand, disp_text end function cry_ascend(num) -- edit this function at your leisure return math.max(num, num*((1.25 + (0.05 * (G.GAME.sunnumber or 0)))^G.GAME.current_round.current_hand.cry_asc_num or 0)) end function cry_pulse_flame(duration, intensity) -- duration is in seconds, intensity is in idfk honestly, but it increases pretty quickly G.cry_flame_override = G.cry_flame_override or {} G.cry_flame_override["duration"] = duration or 0.01 G.cry_flame_override["intensity"] = intensity or 2 end --Will be moved to D20 file when that gets added function roll_dice(seed, min, max, config) local val while not val or (config and config.ignore_value == val) do val = pseudorandom(seed, min, max) end return val end function SMODS.current_mod.reset_game_globals(run_start) G.GAME.cry_ach_conditions = G.GAME.cry_ach_conditions or {} end --Fix a corrupted game state function Controller:queue_L_cursor_press(x, y) if self.locks.frame then return end if G.STATE == G.STATES.SPLASH then if not G.HUD then self:key_press("escape") else G.STATE = G.STATES.BLIND_SELECT end end self.L_cursor_queue = { x = x, y = y } end --Used to check to play the exotic music function cry_has_exotic() if G.jokers then for i = 1, #G.jokers.cards do if G.jokers.cards[i].config.center.rarity == "cry_exotic" then return true end end end 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 G.jokers.cards[i].ability.effect == "M Joker" 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 -- Fix a CCD-related crash local cuc = Card.can_use_consumeable function Card:can_use_consumeable(any_state, skip_check) if not self.ability.consumeable then return false end return cuc(self, any_state, skip_check) end --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 --Stickers and modifiers used by Challenges+Stakes SMODS.Atlas({ key = "sticker", path = "sticker_cry.png", px = 71, py = 95, inject = function(self) local file_path = type(self.path) == "table" and (self.path[G.SETTINGS.language] or self.path["default"] or self.path["en-us"]) or self.path if file_path == "DEFAULT" then return end -- language specific sprites override fully defined sprites only if that language is set if self.language and not (G.SETTINGS.language == self.language) then return end if not self.language and self.obj_table[("%s_%s"):format(self.key, G.SETTINGS.language)] then return end self.full_path = (self.mod and self.mod.path or SMODS.path) .. "assets/" .. G.SETTINGS.GRAPHICS.texture_scaling .. "x/" .. file_path local file_data = assert(NFS.newFileData(self.full_path), ("Failed to collect file data for Atlas %s"):format(self.key)) self.image_data = assert( love.image.newImageData(file_data), ("Failed to initialize image data for Atlas %s"):format(self.key) ) self.image = love.graphics.newImage(self.image_data, { mipmaps = true, dpiscale = G.SETTINGS.GRAPHICS.texture_scaling }) G[self.atlas_table][self.key_noloc or self.key] = self G.shared_sticker_banana = Sprite(0, 0, G.CARD_W, G.CARD_H, G[self.atlas_table][self.key_noloc or self.key], { x = 5, y = 2 }) G.shared_sticker_pinned = Sprite(0, 0, G.CARD_W, G.CARD_H, G[self.atlas_table][self.key_noloc or self.key], { x = 5, y = 0 }) end, }) function Card:set_perishable(_perishable) self.ability.perishable = nil if (self.config.center.perishable_compat or G.GAME.modifiers.cry_any_stickers) and (not self.ability.eternal or G.GAME.modifiers.cry_eternal_perishable_compat) then self.ability.perishable = true self.ability.perish_tally = G.GAME.perishable_rounds or 5 end end function Card:set_eternal(_eternal) self.ability.eternal = nil if (self.config.center.eternal_compat or G.GAME.modifiers.cry_any_stickers) and (not self.ability.perishable or G.GAME.modifiers.cry_eternal_perishable_compat) then self.ability.eternal = _eternal end end function Card:calculate_banana() if not self.ability.extinct then if self.ability.banana and (pseudorandom("banana") < G.GAME.probabilities.normal / 10) then self.ability.extinct = true 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() if self.area then self.area:remove_card(self) end self:remove() self = nil return true end, })) return true end, })) card_eval_status_text(self, "jokers", nil, nil, nil, { message = localize("k_extinct_ex"), delay = 0.1 }) return true elseif self.ability.banana then card_eval_status_text(self, "jokers", nil, nil, nil, { message = localize("k_safe_ex"), delay = 0.1 }) return false end end return false end function Card:set_banana(_banana) self.ability.banana = _banana end function Card:set_pinned(_pinned) self.pinned = _pinned end --Gradients based on Balatrostuck code local upd = Game.update Cryptid.C = { EXOTIC = { HEX("708b91"), HEX("1e9eba") }, TWILIGHT = { HEX("0800ff"), HEX("aa00ff") }, VERDANT = { HEX("00ff22"), HEX("f4ff57") }, EMBER = { HEX("ff0000"), HEX("ffae00") }, DAWN = { HEX("00aaff"), HEX("ff00e3") }, HORIZON = { HEX("c8fd09"), HEX("1ee7d9") }, BLOSSOM = { HEX("ff09da"), HEX("ffd121") }, AZURE = { HEX("0409ff"), HEX("63dcff") }, ASCENDANT = { HEX("2e00f5"), HEX("e5001d") }, JOLLY = { HEX("6ec1f5"), HEX("456b84") }, } function Game:update(dt) upd(self, dt) local anim_timer = self.TIMERS.REAL * 1.5 local p = 0.5 * (math.sin(anim_timer) + 1) for k, c in pairs(Cryptid.C) do if not G.C["CRY_" .. k] then G.C["CRY_" .. k] = { 0, 0, 0, 0 } end for i = 1, 4 do G.C["CRY_" .. k][i] = c[1][i] * p + c[2][i] * (1 - p) end end G.C.RARITY["cry_exotic"] = G.C.CRY_EXOTIC if Incantation and not CryptidIncanCompat then AllowStacking("Code") AllowDividing("Code") CryptidIncanCompat = true end end local jokers = { "j_gros_michel", "j_egg", "j_ice_cream", "j_cavendish", "j_turtle_bean", "j_diet_cola", "j_popcorn", "j_ramen", "j_selzer", } if Cryptid.enabled["Misc. Jokers"] then jokers[#jokers + 1] = "j_cry_pickle" jokers[#jokers + 1] = "j_cry_chili_pepper" end if Cryptid.enabled["Epic Jokers"] then jokers[#jokers + 1] = "j_cry_oldcandy" jokers[#jokers + 1] = "j_cry_caramel" end if Cryptid.enabled["M Jokers"] then jokers[#jokers + 1] = "j_cry_foodm" end if Cryptid.enabled["Spooky"] then jokers[#jokers + 1] = "j_cry_cotton_candy" jokers[#jokers + 1] = "j_cry_wrapped" jokers[#jokers + 1] = "j_cry_candy_cane" jokers[#jokers + 1] = "j_cry_candy_buttons" jokers[#jokers + 1] = "j_cry_jawbreaker" jokers[#jokers + 1] = "j_cry_mellowcreme" jokers[#jokers + 1] = "j_cry_brittle" end for i = 1, #jokers do Cryptid.food[#Cryptid.food+1] = jokers[i] end function Cryptid.get_food(seed) local food_keys = {} for k, v in pairs(Cryptid.food) do if not G.GAME.banned_keys[v] and G.P_CENTERS[v] then table.insert(food_keys, v) end end if #food_keys <= 0 then return "j_reserved_parking" else return pseudorandom_element(food_keys, pseudoseed(seed)) end end SMODS.Sound({ key = "meow1", path = "meow1.ogg", }) SMODS.Sound({ key = "meow2", path = "meow2.ogg", }) SMODS.Sound({ key = "meow3", path = "meow3.ogg", }) SMODS.Sound({ key = "meow4", path = "meow4.ogg", }) SMODS.Sound({ key = "e_mosaic", path = "e_mosaic.ogg", }) SMODS.Sound({ key = "e_glitched", path = "e_glitched.ogg", }) SMODS.Sound({ key = "e_oversaturated", path = "e_oversaturated.ogg", }) SMODS.Sound({ key = "e_blur", path = "e_blur.ogg", }) SMODS.Sound({ key = "e_double_sided", path = "e_double_sided.ogg", }) SMODS.Sound({ key = "e_jolly", path = "e_jolly.ogg", }) SMODS.Sound({ key = "e_noisy", path = "e_noisy.ogg", }) SMODS.Sound({ key = "e_fragile", path = "e_fragile.ogg", }) SMODS.Sound({ key = "e_golden", path = "e_golden.ogg", }) SMODS.Sound({ key = "studiofromhelsinki", path = "studiofromhelsinki.ogg", }) SMODS.Sound({ key = "music_jimball", path = "music_jimball.ogg", sync = false, pitch = 1, select_music_track = function() return next(find_joker("cry-Jimball")) and Cryptid_config.Cryptid.jimball_music and 1.57e308 end, }) SMODS.Sound({ key = "music_code", path = "music_code.ogg", select_music_track = function() return Cryptid_config.Cryptid.code_music and ( ( G.pack_cards and G.pack_cards.cards and G.pack_cards.cards[1] and G.pack_cards.cards[1].ability.set == "Code" ) or (G.GAME and G.GAME.USING_CODE) ) end, }) SMODS.Sound({ key = "music_big", path = "music_big.ogg", select_music_track = function() return Cryptid_config.Cryptid.big_music and to_big(G.GAME.round_scores["hand"].amt) > to_big(10) ^ 1000000 end, }) SMODS.Sound({ key = "music_exotic", path = "music_exotic.ogg", volume = 0.4, select_music_track = function() return Cryptid_config.Cryptid.exotic_music and cry_has_exotic() end, }) --Requires Malverk Mod if (SMODS.Mods["malverk"] or {}).can_load then AltTexture({ key = 'jolly_jokers', set = 'Joker', path = 'jolly.png', loc_txt = { name = 'Jolly Jokers' } }) TexturePack{ -- HD Texture Pack key = 'jolly_texture', textures = { 'cry_jolly_jokers', }, loc_txt = { name = 'Jolly', text = { 'Jolly Jokers lmao', 'Art by B' } } } end --Make Ortalab's Locked jokers not show up on Deck of Equilibrium and Antimatter Deck if (SMODS.Mods["ortalab"] or {}).can_load then for i = 1, 150 do print(i) SMODS.Joker:take_ownership('ortalab_temp_' .. i, { name = "Cry-skibidi", no_doe = true }) end end SMODS.Atlas({ key = "modicon", path = "cry_icon.png", px = 32, py = 32, }):register() SMODS.Atlas({ key = "placeholders", path = "placeholders.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "atlasepic", path = "atlasepic.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "atlasone", path = "atlasone.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "atlastwo", path = "atlastwo.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "atlasthree", path = "atlasthree.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "atlasspooky", path = "atlasspooky.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "atlasexotic", path = "atlasexotic.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "atlasnotjokers", --this is easier to spell then consumables path = "atlasnotjokers.png", px = 71, py = 95, }):register() SMODS.Atlas({ key = "tag_cry", path = "tag_cry.png", px = 34, py = 34, }):register() --Enchancements, seals, other misc things etc SMODS.Atlas({ key = "cry_misc", path = "cry_misc.png", px = 71, py = 95, }):register() SMODS.Sticker:take_ownership("perishable", { atlas = "sticker", pos = { x = 4, y = 4 }, prefix_config = { key = false }, loc_vars = function(self, info_queue, card) if card.ability.consumeable then return { key = "cry_perishable_consumeable" } elseif card.ability.set == "Voucher" then return { key = "cry_perishable_voucher", vars = { G.GAME.cry_voucher_perishable_rounds or 1, card.ability.perish_tally or G.GAME.cry_voucher_perishable_rounds, }, } elseif card.ability.set == "Booster" then return { key = "cry_perishable_booster" } else return { vars = { G.GAME.perishable_rounds or 1, card.ability.perish_tally or G.GAME.perishable_rounds } } end end, }) SMODS.Sticker:take_ownership("pinned", { atlas = "sticker", pos = { x = 5, y = 0 }, prefix_config = { key = false }, loc_vars = function(self, info_queue, card) if card.ability.consumeable then return { key = "cry_pinned_consumeable" } -- this doesn't work. i want this to work :( elseif card.ability.set == "Voucher" then return { key = "cry_pinned_voucher" } elseif card.ability.set == "Booster" then return { key = "cry_pinned_booster" } end end, }) SMODS.Sticker:take_ownership("eternal", { loc_vars = function(self, info_queue, card) if card.ability.set == "Voucher" then return { key = "cry_eternal_voucher" } elseif card.ability.set == "Booster" then return { key = "cry_eternal_booster" } end end, }) SMODS.Sticker:take_ownership("rental", { loc_vars = function(self, info_queue, card) if card.ability.consumeable then return { key = "cry_rental_consumeable", vars = { G.GAME.cry_consumeable_rental_rate or 1 } } elseif card.ability.set == "Voucher" then return { key = "cry_rental_voucher", vars = { G.GAME.cry_voucher_rental_rate or 1 } } elseif card.ability.set == "Booster" then return { key = "cry_rental_booster" } else return { vars = { G.GAME.rental_rate or 1 } } end end, }) --Sticker calc for playing cards local ec = eval_card function eval_card(card, context) local ret = ec(card, context) if card and (card.area == G.hand or card.area == G.play or card.area == G.discard or card.area == G.deck) then for k, v in pairs(SMODS.Stickers) do if card.ability[k] and v.calculate and type(v.calculate) == "function" then context.from_playing_card = true context.ret = ret v:calculate(card, context) end end end return ret end function create_cryptid_notif_overlay(key) if not G.SETTINGS.cryptid_notifs then -- I want this to be across profiles G.SETTINGS.cryptid_notifs = {} end if not G.SETTINGS.cryptid_notifs[key] then G.E_MANAGER:add_event(Event({ trigger = 'immediate', no_delete = true, func = (function() if not G.OVERLAY_MENU then G.SETTINGS.paused = true G.FUNCS.overlay_menu{ definition = create_UIBox_cryptid_notif(key), } play_sound('foil1', 0.7, 0.3) play_sound('gong', 1.4, 0.15) G.SETTINGS.cryptid_notifs[key] = true G:save_settings() return true end end) }), 'unlock') end end function create_UIBox_cryptid_notif(key) local t = create_UIBox_generic_options({padding = 0,back_label = localize('b_continue'), no_pip = true, snap_back = true, back_func = 'continue_unlock', minw = 4.5, contents = { {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ {n=G.UIT.R, config={align = "cm", padding = 0.1}, nodes={ {n=G.UIT.R, config={align = "cm", padding = 0.1}, nodes={ {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ {n=G.UIT.O, config={object = DynaText({string = {localize('cry_notif_'..key..'_1')}, colours = {G.C.BLUE},shadow = true, rotate = true, bump = true, pop_in = 0.3, pop_in_rate = 2, scale = 1.2})}} }}, {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ {n=G.UIT.O, config={object = DynaText({string = {localize('cry_notif_'..key..'_2')}, colours = {G.C.RED},shadow = true, rotate = true, bump = true, pop_in = 0.6, pop_in_rate = 2, scale = 0.8})}} }}, }}, {n=G.UIT.R, config={align = "cm", padding = 0.2}, nodes={ {n=G.UIT.R, config={align = "cm", padding = 0.05, emboss = 0.05, colour = G.C.WHITE, r = 0.1}, nodes={ Cryptid.notifications[key].nodes() }} }} }}, Cryptid.notifications[key].cta and {n=G.UIT.R, config={id = 'overlay_menu_back_button', align = "cm", minw = 2.5, padding =0.1, r = 0.1, hover = true, colour = G.C.BLUE, button = "notif_"..key, shadow = true, focus_args = {nav = 'wide', button = 'b'}}, nodes={ {n=G.UIT.R, config={align = "cm", padding = 0, no_fill = true}, nodes={ {n=G.UIT.T, config={text = localize(Cryptid.notifications[key].cta.label), scale = 0.5, colour = G.C.UI.TEXT_LIGHT, shadow = true, func = 'set_button_pip', focus_args = {button = 'b'}}} }} }} or nil }} }}) return t end -- I couldn't figure out how to use localization for this, so this implementation is pretty scuffed Cryptid.notifications = { jimball = { nodes = function() return {n=G.UIT.R, config={align = "cm", colour = empty and G.C.CLEAR or G.C.UI.BACKGROUND_WHITE, r = 0.1, padding = 0.04, minw = 2, minh = 0.8, emboss = not empty and 0.05 or nil, filler = true}, nodes={ {n=G.UIT.R, config={align = "cm", padding = 0.03}, nodes={ {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ {n=G.UIT.T, config={text = localize('cry_notif_jimball_d1'), scale = 0.5, colour = G.C.BLACK}}, }}, {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ {n=G.UIT.T, config={text = localize('cry_notif_jimball_d2'), scale = 0.5, colour = G.C.BLACK}}, }}, {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ {n=G.UIT.T, config={text = localize('cry_notif_jimball_d3'), scale = 0.5, colour = G.C.BLACK}}, }}, }} }} end, cta = { label = "k_disable_music" } } } ---------------------------------------------- ------------MOD CODE END----------------------