balatro-mods/Cryptid/lib/overrides.lua
2025-04-13 17:53:04 +08:00

1353 lines
42 KiB
Lua

-- overrides.lua - Adds hooks and overrides used by multiple features.
-- get_currrent_pool hook for Deck of Equilibrium and Copies
local gcp = get_current_pool
function get_current_pool(_type, _rarity, _legendary, _append, override_equilibrium_effect)
if type == "Tag" then
for i = 1, #pool do
-- Copies: Turn Double tags into Triple Tags
if pool[i] == "tag_double" and G.GAME.used_vouchers.v_cry_copies then
pool[i] = "tag_cry_triple"
end
-- Tag Printer: Turn Double tags and Triple Tags into Quadruple Tags
if (pool[i] == "tag_double" or pool[i] == "tag_cry_triple") and G.GAME.used_vouchers.v_cry_tag_printer then
pool[i] = "tag_cry_quadruple"
end
-- Clone Machine: Turn Double tags and Triple Tags as well as Quadruple Tags into Quintuple Tags
if
(pool[i] == "tag_double" or pool[i] == "tag_cry_triple" or pool[i] == "tag_cry_quadruple")
and G.GAME.used_vouchers.v_cry_clone_machine
then
pool[i] = "tag_cry_quintuple"
end
end
-- Deck of Equilibrium stuff
elseif
G.GAME.modifiers.cry_equilibrium
and not override_equilibrium_effect
and (_append == "sho" or _type == "Voucher" or _type == "Booster")
then
if
_type ~= "Enhanced"
and _type ~= "Edition"
and _type ~= "Back"
and _type ~= "Tag"
and _type ~= "Seal"
and _type ~= "Stake"
then
-- we're regenerating the pool every time because of banned keys but it's fine tbh
P_CRY_ITEMS = {}
local valid_pools = { "Joker", "Consumeables", "Voucher", "Booster" }
for _, id in ipairs(valid_pools) do
for k, v in pairs(G.P_CENTER_POOLS[id]) do
if
v.unlocked == true
and not Cryptid.no(v, "doe", k)
and not (G.GAME.banned_keys[v.key] or G.GAME.cry_banished_keys[v.key])
then
P_CRY_ITEMS[#P_CRY_ITEMS + 1] = v.key
end
end
end
if #P_CRY_ITEMS <= 0 then
P_CRY_ITEMS[#P_CRY_ITEMS + 1] = "v_blank"
end
return P_CRY_ITEMS, "cry_equilibrium" .. G.GAME.round_resets.ante
end
end
return gcp(_type, _rarity, _legendary, _append)
end
local gnb = get_new_boss
function get_new_boss()
--Fix an issue with adding bosses mid-run
for k, v in pairs(G.P_BLINDS) do
if not G.GAME.bosses_used[k] then
G.GAME.bosses_used[k] = 0
end
end
--This is how nostalgic deck replaces the boss blinds with Nostalgic versions
local bl = gnb()
if G.GAME.modifiers.cry_beta then
local bl_key = string.sub(bl, 4)
local nostalgicblinds = {
arm = (Cryptid.enabled("bl_cry_oldarm") == true),
fish = (Cryptid.enabled("bl_cry_oldfish") == true),
flint = (Cryptid.enabled("bl_cry_oldflint") == true),
house = (Cryptid.enabled("bl_cry_oldhouse") == true),
manacle = (Cryptid.enabled("bl_cry_oldmanacle") == true),
mark = (Cryptid.enabled("bl_cry_oldmark") == true),
ox = (Cryptid.enabled("bl_cry_oldox") == true),
pillar = (Cryptid.enabled("bl_cry_oldpillar") == true),
serpent = (Cryptid.enabled("bl_cry_oldserpent") == true),
}
if nostalgicblinds[bl_key] then
return "bl_cry_old" .. bl_key
end
end
return bl
end
--Add context for after cards are played
local gfep = G.FUNCS.evaluate_play
function G.FUNCS.evaluate_play(e)
gfep(e)
G.GAME.blind:cry_after_play()
end
--Add context for Just before cards are played
local pcfh = G.FUNCS.play_cards_from_highlighted
function G.FUNCS.play_cards_from_highlighted(e)
G.GAME.before_play_buffer = true
G.GAME.blind:cry_before_play()
pcfh(e)
G.GAME.before_play_buffer = nil
end
--Track defeated blinds for Obsidian Orb
local dft = Blind.defeat
function Blind:defeat(s)
dft(self, s)
local obj = self.config.blind
-- Ignore blinds with loc_vars because orb does not properly work with them yet
if obj.boss and (obj.boss.no_orb or obj.boss.epic or obj.loc_vars) then
return
end
if
self.name ~= "cry-Obsidian Orb"
--Stop impossible blind combinations from happening
and self.name ~= "The Sink"
and (self.name ~= "cry-oldarm" or not G.GAME.defeated_blinds["bl_psychic"])
and (self.name ~= "The Psychic" or not G.GAME.defeated_blinds["bl_cry_oldarm"])
and (self.name ~= "The Eye" or not G.GAME.defeated_blinds["bl_mouth"])
and (self.name ~= "The Mouth" or not G.GAME.defeated_blinds["bl_eye"])
and (self.name ~= "cry-Lavender Loop" or not G.GAME.defeated_blinds["bl_cry_tax"])
and (self.name ~= "cry-Tax" or not G.GAME.defeated_blinds["bl_cry_lavender_loop"])
and (self.name ~= "The Needle" or not G.GAME.defeated_blinds["bl_cry_tax"])
and (self.name ~= "cry-Tax" or not G.GAME.defeated_blinds["bl_needle"])
then
G.GAME.defeated_blinds[self.config.blind.key] = true
end
end
local sr = Game.start_run
function Game:start_run(args)
sr(self, args)
if G.P_BLINDS.bl_cry_clock then
G.P_BLINDS.bl_cry_clock.mult = 0
end
if not G.GAME.defeated_blinds then
G.GAME.defeated_blinds = {}
end
end
--patch for multiple Clocks to tick separately and load separately
local bsb = Blind.set_blind
function Blind:set_blind(blind, y, z)
local c = "Boss"
if string.sub(G.GAME.subhash or "", -1) == "S" then
c = "Small"
end
if string.sub(G.GAME.subhash or "", -1) == "B" then
c = "Big"
end
if G.GAME.CRY_BLINDS and G.GAME.CRY_BLINDS[c] and not y and blind and blind.mult and blind.cry_ante_base_mod then
blind.mult = G.GAME.CRY_BLINDS[c]
end
bsb(self, blind, y, z)
end
local rb = reset_blinds
function reset_blinds()
if G.GAME.round_resets.blind_states.Boss == "Defeated" then
G.GAME.CRY_BLINDS = {}
if G.P_BLINDS.bl_cry_clock then
G.P_BLINDS.bl_cry_clock.mult = 0
end
end
rb()
end
--Init stuff at the start of the game
local gigo = Game.init_game_object
function Game:init_game_object()
local g = gigo(self)
-- Add initial dropshot and number blocks card
g.current_round.cry_nb_card = { rank = "Ace" }
g.current_round.cry_dropshot_card = { suit = "Spades" }
g.monstermult = 1
-- Create G.GAME.events when starting a run, so there's no errors
g.events = {}
g.jokers_sold = {}
return g
end
-- reset_castle_card hook for things like Dropshot and Number Blocks
local rcc = reset_castle_card
function reset_castle_card()
rcc()
G.GAME.current_round.cry_nb_card = { rank = "Ace" }
if not G.GAME.current_round.cry_dropshot_card then
G.GAME.current_round.cry_dropshot_card = {}
end
G.GAME.current_round.cry_dropshot_card.suit = "Spades"
local valid_castle_cards = {}
for k, v in ipairs(G.playing_cards) do
if not SMODS.has_no_suit(v) then
valid_castle_cards[#valid_castle_cards + 1] = v
end
end
if valid_castle_cards[1] then
--Dropshot
local castle_card = pseudorandom_element(valid_castle_cards, pseudoseed("cry_dro" .. G.GAME.round_resets.ante))
if not G.GAME.current_round.cry_dropshot_card then
G.GAME.current_round.cry_dropshot_card = {}
end
G.GAME.current_round.cry_dropshot_card.suit = castle_card.base.suit
--Number Blocks
local castle_card_two =
pseudorandom_element(valid_castle_cards, pseudoseed("cry_nb" .. G.GAME.round_resets.ante))
if not G.GAME.current_round.cry_nb_card then
G.GAME.current_round.cry_nb_card = {}
end
G.GAME.current_round.cry_nb_card.rank = castle_card_two.base.value
G.GAME.current_round.cry_nb_card.id = castle_card_two.base.id
end
end
-- Back.apply_to_run Hook for decks
local Backapply_to_runRef = Back.apply_to_run
function Back.apply_to_run(self)
Backapply_to_runRef(self)
if self.effect.config.cry_no_edition_price then
G.GAME.modifiers.cry_no_edition_price = true
end
end
--Game:update hook
local upd = Game.update
--init colors so they have references
G.C.CRY_TWILIGHT = { 0, 0, 0, 0 }
G.C.CRY_VERDANT = { 0, 0, 0, 0 }
G.C.CRY_EMBER = { 0, 0, 0, 0 }
G.C.CRY_DAWN = { 0, 0, 0, 0 }
G.C.CRY_HORIZON = { 0, 0, 0, 0 }
G.C.CRY_BLOSSOM = { 0, 0, 0, 0 }
G.C.CRY_AZURE = { 0, 0, 0, 0 }
G.C.CRY_ASCENDANT = { 0, 0, 0, 0 }
G.C.CRY_JOLLY = { 0, 0, 0, 0 }
G.C.CRY_GREENGRADIENT = { 0, 0, 0, 0 }
G.C.CRY_ALTGREENGRADIENT = { 0, 0, 0, 0 }
Cryptid.C = {
EXOTIC = { HEX("708b91"), HEX("1e9eba") },
TWILIGHT = { HEX("0800ff"), HEX("aa00ff") },
VERDANT = { HEX("00ff22"), HEX("f4ff57") },
EMBER = { HEX("ff0000"), HEX("ffae00") },
DAWN = { HEX("00aaff"), HEX("ff00e3") },
HORIZON = { HEX("c8fd09"), HEX("1ee7d9") },
BLOSSOM = { HEX("ff09da"), HEX("ffd121") },
AZURE = { HEX("0409ff"), HEX("63dcff") },
ASCENDANT = { HEX("2e00f5"), HEX("e5001d") },
JOLLY = { HEX("6ec1f5"), HEX("456b84") },
SELECTED = { HEX("e38039"), HEX("ccdd1b") },
GREENGRADIENT = { HEX("51e099"), HEX("1e523a") },
ALTGREENGRADIENT = { HEX("6bb565"), HEX("bd28bf") },
}
cry_pointer_dt = 0
cry_jimball_dt = 0
cry_glowing_dt = 0
function Game:update(dt)
upd(self, dt)
--Gradients based on Balatrostuck code
local anim_timer = self.TIMERS.REAL * 1.5
local p = 0.5 * (math.sin(anim_timer) + 1)
for k, c in pairs(Cryptid.C) do
if not G.C["CRY_" .. k] then
G.C["CRY_" .. k] = { 0, 0, 0, 0 }
end
for i = 1, 4 do
G.C["CRY_" .. k][i] = c[1][i] * p + c[2][i] * (1 - p)
end
end
G.C.RARITY["cry_exotic"] = G.C.CRY_EXOTIC
G.C.SECONDARY_SET["Content Set"] = G.C.CRY_ASCENDANT
-- Idk what this is for
if Incantation and not CryptidIncanCompat then
AllowStacking("Code")
AllowDividing("Code")
CryptidIncanCompat = true
end
if Cryptid.enabled("set_cry_timer") == true then
cry_pointer_dt = cry_pointer_dt + dt
cry_jimball_dt = cry_jimball_dt + dt
cry_glowing_dt = cry_glowing_dt + dt
end
--Update sprite positions each frame on certain cards to give the illusion of an animated card
if G.P_CENTERS and G.P_CENTERS.c_cry_pointer and cry_pointer_dt > 0.5 then
cry_pointer_dt = 0
local pointerobj = G.P_CENTERS.c_cry_pointer
pointerobj.pos.x = (pointerobj.pos.x == 11) and 12 or 11
end
if G.P_CENTERS and G.P_CENTERS.j_cry_jimball and cry_jimball_dt > 0.1 then
cry_jimball_dt = 0
local jimballobj = G.P_CENTERS.j_cry_jimball
if jimballobj.pos.x == 5 and jimballobj.pos.y == 6 then
jimballobj.pos.x = 0
jimballobj.pos.y = 0
elseif jimballobj.pos.x < 8 then
jimballobj.pos.x = jimballobj.pos.x + 1
elseif jimballobj.pos.y < 6 then
jimballobj.pos.x = 0
jimballobj.pos.y = jimballobj.pos.y + 1
end
end
if G.P_CENTERS and G.P_CENTERS.b_cry_glowing and cry_glowing_dt > 0.1 then
cry_glowing_dt = 0
local glowingobj = G.P_CENTERS.b_cry_glowing
if glowingobj.pos.x == 1 and glowingobj.pos.y == 4 then
glowingobj.pos.x = 0
glowingobj.pos.y = 0
elseif glowingobj.pos.x < 4 then
glowingobj.pos.x = glowingobj.pos.x + 1
elseif glowingobj.pos.y < 6 then
glowingobj.pos.x = 0
glowingobj.pos.y = glowingobj.pos.y + 1
end
end
for k, v in pairs(G.I.CARD) do
if v.children.back and v.children.back.atlas.name == "cry_glowing" then
v.children.back:set_sprite_pos(G.P_CENTERS.b_cry_glowing.pos or G.P_CENTERS["b_red"].pos)
end
end
if not G.OVERLAY_MENU and not G.CHOOSE_CARD and G.GAME.USING_POINTER then
G.CHOOSE_CARD = UIBox({
definition = create_UIBox_pointer(card),
config = {
align = "cm",
offset = { x = 0, y = 10 },
major = G.ROOM_ATTACH,
bond = "Weak",
instance_type = "POPUP",
},
})
G.CHOOSE_CARD.alignment.offset.y = 0
G.ROOM.jiggle = G.ROOM.jiggle + 1
G.CHOOSE_CARD:align_to_major()
end
--Increase the blind size for The Clock and Lavender Loop
local choices = { "Small", "Big", "Boss" }
G.GAME.CRY_BLINDS = G.GAME.CRY_BLINDS or {}
for _, c in pairs(choices) do
if
G.GAME
and G.GAME.round_resets
and G.GAME.round_resets.blind_choices
and G.GAME.round_resets.blind_choices[c]
and G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].cry_ante_base_mod
then
if
G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult ~= 0
and G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult_ante ~= G.GAME.round_resets.ante
then
if G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].name == "cry-Obsidian Orb" then
for i = 1, #G.GAME.defeated_blinds do
G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult = G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult
* G.P_BLINDS[G.GAME.defeated_blinds[i]]
/ 2
end
else
G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult = 0
end
G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult_ante = G.GAME.round_resets.ante
end
if
G.GAME.round_resets.blind_states[c] ~= "Current"
and G.GAME.round_resets.blind_states[c] ~= "Defeated"
then
G.GAME.CRY_BLINDS[c] = (G.GAME.CRY_BLINDS[c] or G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].mult)
+ (
G.P_BLINDS[G.GAME.round_resets.blind_choices[c]].cry_ante_base_mod
and G.P_BLINDS[G.GAME.round_resets.blind_choices[c]]:cry_ante_base_mod(
dt * (G.GAME.modifiers.cry_rush_hour_iii and 2 or 1)
)
or 0
)
--Update UI
--todo: in blinds screen, too
if G.blind_select_opts then
if (SMODS.Mods["StrangeLib"] or {}).can_load then
StrangeLib.dynablind.blind_choice_scores[c] = get_blind_amount(G.GAME.round_resets.blind_ante)
* G.GAME.starting_params.ante_scaling
* G.GAME.CRY_BLINDS[c]
StrangeLib.dynablind.blind_choice_score_texts[c] =
number_format(StrangeLib.dynablind.blind_choice_scores[c])
else
local blind_UI =
G.blind_select_opts[string.lower(c)].definition.nodes[1].nodes[1].nodes[1].nodes[1]
local chip_text_node = blind_UI.nodes[1].nodes[3].nodes[1].nodes[2].nodes[2].nodes[3]
if chip_text_node then
chip_text_node.config.text = number_format(
get_blind_amount(G.GAME.round_resets.blind_ante)
* G.GAME.starting_params.ante_scaling
* G.GAME.CRY_BLINDS[c]
)
chip_text_node.config.scale = score_number_scale(
0.9,
get_blind_amount(G.GAME.round_resets.blind_ante)
* G.GAME.starting_params.ante_scaling
* G.GAME.CRY_BLINDS[c]
)
end
G.blind_select_opts[string.lower(c)]:recalculate()
end
end
elseif
G.GAME.round_resets.blind_states[c] ~= "Defeated"
and not G.GAME.blind.disabled
and to_big(G.GAME.chips) < to_big(G.GAME.blind.chips)
then
G.GAME.blind.chips = G.GAME.blind.chips
+ G.GAME.blind:cry_ante_base_mod(dt * (G.GAME.modifiers.cry_rush_hour_iii and 2 or 1))
* get_blind_amount(G.GAME.round_resets.ante)
* G.GAME.starting_params.ante_scaling
G.GAME.blind.chip_text = number_format(G.GAME.blind.chips)
end
end
if
G.GAME.round_resets.blind_states[c] == "Current"
and G.GAME
and G.GAME.blind
and not G.GAME.blind.disabled
and to_big(G.GAME.chips) < to_big(G.GAME.blind.chips)
then
G.GAME.blind.chips = G.GAME.blind.chips
* (
G.GAME.blind.cry_round_base_mod
and G.GAME.blind:cry_round_base_mod(dt * (G.GAME.modifiers.cry_rush_hour_iii and 2 or 1))
or 1
)
G.GAME.blind.chip_text = number_format(G.GAME.blind.chips)
end
end
end
-- All the scattered set_cost hooks from all the pre refactor files moved into one hook
local sc = Card.set_cost
function Card:set_cost()
-- Makes the edition cost increase usually present not apply if this variable is true
-- Used for some of the Jen's almanac edition decks because having the price increase apply was "unfun"
if self.edition and G.GAME.modifiers.cry_no_edition_price then
local m = Cryptid.deep_copy(self.edition)
self.edition = nil
sc(self)
self.edition = m
else
sc(self)
end
--Makes cube and Big Cube always cost a set amount
if self.ability.name == "cry-Cube" then
if Card.get_gameset(self) ~= "modest" then
self.cost = -27
else
self.cost = -12
end
end
if self.ability.name == "cry-Big Cube" then
self.cost = 27
end
--Multiplies voucher cost by G.GAME.modifiers.cry_voucher_price_hike
--Used by bronze stake to make vouchers %50 more expensive
if self.ability.set == "Voucher" and G.GAME.modifiers.cry_voucher_price_hike then
self.cost = math.floor(self.cost * G.GAME.modifiers.cry_voucher_price_hike)
--Update related costs
self.sell_cost = math.max(1, math.floor(self.cost / 2)) + (self.ability.extra_value or 0)
if self.area and self.ability.couponed and (self.area == G.shop_jokers or self.area == G.shop_booster) then
self.cost = 0
end
self.sell_cost_label = self.facing == "back" and "?" or self.sell_cost
end
--Makes Cursed Jokers always sell for $0
if self.config and self.config.center and self.config.center.rarity == "cry_cursed" then
self.sell_cost = 0
self.sell_cost_label = 0
end
end
local sell_card_stuff = Card.sell_card
function Card:sell_card()
if self.config.center.set == "Joker" then
if self.config.center.key ~= "j_cry_necromancer" then
local contained = false
for _, v in ipairs(G.GAME.jokers_sold) do
if v == self.config.center.key then
contained = true
break
end
end
if not contained then
table.insert(G.GAME.jokers_sold, self.config.center.key)
end
end
-- Add Jolly Joker to the pool if card was treated as Jolly Joker
if self:is_jolly() then
local contained = false
for _, v in ipairs(G.GAME.jokers_sold) do
if v == "j_jolly" then
contained = true
break
end
end
if not contained then
table.insert(G.GAME.jokers_sold, "j_jolly")
end
end
end
--G.P_CENTERS.j_jolly
sell_card_stuff(self)
end
-- Modify to display badges for credits and some gameset badges
-- todo: make this optional
local smcmb = SMODS.create_mod_badges
function SMODS.create_mod_badges(obj, badges)
smcmb(obj, badges)
if not SMODS.config.no_mod_badges and obj and obj.cry_credits then
local function calc_scale_fac(text)
local size = 0.9
local font = G.LANG.font
local max_text_width = 2 - 2 * 0.05 - 4 * 0.03 * size - 2 * 0.03
local calced_text_width = 0
-- Math reproduced from DynaText:update_text
for _, c in utf8.chars(text) do
local tx = font.FONT:getWidth(c) * (0.33 * size) * G.TILESCALE * font.FONTSCALE
+ 2.7 * 1 * G.TILESCALE * font.FONTSCALE
calced_text_width = calced_text_width + tx / (G.TILESIZE * G.TILESCALE)
end
local scale_fac = calced_text_width > max_text_width and max_text_width / calced_text_width or 1
return scale_fac
end
if obj.cry_credits.art or obj.cry_credits.code or obj.cry_credits.idea then
local scale_fac = {}
local min_scale_fac = 1
local strings = { "Cryptid" }
for _, v in ipairs({ "idea", "art", "code" }) do
if obj.cry_credits[v] then
for i = 1, #obj.cry_credits[v] do
strings[#strings + 1] =
localize({ type = "variable", key = "cry_" .. v, vars = { obj.cry_credits[v][i] } })[1]
end
end
end
for i = 1, #strings do
scale_fac[i] = calc_scale_fac(strings[i])
min_scale_fac = math.min(min_scale_fac, scale_fac[i])
end
local ct = {}
for i = 1, #strings do
ct[i] = {
string = strings[i],
}
end
local cry_badge = {
n = G.UIT.R,
config = { align = "cm" },
nodes = {
{
n = G.UIT.R,
config = {
align = "cm",
colour = G.C.CRY_EXOTIC,
r = 0.1,
minw = 2 / min_scale_fac,
minh = 0.36,
emboss = 0.05,
padding = 0.03 * 0.9,
},
nodes = {
{ n = G.UIT.B, config = { h = 0.1, w = 0.03 } },
{
n = G.UIT.O,
config = {
object = DynaText({
string = ct or "ERROR",
colours = { obj.cry_credits and obj.cry_credits.text_colour or G.C.WHITE },
silent = true,
float = true,
shadow = true,
offset_y = -0.03,
spacing = 1,
scale = 0.33 * 0.9,
}),
},
},
{ n = G.UIT.B, config = { h = 0.1, w = 0.03 } },
},
},
},
}
local function eq_col(x, y)
for i = 1, 4 do
if x[1] ~= y[1] then
return false
end
end
return true
end
for i = 1, #badges do
if eq_col(badges[i].nodes[1].config.colour, HEX("708b91")) then
badges[i].nodes[1].nodes[2].config.object:remove()
badges[i] = cry_badge
break
end
end
end
if obj.cry_credits.jolly then
local scale_fac = {}
local min_scale_fac = 1
for i = 1, #obj.cry_credits.jolly do
scale_fac[i] = calc_scale_fac(obj.cry_credits.jolly[i])
min_scale_fac = math.min(min_scale_fac, scale_fac[i])
end
local ct = {}
for i = 1, #obj.cry_credits.jolly do
ct[i] = {
string = obj.cry_credits.jolly[i],
}
end
badges[#badges + 1] = {
n = G.UIT.R,
config = { align = "cm" },
nodes = {
{
n = G.UIT.R,
config = {
align = "cm",
colour = G.C.CRY_JOLLY,
r = 0.1,
minw = 2,
minh = 0.36,
emboss = 0.05,
padding = 0.03 * 0.9,
},
nodes = {
{ n = G.UIT.B, config = { h = 0.1, w = 0.03 } },
{
n = G.UIT.O,
config = {
object = DynaText({
string = ct or "ERROR",
colours = { obj.cry_credits and obj.cry_credits.text_colour_jolly or G.C.WHITE },
silent = true,
float = true,
shadow = true,
offset_y = -0.03,
spacing = 1,
scale = 0.33 * 0.9,
}),
},
},
{ n = G.UIT.B, config = { h = 0.1, w = 0.03 } },
},
},
},
}
end
end
if Cryptid.safe_get(G, "ACTIVE_MOD_UI", "id") == "Cryptid" and obj and not obj.force_gameset then
local set = Cryptid.gameset(obj)
if set == "disabled" or obj.set == "Content Set" then
return
end
local card_type = localize("cry_gameset_" .. Cryptid.gameset(obj))
if card_type == "ERROR" then
card_type = localize("cry_gameset_custom")
end
badges[#badges + 1] = create_badge(
card_type,
set == "modest" and G.C.GREEN
or set == "mainline" and G.C.RED
or set == "madness" and G.C.CRY_EXOTIC
or G.C.CRY_ASCENDANT
)
end
end
-- This is short enough that I'm fine overriding it
function calculate_reroll_cost(skip_increment)
if not G.GAME.current_round.free_rerolls or G.GAME.current_round.free_rerolls < 0 then
G.GAME.current_round.free_rerolls = 0
end
if next(find_joker("cry-crustulum")) or G.GAME.current_round.free_rerolls > 0 then
G.GAME.current_round.reroll_cost = 0
return
end
if 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
-- 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(Cryptid.predict_pseudoseed(x))
end
ps = Cryptid.predict_pseudoseed
end
local center = G.P_CENTERS.b_red
if (_type == "Joker" or _type == "Meme") and G.GAME and G.GAME.modifiers and G.GAME.modifiers.all_rnj then
forced_key = "j_cry_rnjoker"
end
local function aeqviable(center)
return center.unlocked and not Cryptid.no(center, "doe") and not (center.rarity == "cry_exotic")
end
if _type == "Joker" and not _rarity and not legendary then
if not G.GAME.aequilibriumkey then
G.GAME.aequilibriumkey = 1
end
local aeqactive = nil
if next(find_joker("Ace Aequilibrium")) and not forced_key then
while not aeqactive or not aeqviable(G.P_CENTER_POOLS.Joker[aeqactive]) do
if math.ceil(G.GAME.aequilibriumkey) > #G.P_CENTER_POOLS["Joker"] then
G.GAME.aequilibriumkey = 1
end
aeqactive = math.ceil(G.GAME.aequilibriumkey)
G.GAME.aequilibriumkey = math.ceil(G.GAME.aequilibriumkey + 1)
end
end
if aeqactive then
forced_key = G.P_CENTER_POOLS["Joker"][aeqactive].key
end
end
--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
local front = (
(_type == "Base" or _type == "Enhanced")
and pseudorandom_element(G.P_CARDS, 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 = Cryptid.poll_random_edition()
card:set_edition(edition, true)
end
if not (card.edition and (card.edition.cry_oversat or card.edition.cry_glitched)) then
Cryptid.misprintize(card)
end
if card.ability.set == "Joker" and G.GAME.modifiers.cry_common_value_quad then
if card.config.center.rarity == 1 then
Cryptid.misprintize(card, { min = 4, max = 4 }, nil, true)
end
end
if card.ability.set == "Joker" and G.GAME.modifiers.cry_uncommon_value_quad then
if card.config.center.rarity == 2 then
Cryptid.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
-- These two overrides modify the offset to squeeze in more tags when needed
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
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
--add calculation context and callback to tag function
--used for Energia, etc.
local at2 = add_tag
function add_tag(tag, from_skip, no_copy)
if no_copy then
at2(tag)
return
end
local added_tags = 1
local ret = {}
SMODS.calculate_context({ cry_add_tag = true }, ret)
for i = 1, #ret do
if ret[i].jokers then
added_tags = added_tags + (ret[i].jokers.tags or 0)
end
end
if added_tags >= 1 then
at2(tag)
end
for i = 2, added_tags do
local ab = copy_table(G.GAME.tags[#G.GAME.tags].ability)
local new_tag = Tag(tag.key)
at2(new_tag)
new_tag.ability = ab
end
end
-- I don't remember exactly what this patch was for, perhaps issues with syncing hand size with jokers like Effarcire?
local nr = new_round
function new_round()
G.hand:change_size(0)
nr()
end
-- These allow jokers that add joker slots to be obtained even without room, like with Negative Jokers in vanilla
local gfcfbs = G.FUNCS.check_for_buy_space
G.FUNCS.check_for_buy_space = function(card)
if
(card.ability.name == "cry-Negative Joker" and card.ability.extra >= 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
--Cryptid (THE MOD) localization
local function parse_loc_txt(center)
center.text_parsed = {}
if center.text then
for _, line in ipairs(center.text) do
center.text_parsed[#center.text_parsed + 1] = loc_parse_string(line)
end
center.name_parsed = {}
for _, line in ipairs(type(center.name) == "table" and center.name or { center.name }) do
center.name_parsed[#center.name_parsed + 1] = loc_parse_string(line)
end
if center.unlock then
center.unlock_parsed = {}
for _, line in ipairs(center.unlock) do
center.unlock_parsed[#center.unlock_parsed + 1] = loc_parse_string(line)
end
end
end
end
local il = init_localization
function init_localization()
if G.SETTINGS.language == "en-us" then
G.localization.descriptions.Spectral.c_cryptid.text[2] = "{C:attention}#2#{} selected card"
G.localization.descriptions.Spectral.c_talisman.text[2] = "to {C:attention}#1#{} selected"
G.localization.descriptions.Spectral.c_trance.text[2] = "to {C:attention}#1#{} selected"
G.localization.descriptions.Spectral.c_medium.text[2] = "to {C:attention}#1#{} selected"
G.localization.descriptions.Spectral.c_deja_vu.text[2] = "to {C:attention}#1#{} selected"
G.localization.descriptions.Spectral.c_deja_vu.text[2] = "to {C:attention}#1#{} selected"
G.localization.descriptions.Spectral.c_deja_vu.text[2] = "to {C:attention}#1#{} selected" -- why is this done THREE times???
G.localization.descriptions.Voucher.v_antimatter.text[1] = "{C:dark_edition}+#1#{} Joker Slot"
G.localization.descriptions.Voucher.v_overstock_norm.text[1] = "{C:attention}+#1#{} card slot"
G.localization.descriptions.Voucher.v_overstock_plus.text[1] = "{C:attention}+#1#{} card slot"
G.localization.descriptions.Voucher.v_crystal_ball.text[1] = "{C:attention}+#1#{} consumable slot"
G.localization.descriptions.Joker.j_seance.text[1] = "If {C:attention}played hand{} contains a" -- damnit seance
end
il()
if Cryptid.object_buffer and Cryptid.object_buffer.Stake then
for i = 1, #Cryptid.object_buffer.Stake do
local key = Cryptid.object_buffer.Stake[i].key
local color = G.localization.descriptions.Stake[key] and G.localization.descriptions.Stake[key].colour
if color then
local sticker_key = key:sub(7) .. "_sticker"
if not G.localization.descriptions.Other[sticker_key] then
G.localization.descriptions.Other[sticker_key] = {
name = localize({ type = "variable", key = "cry_sticker_name", vars = { color } })[1],
text = localize({
type = "variable",
key = "cry_sticker_desc",
vars = {
color,
"{C:attention}",
"{}",
},
}),
}
parse_loc_txt(G.localization.descriptions.Other[sticker_key])
end
end
end
end
end
--Fix a corrupted game state
function Controller:queue_L_cursor_press(x, y)
if self.locks.frame then
return
end
if G.STATE == G.STATES.SPLASH then
if not G.HUD then
self:key_press("escape")
else
G.STATE = G.STATES.BLIND_SELECT
end
end
self.L_cursor_queue = { x = x, y = y }
end
-- Lemon Trophy's effect
local trophy_mod_mult = mod_mult
function mod_mult(_mult)
hand_chips = hand_chips or 0
if G.GAME.trophymod then
_mult = math.min(_mult, math.max(hand_chips, 0))
end
return trophy_mod_mult(_mult)
end
-- Fix a CCD-related crash
local cuc = Card.can_use_consumeable
function Card:can_use_consumeable(any_state, skip_check)
if not self.ability.consumeable then
return false
end
return cuc(self, any_state, skip_check)
end
-- add second back button to create_UIBox_generic_options
local cuigo = create_UIBox_generic_options
function create_UIBox_generic_options(args)
local ret = cuigo(args)
if args.back2 then
local mainUI = ret.nodes[1].nodes[1].nodes
mainUI[#mainUI + 1] = {
n = G.UIT.R,
config = {
id = args.back2_id or "overlay_menu_back2_button",
align = "cm",
minw = 2.5,
button_delay = args.back2_delay,
padding = 0.1,
r = 0.1,
hover = true,
colour = args.back2_colour or G.C.ORANGE,
button = args.back2_func or "exit_overlay_menu",
shadow = true,
focus_args = { nav = "wide", button = "b", snap_to = args.snap_back2 },
},
nodes = {
{
n = G.UIT.R,
config = { align = "cm", padding = 0, no_fill = true },
nodes = {
{
n = G.UIT.T,
config = {
id = args.back2_id or nil,
text = args.back2_label or localize("b_back"),
scale = 0.5,
colour = G.C.UI.TEXT_LIGHT,
shadow = true,
func = not args.no_pip and "set_button_pip" or nil,
focus_args = not args.no_pip and { button = args.back2_button or "b" } or nil,
},
},
},
},
},
}
end
return ret
end