--- 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----------------------
