-- overrides.lua - Adds hooks and overrides used by multiple features. --Get Pack hooks -- dumb hook because i don't feel like aggressively patching get_pack to do stuff -- very inefficient -- maybe smods should overwrite the function and make it more targetable? local getpackref = get_pack function get_pack(_key, _type) local temp_banned = copy_table(G.GAME.banned_keys) --Add banished keys (via DELETE) to banned_keys so they don't appear in shop for k, v in pairs(G.GAME.cry_banished_keys) do G.GAME.banned_keys[k] = v end local abc = getpackref(_key, _type) --Convert banned keys back to what it was originally G.GAME.banned_keys = copy_table(temp_banned) if G.GAME.modifiers.cry_equilibrium then if not P_CRY_ITEMS then P_CRY_ITEMS = {} local valid_pools = { "Joker", "Consumeables", "Voucher", "Booster" } for _, id in ipairs(valid_pools) do for k, v in pairs(G.P_CENTER_POOLS[id]) do if not Cryptid.no(v, "doe", k) then P_CRY_ITEMS[#P_CRY_ITEMS + 1] = v.key end end end for k, v in pairs(G.P_CARDS) do if not Cryptid.no(v, "doe", k) then P_CRY_ITEMS[#P_CRY_ITEMS + 1] = v.key end end end return G.P_CENTERS[pseudorandom_element( P_CRY_ITEMS, pseudoseed("cry_equipackbrium" .. G.GAME.round_resets.ante) )] end return abc end -- get_currrent_pool hook for Deck of Equilibrium and Copies local gcp = get_current_pool function get_current_pool(_type, _rarity, _legendary, _append, override_equilibrium_effect) if type == "Tag" then for i = 1, #pool do -- Copies: Turn Double tags into Triple Tags if pool[i] == "tag_double" and G.GAME.used_vouchers.v_cry_copies then pool[i] = "tag_cry_triple" end -- Tag Printer: Turn Double tags and Triple Tags into Quadruple Tags if (pool[i] == "tag_double" or pool[i] == "tag_cry_triple") and G.GAME.used_vouchers.v_cry_tag_printer then pool[i] = "tag_cry_quadruple" end -- Clone Machine: Turn Double tags and Triple Tags as well as Quadruple Tags into Quintuple Tags if (pool[i] == "tag_double" or pool[i] == "tag_cry_triple" or pool[i] == "tag_cry_quadruple") and G.GAME.used_vouchers.v_cry_clone_machine then pool[i] = "tag_cry_quintuple" end end -- Deck of Equilibrium stuff elseif G.GAME.modifiers.cry_equilibrium and not override_equilibrium_effect and (_append == "sho" or _type == "Voucher" or _type == "Booster") then if _type ~= "Enhanced" and _type ~= "Edition" and _type ~= "Back" and _type ~= "Tag" and _type ~= "Seal" and _type ~= "Stake" then -- we're regenerating the pool every time because of banned keys but it's fine tbh P_CRY_ITEMS = {} local valid_pools = { "Joker", "Consumeables", "Voucher", "Booster" } for _, id in ipairs(valid_pools) do for k, v in pairs(G.P_CENTER_POOLS[id]) do if v.unlocked == true and not Cryptid.no(v, "doe", k) and not (G.GAME.banned_keys[v.key] or G.GAME.cry_banished_keys[v.key]) then P_CRY_ITEMS[#P_CRY_ITEMS + 1] = v.key end end end if #P_CRY_ITEMS <= 0 then P_CRY_ITEMS[#P_CRY_ITEMS + 1] = "v_blank" end return P_CRY_ITEMS, "cry_equilibrium" .. G.GAME.round_resets.ante end end return gcp(_type, _rarity, _legendary, _append) end local gnb = get_new_boss function get_new_boss() local bl = gnb() --Fix an issue with adding bosses mid-run for k, v in pairs(G.P_BLINDS) do if not G.GAME.bosses_used[k] then G.GAME.bosses_used[k] = 0 end end -- Force Clock and Lavender Loop for Rush hour if G.GAME.modifiers.cry_rush_hour then --Check if Clock and Lavender Loop are both enabled if (Cryptid.enabled("bl_cry_clock") == true) and (Cryptid.enabled("bl_cry_lavender_loop") == true) then return (G.GAME.round_resets.ante % G.GAME.win_ante == 0 and G.GAME.round_resets.ante >= 2) and "bl_cry_lavender_loop" or "bl_cry_clock" else -- Note: code elsewhere will force losses until both blinds are enabled return bl end end -- Log if G.GAME.LOG_BOSS then local v = "" .. G.GAME.LOG_BOSS if not G.GAME.USING_LOG then G.GAME.LOG_BOSS = nil end return v end --This is how nostalgic deck replaces the boss blinds with Nostalgic versions if G.GAME.modifiers.cry_beta then local bl_key = string.sub(bl, 4) local nostalgicblinds = { arm = (Cryptid.enabled("bl_cry_oldarm") == true), fish = (Cryptid.enabled("bl_cry_oldfish") == true), flint = (Cryptid.enabled("bl_cry_oldflint") == true), house = (Cryptid.enabled("bl_cry_oldhouse") == true), manacle = (Cryptid.enabled("bl_cry_oldmanacle") == true), mark = (Cryptid.enabled("bl_cry_oldmark") == true), ox = (Cryptid.enabled("bl_cry_oldox") == true), pillar = (Cryptid.enabled("bl_cry_oldpillar") == true), serpent = (Cryptid.enabled("bl_cry_oldserpent") == true), } if nostalgicblinds[bl_key] then return "bl_cry_old" .. bl_key end end return bl end --Add context for after cards are played local gfep = G.FUNCS.evaluate_play function G.FUNCS.evaluate_play(e) gfep(e) G.GAME.blind:cry_after_play() end --Track defeated blinds for Obsidian Orb local dft = Blind.defeat function Blind:defeat(s) dft(self, s) local obj = self.config.blind if (obj.boss and obj.boss.yes_orb) or ( obj.name == "The Hook" or obj.name == "The Ox" or obj.name == "The House" or obj.name == "The Wall" or obj.name == "The Wheel" or obj.name == "The Arm" or obj.name == "The Club" or obj.name == "The Fish" or obj.name == "The Psychic" or obj.name == "The Goad" or obj.name == "The Water" or obj.name == "The Window" or obj.name == "The Manacle" or obj.name == "The Eye" or obj.name == "The Mouth" or obj.name == "The Plant" or obj.name == "The Serpent" or obj.name == "The Pillar" or obj.name == "The Needle" or obj.name == "The Head" or obj.name == "The Tooth" or obj.name == "The Flint" or obj.name == "The Mark" or obj.name == "Amber Acorn" or obj.name == "Verdant Leaf" or obj.name == "Violet Vessel" or obj.name == "Crimsion Heart" or obj.name == "Cerulean Bell" ) then else return end if --Stop impossible blind combinations from happening (self.name ~= "cry-oldarm" or not G.GAME.defeated_blinds["bl_psychic"]) and (self.name ~= "The Psychic" or not G.GAME.defeated_blinds["bl_cry_oldarm"]) and (self.name ~= "cry-oldarm" or not G.GAME.defeated_blinds["bl_cry_scorch"]) and (self.name ~= "cry-scorch" or not G.GAME.defeated_blinds["bl_cry_oldarm"]) and (self.name ~= "The Eye" or not G.GAME.defeated_blinds["bl_mouth"]) and (self.name ~= "The Mouth" or not G.GAME.defeated_blinds["bl_eye"]) and (self.name ~= "cry-Lavender Loop" or not G.GAME.defeated_blinds["bl_cry_tax"]) and (self.name ~= "cry-Tax" or not G.GAME.defeated_blinds["bl_cry_lavender_loop"]) and (self.name ~= "The Needle" or not G.GAME.defeated_blinds["bl_cry_tax"]) and (self.name ~= "cry-Tax" or not G.GAME.defeated_blinds["bl_needle"]) then G.GAME.defeated_blinds[self.config.blind.key or ""] = true end end local sr = Game.start_run function Game:start_run(args) sr(self, args) if G.P_BLINDS.bl_cry_clock then G.P_BLINDS.bl_cry_clock.mult = 0 end if not G.GAME.defeated_blinds then G.GAME.defeated_blinds = {} end end --patch for multiple Clocks to tick separately and load separately local bsb = Blind.set_blind function Blind:set_blind(blind, y, z) local c = "Boss" if string.sub(G.GAME.subhash or "", -1) == "S" then c = "Small" end if string.sub(G.GAME.subhash or "", -1) == "B" then c = "Big" end if G.GAME.CRY_BLINDS and G.GAME.CRY_BLINDS[c] and not y and blind and blind.mult and blind.cry_ante_base_mod then blind.mult = G.GAME.CRY_BLINDS[c] end bsb(self, blind, y, z) end local rb = reset_blinds function reset_blinds() if G.GAME.round_resets.blind_states.Boss == "Defeated" then G.GAME.CRY_BLINDS = {} if G.P_BLINDS.bl_cry_clock then G.P_BLINDS.bl_cry_clock.mult = 0 end end rb() end --Init stuff at the start of the game local gigo = Game.init_game_object function Game:init_game_object() local g = gigo(self) -- Add initial dropshot and number blocks card g.current_round.cry_nb_card = { rank = "Ace" } g.current_round.cry_dropshot_card = { suit = "Spades" } g.monstermult = 1 g.neutronstarsusedinthisrun = 0 g.sunlevel = 1 g.sunnumber = { modest = 0, not_modest = 0 } g.bonus_asc_power = 0 g.cry_oboe = 0 g.boostertag = 0 -- Create G.GAME.events when starting a run, so there's no errors g.events = {} g.jokers_sold = {} g.cry_banished_keys = {} g.cry_last_used_consumeables = {} g.cry_function_stupid_workaround = {} -- Added by IcyEthics: Converted the voucher-related modifiers for the tier 3 -- acclimator vouchers to be more generically accessible g.cry_bonusvouchercount = 0 g.cry_bonusvouchersused = {} -- Automatically sets up the cry_percrate for each consumable type and sets it to 100 as a default g.cry_percrate = {} for _, v in ipairs(SMODS.ConsumableType.ctype_buffer) do g.cry_percrate[v:lower()] = 100 end return g end -- reset_castle_card hook for things like Dropshot and Number Blocks -- Also exclude specific ranks/suits (such as abstract cards) local rcc = reset_castle_card function reset_castle_card() rcc() G.GAME.current_round.cry_nb_card = { rank = "Ace" } G.GAME.current_round.cry_dropshot_card = { suit = "Spades" } local valid_castle_cards = {} for k, v in ipairs(G.playing_cards) do if not SMODS.has_no_suit(v) and not SMODS.has_enhancement(v, "m_cry_abstract") then valid_castle_cards[#valid_castle_cards + 1] = v end end if valid_castle_cards[1] then --Dropshot local castle_card = pseudorandom_element(valid_castle_cards, pseudoseed("cry_dro" .. G.GAME.round_resets.ante)) G.GAME.current_round.cry_dropshot_card.suit = castle_card.base.suit --Number Blocks local castle_card_two = pseudorandom_element(valid_castle_cards, pseudoseed("cry_nb" .. G.GAME.round_resets.ante)) G.GAME.current_round.cry_nb_card.rank = castle_card_two.base.value G.GAME.current_round.cry_nb_card.id = castle_card_two.base.id end end -- Back.apply_to_run Hook for decks local Backapply_to_runRef = Back.apply_to_run function Back.apply_to_run(self) Backapply_to_runRef(self) if self.effect.config.cry_no_edition_price then G.GAME.modifiers.cry_no_edition_price = true end end --Game:update hook local upd = Game.update --init colors so they have references G.C.CRY_TWILIGHT = { 0, 0, 0, 0 } G.C.CRY_VERDANT = { 0, 0, 0, 0 } G.C.CRY_EMBER = { 0, 0, 0, 0 } G.C.CRY_DAWN = { 0, 0, 0, 0 } G.C.CRY_HORIZON = { 0, 0, 0, 0 } G.C.CRY_BLOSSOM = { 0, 0, 0, 0 } G.C.CRY_AZURE = { 0, 0, 0, 0 } G.C.CRY_ASCENDANT = { 0, 0, 0, 0 } G.C.CRY_JOLLY = { 0, 0, 0, 0 } G.C.CRY_GREENGRADIENT = { 0, 0, 0, 0 } G.C.CRY_ALTGREENGRADIENT = { 0, 0, 0, 0 } 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") }, SELECTED = { HEX("e38039"), HEX("ccdd1b") }, GREENGRADIENT = { HEX("51e099"), HEX("1e523a") }, ALTGREENGRADIENT = { HEX("6bb565"), HEX("bd28bf") }, TAX_MULT = { HEX("FE5F55"), HEX("40ff40") }, TAX_CHIPS = { HEX("009dff"), HEX("40ff40") }, } cry_pointer_dt = 0 cry_jimball_dt = 0 cry_glowing_dt = 0 cry_glowing_dt2 = 0 local none_eval = 0 function Game:update(dt) upd(self, dt) if not Cryptid.member_count_delay then Cryptid.member_count_delay = 0 end if (Cryptid.member_count_delay > 5) or not Cryptid.member_count then -- it doesn't need to update this frequently? but it also doesn't need to be higher tbh... if Cryptid.update_member_count then Cryptid.update_member_count() end -- i honestly hate nil checks like this, wish there was a shorthand Cryptid.member_count_delay = 0 else Cryptid.member_count_delay = Cryptid.member_count_delay + dt end --Gradients based on Balatrostuck code 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 G.C.SECONDARY_SET["Content Set"] = G.C.CRY_ASCENDANT -- Idk what this is for if Incantation and not CryptidIncanCompat then AllowStacking("Code") AllowDividing("Code") CryptidIncanCompat = true end if Cryptid.enabled("set_cry_timer") == true then cry_pointer_dt = cry_pointer_dt + dt cry_jimball_dt = cry_jimball_dt + dt cry_glowing_dt = cry_glowing_dt + dt cry_glowing_dt2 = cry_glowing_dt2 + dt end --Update sprite positions each frame on certain cards to give the illusion of an animated card if G.P_CENTERS and G.P_CENTERS.c_cry_pointer and cry_pointer_dt > 0.5 then cry_pointer_dt = 0 local pointerobj = G.P_CENTERS.c_cry_pointer pointerobj.pos.x = (pointerobj.pos.x == 11) and 12 or 11 end if G.P_CENTERS and G.P_CENTERS.j_cry_jimball and cry_jimball_dt > 0.1 then cry_jimball_dt = 0 local jimballobj = G.P_CENTERS.j_cry_jimball if jimballobj.pos.x == 5 and jimballobj.pos.y == 6 then jimballobj.pos.x = 0 jimballobj.pos.y = 0 elseif jimballobj.pos.x < 8 then jimballobj.pos.x = jimballobj.pos.x + 1 elseif jimballobj.pos.y < 6 then jimballobj.pos.x = 0 jimballobj.pos.y = jimballobj.pos.y + 1 end end if G.P_CENTERS and G.P_CENTERS.b_cry_glowing and cry_glowing_dt > 0.1 then cry_glowing_dt = 0 local glowingobj = G.P_CENTERS.b_cry_glowing if glowingobj.pos.x == 1 and glowingobj.pos.y == 4 then glowingobj.pos.x = 0 glowingobj.pos.y = 0 elseif glowingobj.pos.x < 4 then glowingobj.pos.x = glowingobj.pos.x + 1 elseif glowingobj.pos.y < 6 then glowingobj.pos.x = 0 glowingobj.pos.y = glowingobj.pos.y + 1 end end if G.P_CENTERS and G.P_CENTERS.sleeve_cry_glowing_sleeve and cry_glowing_dt2 > 0.1 then cry_glowing_dt2 = 0 local glowingobj = G.P_CENTERS.sleeve_cry_glowing_sleeve if glowingobj.pos.x == 1 and glowingobj.pos.y == 4 then glowingobj.pos.x = 0 glowingobj.pos.y = 0 elseif glowingobj.pos.x < 4 then glowingobj.pos.x = glowingobj.pos.x + 1 elseif glowingobj.pos.y < 6 then glowingobj.pos.x = 0 glowingobj.pos.y = glowingobj.pos.y + 1 end end for k, v in pairs(G.I.CARD) do if v.children.back and v.children.back.atlas.name == "cry_glowing" then v.children.back:set_sprite_pos(G.P_CENTERS.b_cry_glowing.pos or G.P_CENTERS["b_red"].pos) end end if not G.OVERLAY_MENU and G.GAME.CODE_DESTROY_CARD and not G.OVERLAY_MENU_POINTER then G.FUNCS.exit_overlay_menu_code() end if not G.OVERLAY_MENU then G.GAME.USING_POINTER = nil else G.OVERLAY_MENU_POINTER = nil end --Increase the blind size for The Clock and Lavender Loop local choices = { "Small", "Big", "Boss" } G.GAME.CRY_BLINDS = G.GAME.CRY_BLINDS or {} for _, c in pairs(choices) do if G.GAME and G.GAME.round_resets and G.GAME.round_resets.blind_choices and G.GAME.round_resets.blind_choices[c] and G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].cry_ante_base_mod then if G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult ~= 0 and G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult_ante ~= G.GAME.round_resets.ante then if G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].name == "cry-Obsidian Orb" then for i = 1, #G.GAME.defeated_blinds do G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult = G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult * G.P_BLINDS[G.GAME.defeated_blinds[i]] / 2 end else G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult = 0 end G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult_ante = G.GAME.round_resets.ante end if G.GAME.round_resets.blind_states[c] ~= "Current" and G.GAME.round_resets.blind_states[c] ~= "Defeated" then G.GAME.CRY_BLINDS[c] = (G.GAME.CRY_BLINDS[c] or G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult) + ( G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].cry_ante_base_mod and G.P_BLINDS[G.GAME.round_resets.blind_choices[c]]:cry_ante_base_mod(dt) or 0 ) --Update UI --todo: in blinds screen, too if G.blind_select_opts then if (SMODS.Mods["StrangeLib"] or {}).can_load then StrangeLib.dynablind.blind_choice_scores[c] = get_blind_amount(G.GAME.round_resets.blind_ante) * G.GAME.starting_params.ante_scaling * G.GAME.CRY_BLINDS[c] StrangeLib.dynablind.blind_choice_score_texts[c] = number_format(StrangeLib.dynablind.blind_choice_scores[c]) else local blind_UI = G.blind_select_opts[string.lower(c)].definition.nodes[1].nodes[1].nodes[1].nodes[1] local chip_text_node = blind_UI.nodes[1].nodes[3].nodes[1].nodes[2].nodes[2].nodes[3] if chip_text_node then chip_text_node.config.text = number_format( get_blind_amount(G.GAME.round_resets.blind_ante) * G.GAME.starting_params.ante_scaling * G.GAME.CRY_BLINDS[c] ) chip_text_node.config.scale = score_number_scale( 0.9, get_blind_amount(G.GAME.round_resets.blind_ante) * G.GAME.starting_params.ante_scaling * G.GAME.CRY_BLINDS[c] ) end G.blind_select_opts[string.lower(c)]:recalculate() end end elseif G.GAME.round_resets.blind_states[c] ~= "Defeated" and not G.GAME.blind.disabled and to_big(G.GAME.chips) < to_big(G.GAME.blind.chips) then G.GAME.blind.chips = G.GAME.blind.chips + G.GAME.blind:cry_ante_base_mod(dt) * get_blind_amount(G.GAME.round_resets.ante) * G.GAME.starting_params.ante_scaling G.GAME.blind.chip_text = number_format(G.GAME.blind.chips) end end if G.GAME.round_resets.blind_states[c] == "Current" and G.GAME and G.GAME.blind and not G.GAME.blind.disabled and to_big(G.GAME.chips) < to_big(G.GAME.blind.chips) then G.GAME.blind.chips = G.GAME.blind.chips * (G.GAME.blind.cry_round_base_mod and G.GAME.blind:cry_round_base_mod(dt) or 1) G.GAME.blind.chip_text = number_format(G.GAME.blind.chips) end end if G.STATE == G.STATES.DRAW_TO_HAND and not G.hand.cards[1] and not G.deck.cards[1] and G.PROFILES[G.SETTINGS.profile].cry_none then G.STATE = G.STATES.SELECTING_HAND G.STATE_COMPLETE = false end if G.STATE == G.STATES.NEW_ROUND or G.STATE == G.STATES.HAND_PLAYED then none_eval = none_eval + dt else none_eval = 0 end end -- All the scattered set_cost hooks from all the pre refactor files moved into one hook local sc = Card.set_cost function Card:set_cost() -- Makes the edition cost increase usually present not apply if this variable is true if self.edition and G.GAME.modifiers.cry_no_edition_price then local m = Cryptid.deep_copy(self.edition) self.edition = nil sc(self) self.edition = m else sc(self) end --Makes cube and Big Cube always cost a set amount if self.ability.name == "cry-Cube" then if Card.get_gameset(self) ~= "modest" then self.cost = -27 else self.cost = -12 end end if self.ability.name == "cry-Big Cube" then self.cost = 27 end --Make Tarots free if Tarot Acclimator is redeemed --Make Planets free if Planet Acclimator is redeemed if self.ability.set == "Tarot" and G.GAME.used_vouchers.v_cry_tacclimator then self.cost = 0 end if self.ability.set == "Planet" and G.GAME.used_vouchers.v_cry_pacclimator then self.cost = 0 end --Multiplies voucher cost by G.GAME.modifiers.cry_voucher_price_hike --Used by bronze stake to make vouchers %50 more expensive if self.ability.set == "Voucher" and G.GAME.modifiers.cry_voucher_price_hike then self.cost = math.floor(self.cost * G.GAME.modifiers.cry_voucher_price_hike) end --Clone Tag for i = 1, #G.GAME.tags do if G.GAME.tags[i].key == "tag_cry_clone" then self.cost = self.cost * 1.5 break end end --Update related costs self.sell_cost = math.max(1, math.floor(self.cost / 2)) + (self.ability.extra_value or 0) if self.area and self.ability.couponed and (self.area == G.shop_jokers or self.area == G.shop_booster) and self.ability.name ~= "cry-Cube" then self.cost = 0 end --Makes Cursed Jokers always sell for $0 if self.config and self.config.center and self.config.center.rarity == "cry_cursed" then self.sell_cost = 0 --Rotten Egg elseif G.GAME.cry_rotten_amount then self.sell_cost = G.GAME.cry_rotten_amount end self.sell_cost_label = self.facing == "back" and "?" or self.sell_cost end local sell_card_stuff = Card.sell_card function Card:sell_card() if self.config.center.set == "Joker" then if self.config.center.key ~= "j_cry_necromancer" then local contained = false for _, v in ipairs(G.GAME.jokers_sold) do if v == self.config.center.key then contained = true break end end if not contained then table.insert(G.GAME.jokers_sold, self.config.center.key) end end -- Add Jolly Joker to the pool if card was treated as Jolly Joker if self:is_jolly() then local contained = false for _, v in ipairs(G.GAME.jokers_sold) do if v == "j_jolly" then contained = true break end end if not contained then table.insert(G.GAME.jokers_sold, "j_jolly") end end end sell_card_stuff(self) end -- Modify to display badges for credits and some gameset badges -- todo: make this optional local smcmb = SMODS.create_mod_badges function SMODS.create_mod_badges(obj, badges) smcmb(obj, badges) if not SMODS.config.no_mod_badges and 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 local id = obj and obj.mod and obj.mod.id if Cryptid.safe_get(G, "ACTIVE_MOD_UI", "id") == id and obj and not obj.force_gameset and Cryptid.mod_gameset_whitelist[id] then local set = Cryptid.gameset(obj) if set == "disabled" or obj.set == "Content Set" then return end local card_type = localize("cry_gameset_" .. Cryptid.gameset(obj)) if card_type == "ERROR" then card_type = localize("cry_gameset_custom") end badges[#badges + 1] = create_badge( card_type, set == "modest" and G.C.GREEN or set == "mainline" and G.C.RED or set == "madness" and G.C.CRY_EXOTIC or G.C.CRY_ASCENDANT ) end end -- This is short enough that I'm fine overriding it function calculate_reroll_cost(skip_increment) local limit = G.GAME.reroll_limit_buffer or nil if not limit then if next(find_joker("cry-candybuttons")) then limit = 1 elseif G.GAME.used_vouchers.v_cry_rerollexchange then limit = 2 end end if not G.GAME.current_round.free_rerolls or 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 limit and (G.GAME.round_resets.temp_reroll_cost or G.GAME.round_resets.reroll_cost) + G.GAME.current_round.reroll_cost_increase >= limit then G.GAME.current_round.reroll_cost_increase = 0 G.GAME.current_round.reroll_cost = limit 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 local create_card_ref = create_card 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(Cryptid.predict_pseudoseed(x)) end ps = Cryptid.predict_pseudoseed end if (_type == "Joker" or _type == "Meme") 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 Cryptid.no(center, "doe") and not (center.rarity == "cry_exotic") end if _type == "Joker" and not _rarity and not legendary 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 if _type == "Base" then forced_key = "c_base" end if forced_key and not G.GAME.banned_keys[forced_key] then _type = (G.P_CENTERS[forced_key].set ~= "Default" and G.P_CENTERS[forced_key].set or _type) end local front = (SMODS.set_create_card_front and (_type == "Base" or _type == "Enhanced")) or nil if area == "ERROR" then local ret = (front or G.P_CENTERS[forced_key] or G.P_CENTERS.b_red) 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 = create_card_ref(_type, area, legendary, _rarity, skip_materialize, soulable, forced_key, key_append) local center = card and card.config and card.config.center or {} 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) card:add_to_deck() end if front and G.GAME.modifiers.cry_force_seal then card:set_seal(G.GAME.modifiers.cry_force_seal) 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 SMODS.is_eternal(card) 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 end if (card.ability.set == "Code") and G.GAME.used_vouchers.v_cry_quantum_computing then local tot = 0 for k, v in pairs(SMODS.find_card("v_cry_quantum_computing")) do tot = tot + v.ability.extra end if card.ability.cry_multiuse then card.ability.cry_multiuse = math.ceil((card.ability.cry_multiuse + tot)) else card.ability.cry_multiuse = tot + 1 end 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 = Cryptid.poll_random_edition() card:set_edition(edition, true) end if not (card.edition and (card.edition.cry_oversat or card.edition.cry_glitched)) then Cryptid.manipulate(card) end if card.ability.set == "Joker" and G.GAME.modifiers.cry_common_value_quad then if card.config.center.rarity == 1 then Cryptid.manipulate(card, { value = 4 }) end end if card.ability.set == "Joker" and G.GAME.modifiers.cry_uncommon_value_quad then if card.config.center.rarity == 2 then Cryptid.manipulate(card, { value = 4 }) 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 return card end local at = add_tag function add_tag(tag) at(tag) -- Update Costs for Clone Tag if tag.name == "cry-Clone Tag" then for k, v in pairs(G.I.CARD) do if v.set_cost then v:set_cost() end end end -- Make tags fit if there's more than 13 of them -- This + Tag.remove Hook modify the offset to squeeze in more tags when needed 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 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() -- I don't remember exactly what this patch was for, perhaps issues with syncing hand size with jokers like Effarcire? G.hand:change_size(0) nr() -- Reset Semicolon G.GAME.current_round.semicolon = false -- Force losses if Rush hour is played with clock and lavender loop disabled if G.GAME.modifiers.cry_rush_hour then if not (Cryptid.enabled("bl_cry_clock") == true) or not (Cryptid.enabled("bl_cry_lavender_loop") == true) then G.E_MANAGER:add_event(Event({ func = function() if G.STAGE == G.STAGES.RUN then G.STATE = G.STATES.GAME_OVER G.STATE_COMPLETE = false end print(localize("rush_hour_reminder")) return true end, })) end end end local stamp_can_play = G.FUNCS.can_play G.FUNCS.can_play = function(e) local value = 0 -- Allow 0 card hand to always be played if none is unlocked and poker hands aren't disabled if Cryptid.enabled("set_cry_poker_hand_stuff") == true and G.PROFILES[G.SETTINGS.profile].cry_none then value = -1 end -- Prevent 1 card hand from being played if Sapphire Stamp is active and poker hands aren't enabled (would result in 0 card hand) if G.GAME.stamp_mod and Cryptid.enabled("set_cry_poker_hand_stuff") ~= true then value = 1 end if value == 0 then stamp_can_play(e) else if #G.hand.highlighted <= value or G.GAME.blind.block_play or #G.hand.highlighted > math.max(G.GAME.starting_params.play_limit, 1) then e.config.colour = G.C.UI.BACKGROUND_INACTIVE e.config.button = nil else e.config.colour = G.C.BLUE e.config.button = "play_cards_from_highlighted" end end end local stamp_can_discard = G.FUNCS.can_discard G.FUNCS.can_discard = function(e) local value = 0 -- Allow 0 card hand to always be discarded if none is unlocked and poker hands aren't disabled if Cryptid.enabled("set_cry_poker_hand_stuff") == true and G.PROFILES[G.SETTINGS.profile].cry_none then value = -1 end if value == 0 then stamp_can_discard(e) else if G.GAME.current_round.discards_left <= 0 or #G.hand.highlighted <= value or #G.hand.highlighted > math.max(G.GAME.starting_params.discard_limit, 0) then e.config.colour = G.C.UI.BACKGROUND_INACTIVE e.config.button = nil else e.config.colour = G.C.RED e.config.button = "discard_cards_from_highlighted" end end end -- These allow jokers that add joker slots to be obtained even without room, like with Negative Jokers in vanilla 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.slots >= 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.slots >= 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 --Cryptid (THE MOD) localization local function parse_loc_txt(center) center.text_parsed = {} if center.text then 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() 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" -- why is this done THREE times??? 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 il() if Cryptid.object_buffer and Cryptid.object_buffer.Stake then for i = 1, #Cryptid.object_buffer.Stake do local key = Cryptid.object_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 for _, group in pairs(G.localization.descriptions) do if _ ~= "Back" and _ ~= "Content Set" and _ ~= "Edition" and _ ~= "Enhanced" and _ ~= "Stake" and _ ~= "Other" then for key, card in pairs(group) do if G.P_CENTERS[key] then Cryptid.pointeraliasify(key, card.name, true) end end end end Cryptid.inject_pointer_aliases() 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 -- Lemon Trophy's effect local trophy_mod_mult = mod_mult function mod_mult(_mult) hand_chips = hand_chips or 0 if G.GAME.trophymod then _mult = math.min(_mult, math.max(hand_chips, 0)) end return trophy_mod_mult(_mult) 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 -- add second back button to create_UIBox_generic_options local cuigo = create_UIBox_generic_options function create_UIBox_generic_options(args) local ret = cuigo(args) if args.back2 then local mainUI = ret.nodes[1].nodes[1].nodes mainUI[#mainUI + 1] = { n = G.UIT.R, config = { id = args.back2_id or "overlay_menu_back2_button", align = "cm", minw = 2.5, button_delay = args.back2_delay, padding = 0.1, r = 0.1, hover = true, colour = args.back2_colour or G.C.ORANGE, button = args.back2_func or "exit_overlay_menu", shadow = true, focus_args = { nav = "wide", button = "b", snap_to = args.snap_back2 }, }, nodes = { { n = G.UIT.R, config = { align = "cm", padding = 0, no_fill = true }, nodes = { { n = G.UIT.T, config = { id = args.back2_id or nil, text = args.back2_label or localize("b_back"), scale = 0.5, colour = G.C.UI.TEXT_LIGHT, shadow = true, func = not args.no_pip and "set_button_pip" or nil, focus_args = not args.no_pip and { button = args.back2_button or "b" } or nil, }, }, }, }, }, } end return ret end local scuref = set_consumeable_usage function set_consumeable_usage(card) if not G.GAME.cry_last_used_consumeables then G.GAME.cry_last_used_consumeables = {} end for i = 1, #G.GAME.cry_last_used_consumeables do if not G.GAME.cry_function_stupid_workaround then G.GAME.cry_function_stupid_workaround = {} end G.GAME.cry_function_stupid_workaround[i] = G.GAME.cry_last_used_consumeables[i] end local nextindex = #G.GAME.cry_last_used_consumeables + 1 G.GAME.cry_last_used_consumeables[nextindex] = card.config.center_key if nextindex > 3 then table.remove(G.GAME.cry_last_used_consumeables, 1) end scuref(card) end --Abstract cards: Fix to avoid "ghost cards", as aresult of destroying discarded cards by adding a flag checcking its not destroyed G.FUNCS.draw_from_discard_to_deck = function(e) G.E_MANAGER:add_event(Event({ trigger = "immediate", func = function() local discard_count = #G.discard.cards for i = 1, discard_count do --draw cards from deck local card = G.discard.cards[i] if not card.shattered and not card.destroyed then draw_card( G.discard, G.deck, i * 100 / discard_count, "up", nil, card, 0.005, i % 2 == 0, nil, math.max((21 - i) / 20, 0.7) ) end end return true end, })) end --Add a hook to getID for abstracts (and to conditionally enable the check) local getIDenhance = Card.get_id function Card:get_id() --Force suit to be suit X if specified in enhancement, only if not vampired if Cryptid.cry_enhancement_has_specific_rank(self) and not self.vampired then --Get the max value + 1, to always be the last at the list return SMODS.Rank.max_id.value + 1 end local vars = getIDenhance(self) return vars end --override shatter function to adjust volume (it has been requested that at end of deck, abstract cards should shatter a bit quieter) function Card:shatter(volume) local dissolve_time = 0.7 self.shattered = true self.dissolve = 0 self.dissolve_colours = { { 1, 1, 1, 0.8 } } self:juice_up() local childParts = Particles(0, 0, 0, 0, { timer_type = "TOTAL", timer = 0.007 * dissolve_time, scale = 0.3, speed = 4, lifespan = 0.5 * dissolve_time, attach = self, colours = self.dissolve_colours, fill = true, }) G.E_MANAGER:add_event(Event({ trigger = "after", blockable = false, delay = 0.5 * dissolve_time, func = function() childParts:fade(0.15 * dissolve_time) return true end, })) G.E_MANAGER:add_event(Event({ blockable = false, func = function() play_sound("glass" .. math.random(1, 6), math.random() * 0.2 + 0.9, volume or 0.5) play_sound("generic1", math.random() * 0.2 + 0.9, volume or 0.5) return true end, })) G.E_MANAGER:add_event(Event({ trigger = "ease", blockable = false, ref_table = self, ref_value = "dissolve", ease_to = 1, delay = 0.5 * dissolve_time, func = function(t) return t end, })) G.E_MANAGER:add_event(Event({ trigger = "after", blockable = false, delay = 0.55 * dissolve_time, func = function() self:remove() return true end, })) G.E_MANAGER:add_event(Event({ trigger = "after", blockable = false, delay = 0.51 * dissolve_time, })) end -- Buttercup's store joker mechanic, creates a specified joker local ccfs = create_card_for_shop function create_card_for_shop(area) local guaranteed_card = Card( area.x, area.y, G.CARD_W, G.CARD_H, nil, G.P_CENTERS.j_jolly, { bypass_discovery_center = true, bypass_discovery_ui = true } ) local areas_to_check = { shop_jokers = G.shop_jokers, shop_vouchers = G.shop_vouchers, shop_booster = G.shop_booster, } local loaded_card_data = nil local loaded_card_pos = -1 -- check if there's a card for `area` within `next_shop_cards`, -- then put its data in `loaded_card_data` and its index in the table in `loaded_card_pos` if G.GAME.next_shop_cards and #G.GAME.next_shop_cards > 0 then for i, card in ipairs(G.GAME.next_shop_cards) do if not card.cry_from_shop then card.cry_from_shop = "shop_jokers" end -- failsafe :3 if areas_to_check[card.cry_from_shop] == area and loaded_card_pos == -1 then loaded_card_data = card loaded_card_pos = i break elseif areas_to_check[card.cry_from_shop] ~= G.shop_jokers then local other_card = Card( area.x, area.y, G.CARD_W, G.CARD_H, nil, G.P_CENTERS.j_jolly, { bypass_discovery_center = true, bypass_discovery_ui = true } ) other_card:load(card, nil) other_card.VT.h = other_card.T.h table.remove(G.GAME.next_shop_cards, i) create_shop_card_ui( other_card, G.P_CENTERS[card.save_fields.center], set, areas_to_check[card.cry_from_shop] ) areas_to_check[card.cry_from_shop]:emplace(other_card) other_card.states.visible = false G.E_MANAGER:add_event(Event({ delay = 0.4, trigger = "after", func = function() other_card:start_materialize() other_card:set_cost() return true end, })) other_card:set_cost() end end end if loaded_card_data then -- guaranteed_card.T.h = G.CARD_H guaranteed_card:load(loaded_card_data, nil) guaranteed_card.VT.h = guaranteed_card.T.h table.remove(G.GAME.next_shop_cards, loaded_card_pos) create_shop_card_ui(guaranteed_card, "Joker", area) guaranteed_card.states.visible = false G.E_MANAGER:add_event(Event({ delay = 0.4, trigger = "after", func = function() guaranteed_card:start_materialize() guaranteed_card:set_cost() return true end, })) guaranteed_card:set_cost() return guaranteed_card else guaranteed_card:remove() end return ccfs(area) end -- Again, buttercup, making sure you can savescum safely :gjumbsup: local carsv = Card.save function Card:save() local saved_table = carsv(self) if self.cry_storage then saved_table.cry_storage = self.cry_storage:save() end if self.cry_from_shop then saved_table.cry_from_shop = self.cry_from_shop end return saved_table end local carld = Card.load function Card:load(cardTable, other_card) carld(self, cardTable, other_card) local storage_area_config = { type = "play", card_w = G.CARD_W, } if cardTable.cry_storage then self.cry_storage = CardArea(self.T.x, 2, 1, 1, storage_area_config) self.cry_storage:load(cardTable.cry_storage) for i, card in ipairs(self.cry_storage.cards) do card.T.orig = { w = card.T.w, h = card.T.h } card.T.w = card.T.w * 0.5 card.T.h = card.T.h * 0.5 end end if cardTable.cry_from_shop then self.cry_from_shop = cardTable.cry_from_shop end end -- Attach Buttercup's stored cards card area local carmv = Card.move function Card:move(dt) carmv(self, dt) if self.cry_storage ~= nil and self.cry_storage.cards ~= nil then self.cry_storage.config.card_limit = #self.cry_storage.cards + 1 self.cry_storage.T.w = G.CARD_W * 2 self.cry_storage.T.x = self.T.x - (G.CARD_W * 0.5) self.cry_storage.T.y = self.T.y self.cry_storage.VT.x = self.VT.x self.cry_storage.VT.y = self.VT.y end end --Hook for booster skip to automatically destroy and banish the rightmost Joker, regardless of eternal local banefulSkipPenalty = G.FUNCS.skip_booster G.FUNCS.skip_booster = function(e) --Imported from my Epic Decision and also works in Polterworx and with unpleasant card, in the event youc an still skip with all eternals/cursed jokers local obj = SMODS.OPENED_BOOSTER.config.center -- local obj2 = G.P_BLINDS[G.GAME.round_resets.blind_choices.Boss] if obj.unskippable and type(obj.unskippable) == "function" and obj:unskippable() == true then if G.GAME.blind then --Unplesant card will continously spam, so that will do for now without patching that; it is "unpleasant" after all; -- play_sound('cancel', 0.8, 1) -- local text = localize('k_nope_ex') -- attention_text({ -- scale = 0.9, text = text, hold = 0.75, align = 'cm', offset = {x = 0,y = -2.7},major = G.play,colour = obj2.boss_colour or G.C.RED -- }) G.GAME.blind:wiggle() G.GAME.blind.triggered = true end if e and e.disable_button then e.disable_button = nil -- print("disble") end else if SMODS.OPENED_BOOSTER.config.center.cry_baneful_punishment then if not G.GAME.banned_keys then G.GAME.banned_keys = {} end -- i have no idea if this is always initialised already tbh if not G.GAME.cry_banned_pcards then G.GAME.cry_banished_keys = {} end local c = nil c = G.jokers.cards[#G.jokers.cards] --fallback to rightmost if somehow, you skipped without disabling and its unskippable. --Iterate backwards to get the rightmost valid (non eternal or cursed) Joker if G.jokers and G.jokers.cards then for i = #G.jokers.cards, 1, -1 do if not ( SMODS.is_eternal(G.jokers.cards[i]) or G.jokers.cards[i].config.center.rarity == "cry_cursed" ) then c = G.jokers.cards[i] break end end end if c.config.center.rarity == "cry_exotic" then check_for_unlock({ type = "what_have_you_done" }) end G.GAME.cry_banished_keys[c.config.center.key] = true if G.GAME.blind then G.GAME.blind:wiggle() G.GAME.blind.triggered = true end c:start_dissolve() end banefulSkipPenalty(e) end end --Overriding the skip booster function. G.FUNCS.can_skip_booster = function(e) if G.pack_cards and not (G.GAME.STOP_USE and G.GAME.STOP_USE > 0) and ( G.STATE == G.STATES.SMODS_BOOSTER_OPENED or G.STATE == G.STATES.PLANET_PACK or G.STATE == G.STATES.STANDARD_PACK or G.STATE == G.STATES.BUFFOON_PACK or G.hand ) then --if a booster is unskippable (when its unskippable conditionsa re fulfilled), unhighlight it local obj = SMODS.OPENED_BOOSTER.config.center if obj.unskippable and type(obj.unskippable) == "function" then if obj:unskippable() == true then e.config.colour = G.C.UI.BACKGROUND_INACTIVE e.config.button = nil else e.config.colour = G.C.GREY e.config.button = "skip_booster" end else e.config.colour = G.C.GREY e.config.button = "skip_booster" end else e.config.colour = G.C.UI.BACKGROUND_INACTIVE e.config.button = nil end end --none stuff local set_blindref = Blind.set_blind function Blind:set_blind(blind, reset, silent) set_blindref(self, blind, reset, silent) if G.GAME.hands["cry_None"].visible and (G.STATE == G.STATES.SELECTING_HAND or G.STATE == G.STATES.DRAW_TO_HAND) and #G.hand.highlighted == 0 then G.E_MANAGER:add_event(Event({ trigger = "after", func = function() Cryptid.reset_to_none() return true end, })) end end local end_roundref = end_round function end_round() if ((#G.hand.cards < 1 and #G.deck.cards < 1 and #G.play.cards < 1) or (#G.hand.cards < 1 and #G.deck.cards < 1)) and G.STATE == G.STATES.SELECTING_HAND then if Cryptid.enabled("set_cry_poker_hand_stuff") == true and not Cryptid.safe_get(G.PROFILES, G.SETTINGS.profile, "cry_none") then G.PROFILES[G.SETTINGS.profile].cry_none = true end if not Cryptid.enabled("set_cry_poker_hand_stuff") then end_roundref() end else end_roundref() end G.E_MANAGER:add_event(Event({ trigger = "after", func = function() update_hand_text({ delay = 0 }, { mult = 0, chips = 0, handname = "", level = "" }) return true end, })) end local after_ref = evaluate_play_after function evaluate_play_after(text, disp_text, poker_hands, scoring_hand, non_loc_disp_text, percent, percent_delta) local ret = after_ref(text, disp_text, poker_hands, scoring_hand, non_loc_disp_text, percent, percent_delta) if G.GAME.hands["cry_None"].visible then G.reset_to_none = true end return ret end local update_handref = Game.update_selecting_hand function Game:update_selecting_hand(dt) local ret = update_handref(self, dt) if G.reset_to_none then G.E_MANAGER:add_event(Event({ trigger = "after", func = function() Cryptid.reset_to_none() return true end, })) G.reset_to_none = nil end return ret end local blind_loadref = Blind.load function Blind:load(blindTable) blind_loadref(self, blindTable) if G.GAME.hands["cry_None"].visible and self.blind_set and (G.STATE == G.STATES.SELECTING_HAND or G.STATE == G.STATES.DRAW_TO_HAND) then G.E_MANAGER:add_event(Event({ trigger = "after", func = function() Cryptid.reset_to_none() return true end, })) end end local evaluate_ref = G.FUNCS.evaluate_round G.FUNCS.evaluate_round = function() evaluate_ref() update_hand_text({ delay = 0 }, { mult = 0, chips = 0, handname = "", level = "" }) G.E_MANAGER:add_event(Event({ trigger = "after", func = function() update_hand_text({ delay = 0 }, { mult = 0, chips = 0, handname = "", level = "" }) return true end, })) end local discard_ref = G.FUNCS.discard_cards_from_highlighted G.FUNCS.discard_cards_from_highlighted = function(e, hook) --Labyrinth: set current_round_discards_used to 0 for effects G.GAME.current_round.discards_used2 = G.GAME.current_round.discards_used if next(find_joker("cry-maze")) then G.GAME.current_round.discards_used = 0 end discard_ref(e, hook) local highlighted_count = math.min(#G.hand.highlighted, G.discard.config.card_limit - #G.play.cards) if highlighted_count <= 0 then table.sort(G.hand.highlighted, function(a, b) return a.T.x < b.T.x end) check_for_unlock({ type = "discard_custom", cards = {} }) for j = 1, #G.jokers.cards do G.jokers.cards[j]:calculate_joker({ pre_discard = true, full_hand = G.hand.highlighted, hook = hook }) end if not hook then if G.GAME.modifiers.discard_cost then ease_dollars(-G.GAME.modifiers.discard_cost) end ease_discard(-1) G.GAME.current_round.discards_used = G.GAME.current_round.discards_used + 1 G.STATE = G.STATES.DRAW_TO_HAND G.E_MANAGER:add_event(Event({ trigger = "immediate", func = function() G.STATE_COMPLETE = false return true end, })) end end if G.GAME.hands["cry_None"].visible then G.E_MANAGER:add_event(Event({ trigger = "after", func = function() Cryptid.reset_to_none() return true end, })) end --Labyrinth: return current_round_discards_used back to the amount it is supposed to be after G.GAME.current_round.discards_used = G.GAME.current_round.discards_used2 + 1 end local play_ref = G.FUNCS.play_cards_from_highlighted G.FUNCS.play_cards_from_highlighted = function(e) --Labyrinth: set current_round_hands played to 0 for effects G.E_MANAGER:add_event(Event({ trigger = "immediate", func = function() G.GAME.current_round.hands_played2 = G.GAME.current_round.hands_played if next(find_joker("cry-maze")) then G.GAME.current_round.hands_played = 0 end return true end, })) G.GAME.before_play_buffer = true -- None Stuff if G.GAME.stamp_mod and not G.PROFILES[G.SETTINGS.profile].cry_none and #G.hand.highlighted == 1 then G.PROFILES[G.SETTINGS.profile].cry_none = true print("nonelock stuff here") G.GAME.hands["cry_None"].visible = true end if G.PROFILES[G.SETTINGS.profile].cry_none and #G.hand.highlighted == 0 then G.GAME.hands["cry_None"].visible = true end --Add blind context for Just before cards are played G.GAME.blind:cry_before_play() play_ref(e) --Labyrinth: return current_round_hands played to the amount it is supposed to be at after G.E_MANAGER:add_event(Event({ trigger = "immediate", func = function() G.E_MANAGER:add_event(Event({ trigger = "after", delay = 0.1, func = function() G.GAME.current_round.hands_played = G.GAME.current_round.hands_played2 + 1 return true end, })) return true end, })) G.GAME.before_play_buffer = nil end local use_cardref = G.FUNCS.use_card G.FUNCS.use_card = function(e, mute, nosave) use_cardref(e, mute, nosave) if G.STATE == G.STATES.SELECTING_HAND then G.E_MANAGER:add_event(Event({ trigger = "after", func = function() G.hand:parse_highlighted() return true end, })) else update_hand_text({ delay = 0 }, { mult = 0, chips = 0, handname = "", level = "" }) end end local emplace_ref = CardArea.emplace function CardArea:emplace(card, location, stay_flipped) return emplace_ref(self, card or {}, location, stay_flipped) end -- Added by IcyEthics: Adding a hook to the shuffle function so that there can be a context to modify randomization -- Any card using this will most likely want to use cry_post_shuffle. -- added cry_pre_shuffle for posterity local o_ca_shuffle = CardArea.shuffle function CardArea:shuffle(_seed) SMODS.calculate_context({ cry_shuffling_area = true, cardarea = self, cry_pre_shuffle = true }) o_ca_shuffle(self, _seed) SMODS.calculate_context({ cry_shuffling_area = true, cardarea = self, cry_post_shuffle = true }) end local smods_four_fingers = SMODS.four_fingers function SMODS.four_fingers() return smods_four_fingers() - Cryptid.get_paved_joker() end function Cryptid.create_dummy_from_stone(rank) local r = tostring(rank) rank = SMODS.Ranks[r].id return { get_id = function() return rank end, config = { center = {}, }, ability = {}, base = { id = rank, value = rank >= 11 and "Queen" or "10", }, } end function Cryptid.next_ranks(key, start, recurse) key = ({ ["14"] = "Ace", ["13"] = "King", ["12"] = "Queen", ["11"] = "Jack", })[tostring(key)] or key local rank = SMODS.Ranks[tostring(key)] local ret = {} if not rank or (not start and not wrap and rank.straight_edge) then return ret end for _, v in ipairs(rank.next) do ret[#ret + 1] = v local curr = #ret if recurse and recurse > 0 then for i, v in pairs(Cryptid.next_ranks(ret[#ret], start, recurse - 1)) do ret[#ret + 1] = v end end end return ret end local function append(t, new) local clone = {} for _, item in ipairs(t) do clone[#clone + 1] = item end clone[#clone + 1] = new return clone end function Cryptid.unique_combinations(tbl, sub, min) sub = sub or {} min = min or 1 local wrap, yield = coroutine.wrap, coroutine.yield return wrap(function() if #sub > 0 then yield(sub) -- yield short combination. end if #sub < #tbl then for i = min, #tbl do -- iterate over longer combinations. for combo in Cryptid.unique_combinations(tbl, append(sub, tbl[i]), i + 1) do yield(combo) end end end end) end get_straight_ref = get_straight function get_straight(hand, min_length, skip, wrap) local permutations = {} local ranks = {} local cards = {} local stones = Cryptid.get_paved_joker() if stones > 0 then for i, v in pairs(hand) do if v.config.center.key ~= "m_stone" then cards[#cards + 1] = v for i, v in pairs(Cryptid.next_ranks(v:get_id(), nil, stones)) do --this means its inaccurate in some situations like K S S S S but its fine there isnt a better way ranks[v] = true end end if v:get_id() >= 11 then new_ranks = { "Ace", "King", "Queen", "Jack", 10, } for i, v in pairs(new_ranks) do ranks[v] = true end end end local rranks = {} for i, v in pairs(ranks) do rranks[#rranks + 1] = i end for i, v in Cryptid.unique_combinations(rranks) do if #i == stones then permutations[#permutations + 1] = i end end for i, v in ipairs(permutations) do local actual = {} local ranks = {} for i, v in pairs(cards) do actual[#actual + 1] = v ranks[v:get_id()] = true end for i, p in pairs(v) do local d = Cryptid.create_dummy_from_stone(p) if not ranks[d:get_id()] then actual[#actual + 1] = d end end local ret = get_straight_ref(actual, min_length + stones, skip, true) if ret and #ret > 0 then return ret end end end return get_straight_ref(hand, min_length + stones, skip, wrap) end local get_prob_vars_ref = SMODS.get_probability_vars function SMODS.get_probability_vars(trigger_obj, base_numerator, base_denominator, identifier, from_roll) local mod = trigger_obj and trigger_obj.ability and trigger_obj.ability.cry_prob or 1 local numerator = base_numerator * mod if trigger_obj and trigger_obj.ability and trigger_obj.ability.cry_rigged then numerator = base_denominator end return get_prob_vars_ref(trigger_obj, numerator, base_denominator, identifier, from_roll) end local pseudorandom_probability_ref = SMODS.pseudorandom_probability function SMODS.pseudorandom_probability(trigger_obj, seed, base_numerator, base_denominator, identifier) local mod = trigger_obj and trigger_obj.ability and trigger_obj.ability.cry_prob or 1 local numerator = base_numerator * mod if trigger_obj and trigger_obj.ability and trigger_obj.ability.cry_rigged then SMODS.post_prob = SMODS.post_prob or {} SMODS.post_prob[#SMODS.post_prob + 1] = { pseudorandom_result = true, result = true, trigger_obj = trigger_obj, numerator = base_denominator, denominator = base_denominator, identifier = identifier or seed, } return true end return pseudorandom_probability_ref(trigger_obj, seed, numerator, base_denominator, identifier) end local is_eternalref = SMODS.is_eternal function SMODS.is_eternal(card) if Cryptid.safe_get(card, "ability", "cry_absolute") then return true end return is_eternalref(card) end local unlock_allref = G.FUNCS.unlock_all G.FUNCS.unlock_all = function(e) unlock_allref(e) G.PROFILES[G.SETTINGS.profile].cry_none = (Cryptid.enabled("set_cry_poker_hand_stuff") == true) end -- Calc ante gain for The Joke (Scuffed) local smods_calc = SMODS.calculate_context function SMODS.calculate_context(context, return_table, no_resolve) local aaa = smods_calc(context, return_table, no_resolve) if context.modify_ante and context.ante_end then if G.GAME.blind then aaa.modify = G.GAME.blind:cry_calc_ante_gain() - 1 end end return aaa end