balatro-mods/UnStable-main/Unstable.lua
2025-02-24 13:28:04 +08:00

9550 lines
265 KiB
Lua

local unstb = SMODS.current_mod
local filesystem = NFS or love.filesystem
local path = unstb.path
local unstb_config = unstb.config
--Global Table
unstb_global = {}
unstb_global.config = unstb.config
-- Debug message
local function print(message)
sendDebugMessage('[UnStable] - '..(tostring(message) or '???'))
end
print("Starting Unstable")
--Initialize All Colors
G.C.UNSTB_AUX = HEX('00a669')
--Localization Colors
--Properly hook into the function, so it does not error
local ref_loc_colour = loc_colour
function loc_colour(_c, default)
if not G.ARGS.LOC_COLOURS then
ref_loc_colour(_c, default)
elseif not G.ARGS.LOC_COLOURS.unstb_colour then --Init UNSTB's exclusive color
G.ARGS.LOC_COLOURS.unstb_colour = true
G.ARGS.LOC_COLOURS['auxiliary'] = G.C.UNSTB_AUX
end
return ref_loc_colour(_c, default)
end
--Description page formatting
unstb.description_loc_vars = function()
return { background_colour = G.C.CLEAR, text_colour = G.C.WHITE, scale = 1.2 }
end
--Config Stuff
function unstb.save_config(self)
SMODS.save_mod_config(self)
end
local unstb_config_tab = function()
return{
{
label = localize("unstb_config_header_mech_setting"),
chosen = true,
tab_definition_function = 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 = {
{n=G.UIT.R, config={align = "cm"}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = localize("unstb_config_requires_restart"), colour = G.C.RED, scale = 0.4}}}},
}},
{n=G.UIT.R, config={align = "cm"}, nodes={ --Base Box containing everything
-- Left Side Column
{n=G.UIT.C, config={align = "cl", padding = 0.2}, nodes={
{n=G.UIT.R, config={align = "cl"}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = localize("unstb_config_header_rank"), colour = G.C.ORANGE, scale = 0.5}}}},
create_toggle({label = localize("unstb_config_rank21"), ref_table = unstb.config.rank, ref_value = 'rank_21', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_rank_bi"), ref_table = unstb.config.rank, ref_value = 'rank_binary', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_rank_decimal"), ref_table = unstb.config.rank, ref_value = 'rank_decimal', callback = function() unstb:save_config() end}),
}},
{n=G.UIT.R, config={align = "cl"}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = localize("unstb_config_header_enh"), colour = G.C.ORANGE, scale = 0.5}}}},
create_toggle({label = localize("unstb_config_enh_custom"), ref_table = unstb.config.enh, ref_value = 'enh_custom', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_enh_disenh"), ref_table = unstb.config.enh, ref_value = 'enh_disenh', callback = function() unstb:save_config() end}),
}}
}},
-- Right Side Column
{n=G.UIT.C, config={align = "cl"}, nodes={
{n=G.UIT.R, config={align = "cl"}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = localize("unstb_config_header_mechanics"), colour = G.C.ORANGE, scale = 0.5}}}},
create_toggle({label = localize("unstb_config_mech_upgrade"), ref_table = unstb.config.gameplay, ref_value = 'edition_upgrade', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_mech_suitseal"), ref_table = unstb.config.gameplay, ref_value = 'seal_suit', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_mech_aux"), ref_table = unstb.config.gameplay, ref_value = 'c_aux', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_mech_music"), ref_table = unstb.config.gameplay, ref_value = 'music', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_mech_fallback"), info = localize("unstb_config_mech_fallback_desc"), ref_table = unstb.config.gameplay, ref_value = 'c_rebundant', callback = function() unstb:save_config() end}),
create_toggle({label = localize("unstb_config_mech_new_spectral"), ref_table = unstb.config.gameplay, ref_value = 'new_spectrals', callback = function() unstb:save_config() end}),
}},
}},
}}
},
}
end
},
{ --Reserved Tab, in case the settings are expended in the future
label = localize("unstb_config_header_joker_settings"),
tab_definition_function = 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 = {
{n=G.UIT.R, config={align = "cm"}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = localize("unstb_config_requires_restart"), colour = G.C.RED, scale = 0.4}}}},
}},
{n=G.UIT.R, config={align = "cm"}, nodes={ --Base Box containing everything
-- Right Side Column
{n=G.UIT.C, config={align = "cl"}, nodes={
{n=G.UIT.R, config={align = "cl"}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = "Joker", colour = G.C.ORANGE, scale = 0.5}}}},
create_toggle({label = 'Vanilla Joker Overrides', info = {'Re-coded Vanilla Jokers to work with new features'}, ref_table = unstb.config.joker, ref_value = 'vanilla', callback = function() unstb:save_config() end}),
create_toggle({label = 'Shitpost Jokers', ref_table = unstb.config.joker, ref_value = 'shitpost', callback = function() unstb:save_config() end}),
create_toggle({label = 'Major Gameplay-Altering Jokers', info = {'Jokers that may alters the playstyle drastically'}, ref_table = unstb.config.joker, ref_value = 'alter', callback = function() unstb:save_config() end}),
create_toggle({label = 'Powerful Jokers', info = {'Jokers that is significantly powerful than base game'}, ref_table = unstb.config.joker, ref_value = 'powerful', callback = function() unstb:save_config() end}),
}}
}},
}}
},
}
end
}
}
end
unstb.extra_tabs = unstb_config_tab
--Just so the gear icon shows up
unstb.config_tab = true
--Map config value with a single string keyword
local config_value = {
["rank_21"] = unstb_config.rank.rank_21,
["rank_binary"] = unstb_config.rank.rank_binary,
["rank_decimal"] = unstb_config.rank.rank_decimal,
["enh_custom"] = unstb_config.enh.enh_custom,
["enh_disenh"] = unstb_config.enh.enh_disenh,
["edition_upgrade"] = unstb_config.gameplay.edition_upgrade,
["seal_suit"] = unstb_config.gameplay.seal_suit,
["c_aux"] = unstb_config.gameplay.c_aux,
["music"] = unstb_config.gameplay.music,
["c_rebundant"] = unstb_config.gameplay.c_rebundant,
["new_spectrals"] = unstb_config.gameplay.new_spectrals,
["j_vanilla"] = unstb_config.joker.vanilla,
["j_shitpost"] = unstb_config.joker.shitpost,
["j_alter"] = unstb_config.joker.alter,
["j_powerful"] = unstb_config.joker.powerful,
}
--Inclusion Check by a list of keyword
local function check_enable_taglist(taglist)
local isAdded = true
for _, v in ipairs(taglist) do
isAdded = isAdded and config_value[v]
end
return isAdded
end
--Talisman Compatibility
to_big = to_big or function(num)
return num
end
-- Index-based coordinates generation
local function get_coordinates(position, width)
if width == nil then width = 10 end -- 10 is default for Jokers
return {x = (position) % width, y = math.floor((position) / width)}
end
--Mod Icon
SMODS.Atlas {
-- Key for code to find it with
key = "modicon",
-- The name of the file, for the code to pull the atlas from
path = "modicon.png",
-- Width of each sprite in 1x size
px = 32,
-- Height of each sprite in 1x size
py = 32
}
--Creates an atlas for Jokers to use
SMODS.Atlas {
-- Key for code to find it with
key = "unstb_jokers",
-- The name of the file, for the code to pull the atlas from
path = "jokers.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for special Jokers (multi-face, dynamic graphic, etc)
SMODS.Atlas {
-- Key for code to find it with
key = "unstb_jokers_ex",
-- The name of the file, for the code to pull the atlas from
path = "jokers_ex.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "unstb_jokers_wip",
-- The name of the file, for the code to pull the atlas from
path = "jokers_wip.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for new enhancements
SMODS.Atlas {
-- Key for code to find it with
key = "unstb_back",
-- The name of the file, for the code to pull the atlas from
path = "back.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "enh_slop",
-- The name of the file, for the code to pull the atlas from
path = "enh_slop.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "enh_slop_hc",
-- The name of the file, for the code to pull the atlas from
path = "enh_slop_hc.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "enh_resource",
-- The name of the file, for the code to pull the atlas from
path = "enh_res.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for Suit Seals
SMODS.Atlas {
-- Key for code to find it with
key = "suit_seal",
-- The name of the file, for the code to pull the atlas from
path = "suit_seal.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for Booster Pack
SMODS.Atlas {
-- Key for code to find it with
key = "booster",
-- The name of the file, for the code to pull the atlas from
path = "booster.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for Auxiliary Cards
SMODS.Atlas {
-- Key for code to find it with
key = "auxiliary_undiscovered",
-- The name of the file, for the code to pull the atlas from
path = "auxiliary_undiscovered.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "auxiliary",
-- The name of the file, for the code to pull the atlas from
path = "auxiliary.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for Other Consumable Cards
--Tarot
SMODS.Atlas {
-- Key for code to find it with
key = "tarot",
-- The name of the file, for the code to pull the atlas from
path = "tarot.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Spectral
SMODS.Atlas {
-- Key for code to find it with
key = "spectral",
-- The name of the file, for the code to pull the atlas from
path = "spectral.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for Vouchers
SMODS.Atlas {
-- Key for code to find it with
key = "voucher",
-- The name of the file, for the code to pull the atlas from
path = "voucher.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for Decks
SMODS.Atlas {
-- Key for code to find it with
key = "deck",
-- The name of the file, for the code to pull the atlas from
path = "deck.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Atlas for extra ranks
SMODS.Atlas {
-- Key for code to find it with
key = "rank_ex",
-- The name of the file, for the code to pull the atlas from
path = "rank_ex.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "rank_ex2",
-- The name of the file, for the code to pull the atlas from
path = "rank_ex2.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "rank_ex_hc",
-- The name of the file, for the code to pull the atlas from
path = "rank_ex_hc.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
SMODS.Atlas {
-- Key for code to find it with
key = "rank_ex2_hc",
-- The name of the file, for the code to pull the atlas from
path = "rank_ex2_hc.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}
--Music
local aux_music_enable = unstb_config.gameplay.music
SMODS.Sound({
key = "music_aux",
path = "music_aux.ogg",
select_music_track = function()
return aux_music_enable
and ( G.pack_cards and G.pack_cards.cards and G.pack_cards.cards[1] and G.pack_cards.cards[1].ability.set == "Auxiliary")
end,
})
--SFXs
SMODS.Sound({key = 'heal', path = 'heal.ogg'})
SMODS.Sound({key = 'poison', path = 'poison.ogg'})
--Jokers
--filesystem.load(unstb.path..'joker\\joker.lua')()
--Utility
--Check if table value already exists in the List
local function table_has_value(list, value)
for i, v in ipairs(list) do
if v==value then
return true
end
end
return false
end
--Expose this function so I can use this in global scope
unstb_global.table_has_value = table_has_value
--Auto event scheduler, based on Bunco
local function event(config)
local e = Event(config)
G.E_MANAGER:add_event(e)
return e
end
local function big_juice(card)
card:juice_up(0.7)
end
local function extra_juice(card)
card:juice_up(0.6, 0.1)
end
local function play_nope_sound()
--Copied from Wheel of Fortune lol
event({trigger = 'after', delay = 0.06*G.SETTINGS.GAMESPEED, blockable = false, blocking = false, func = function()
play_sound('tarot2', 0.76, 0.4);return true end})
play_sound('tarot2', 1, 0.4)
end
local function forced_message(message, card, color, delay, juice)
if delay == true then
delay = 0.7 * 1.25
elseif delay == nil then
delay = 0
end
event({trigger = 'before', delay = delay, func = function()
if juice then big_juice(juice) end
card_eval_status_text(
card,
'extra',
nil, nil, nil,
{message = message, colour = color, instant = true}
)
return true
end})
end
--Joker creation wrapper, based on Bunco
local function create_joker(joker)
--Check Joker's keyword tag, if the setting is turned off then don't add anything
local isAdded = true
if joker.gameplay_tag and type(joker.gameplay_tag) == 'table' then
for _, v in ipairs(joker.gameplay_tag) do
isAdded = check_enable_taglist(joker.gameplay_tag)
end
end
if not isAdded then return end
-- Sprite position
local width = 10 -- Width of the spritesheet (in Jokers)
-- Soul sprite
if joker.rarity == 'Legendary' then
joker.soul = get_coordinates(joker.id) -- Calculates coordinates based on the position variable
end
joker.position = get_coordinates(joker.id)
-- Sprite atlas
if joker.type == nil then
joker.atlas = 'unstb_jokers'
elseif joker.type == 'Banned' then
joker.atlas = 'unstb_jokers_banned'
end
if joker.rarity == 'Legendary' then
joker.atlas = 'unstb_jokers_legend'
end
--If the joker has ex property, used extra sheet
if joker.ex_art then
joker.atlas = 'unstb_jokers_ex'
end
--If the joker has no art, fallback to WIP sheet
if joker.no_art then
joker.atlas = 'unstb_jokers_wip'
end
-- Key generation from name
local key = string.gsub(string.lower(joker.name), '%s', '_') -- Removes spaces and uppercase letters
-- Rarity conversion
if joker.rarity == 'Common' then
joker.rarity = 1
elseif joker.rarity == 'Uncommon' then
joker.rarity = 2
elseif joker.rarity == 'Rare' then
joker.rarity = 3
elseif joker.rarity == 'Legendary' then
joker.rarity = 4
end
-- Config values
if joker.vars == nil then joker.vars = {} end
joker.config = {extra = {}}
for _, kv_pair in ipairs(joker.vars) do
-- kv_pair is {a = 1}
local k, v = next(kv_pair)
joker.config.extra[k] = v
end
-- Joker creation
SMODS.Joker{
name = joker.name,
key = key,
atlas = joker.atlas,
pos = joker.position,
soul_pos = joker.soul,
rarity = joker.rarity,
cost = joker.cost,
unlocked = true,
--check_for_unlock = joker.check_for_unlock,
--unlock_condition = joker.unlock_condition,
--discovered = true, --false,
blueprint_compat = joker.blueprint or false,
eternal_compat = (joker.eternal == nil) or joker.eternal,
perishable_compat = (joker.perishable == nil) or joker.perishable,
process_loc_text = joker.process_loc_text,
config = joker.custom_config or joker.config,
loc_vars = joker.custom_vars or function(self, info_queue, card)
-- Localization values
local vars = {}
for _, kv_pair in ipairs(joker.vars) do
-- kv_pair is {a = 1}
local k, v = next(kv_pair)
-- k is `a`, v is `1`
table.insert(vars, card.ability.extra[k])
end
return {vars = vars}
end,
calculate = joker.calculate,
update = joker.update,
remove_from_deck = joker.remove_from_deck,
add_to_deck = joker.add_to_deck,
set_ability = joker.set_ability,
set_sprites = joker.set_sprites,
load = joker.load,
calc_dollar_bonus = joker.calc_dollar_bonus,
in_pool = joker.custom_in_pool or pool,
effect = joker.effect
}
end
--New global function to get a random eligible suit and rank from the deck without rank-overrides enhancements getting in the way
--Code based on Castle from base game
function get_valid_card_from_deck(seed)
local res_suit = 'Spades'
local res_rank = '2'
local valid_cards = {}
for k, v in ipairs(G.playing_cards) do
if not v.config.center.replace_base_card then --Excludes all cards with replace_base_card enhancements
valid_cards[#valid_cards+1] = v
end
end
if valid_cards[1] then
local target_card = pseudorandom_element(valid_cards, pseudoseed(seed or 'validcard'..G.GAME.round_resets.ante))
res_suit = target_card.base.suit
res_rank = target_card.base.value
end
return {suit = res_suit, rank = res_rank}
end
--Black Jack Rank Calculation
local function blackJack_evalrank(hand, bustAmount)
--Black Jack-style total rank evaluation
bustAmount = bustAmount or 21
local aceCount = 0
local rank = 0
for i = 1, #hand do
local currentCard = hand[i]
if not (currentCard.config.center == G.P_CENTERS.m_stone or currentCard.config.center.no_rank) and not currentCard.debuff then
if currentCard.base.value ~= 'Ace' then
rank = rank + (SMODS.Ranks[currentCard.base.value].nominal or 0) --Supports modded ranks as well, just in case
else
aceCount = aceCount + 1
rank = rank + 11
end
end
end
--Handle Ace rank
while( aceCount > 0 )
do
if rank > bustAmount then
rank = rank - 10
end
aceCount = aceCount - 1
end
return rank
end
--"Upgrade" function, used on card
local function edition_upgrade(card)
local edition = (card.edition or {}).key
if not edition then
card:set_edition("e_foil", true, false)
elseif edition=="e_foil" then
card:set_edition("e_holo", true, false)
elseif edition=="e_holo" then
card:set_edition("e_polychrome", true, false)
end
end
--General Helper function for rank increment / decrement
--Insert "prev" property into SMODS.Ranks
function init_prev_rank_data()
print("Initialize Remaining Previous Rank Data")
for _, rank in pairs(SMODS.Ranks) do
--Initialize
--In case the rank table does not have prev existed
--Base rank and UNSTB one has them defined manually by default
if not rank.prev then
rank.prev = {}
end
next_rank_list = rank.next
for i=1, #next_rank_list do
local next_rank = SMODS.Ranks[next_rank_list[i]]
local prev = next_rank.prev or {}
if not table_has_value(prev, rank.key) then
table.insert(prev, rank.key)
next_rank.prev = prev
end
end
end
end
--Utility function to get the next rank by specified step, walked using the same algorithm as SMODS Implementation of Strength Tarot
--Negative Step is also ok
function get_next_x_rank(rank, step)
local currentRank = SMODS.Ranks[rank]
if not currentRank then
return 'unstb_???' --Fallback, if the rank is invalid then returning ??? rank card
end
local behavior
local new_rank
local mul = (step > 0 and 1) or -1
--Based on SMODS Current implementation of Strength
for i=mul, step, mul do
if mul > 0 then
behavior = currentRank.strength_effect or { fixed = 1, ignore = false, random = false }
else
behavior = currentRank.decrease_effect or { fixed = 1, ignore = false, random = false }
end
if behavior.ignore or not next(currentRank.next) then
return rank
elseif behavior.random then
-- TODO doesn't respect in_pool
if mul>0 then
new_rank = pseudorandom_element(currentRank.next, pseudoseed('nextrank'))
else
new_rank = pseudorandom_element(currentRank.prev, pseudoseed('prevrank'))
end
else
if mul>0 then
local ii = (behavior.fixed and currentRank.next[behavior.fixed]) and behavior.fixed or 1
new_rank = currentRank.next[ii]
else
local ii = (behavior.fixed and currentRank.prev[behavior.fixed]) and behavior.fixed or 1
new_rank = currentRank.prev and currentRank.prev[ii] or currentRank.key
end
end
currentRank = SMODS.Ranks[new_rank]
--print(SMODS.Ranks[new_rank].key)
end
return new_rank or rank
end
--Face Seal
if unstb_config.gameplay.seal_suit then
SMODS.Seal({
key = "face",
atlas = "suit_seal",
pos = { x = 4, y = 0 },
badge_colour = HEX "f59c00",
shiny = true,
weight = 0,
config = {extra = {}},
loc_vars = function(self, info_queue, card)
return {vars = {}}
end,
--[[calculate = function(self, card, context)
end]]
--This cannot spawn naturally at all
in_pool = function(self, args)
return false
end
})
end
--Hook into is_face to account for Face Seal
local card_isfaceref = Card.is_face
function Card:is_face(from_boss)
if self.debuff and not from_boss then return end
if self.seal == 'unstb_face' then
return true
end
return card_isfaceref(self, from_boss)
end
--Suit Seals
--Global table for Suit Seals-related stuff
SuitSeal = {}
SuitSeal.suit_seal_colors = {
Spades = HEX "603de8",
Hearts = HEX "e83d61",
Clubs = HEX "19b8a1",
Diamonds = HEX "e8793d",
}
--External function to supports register more suits (especially modded) if needed
function SuitSeal.registerSealColor(suit, color)
SuitSeal.suit_seal_colors[suit] = HEX(color)
end
--Creating an actual SMODS Seal object
function SuitSeal.initSeal(suit, atlas, posX)
SMODS.Seal({
key = string.lower(suit),
atlas = atlas or "suit_seal",
--Extra field for suit seal only,
suit_seal = suit,
pos = { x = posX or 0, y = 0 },
badge_colour = SuitSeal.suit_seal_colors[suit],
weight = 0,
config = {extra = {}},
loc_vars = function(self, info_queue, card)
return {vars = {localize(self.suit_seal, 'suits_plural'), colours = {G.C.SUITS[self.suit_seal]}}}
end,
--This cannot spawn naturally at all
in_pool = function(self, args)
return false
end
})
end
local suit_seal_list = {"Spades", "Hearts", "Clubs", "Diamonds"}
--Global Suit Seal map from suit
unstb_global.SUIT_SEAL = {}
--TODO: A function wrapper that registers extra suit seal, corresponding auxillary card at the same time
--Might be a part of UnStableEX
if unstb_config.gameplay.seal_suit then
for i = 1, #suit_seal_list do
SuitSeal.initSeal(suit_seal_list[i], "suit_seal", i-1 )
unstb_global.SUIT_SEAL[suit_seal_list[i]] = {}
unstb_global.SUIT_SEAL[suit_seal_list[i]].seal_key = 'unstb_'..string.lower(suit_seal_list[i])
unstb_global.SUIT_SEAL[suit_seal_list[i]].aux_key = 'c_unstb_aux_'..string.lower(suit_seal_list[i])
end
end
--Heal Seal
function Card:heal(initial, delay_sprites)
local prev_center = self.config.prev_center or G.P_CENTERS.c_base
--Nullify hand xmult just in case
if self.ability.h_x_mult then
self.ability.h_x_mult = 0
end
self:set_ability(prev_center, initial, delay_sprites)
end
if unstb_config.enh.enh_disenh then
SMODS.Seal({
key = "heal",
atlas = "suit_seal",
pos = { x = 5, y = 0 },
badge_colour = HEX "7ce83d",
weight = 0,
config = {extra = {}},
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'disenhancement'}
return {vars = {}}
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
local valid_cards = {}
for i = 1, #G.hand.cards do
if G.hand.cards[i].config.center.disenhancement and not G.hand.cards[i].healed then
valid_cards[#valid_cards+1] = G.hand.cards[i]
end
end
if valid_cards[1] then
local target_card = pseudorandom_element(valid_cards, pseudoseed('heal'..G.GAME.round_resets.ante))
--Set variable in advance because events are delayed
target_card.healed = true
--Nullify hand xmult in advance
if target_card.config.center == G.P_CENTERS.m_unstb_radioactive then
target_card.ability.h_x_mult = 0
end
event({func = function()
target_card:heal()
play_sound('unstb_heal', 1, 0.3)
target_card:juice_up()
target_card.healed = nil
return true
end
})
forced_message("Healed!", target_card, G.C.GREEN, true)
end
end
end,
--This cannot spawn naturally at all
in_pool = function(self, args)
return false
end
})
end
--New Enhancements
function get_valid_card_from_deck(seed)
local res_suit = 'Spades'
local res_rank = '2'
local valid_cards = {}
for k, v in ipairs(G.playing_cards) do
if not v.config.center.replace_base_card then --Excludes all cards with replace_base_card enhancements
valid_cards[#valid_cards+1] = v
end
end
if valid_cards[1] then
local target_card = pseudorandom_element(valid_cards, pseudoseed(seed or 'validcard'..G.GAME.round_resets.ante))
res_suit = target_card.base.suit
res_rank = target_card.base.value
end
return {suit = res_suit, rank = res_rank}
end
--Patch get_chip_bonus to allow total chip override
local cardGetChipBonusPointer = Card.get_chip_bonus
function Card:get_chip_bonus()
if self.config.center.override_chip then
return self.config.center.override_chip
end
return cardGetChipBonusPointer(self)
end
if unstb_config.enh.enh_custom then
--Acorn
SMODS.Enhancement {
key = "acorn",
atlas = "unstb_back",
pos = {x=0, y = 0},
replace_base_card = false,
no_suit = false,
no_rank = false,
always_scores = false,
override_chip = 0,
config = {extra = { totalchips = 0, originalchips = 0}},
loc_vars = function(self, info_queue, card)
return {
vars = { (card and (card.base.nominal + (card.ability.perma_bonus or 0)) *2) or 0 }
}
end,
--Override genere_ui so it does not display any chips
generate_ui = function(self, info_queue, card, desc_nodes, specific_vars, full_UI_table)
SMODS.Enhancement.super.generate_ui(self, info_queue, card, desc_nodes, specific_vars, full_UI_table)
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
local rulesJoker = SMODS.find_card('j_unstb_rules_errata')
if next(rulesJoker) then
--Makes the Joker bounce when this card score
event({trigger = 'after', func = function()
big_juice(rulesJoker[1])
return true end })
return {
mult = card.base.nominal * 0.5,
}
else
event({trigger = 'after', delay = 0.05, func = function()
play_sound('tarot2', 1, 0.4);
return true end })
forced_message("Not Allowed!", card, G.C.RED, true)
end
end
if context.cardarea == G.hand and context.main_scoring then
card.ability.extra.totalchips = (card.base.nominal + (card.ability.perma_bonus or 0)) * 2
if not card.debuff then
--ret.unstb_h_chips = card.ability.extra.totalchips
return {
h_chips = card.ability.extra.totalchips
}
end
end
end
}
--Vintage
SMODS.Enhancement {
key = "vintage",
atlas = "unstb_back",
pos = {x=1, y = 0},
config = {extra = { bonus_chip = 0, chip_gain_rate = 10, current_odd = 0, odd_destroy = 15, destroy_rate = 1}},
loc_vars = function(self, info_queue, card)
local odds_current = 0
local destroy_rate = 1
if card then
odds_current = card.ability.extra.current_odd or 0
destroy_rate = card.ability.extra.destroy_rate
end
if G.GAME and G.GAME.probabilities.normal then
odds_current = odds_current * G.GAME.probabilities.normal
destroy_rate = destroy_rate * G.GAME.probabilities.normal
end
return {
vars = { self.config.extra.chip_gain_rate, odds_current, self.config.extra.odd_destroy, destroy_rate}
}
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
card.ability.perma_bonus = (card.ability.perma_bonus or 0) + card.ability.extra.chip_gain_rate
card.ability.extra.current_odd = (card.ability.extra.current_odd or 0) + card.ability.extra.destroy_rate
forced_message("Upgrade!", card, G.C.CHIPS, true)
end
end,
after_play = function(self, card, context)
local isDestroy = pseudorandom('vintage'..G.SEED) < card.ability.extra.current_odd * G.GAME.probabilities.normal / card.ability.extra.odd_destroy
if isDestroy then
event({trigger = 'after', delay = 0.05, func = function()
play_sound('tarot2', 1, 0.4);
return true end })
forced_message("Torn...", card, G.C.BLACK, true)
return {
to_destroy = true
}
end
end,
}
--Promo
SMODS.Enhancement {
key = "promo",
atlas = "unstb_back",
pos = {x=2, y = 0},
replace_base_card = false,
no_suit = false,
no_rank = false,
always_scores = false,
config = {extra = { gold = 0, gold_rate = 1, odds_destroy = 8}},
loc_vars = function(self, info_queue, card)
return {
vars = { (card and card.ability and card.ability.extra.gold) or 0, self.config.extra.gold_rate, (G.GAME and G.GAME.probabilities.normal or 1), self.config.extra.odds_destroy }
}
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
card.ability.extra.gold = (card.ability.extra.gold or 0) + card.ability.extra.gold_rate
forced_message("Upgrade!", card, G.C.GOLD, true)
end
if context.cardarea == G.hand and context.main_scoring then
if not card.debuff and card.ability.extra.gold>0 then
--ret.dollars = card.ability.extra.gold
return {
dollars = card.ability.extra.gold
}
end
end
end,
after_play = function(self, card, context)
--print("Trigger afterplay")
local isDestroy = pseudorandom('promo'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_destroy
if isDestroy then
event({trigger = 'after', delay = 0.05, func = function()
play_sound('tarot2', 1, 0.4);
return true end })
forced_message("Sold", card, G.C.ORANGE, true)
return {
to_destroy = true
}
end
end,
}
--Slop
SMODS.Enhancement {
key = "slop",
lc_atlas = "enh_slop",
hc_atlas = "enh_slop_hc",
atlas = "enh_slop",
is_hc = false,
pos = {x=0, y = 0},
replace_base_card = true,
no_suit = false,
no_rank = false,
always_scores = true,
config = {extra = { suit = 'Spades', rank = '2', chips = 0}},
loc_vars = function(self, info_queue, card)
local suit_text = '(Suit)'
local suit_text_color = G.C.ORANGE
if card and card.ability then
suit_text = localize(card.ability.extra.suit, 'suits_plural');
suit_text_color = G.C.SUITS[card.ability.extra.suit]
end
return {
vars = { card and card.ability.extra.chips or 2, suit_text, localize(card and card.ability.extra.rank or '2', 'ranks') ,
colours = {suit_text_color} }
}
end,
suit_map = {
Hearts = 0,
Clubs = 1,
Diamonds = 2,
Spades = 3,
},
set_sprites = function(self, card, front)
local isCollection = (card.area and card.area.config.collection) or false
if not isCollection and card.ability and card.ability.extra then
local suit = (card.base and card.base.suit) or 'Spades'
local pos = get_coordinates( (self.suit_map[suit] or -1) +1 )
card.children.center:set_sprite_pos(pos)
end
end,
set_ability = function(self, card, initial, delay_sprites)
if card.base then
card.ability.extra.suit = card.base.suit or 'Hearts'
card.ability.extra.rank = card.base.value or '2'
card.ability.extra.chips = SMODS.Ranks[card.ability.extra.rank].nominal
end
end,
update = function(self, card)
--Jank, supporting for high contrast texture change settings
if G.SETTINGS.colourblind_option ~= card.atlasmode then
if G.SETTINGS.colourblind_option then
self.atlas = self.hc_atlas
else
self.atlas = self.lc_atlas
end
card.children.center.atlas = G.ASSET_ATLAS[self.atlas]
card.atlasmode = G.SETTINGS.colourblind_option
end
--Update the value for the 'wise' player who tries to change card value using tarots
if (card.VT.w <= 0) and card.base.suit and card.base.value then
local isCollection = (card.area and card.area.config.collection) or false
if not isCollection then
card.ability.extra.suit = card.base.suit or 'Hearts'
card.ability.extra.rank = card.base.value or '2'
card.ability.extra.chips = SMODS.Ranks[card.ability.extra.rank].nominal
local suit = (card.base and card.base.suit) or 'Spades'
local pos = get_coordinates( (self.suit_map[suit] or -1) +1 )
card.children.center:set_sprite_pos(pos)
end
end
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
local scoredAmount = card.ability.extra.chips + card.ability.perma_bonus
--Store the current scoring hand, for after_play
--Had to check to not include itself and other Slop Cards so it won't have cyclic references
if not card.ability.extra.prevhand and context.scoring_hand then
local hand = {}
for i=1, #context.scoring_hand do
if context.scoring_hand[i] ~= card and not context.scoring_hand[i].config.center.replace_base_card then
hand[#hand+1] = {suit = context.scoring_hand[i].base.suit, rank = context.scoring_hand[i].base.value}
end
end
card.ability.extra.prevhand = hand
--print(inspectDepth(card.ability.extra.prevhand))
end
return {
chips = scoredAmount,
}
end
end,
after_play = function(self, card, context)
forced_message("Randomize!", card, G.C.RED, true)
event({trigger = 'after', delay = 0.05, func = function()
card.ability.extra.suit = pseudorandom_element(SMODS.Suits, pseudoseed('slop_card')..G.SEED).key
card.ability.extra.rank = pseudorandom_element(SMODS.Ranks, pseudoseed('slop_card')..G.SEED).key
local prompt_joker = SMODS.find_card('j_unstb_prompt')
if next(prompt_joker) then
if #card.ability.extra.prevhand>0 then
card.ability.extra.suit = pseudorandom_element(card.ability.extra.prevhand, pseudoseed('slop_card')..G.SEED).suit
card.ability.extra.rank = pseudorandom_element(card.ability.extra.prevhand, pseudoseed('slop_card')..G.SEED).rank
--Bounce the Joker a lil bit for the feedback effect
big_juice(prompt_joker[1])
end
end
--Clear the variable, unsure why this caused the game to crash when hovering on the deck if left uncleared
card.ability.extra.prevhand = nil
card.ability.extra.chips = SMODS.Ranks[card.base.value].nominal
local suit_data = SMODS.Suits[card.ability.extra.suit]
local suit_prefix = suit_data.card_key
local targetCard = suit_prefix .. '_' ..SMODS.Ranks[card.ability.extra.rank].card_key
--print(targetCard)
card:set_base(G.P_CARDS[targetCard])
local suit = (card.base and card.base.suit) or 'Spades'
local pos = get_coordinates( (self.suit_map[suit] or -1) +1 )
card.children.center:set_sprite_pos(pos)
return true end })
end,
--This cannot spawn naturally at all
in_pool = function(self, args)
return false
end
}
--Resource
SMODS.Enhancement {
key = "resource",
atlas = "enh_resource",
pos = {x=0, y = 0},
replace_base_card = true,
--no_suit = false,
no_rank = true,
always_scores = true,
config = {extra = { xmult = 5, suit = "undefined"} },
loc_vars = function(self, info_queue, card)
local suit_text = 'undefined'
local suit_text_color = {}
if card.ability then
suit_text = card.ability.extra.suit;
if suit_text ~= '(Corresponding Suit)' then
suit_text = localize(card.ability.extra.suit, 'suits_singular');
suit_text_color = G.C.SUITS[card.ability.extra.suit]
else
suit_text_color = G.C.ORANGE
end
end
return {
vars = { (card.ability and card.ability.extra.xmult) or 5, suit_text,
colours = {suit_text_color} }
}
end,
suit_map = {
Hearts = 0,
Clubs = 1,
Diamonds = 2,
Spades = 3,
},
set_sprites = function(self, card, front)
local isCollection = (card.area and card.area.config.collection) or false
if not isCollection and card.ability and card.ability.extra then
local suit = (card.base and card.base.suit) or 'Spades'
card.ability.extra.suit = suit
local pos = get_coordinates( (self.suit_map[suit] or -1) +2 )
card.children.center:set_sprite_pos(pos)
end
end,
update = function(self, card)
if (card.VT.w <= 0 or card.ability.extra.suit == 'undefined') then
local isCollection = (card.area and card.area.config.collection) or false
if not isCollection then
card.ability.extra.suit = card.base.suit
card.children.center:set_sprite_pos( get_coordinates( (self.suit_map[card.base.suit] or -1) +2 ) )
else
card.ability.extra.suit = "(Corresponding Suit)"
end
end
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
local has_suit = false
if context.scoring_hand then
for i = 1, #context.scoring_hand do
local currentCard = context.scoring_hand[i]
if currentCard ~= card and currentCard.config.center ~= G.P_CENTERS.m_unstb_resource and currentCard:is_suit(card.ability.extra.suit) then
has_suit = true
break
end
end
end
if has_suit then
--[[event({trigger = 'after', func = function()
play_sound('multhit1')
return true end })]]
card.ability.extra.to_destroy = true
return {
xmult = card.ability.extra.xmult,
}
else
event({trigger = 'after', delay = 0.05, func = function()
play_sound('tarot2', 1, 0.4);
return true end })
forced_message("Invalid", card, G.C.ORANGE, true)
end
return {
repetitions = -1,
card = context.other_card
}
end
end,
--Injected the trigger using lovely, can be used for any post-play enchantment stuff
after_play = function(self, card, context)
if card.ability.extra.to_destroy then
forced_message("Redeemed!", card, G.C.RED, true)
return {
to_destroy = true
}
end
end,
--Can spawn when Catan mode enabled
in_pool = function(self, args)
return G.GAME.pool_flags.catan_enabled
end
}
end
--"Negative" Enhancements
--Global function wrapper to set DisEnhancements
function Card:set_disenhancement(center, initial, delay_sprites)
--If the card has heal seal
if self.seal and self.seal == 'unstb_heal' then
if self.debuff then
--If debuff, remove the seal and proceed through the rest of the process
self.seal = nil
else
--If the card is on hand, show resisting animation
if self.area == G.hand then
event({func = function() play_sound('unstb_heal', 1, 0.3) return true end})
forced_message("Resist!", self, G.C.GREEN, true)
elseif self.area == G.play then
--Play healing sound effect if it's in play
event({func = function() play_sound('unstb_heal', 1, 0.3) return true end})
end
--Don't turn the card into DisEnhancements
return
end
end
--If the card is debuffed, removed the heal seal entirely
if self.seal and self.seal == 'unstb_heal' and self.debuff then
self.seal = nil
end
local old_center = self.config.center
--Set the previous enhancement to keep track
if not old_center.disenhancement then
self.config.prev_center = old_center
end
--Calling appropriate set_ability
self:set_ability(center, initial, delay_sprites)
end
if unstb_config.enh.enh_disenh then
--Global blacklist for card enhancements that cannot be converted by radioactive card
unstb_global.radioactive_blacklist = {
m_unstb_radioactive = true,
m_unstb_slop = true,
}
--Radioactive
SMODS.Enhancement {
key = "radioactive",
atlas = "unstb_back",
pos = {x=3, y = 0},
replace_base_card = true,
no_suit = true,
no_rank = true,
always_scores = true,
disenhancement = true, --exclusive property to check if it's disenhancement or not
config = {extra = { chips = 13, odds_conv = 2, odds_mult = 3, mult_good = 1.75, mult_bad = 0.5 }, h_x_mult = 1},
loc_vars = function(self)
return {
vars = { self.config.extra.chips, (G.GAME and G.GAME.probabilities.normal or 1), self.config.extra.odds_conv, self.config.extra.odds_mult, self.config.extra.mult_good, self.config.extra.mult_bad }
}
end,
set_ability = function(self, card, initial, delay_sprites)
--Set the hand multiplier the first time
if pseudorandom('radioactive'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_mult then
card.ability.h_x_mult = card.ability.extra.mult_good
else
card.ability.h_x_mult = card.ability.extra.mult_bad
end
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
if #context.scoring_hand > 1 then
--polling the eligible hand first
local eligible_hand = {}
for i=1, #context.scoring_hand do
--Exclude slop card because it interacts horribly with this
if not unstb_global.radioactive_blacklist[context.scoring_hand[i].config.center.key] and not context.scoring_hand[i].to_convert then
eligible_hand[#eligible_hand+1] = context.scoring_hand[i]
end
end
--Update: no longer show "Safe!" if the entire hand is not convertable
if #eligible_hand > 0 then
local target = pseudorandom_element(eligible_hand, pseudoseed('radioactive_conv'..G.SEED))
if pseudorandom('radioactive'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_conv then
target.to_convert = true
--Flipping Animation
event({trigger = 'after', delay = 0.1, func = function() target:flip(); play_sound('card1', 1); target:juice_up(0.3, 0.3); return true end })
--Changing Card Property
event({trigger = 'after', delay = 0.05, func = function()
target:set_disenhancement(G.P_CENTERS.m_unstb_radioactive)
return true end })
--Unflipping Animation
event({trigger = 'after', delay = 0.1, func = function() target:flip(); play_sound('tarot2', 1, 0.6); big_juice(card); target:juice_up(0.3, 0.3); target.to_convert = nil; return true end })
forced_message("Decayed!", target, G.C.RED, true)
else
forced_message("Safe!", card, G.C.GREEN, true)
end
end
end
return {
chips = card.ability.extra.chips
}
end
if context.cardarea == G.hand and not card.healed and context.main_scoring then
--Xmult handling ability is built-in, so this one just checks for odds and alter it respectively.
if pseudorandom('radioactive'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_mult then
card.ability.h_x_mult = card.ability.extra.mult_good
else
card.ability.h_x_mult = card.ability.extra.mult_bad
end
end
end,
--This cannot spawn naturally at all
in_pool = function(self, args)
return false
end
}
SMODS.Enhancement {
key = "biohazard",
atlas = "unstb_back",
pos = {x=4, y = 0},
replace_base_card = true,
no_suit = true,
no_rank = true,
always_scores = true,
disenhancement = true,
config = {extra = { xmult = 0.9, h_money = 1, odds_conv = 3}},
loc_vars = function(self)
return {
vars = { self.config.extra.xmult, self.config.extra.h_money, (G.GAME and G.GAME.probabilities.normal or 1), self.config.extra.odds_conv }
}
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
return {
xmult = card.ability.extra.xmult,
}
end
if context.discard and context.other_card == card then
local is_neutralized = next(SMODS.find_card('j_unstb_vaccination_card'))
--check if it is activated
if pseudorandom('biohazard'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_conv and not is_neutralized then
--check hand card
local hand_card = {}
for i = 1, #G.hand.cards do
hand_card[G.hand.cards[i]] = true
end
--populate valid cards
local valid_cards = {}
for k, v in ipairs(G.playing_cards) do
if v.config.center ~= G.P_CENTERS.m_unstb_biohazard and not hand_card[v] then --Exclude biohazard cards
valid_cards[#valid_cards+1] = v
end
end
if valid_cards[1] then
local target_card = pseudorandom_element(valid_cards, pseudoseed(seed or 'validcard'..G.GAME.round_resets.ante))
target_card:set_disenhancement(G.P_CENTERS.m_unstb_biohazard , nil, true)
end
end
end
if context.cardarea == G.hand and not card.healed and context.main_scoring then
local is_neutralized = next(SMODS.find_card('j_unstb_vaccination_card'))
if not card.debuff and not is_neutralized then
return {
dollars = -card.ability.extra.h_money
}
end
end
end,
--This cannot spawn naturally at all
in_pool = function(self, args)
return false
end
}
SMODS.Enhancement {
key = "poison",
atlas = "unstb_back",
pos = {x=5, y = 0},
replace_base_card = false,
no_suit = false,
no_rank = false,
always_scores = true,
disenhancement = true,
override_chip = 0,
config = {extra = { chip = 0.9, h_money = 1}},
loc_vars = function(self, info_queue, card)
local chip = 0
if card and card.base then
chip = card.base.nominal + (card.ability.perma_bonus or 0)
end
return {
vars = { chip , self.config.extra.h_money }
}
end,
--Override generate_ui so it does not display any chips
generate_ui = function(self, info_queue, card, desc_nodes, specific_vars, full_UI_table)
SMODS.Enhancement.super.generate_ui(self, info_queue, card, desc_nodes, specific_vars, full_UI_table)
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.main_scoring then
local totalChip = card.base.nominal + (card.ability.perma_bonus or 0) --Counted bonus chips too, go crying
return {
chips = -totalChip,
colour = G.C.RED
}
end
end,
discard_override = function(self, card, args) --Discard overridden function, injected by Lovely
card.ability.discarded = nil
draw_card(G.hand, G.deck, args.delay, 'down', false, card)
--Wrap shuffle in the event instead, so it should work now hopefully???
event({trigger = 'after', func = function()
G.deck:shuffle('poison'..G.GAME.round_resets.ante)
return true end})
end,
--This cannot spawn naturally at all
in_pool = function(self, args)
return false
end
}
end
--Add proper tag to stone cards, nothing should change gameplay-wise
SMODS.Enhancement:take_ownership('m_stone', {
replace_base_card = true,
no_suit = true,
no_rank = true,
always_scores = true,
}, true)
--New Ranks
--Hook for Card:get_id()
local decimal_rank_map = { ['unstb_0.5'] = 'unstb_1', unstb_r2 = '2', unstb_e = '3', unstb_Pi = '4'}
--Expose this part so other mods can register decimal rank used for Engineer
function unstb_global.register_decimal_rank_map(decimal_rank, integer_rank)
decimal_rank_map[decimal_rank] = integer_rank
end
local ref_getid = Card.get_id
function Card:get_id()
local engineer_joker = next(SMODS.find_card('j_unstb_engineer'))
--If 'Engineer' Joker exists, returns rounded up rank instead
if engineer_joker and SMODS.Ranks[self.base.value].is_decimal then
return SMODS.Ranks[decimal_rank_map[self.base.value]].id
end
return ref_getid(self)
end
--Function wrapper to check if a card has decimal rank
local function is_decimal(card)
return (SMODS.Ranks[card.base.value].is_decimal or SMODS.Ranks[card.base.value].decimal_compat) and not card.config.center.no_rank
end
--Hook for Poker Hand name
local ref_get_poker_hand_info = G.FUNCS.get_poker_hand_info
G.FUNCS.get_poker_hand_info = function(_cards)
local text, loc_disp_text, poker_hands, scoring_hand, disp_text = ref_get_poker_hand_info(_cards)
--print(disp_text)
if string.find(disp_text, 'Straight') then
for i=1, #scoring_hand do
if is_decimal(scoring_hand[i]) then
loc_disp_text = string.gsub(loc_disp_text, 'Straight', 'Gay')
break
end
end
end
return text, loc_disp_text, poker_hands, scoring_hand, disp_text
end
--Pool flag wrapper function to help assist managing ranks enable / disable
function setPoolRankFlagEnable(rank, isEnable)
if not G.GAME or G.GAME.pool_flags[rank] == isEnable then return end
G.GAME.pool_flags[rank] = isEnable
end
function getPoolRankFlagEnable(rank)
return (G.GAME and G.GAME.pool_flags[rank] or false)
end
--Shared pool rank checking function
local function unstb_rankCheck(self, args)
if args and args.initial_deck then
return false
end
return getPoolRankFlagEnable(self.key)
end
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = '0',
card_key = '0',
pos = { x = 6 },
nominal = 0,
strength_effect = {
fixed = 2,
random = false,
ignore = false
},
next = { 'unstb_0.5', 'unstb_1', 'unstb_r2' },
prev = { 'unstb_???' },
shorthand = '0',
straight_edge = true,
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = '0.5',
card_key = '0.5',
pos = { x = 2 },
nominal = 0.5,
next = {'unstb_1', 'unstb_r2', '2', 'unstb_e' },
prev = { 'unstb_0' },
shorthand = '0.5',
is_decimal = true,
rank_act = {'0', '0.5', '1'},
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = '1',
card_key = '1',
pos = { x = 5 },
nominal = 1,
strength_effect = {
fixed = 2,
random = false,
ignore = false
},
next = {'unstb_r2', '2', 'unstb_e' },
prev = { 'unstb_0' },
shorthand = '1',
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = 'r2',
card_key = 'R',
pos = { x = 7 },
nominal = 1.41,
next = {'2', 'unstb_e', '3', 'unstb_Pi' },
prev = { 'unstb_1' },
shorthand = '/2',
is_decimal = true,
rank_act = {'1', '1.41', '2'},
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = 'e',
card_key = 'E',
pos = { x = 3 },
nominal = 2.72,
next = { '3', 'unstb_Pi', '4' },
prev = { '2' },
shorthand = 'e',
is_decimal = true,
rank_act = {'2', '2.72', '3'},
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = 'Pi',
card_key = 'P',
pos = { x = 4 },
nominal = 3.14,
next = { '4', '5' },
prev = { '3' },
shorthand = 'Pi',
is_decimal = true,
rank_act = {'3', '3.14', '4'},
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = '???',
card_key = '?',
pos = { x = 1 },
nominal = 0,
next = { 'unstb_???' },
prev = { 'unstb_???' },
shorthand = '?',
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex_hc',
lc_atlas = 'rank_ex',
hidden = true,
key = '21', -- the number or name (ex. "Jack") of your rank if it has one
card_key = '21', -- the short key put after the suit when coding a card object (ex. for the card "H_5" the card_key is 5). this seems to usually match the shorthand
pos = { x = 0 }, -- x position on the card atlas
nominal = 21, -- the number of chips this card scores
next = { 'unstb_???' }, -- the next rank directly above it, used for Strength Tarot
prev = { 'unstb_???' }, -- UNSTB addition: the previous rank directly below it
shorthand = '21', -- used for deck preview
in_pool = unstb_rankCheck,
}
-- "Premium Pack" Ranks
SMODS.Rank {
hc_atlas = 'rank_ex2_hc',
lc_atlas = 'rank_ex2',
hidden = true,
key = '11',
card_key = '11',
pos = { x = 0 },
nominal = 11,
next = { 'unstb_12' },
prev = {'10'},
shorthand = '11',
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex2_hc',
lc_atlas = 'rank_ex2',
hidden = true,
key = '12',
card_key = '12',
pos = { x = 1 },
nominal = 12,
next = { 'unstb_13' },
prev = {'unstb_11'},
shorthand = '12',
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex2_hc',
lc_atlas = 'rank_ex2',
hidden = true,
key = '13',
card_key = '13',
pos = { x = 2 },
nominal = 13,
next = { 'Ace' },
prev = {'unstb_12'},
shorthand = '13',
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex2_hc',
lc_atlas = 'rank_ex2',
hidden = true,
key = '25',
card_key = '25',
pos = { x = 3 },
nominal = 25,
next = { 'unstb_???' },
prev = { 'unstb_???' },
shorthand = '25',
in_pool = unstb_rankCheck,
}
SMODS.Rank {
hc_atlas = 'rank_ex2_hc',
lc_atlas = 'rank_ex2',
hidden = true,
key = '161',
card_key = '161',
pos = { x = 4 },
nominal = 161,
next = { 'unstb_???' },
prev = { 'unstb_???' },
shorthand = '161',
in_pool = unstb_rankCheck,
}
--
SMODS.Ranks['2'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['2'].next = {'unstb_e', '3', 'unstb_Pi'}
SMODS.Ranks['3'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['3'].next = {'unstb_Pi', '4'}
--Change straight edge off from Ace, so it start to look at rank 0 instead
--SMODS.Ranks['Ace'].straight_edge = false
SMODS.Ranks['Ace'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['Ace'].next = {'unstb_r2', '2', 'unstb_e'}
--Vanilla Rank Alteration for Set 2
SMODS.Ranks['10'].next = {'Jack', 'unstb_11'}
--Add preliminary prev property into vanilla rank list, so the default behavior will always point to this one
local vanilla_rank_list = {'2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace'}
for i=#vanilla_rank_list, 2, -1 do
SMODS.Ranks[vanilla_rank_list[i]].prev = {vanilla_rank_list[i-1]}
end
SMODS.Ranks['2'].prev = {'Ace'}
--Add a custom in_pool for high vanilla ranks, so they can be hidden from appearing in Lowkey Combo Decks HUD
for i=#vanilla_rank_list, 5, -1 do
SMODS.Ranks[vanilla_rank_list[i]].in_pool = function()
return not G.GAME.prevent_high_rank
end
end
--Booster Pack for Premium Card
local premium_booster_rate = {0.75, 0.75, 0.5, 0.1}
local premium_booster_cost = {4, 4, 6, 8}
--Needs all three ranks setting to be on to start appearing
if check_enable_taglist({'rank_binary', 'rank_decimal', 'rank_21'}) then
for i = 1, 4 do
SMODS.Booster{
key = 'prem_'..(i <= 2 and i or i == 3 and 'jumbo' or 'mega'),
cost = premium_booster_cost[i],
config = {extra = i <= 2 and 3 or 5, choose = i <= 3 and 1 or 2},
draw_hand = false,
create_card = function(self, card)
local card = create_card("Base", G.pack_cards, nil, nil, nil, true, nil, 'prem')
local edition_rate = 3
local edition = poll_edition('premium_edition'..G.GAME.round_resets.ante, edition_rate, true)
local rank_set = {"unstb_0", "unstb_0.5", "unstb_1", "unstb_r2", "unstb_e", "unstb_Pi", "unstb_11", "unstb_12", "unstb_13", "unstb_21", "unstb_25", "unstb_161"}
--Lowkey Deck / Sleeve combo mode, prevent high rank from being spawned
if G.GAME.prevent_high_rank then
rank_set = {"unstb_0", "unstb_0.5", "unstb_1", "unstb_r2", "unstb_e", "unstb_Pi"}
end
SMODS.change_base(card, nil, pseudorandom_element(rank_set, pseudoseed('premium_rank'..G.GAME.round_resets.ante)))
--Pooling Enhancements
local cen_pool = {}
for k, v in pairs(G.P_CENTER_POOLS["Enhanced"]) do
if not v.replace_base_card and not v.disenhancement then
cen_pool[#cen_pool+1] = v
end
end
local enh = pseudorandom_element(cen_pool, pseudoseed('premium_enhance'))
card:set_ability(enh)
card:set_edition(edition)
card:set_seal(SMODS.poll_seal({mod = 10}))
return card
-- return {set = 'Polymino', area = G.pack_cards, skip_materialize = nil, soulable = nil, key_append = 'vir'}
end,
ease_background_colour = function(self) ease_background_colour{new_colour = HEX('62a1b4'), special_colour = HEX('fce1b6'), contrast = 2} end,
particles = function(self)
G.booster_pack_sparkles = Particles(1, 1, 0,0, {
timer = 0.015,
scale = 0.3,
initialize = true,
lifespan = 3,
speed = 0.2,
padding = -1,
attach = G.ROOM_ATTACH,
colours = {G.C.BLACK, G.C.GOLD},
fill = true
})
G.booster_pack_sparkles.fade_alpha = 1
G.booster_pack_sparkles:fade(1, 0)
end,
pos = get_coordinates(i+3),
atlas = 'booster',
weight = premium_booster_rate[i],
}
end
end
-- Poker Hand Rewrite to support new ranks probably?
-- Currently unused: Straights use SMODS Implementation perfectly fine
local function get_keys(t)
local keys={}
for key,_ in pairs(t) do
table.insert(keys, key)
end
return keys
end
local isAllowDecimal = true
local decimalHands = {['unstb_0.5'] = {0, 1}, ['unstb_e'] = {2, 3}, ['unstb_Pi'] = {3, 4}}
--Unused, redirect the entire straight calculation to ustb_get_straight instead due to straight_edge shenanigans
function ustb_get_straight_wrapper(hand)
local ret = {}
if #hand < (5 - (four_fingers and 1 or 0)) then return ret end
local hasDecimal = false
for i = 1, #hand do
if decimalHands[hand[i].base.value] then
hasDecimal = true
break
end
end
return ustb_straight_calculation(hand)
--[[if hasDecimal then
--print('Has decimal')
return ustb_straight_decimal(hand)
else
--print('do not has decimal')
return get_straight(hand)
end]]
end
function ustb_get_straight(hand)
--Basically SMOD's implementation of straight_edge
--But with a slightly different fix to make extra ranks work
local ret = {}
local four_fingers = next(SMODS.find_card('j_four_fingers'))
local can_skip = next(SMODS.find_card('j_shortcut'))
local can_loop = next(SMODS.find_card('j_unstb_portal'))
if #hand < (5 - (four_fingers and 1 or 0)) then return ret end
local t = {}
local RANKS = {}
for i = 1, #hand do
if hand[i]:get_id() > 0 then
local rank = hand[i].base.value
RANKS[rank] = RANKS[rank] or {}
RANKS[rank][#RANKS[rank] + 1] = hand[i]
end
end
local straight_length = 0
local straight = false
local skipped_rank = false
local vals = {}
for k, v in pairs(SMODS.Ranks) do
if v.straight_edge then
table.insert(vals, k)
end
end
local init_vals = {}
for _, v in ipairs(vals) do
init_vals[v] = true
end
if not next(vals) then table.insert(vals, 'Ace') end
local initial = true
local br = false
local end_iter = false
local i = 0
--print(inspect(vals))
--Manually swapping vals because it would not work properly if it starts at Ace
--However, Ace has to be kept otherwise it allows the loop back
--TODO: Honestly could figure out the proper logic so it can be converted to lovely patch instead
vals[1] = 'unstb_0'
--vals[2] = 'Ace'
if not can_loop then
vals[2] = 'Ace'
end
--If there is no possible RANKS, breaks the function early
if not next(RANKS) then return ret end
while 1 do
end_iter = false
if straight_length >= (5 - (four_fingers and 1 or 0)) then
straight = true
end
i = i + 1
if br or (i > #SMODS.Rank.obj_buffer + 1) then break end
if not next(vals) then break end
for _, val in ipairs(vals) do
--print(val)
--print('===')
if init_vals[val] and not initial and not can_loop then br = true end
if RANKS[val] then
straight_length = straight_length + 1
skipped_rank = false
for _, vv in ipairs(RANKS[val]) do
t[#t + 1] = vv
end
vals = SMODS.Ranks[val].next
--print(inspect(vals))
--print('---')
initial = false
end_iter = true
break
end
end
if not end_iter then
local new_vals = {}
for _, val in ipairs(vals) do
for _, r in ipairs(SMODS.Ranks[val].next) do
table.insert(new_vals, r)
end
end
vals = new_vals
if can_skip and not skipped_rank then
skipped_rank = true
else
straight_length = 0
skipped_rank = false
if not straight then t = {} end
if straight then break end
end
end
end
if not straight then return ret end
table.insert(ret, t)
return ret
end
SMODS.PokerHandPart:take_ownership('_straight', {
func = function(hand) return ustb_get_straight(hand) end
})
--Generic consumable selection check utility function
--Written this way for Cryptid compatibility
local function get_consumable_use_hand_count(card, hand)
local selected_cards = {}
for i = 1, #hand do
if hand[i] ~= card then selected_cards[#selected_cards+1] = hand[i] end
end
return #selected_cards
end
--Returns selected hands excluding itself (in case of Cryptid merged consumable cards)
local function get_selected_cards(card, hand)
local selected_cards = {}
for i = 1, #hand do
if hand[i] ~= card then selected_cards[#selected_cards+1] = hand[i] end
end
return selected_cards
end
--Auxiliary Card
if unstb_config.gameplay.c_aux then
SMODS.ConsumableType{
key = 'Auxiliary',
primary_colour = HEX('424e54'),
secondary_colour = G.C.UNSTB_AUX,
loc_txt = {},
default = 'c_unstb_aux_seal_move',
collection_rows = {5, 5}
}
SMODS.UndiscoveredSprite{
key = 'Auxiliary',
atlas = 'auxiliary_undiscovered',
pos = get_coordinates(0)
}
--Booster Pack for Auxiliary Cards
local aux_booster_rate = {1, 1, 1, 0.25}
local aux_booster_cost = {4, 4, 6, 8}
for i = 1, 4 do
SMODS.Booster{
key = 'aux_'..(i <= 2 and i or i == 3 and 'jumbo' or 'mega'),
cost = aux_booster_cost[i],
config = {extra = i <= 2 and 3 or 5, choose = i <= 3 and 1 or 2},
draw_hand = false,
create_card = function(self, card)
local card = create_card('Auxiliary', G.pack_cards, nil, nil, true, true, nil, 'aux')
local neg_chance = pseudorandom('aux_rng')
if neg_chance < 0.4 and G.GAME.used_vouchers.v_unstb_aux2 and card.config.center.key ~= 'c_unstb_aux_dark_matter' then
card:set_edition({negative = true}, true)
end
return card
end,
ease_background_colour = function(self)
ease_colour(G.C.DYN_UI.MAIN, G.C.UNSTB_AUX)
ease_background_colour{new_colour = HEX('477978'), special_colour = HEX('00a669'), contrast = 4}
end,
pos = get_coordinates(i-1),
atlas = 'booster',
weight = aux_booster_rate[i],
--in_pool = function() return (pseudorandom('aux'..G.SEED) < aux_booster_rate[i]) end
}
end
--Original Reserve Card Button Code from MoreFluff (which credits to Cryptid and Betmma)
G.FUNCS.can_take_card = function(e)
local card = e.config.ref_table
--If the edition is negative, adds one more buffer to check if you can take it or not
local neg_buffer = (card.edition or {}).key == 'e_negative' and 1 or 0
if #G.consumeables.cards < G.consumeables.config.card_limit + neg_buffer then
e.config.colour = G.C.GREEN
e.config.button = "take_card"
else
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
end
end
G.FUNCS.take_card = function(e)
local c1 = e.config.ref_table
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.1,
func = function()
c1.area:remove_card(c1)
c1:add_to_deck()
if c1.children.price then
c1.children.price:remove()
end
c1.children.price = nil
if c1.children.buy_button then
c1.children.buy_button:remove()
end
c1.children.buy_button = nil
remove_nils(c1.children)
play_sound('generic1')
G.consumeables:emplace(c1)
G.GAME.pack_choices = G.GAME.pack_choices - 1
if G.GAME.pack_choices <= 0 then
G.FUNCS.end_consumeable(nil, delay_fac)
end
return true
end,
}))
end
local G_UIDEF_card_focus_ui_ref = G.UIDEF.card_focus_ui
function G.UIDEF.card_focus_ui(card)
base_background = G_UIDEF_card_focus_ui_ref(card)
local card_width = card.T.w + (card.ability.consumeable and -0.1 or card.ability.set == 'Voucher' and -0.16 or 0)
local base_attach = base_background:get_UIE_by_ID('ATTACH_TO_ME')
if ((card.area == G.pack_cards and G.pack_cards)) and card.ability.consumeable and card.ability.set == "Auxiliary" then
base_attach.children.use = G.UIDEF.card_focus_button{
card = card, parent = base_attach, type = 'select',
func = 'can_take_card', button = 'use_card', card_width = card_width
}
end
return base_background
end
local G_UIDEF_use_and_sell_buttons_ref = G.UIDEF.use_and_sell_buttons
function G.UIDEF.use_and_sell_buttons(card)
if (card.area == G.pack_cards and G.pack_cards) and card.ability.consumeable then --Add a take button for Auxiliary pack
if card.ability.set == "Auxiliary" then
return {
n = G.UIT.ROOT,
config = { padding = -0.1, colour = G.C.CLEAR },
nodes = {
{
n = G.UIT.R,
config = {
ref_table = card,
r = 0.08,
padding = 0.1,
align = "bm",
minw = 0.5 * card.T.w - 0.15,
minh = 0.7 * card.T.h,
maxw = 0.7 * card.T.w - 0.15,
hover = true,
shadow = true,
colour = G.C.UI.BACKGROUND_INACTIVE,
one_press = true,
button = "use_card",
func = "can_take_card",
},
nodes = {
{
n = G.UIT.T,
config = {
text = 'SELECT',
colour = G.C.UI.TEXT_LIGHT,
scale = 0.55,
shadow = true,
},
},
},
},
},
}
end
end
return G_UIDEF_use_and_sell_buttons_ref(card)
end
--Auxiliary Cards Code Starts Here--
if unstb_config.gameplay.seal_suit then
--Suit Seals Addition Cards
for i = 1, #suit_seal_list do
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_'..string.lower(suit_seal_list[i]),
config = {extra = {count = 2, seal = 'unstb_'..string.lower(suit_seal_list[i])}},
--discovered = true,
loc_vars = function(self, info_queue, card)
local suit = localize(suit_seal_list[i], 'suits_singular') ..' Seal'
local suit_color = G.C.SUITS[suit_seal_list[i]]
info_queue[#info_queue+1] = {set = 'Other', key = 'suit_seal'}
return {vars = {card.ability.extra.count, suit, colours = {suit_color}}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) >= 1 and get_consumable_use_hand_count(card, G.hand.highlighted) <= card.ability.extra.count) then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
for i = 1, #G.hand.highlighted do
event({trigger = 'after', delay = 0.2,
func = function()
G.hand.highlighted[i]:set_seal(card.ability.extra.seal, nil, true)
return true end })
end
delay(0.5)
event({trigger = 'after', delay = 0.2,
func = function()
G.hand:unhighlight_all();
return true end })
end,
pos = get_coordinates(i+1),
}
end
--Face Seal Card
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_face',
config = {extra = {count = 2, seal = 'unstb_face'}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'unstb_face_seal'}
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) >= 1 and get_consumable_use_hand_count(card, G.hand.highlighted) <= card.ability.extra.count) then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
for i = 1, #G.hand.highlighted do
event({trigger = 'after', delay = 0.2,
func = function()
G.hand.highlighted[i]:set_seal(card.ability.extra.seal, nil, true)
return true end })
end
delay(0.5)
event({trigger = 'after', delay = 0.2,
func = function()
G.hand:unhighlight_all();
return true end })
end,
pos = get_coordinates(6),
}
end
-- +2
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_plus_two',
config = {extra = {count = 2}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
local selected_hands = get_selected_cards(card, G.hand.highlighted)
if G.hand and (#selected_hands == 1) and not selected_hands[1].config.center.no_suit then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local targetCard = G.hand.highlighted[1]
event({
trigger = 'after',
delay = 0.7,
func = function()
local cards = {}
for i=1, card.ability.extra.count do
cards[i] = true
local _rank = pseudorandom_element(SMODS.Ranks, pseudoseed('aux_plus_two')) or SMODS.Ranks['2']
local _suit = SMODS.Suits[targetCard.base.suit]
create_playing_card({front = G.P_CARDS[(_suit.card_key)..'_'..(_rank.card_key)], center = G.P_CENTERS.c_base}, G.hand, nil, i ~= 1, {G.C.SECONDARY_SET.Spectral})
end
playing_card_joker_effects(cards)
return true end })
end,
pos = get_coordinates(7),
}
-- Wild +4
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_plus_four_wild',
config = {extra = {count = 4}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
local selected_hands = get_selected_cards(card, G.hand.highlighted)
if G.hand and (#selected_hands == 1) and not selected_hands[1].config.center.no_rank then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local targetCard = G.hand.highlighted[1]
event({
trigger = 'after',
delay = 0.7,
func = function()
local cards = {}
for i=1, card.ability.extra.count do
cards[i] = true
local _rank = SMODS.Ranks[targetCard.base.value]
local _suit = pseudorandom_element(SMODS.Suits, pseudoseed('aux_wild_plus_four')) or SMODS.Suits['Spades']
create_playing_card({front = G.P_CARDS[(_suit.card_key)..'_'..(_rank.card_key)], center = G.P_CENTERS.c_base}, G.hand, nil, i ~= 1, {G.C.SECONDARY_SET.Spectral})
end
playing_card_joker_effects(cards)
return true end })
end,
pos = get_coordinates(8),
}
-- In-round Instants
local function get_instant_effect(id, amount)
if id == 3 then
ease_hands_played(amount)
elseif id == 1 then
ease_discard(amount)
elseif id == 2 then
--Base game has this for tags, so I utilize them here
G.hand:change_size(amount)
G.FUNCS.draw_from_deck_to_hand(amount)
G.GAME.round_resets.temp_handsize = (G.GAME.round_resets.temp_handsize or 0) + amount
else
forced_message("Nope!", currentCard, G.C.PURPLE, true)
end
end
local aux_instants = {'aux_inst_disc', 'aux_inst_hsize', 'aux_inst_hand'}
for i = 1, #aux_instants do
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = aux_instants[i],
config = {extra = {amount = 3}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.amount}}
end,
can_use = function(self, card)
--Only usable in-round. I can't believe it had to be checked *this much*
return G.hand and not G.blind_select and G.STATE ~= G.STATES.ROUND_EVAL and not G.shop and not G.booster_pack
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
event({
trigger = 'after',
delay = 0.2,
func = function()
play_sound('generic1')
get_instant_effect(i, card.ability.extra.amount)
return true end })
end,
pos = get_coordinates(8+i),
}
end
--Seal Swap
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_seal_move',
config = {extra = {}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) == 2) then
return true
end
return false
end,
use = function(self, card)
local card_l_seal = G.hand.highlighted[1].seal
--Animation code mostly ported from basegame tarot
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
for i=1, #G.hand.highlighted do
local percent = 1.15 - (i-0.999)/(#G.hand.highlighted-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() G.hand.highlighted[i]:flip();play_sound('card1', percent);G.hand.highlighted[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Handle the conversion
event({trigger = 'after',delay = 0.1,func = function()
G.hand.highlighted[1]:set_seal(G.hand.highlighted[2].seal, true, true)
G.hand.highlighted[2]:set_seal(card_l_seal, true, true)
--If a heal seal is on disenhanced card, heal it
for i=1, #G.hand.highlighted do
if G.hand.highlighted[i].config.center.disenhancement and G.hand.highlighted[i].seal == 'unstb_heal' then
G.hand.highlighted[i]:heal()
end
end
return true end })
for i=1, #G.hand.highlighted do
local percent = 0.85 + (i-0.999)/(#G.hand.highlighted-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() G.hand.highlighted[i]:flip();play_sound('tarot2', percent, 0.6);G.hand.highlighted[i]:juice_up(0.3, 0.3);return true end })
end
event({trigger = 'after', delay = 0.2,func = function() G.hand:unhighlight_all(); return true end })
delay(0.5)
end,
pos = get_coordinates(12),
}
if unstb_config.rank.rank_binary then
--All for One
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_conv_1',
config = {extra = {count = 3}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) >= 1) and (get_consumable_use_hand_count(card, G.hand.highlighted) <= card.ability.extra.count) then
return true
end
return false
end,
use = function(self, card)
--Enable Rank Flag
setPoolRankFlagEnable('unstb_1', true);
--Animation code mostly ported from basegame tarot
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
for i=1, #G.hand.highlighted do
local percent = 1.15 - (i-0.999)/(#G.hand.highlighted-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() G.hand.highlighted[i]:flip();play_sound('card1', percent);G.hand.highlighted[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Handle the conversion
for i=1, #G.hand.highlighted do
event({trigger = 'after',delay = 0.1,func = function()
SMODS.change_base(G.hand.highlighted[i], G.hand.highlighted[i].base.suit, 'unstb_1')
return true end })
end
for i=1, #G.hand.highlighted do
local percent = 0.85 + (i-0.999)/(#G.hand.highlighted-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() G.hand.highlighted[i]:flip();play_sound('tarot2', percent, 0.6);G.hand.highlighted[i]:juice_up(0.3, 0.3);return true end })
end
event({trigger = 'after', delay = 0.2,func = function() G.hand:unhighlight_all(); return true end })
delay(0.5)
end,
pos = get_coordinates(13),
}
end
if unstb_config.rank.rank_21 then
--The Twenty-One
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_21',
config = {extra = {count = 5}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
local selected_hands = get_selected_cards(card, G.hand.highlighted)
if G.hand and (#selected_hands >= 1) and (#selected_hands <= card.ability.extra.count) and blackJack_evalrank(selected_hands, 21)>=21 then
return true
end
return false
end,
use = function(self, card)
--Enable Rank Flag
setPoolRankFlagEnable('unstb_21', true);
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Populate removed cards
local destroyed_cards = {}
for i = 1, #G.hand.highlighted do destroyed_cards[#destroyed_cards+1] = G.hand.highlighted[i] end
--Destroy Selected Card
event({
trigger = 'after',
delay = 0.1,
func = function()
for i=#destroyed_cards, 1, -1 do
local c = destroyed_cards[i]
if c.ability.name == 'Glass Card' then
c:shatter()
else
c:start_dissolve(nil, i == #destroyed_cards)
end
end
return true end })
--Adds Card
event({
trigger = 'after',
delay = 0.7,
func = function()
local cards = {true}
local _rank = SMODS.Ranks['unstb_21']
local _suit = pseudorandom_element(SMODS.Suits, pseudoseed('aux_21')) or SMODS.Suits['Spades']
create_playing_card({front = G.P_CARDS[(_suit.card_key)..'_'..(_rank.card_key)], center = G.P_CENTERS.c_base}, G.hand, nil, i ~= 1, {G.C.SECONDARY_SET.Spectral})
playing_card_joker_effects(cards)
return true end })
--Call joker calculations for post-destroyed stuff
delay(0.3)
for i = 1, #G.jokers.cards do
G.jokers.cards[i]:calculate_joker({remove_playing_cards = true, removed = destroyed_cards})
end
end,
pos = get_coordinates(14),
}
end
if check_enable_taglist({'edition_upgrade', 'enh_disenh'}) then
--Monkey Paw
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_upgrade',
config = {extra = {}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'upgrade_edition'}
info_queue[#info_queue+1] = {set = 'Other', key = 'poison_tooltip'}
return {vars = {}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) == 1) then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local selected_card = G.hand.highlighted[1]
local selected_index
local poisoned_card = {}
--look for two adjacent cards in hand
for i=1, #G.hand.cards do
if G.hand.cards[i] == selected_card then
selected_index = i
end
end
--print(selected_index)
--Cover edge cases
local handCount = #G.hand.cards
if handCount >=3 then --If the hand has 3 cards or more, pick two adjacent cards to poison
if selected_index>1 then
poisoned_card[1] = G.hand.cards[selected_index-1]
else
poisoned_card[1] = G.hand.cards[handCount]
end
if selected_index<handCount then
poisoned_card[2] = G.hand.cards[selected_index+1]
else
poisoned_card[2] = G.hand.cards[1]
end
elseif #G.hand.cards ==2 then --If the hand has only 2 cards, only one other card can be poisoned
if selected_index == 1 then
poisoned_card[1] = G.hand.cards[2]
else
poisoned_card[1] = G.hand.cards[1]
end
end
for i=1, #poisoned_card do
local percent = 1.15 - (i-0.999)/(#poisoned_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() poisoned_card[i]:flip();play_sound('card1', percent);poisoned_card[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Handle the conversion
event({trigger = 'after',delay = 0.1,func = function()
edition_upgrade(selected_card)
return true end })
for i=1, #poisoned_card do
event({trigger = 'after',delay = 0.1,func = function()
poisoned_card[i]:set_disenhancement(G.P_CENTERS.m_unstb_poison)
return true end })
end
for i=1, #poisoned_card do
local percent = 0.85 + (i-0.999)/(#poisoned_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() poisoned_card[i]:flip();play_sound('tarot2', percent, 0.6);poisoned_card[i]:juice_up(0.3, 0.3);return true end })
end
event({trigger = 'after', delay = 0.2,func = function() G.hand:unhighlight_all(); return true end })
delay(0.5)
end,
pos = get_coordinates(15),
}
end
if unstb_config.enh.enh_disenh then
--Heal Seal Card
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_heal',
config = {extra = {count = 1, seal = 'unstb_heal'}},
--discovered = true,
weight = 10, --Large chance to appear when it is needed
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'unstb_heal_seal'}
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) == card.ability.extra.count) then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
for i = 1, #G.hand.highlighted do
event({trigger = 'after', delay = 0.2,
func = function()
G.hand.highlighted[i]:set_seal(card.ability.extra.seal, nil, true)
--If the selected card has DisEnhancements, heal it
if G.hand.highlighted[i].config.center.disenhancement then
G.hand.highlighted[i]:heal()
play_sound('unstb_heal', 1, 0.3)
forced_message("Healed!", G.hand.highlighted[i], G.C.GREEN, true)
end
return true end })
end
delay(0.5)
event({trigger = 'after', delay = 0.2,
func = function()
G.hand:unhighlight_all();
return true end })
end,
pos = get_coordinates(16),
--Can spawn only when more than 10 cards the deck is Disenhanced
in_pool = function(self, args)
--For some reason, not checking this crash Cryptid on start up
if not G.playing_cards then return false end
local count = 0
for k, v in ipairs(G.playing_cards) do
if v.config.center.disenhancement then
count = count+1
end
end
return count > 10
end,
}
--Heal Hand
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_heal_hand',
config = {extra = {}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'disenhancement'}
return {vars = {card.ability.extra.count}}
end,
weight = 10, --Large chance to appear when it is needed
can_use = function(self, card)
--Usable only in-game
if G.hand and not G.blind_select and G.STATE ~= G.STATES.ROUND_EVAL and not G.shop and not G.booster_pack then
--Check if the hand contains at least one DisEnhancements
for i = 1, #G.hand.cards do
if G.hand.cards[i].config.center.disenhancement then
return true
end
end
return false
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Populate List of cards to heal
local heal_cards = {}
for i = 1, #G.hand.cards do
if G.hand.cards[i].config.center.disenhancement then
heal_cards[#heal_cards+1] = G.hand.cards[i]
end
end
if #heal_cards>0 then
play_sound('unstb_heal', 1, 0.3)
end
for i = 1, #heal_cards do
event({trigger = 'after', delay = 0.1, func = function()
heal_cards[i]:heal()
play_sound('tarot2', 1, 0.4)
heal_cards[i]:juice_up()
return true
end
})
end
end,
pos = get_coordinates(17),
--Can spawn only when more than 1/4 of deck is Disenhanced
in_pool = function(self, args)
--For some reason, not checking this crash Cryptid on start up
if not G.playing_cards then return false end
local count = 0
for k, v in ipairs(G.playing_cards) do
if v.config.center.disenhancement then
count = count+1
end
end
return count > #G.playing_cards * 0.25
end,
}
end
--Lottery
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_lottery',
config = {extra = {odds_win = 4, prize = 20}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_win, card.ability.extra.prize }}
end,
can_use = function(self, card)
return true
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
event({trigger = 'after', func = function()
if pseudorandom('aux_lottery'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_win then
ease_dollars(20, true)
else
--Port from Wheel of Fortune
event({trigger = 'after', delay = 0.4, func = function()
attention_text({
text = localize('k_nope_ex'),
scale = 1.3,
hold = 1.4,
major = card,
backdrop_colour = G.C.UNSTB_AUX,
align = (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) and 'tm' or 'cm',
offset = {x = 0, y = (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.SMODS_BOOSTER_OPENED) and -0.2 or 0},
silent = true
})
event({trigger = 'after', delay = 0.06*G.SETTINGS.GAMESPEED, blockable = false, blocking = false, func = function()
play_sound('tarot2', 0.76, 0.4);return true end})
play_sound('tarot2', 1, 0.4)
card:juice_up(0.3, 0.5)
return true end })
end
return true
end
})
end,
pos = get_coordinates(18),
}
--Blank
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_blank',
config = {extra = {}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {}}
end,
can_use = function(self, card)
return true
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
event({trigger = 'after', func = function()
G.GAME.aux_blank_count = (G.GAME.aux_blank_count or 0) + 1
return true
end
})
end,
pos = get_coordinates(0),
in_pool = function(self, args)
if not G.GAME then return false end
return G.booster_pack and (G.GAME.aux_blank_count or 0) < (G.GAME.aux_blank_max_count or 2)
end,
}
--Dark Matter
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_dark_matter',
config = {extra = {slot = 1}},
--discovered = true,
loc_vars = function(self, info_queue, card)
local key = self.key
if (card.edition or {}).key == 'e_negative' then
key = self.key..'_n'
end
return {key = key, vars = {card.ability.extra.slot}}
end,
can_use = function(self, card)
return G.jokers.config.card_limit > 0
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
G.GAME.aux_blank_count = 0
--Increase the count for the next Dark Matter
G.GAME.aux_blank_max_count = (G.GAME.aux_blank_max_count or 2)+1
event({trigger = 'after', func = function()
if (card.edition or {}).key == 'e_negative' then
G.jokers.config.card_limit = G.jokers.config.card_limit - card.ability.extra.slot
else
G.jokers.config.card_limit = G.jokers.config.card_limit + card.ability.extra.slot
end
return true
end
})
end,
pos = get_coordinates(20),
--Can spawn only when redeemed Blank Auxiliary Card enough time
in_pool = function(self, args)
if not G.GAME then return false end
return G.booster_pack and (G.GAME.aux_blank_count or 0) >= (G.GAME.aux_blank_max_count or 2)
end,
}
--Random
SMODS.Consumable{
set = 'Auxiliary', atlas = 'auxiliary',
key = 'aux_random',
config = {extra = {count = 2}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
return true
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
for i = 1, math.min(card.ability.extra.count, G.consumeables.config.card_limit - #G.consumeables.cards) do
event({trigger = 'after', delay = 0.4, func = function()
if G.consumeables.config.card_limit > #G.consumeables.cards then
play_sound('timpani')
local c = create_card('Auxiliary', G.consumeables, nil, nil, nil, nil, nil, 'aux_random')
c:add_to_deck()
G.consumeables:emplace(c)
card:juice_up(0.3, 0.5)
end
return true end })
end
delay(0.6)
end,
pos = get_coordinates(19),
}
end
--Other Basegame Consumable Supports for new features
--Tarots
--main function for all three tarots
local function conversionTarot(hand, newcenter)
--Animation ported from basegame Tarot
for i=1, #hand do
local percent = 1.15 - (i-0.999)/(#hand-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() hand[i]:flip();play_sound('card1', percent);hand[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Handle the conversion
for i=1, #hand do
event({trigger = 'after',delay = 0.1,func = function()
hand[i]:set_ability(G.P_CENTERS[newcenter])
return true end })
end
for i=1, #hand do
local percent = 0.85 + (i-0.999)/(#hand-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() hand[i]:flip();play_sound('tarot2', percent, 0.6);hand[i]:juice_up(0.3, 0.3);return true end })
end
event({trigger = 'after', delay = 0.2,func = function() G.hand:unhighlight_all(); return true end })
delay(0.5)
end
if unstb_config.enh.enh_custom then
--The Time
SMODS.Consumable{
set = 'Tarot', atlas = 'tarot',
key = 'trt_time',
set_card_type_badge = function(self, card, badges)
badges[1] = create_badge(localize("k_tarot_exclaim"), get_type_colour(self or card.config, card), nil, 1.2)
end,
config = {extra = {count = 2}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_vintage']
return {vars = {card and card.ability.extra.count or self.config.extra.count}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) >= 1) and (get_consumable_use_hand_count(card, G.hand.highlighted) <= card.ability.extra.count) then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Special Interaction: If it is a Vintage Card already, reset the breakdown chance
if G.hand.highlighted[1].config.center.key == 'm_unstb_vintage' then
G.hand.highlighted[1].ability.extra.current_odd = 0
end
conversionTarot(G.hand.highlighted, 'm_unstb_vintage')
end,
pos = get_coordinates(0),
}
--The Acorn
SMODS.Consumable{
set = 'Tarot', atlas = 'tarot',
key = 'trt_acorn',
set_card_type_badge = function(self, card, badges)
badges[1] = create_badge(localize("k_tarot_exclaim"), get_type_colour(self or card.config, card), nil, 1.2)
end,
config = {extra = {count = 1}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'acorn_tooltip'}
return {vars = {card and card.ability.extra.count or self.config.extra.count}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) >= 1) and (get_consumable_use_hand_count(card, G.hand.highlighted) <= card.ability.extra.count) then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
conversionTarot(G.hand.highlighted, 'm_unstb_acorn')
end,
pos = get_coordinates(1),
}
--The Greed
SMODS.Consumable{
set = 'Tarot', atlas = 'tarot',
key = 'trt_greed',
set_card_type_badge = function(self, card, badges)
badges[1] = create_badge(localize("k_tarot_exclaim"), get_type_colour(self or card.config, card), nil, 1.2)
end,
config = {extra = {count = 2}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_promo']
return {vars = {card and card.ability.extra.count or self.config.extra.count}}
end,
can_use = function(self, card)
if G.hand and (get_consumable_use_hand_count(card, G.hand.highlighted) >= 1) and (get_consumable_use_hand_count(card, G.hand.highlighted) <= card.ability.extra.count) then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
conversionTarot(G.hand.highlighted, 'm_unstb_promo')
end,
pos = get_coordinates(2),
}
end
--New Rank-based Tarot
local tarot_half_rankList = {['unstb_0'] = 'unstb_0',
['unstb_1'] = 'unstb_0',
['2'] = 'unstb_1',
['3'] = 'unstb_1',
['4'] = '2',
['5'] = '2',
['6'] = '3',
['7'] = '3',
['8'] = '4',
['9'] = '4',
['10'] = '5',
['Ace'] = '5',
['unstb_11'] = '5',
['unstb_12'] = '6',
['unstb_13'] = '6',
['unstb_21'] = '10',
['unstb_25'] = 'unstb_12',}
if check_enable_taglist({'rank_binary', 'rank_decimal'}) then
SMODS.Consumable{
set = 'Tarot', atlas = 'tarot',
key = 'trt_half',
set_card_type_badge = function(self, card, badges)
badges[1] = create_badge(localize("k_tarot_exclaim"), get_type_colour(self or card.config, card), nil, 1.2)
end,
config = {extra = {count = 2}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {}}
end,
can_use = function(self, card)
local selected_hands = get_selected_cards(card, G.hand.highlighted)
if G.hand and (#selected_hands == 1) and not selected_hands[1].config.center.replace_base_card and tarot_half_rankList[selected_hands[1].base.value] then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local target_card = G.hand.highlighted[1]
--Create two copies of the card, with half rank
--Create one additional rank 0.5 card if applicable, copy the enhancement only, no other bonus property
local new_rank = tarot_half_rankList[target_card.base.value]
local extra_card = 0
if target_card.base.nominal%2 ==1 then
extra_card = 1
--Enable Rank Flag
setPoolRankFlagEnable('unstb_0.5', true);
end
event({
trigger = 'after',
delay = 0.7,
func = function()
local _first_dissolve = nil
local new_cards = {}
for i = 1, card.ability.extra.count + extra_card do
G.playing_card = (G.playing_card and G.playing_card + 1) or 1
local _card = copy_card(target_card, nil, nil, G.playing_card)
if extra_card>0 and i==card.ability.extra.count + extra_card then
--Remove special ability
--_card.set_ability(G.P_CENTERS.c_base)
_card:set_edition(nil, true, true)
_card:set_seal(nil, true, true)
_card.ability.perma_bonus = 0
SMODS.change_base(_card, nil, 'unstb_0.5')
else
SMODS.change_base(_card, nil, new_rank)
end
_card:add_to_deck()
G.deck.config.card_limit = G.deck.config.card_limit + 1
table.insert(G.playing_cards, _card)
G.hand:emplace(_card)
_card:start_materialize(nil, _first_dissolve)
_first_dissolve = true
new_cards[#new_cards+1] = _card
end
playing_card_joker_effects(new_cards)
return true end })
--Destroy the original card
local destroyed_cards = {target_card}
event({
trigger = 'after',
delay = 0.1,
func = function()
for i=#destroyed_cards, 1, -1 do
local c = destroyed_cards[i]
if c.ability.name == 'Glass Card' then
c:shatter()
else
c:start_dissolve(nil, i == #destroyed_cards)
end
end
return true end })
--Calling Jokers to process the card destroying
--Edit: Disable this part for now, technically it should not be counted as destroying card because the new card is considered a split
--[[delay(0.3)
for i = 1, #G.jokers.cards do
G.jokers.cards[i]:calculate_joker({remove_playing_cards = true, removed = destroyed_cards})
end]]
end,
pos = get_coordinates(3),
}
end
if unstb_config.rank.rank_decimal then
SMODS.Consumable{
set = 'Tarot', atlas = 'tarot',
key = 'trt_knowledge',
set_card_type_badge = function(self, card, badges)
badges[1] = create_badge(localize("k_tarot_exclaim"), get_type_colour(self or card.config, card), nil, 1.2)
end,
config = {extra = {count = 1}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {}}
end,
can_use = function(self, card)
local selected_hands = get_selected_cards(card, G.hand.highlighted)
if G.hand and (#selected_hands == 1) and not selected_hands[1].config.center.replace_base_card then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local targetCard = G.hand.highlighted[1]
event({
trigger = 'after',
delay = 0.7,
func = function()
local cards = {}
for i=1, card.ability.extra.count do
cards[i] = true
local created_rank = pseudorandom_element({'unstb_0.5', 'unstb_r2', 'unstb_e', 'unstb_Pi'}, pseudoseed('trt_knowledge'))
setPoolRankFlagEnable(created_rank, true)
local _rank = SMODS.Ranks[created_rank]
local _suit = SMODS.Suits[targetCard.base.suit]
create_playing_card({front = G.P_CARDS[(_suit.card_key)..'_'..(_rank.card_key)], center = G.P_CENTERS.c_base}, G.hand, nil, i ~= 1, {G.C.SECONDARY_SET.Spectral})
end
playing_card_joker_effects(cards)
return true end })
event({trigger = 'after', delay = 0.2,func = function() G.hand:unhighlight_all(); return true end })
end,
pos = get_coordinates(4),
}
end
--"Rebundant" Spectral (Same ability set as some Auxiliary exclusives, but worse)
if unstb_config.gameplay.c_rebundant then
if unstb_config.enh.enh_disenh then
--Elixir of Life
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_elixir',
config = {extra = {}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'disenhancement'}
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
--Check the entire deck
for k, v in ipairs(G.playing_cards) do
if v.config.center.disenhancement then
return true
end
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Populate List of cards to heal
local heal_cards = {}
for k, v in ipairs(G.playing_cards) do
if v.config.center.disenhancement then
heal_cards[#heal_cards+1] = v
end
end
if #heal_cards>0 then
play_sound('unstb_heal', 1, 0.3)
end
for i = 1, #heal_cards do
if heal_cards[i].area == G.hand then --If card is in hand, play animation
event({trigger = 'after', delay = 0.1, func = function()
heal_cards[i]:heal()
play_sound('tarot2', 1, 0.4)
heal_cards[i]:juice_up()
return true
end
})
else --Otherwise, just heal
heal_cards[i]:heal()
end
end
--Reduce Money if it's greater than 0, otherwise do nothing
if G.GAME.dollars > 0 then
ease_dollars(-math.ceil(G.GAME.dollars*0.5), true)
end
end,
weight = 10, --Large chance to appear when it is needed
pos = get_coordinates(0),
--Can spawn only when more than 1/3 of deck is Disenhanced
in_pool = function(self, args)
--For some reason, not checking this crash Cryptid on start up
if not G.playing_cards then return false end
local count = 0
for k, v in ipairs(G.playing_cards) do
if v.config.center.disenhancement then
count = count+1
end
end
return count > #G.playing_cards * 0.3
end,
}
end
if unstb_config.gameplay.seal_suit then
--Vessel
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_vessel',
config = {extra = {count = 4}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'suit_seal'}
return {vars = {card.ability.extra.count}}
end,
can_use = function(self, card)
local selected_hands = get_selected_cards(card, G.hand.highlighted)
if G.hand and (#selected_hands > 1 and #selected_hands <= card.ability.extra.count) then
local targetCard = selected_hands[1]
--Sort to get the actual target card
for i=1, #selected_hands do if selected_hands[i].T.x < targetCard.T.x then targetCard = selected_hands[i] end end
return not targetCard.config.center.no_suit and (unstb_global.SUIT_SEAL[targetCard.base.suit] or {}).seal_key
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Figure out what is left card, what is the left card
local targetCard = G.hand.highlighted[1]
--Sort to get the actual target card
for i=1, #G.hand.highlighted do if G.hand.highlighted[i].T.x < targetCard.T.x then targetCard = G.hand.highlighted[i] end end
--Adds Suit Seal to the right card (or none, if there's no matching suit)
local suit_seal = (unstb_global.SUIT_SEAL[targetCard.base.suit] or {}).seal_key
for i=1, #G.hand.highlighted do
if G.hand.highlighted[i] ~= targetCard then
G.hand.highlighted[i]:set_seal(suit_seal, nil, true)
end
end
--Destroy the leftmost card
local destroyed_cards = {targetCard}
event({
trigger = 'after',
delay = 0.1,
func = function()
for i=#destroyed_cards, 1, -1 do
local c = destroyed_cards[i]
if c.ability.name == 'Glass Card' then
c:shatter()
else
c:start_dissolve(nil, i == #destroyed_cards)
end
end
return true end })
--Calling Jokers to process the card destroying
delay(0.3)
for i = 1, #G.jokers.cards do
G.jokers.cards[i]:calculate_joker({remove_playing_cards = true, removed = destroyed_cards})
end
end,
pos = get_coordinates(1),
}
--Conferment
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_conferment',
config = {extra = {count = 4, cost = 8}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'unstb_face_seal'}
return {vars = {card and card.ability.extra.count or self.config.extra.count, card and card.ability.extra.cost or self.config.extra.cost}}
end,
can_use = function(self, card)
if G.hand and #G.hand.cards > 1 then
--Check if the hand contains at least one non-face card
for i = 1, #G.hand.cards do
if not G.hand.cards[i]:is_face() then
return true
end
end
return false
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
for i = 1, card.ability.extra.count do
event({
trigger = 'after',
delay = 0.2,
func = function()
local hand_card = {}
--Pool All eligible cards in hand, this has to be done each repetition
--Maybe there could be a prettier way for this?
for i = 1, #G.hand.cards do
if not G.hand.cards[i]:is_face() then
hand_card[#hand_card+1] = G.hand.cards[i]
end
end
local target_card = pseudorandom_element(hand_card, pseudoseed('conferment'))
if target_card then
target_card:set_seal('unstb_face', nil, true)
end
return true end
})
end
--Always decrease regardless of the current money
ease_dollars(-card.ability.extra.cost, true)
end,
pos = get_coordinates(2),
}
end
if unstb_config.rank.rank_binary then
--Amnesia
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_amnesia',
config = {extra = {count = 3}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card and card.ability.extra.count or self.config.extra.count}}
end,
can_use = function(self, card)
if G.hand and #G.hand.cards > 1 then
--Check if the hand contains at least one non-face card
for i = 1, #G.hand.cards do
if not G.hand.cards[i]:is_face() then
return true
end
end
return false
end
return false
end,
use = function(self, card)
--Enable Rank Flag
setPoolRankFlagEnable('unstb_0', true);
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Based on Basegame's Immolate
local converted_card = {}
local temp_hand = {}
for k, v in ipairs(G.hand.cards) do temp_hand[#temp_hand+1] = v end
table.sort(temp_hand, function (a, b) return not a.playing_card or not b.playing_card or a.playing_card < b.playing_card end)
pseudoshuffle(temp_hand, pseudoseed('amnesia'))
for i = 1, card.ability.extra.count do converted_card[#converted_card+1] = temp_hand[i] end
if #converted_card > 0 then
--Animation from Basegame Tarot
for i=1, #converted_card do
local percent = 1.15 - (i-0.999)/(#converted_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() converted_card[i]:flip();play_sound('card1', percent);converted_card[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Handle the conversion
for i=1, #converted_card do
event({trigger = 'after',delay = 0.1,func = function()
SMODS.change_base(converted_card[i], nil, 'unstb_0')
return true end })
end
for i=1, #converted_card do
local percent = 0.85 + (i-0.999)/(#converted_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() converted_card[i]:flip();play_sound('tarot2', percent, 0.6);converted_card[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.5)
end
end,
pos = get_coordinates(3),
}
end
if unstb_config.rank.rank_21 then
--Altar
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_altar',
config = {extra = {destroy_count = 4, create_count = 3}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card and card.ability.extra.destroy_count or self.config.extra.destroy_count, card and card.ability.extra.create_count or self.config.extra.create_count}}
end,
can_use = function(self, card)
if G.hand and #G.hand.cards > 1 then
return true
end
return false
end,
use = function(self, card)
--Enable Rank Flag
setPoolRankFlagEnable('unstb_21', true);
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Based on Basegame's Immolate
local destroyed_cards = {}
local temp_hand = {}
for k, v in ipairs(G.hand.cards) do temp_hand[#temp_hand+1] = v end
table.sort(temp_hand, function (a, b) return not a.playing_card or not b.playing_card or a.playing_card < b.playing_card end)
pseudoshuffle(temp_hand, pseudoseed('altar'))
for i = 1, card.ability.extra.destroy_count do destroyed_cards[#destroyed_cards+1] = temp_hand[i] end
--Destroy Cards
event({
trigger = 'after',
delay = 0.1,
func = function()
for i=#destroyed_cards, 1, -1 do
local c = destroyed_cards[i]
--print(c)
if c.ability.name == 'Glass Card' then
c:shatter()
else
c:start_dissolve(nil, i == #destroyed_cards)
end
end
return true end })
--Calling Jokers to process the card destroying
delay(0.3)
--print('joker')
for i = 1, #G.jokers.cards do
G.jokers.cards[i]:calculate_joker({remove_playing_cards = true, removed = destroyed_cards})
end
--Adding New Cards
event({
trigger = 'after',
delay = 0.7,
func = function()
local cards = {}
for i=1, card.ability.extra.create_count do
cards[i] = true
local _rank = SMODS.Ranks['unstb_21']
local _suit = pseudorandom_element(SMODS.Suits, pseudoseed('altar')) or SMODS.Suits['Spades']
--Pooling Enhancements
local cen_pool = {}
for k, v in pairs(G.P_CENTER_POOLS["Enhanced"]) do
if not v.replace_base_card and not v.disenhancement then
cen_pool[#cen_pool+1] = v
end
end
create_playing_card({front = G.P_CARDS[(_suit.card_key)..'_'..(_rank.card_key)], center = pseudorandom_element(cen_pool, pseudoseed('altar'))}, G.hand, nil, i ~= 1, {G.C.SECONDARY_SET.Spectral})
end
playing_card_joker_effects(cards)
return true end })
end,
pos = get_coordinates(4),
}
end
if check_enable_taglist({'edition_upgrade', 'enh_disenh'}) then
--Devil's Contract
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_contract',
config = {extra = {upgrade_count = 2, disenc_count = 2}},
--discovered = true,
loc_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'upgrade_edition'}
info_queue[#info_queue+1] = {set = 'Other', key = 'disenhancement'}
return {vars = {card and card.ability.extra.upgrade_count or self.config.extra.upgrade_count, card and card.ability.extra.disenc_count or self.config.extra.disenc_count}}
end,
can_use = function(self, card)
if G.hand and #G.hand.cards > 1 then
return true
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
--Based on Basegame's Immolate
local upgrade_card = {}
local disenc_card = {}
local temp_hand = {}
for k, v in ipairs(G.hand.cards) do temp_hand[#temp_hand+1] = v end
table.sort(temp_hand, function (a, b) return not a.playing_card or not b.playing_card or a.playing_card < b.playing_card end)
pseudoshuffle(temp_hand, pseudoseed('dvcontract'))
for i = 1, card.ability.extra.upgrade_count do upgrade_card[#upgrade_card+1] = temp_hand[i] end
for i = card.ability.extra.upgrade_count+1, card.ability.extra.upgrade_count+card.ability.extra.disenc_count do disenc_card[#disenc_card+1] = temp_hand[i] end
--Upgrade the card
for i=1, #upgrade_card do
event({trigger = 'after',delay = 0.1,func = function()
edition_upgrade(upgrade_card[i])
return true end })
end
--Disenhanced the Remaining Cards
if #disenc_card > 0 then
--Animation from Basegame Tarot
for i=1, #disenc_card do
local percent = 1.15 - (i-0.999)/(#disenc_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() disenc_card[i]:flip();play_sound('card1', percent);disenc_card[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Handle the conversion
for i=1, #disenc_card do
event({trigger = 'after',delay = 0.1,func = function()
disenc_card[i]:set_ability(G.P_CENTERS[pseudorandom_element({'m_unstb_radioactive', 'm_unstb_biohazard', 'm_unstb_poison'}, pseudoseed('dvcontract'))])
return true end })
end
for i=1, #disenc_card do
local percent = 0.85 + (i-0.999)/(#disenc_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() disenc_card[i]:flip();play_sound('tarot2', percent, 0.6);disenc_card[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.5)
end
end,
pos = get_coordinates(5),
}
end
end
--Other Spectrals
if unstb_config.gameplay.new_spectrals then
--Poltergeist
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_poltergeist',
config = {extra = {}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {}}
end,
can_use = function(self, card)
if G.hand and #G.jokers.cards > 1 then
--Check if there is at least one joker with edition
for i = 1, #G.jokers.cards do
if G.jokers.cards[i].edition then
return true
end
end
return false
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local joker_list = G.jokers.cards
local temp_edition = {}
for k, v in ipairs(joker_list) do temp_edition[#temp_edition+1] = v.edition or 'none' end
pseudoshuffle(temp_edition, pseudoseed('poltergeist'))
--Give all joker new editions
--Animation from Basegame Tarot
for i=1, #joker_list do
local percent = 1.15 - (i-0.999)/(#joker_list-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() joker_list[i]:flip();play_sound('card1', percent);joker_list[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Set the new edition
for i=1, #joker_list do
event({trigger = 'after',delay = 0.1,func = function()
local edition = temp_edition[i]
if edition == 'none' then
edition = nil
end
joker_list[i]:set_edition(edition, true, true)
return true end })
end
for i=1, #joker_list do
local percent = 0.85 + (i-0.999)/(#joker_list-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() joker_list[i]:flip();play_sound('tarot2', percent, 0.6);joker_list[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.5)
end,
pos = get_coordinates(6),
--Can spawn only if there is more than one joker, and at least one with edition
in_pool = function(self, args)
--For some reason, not checking this crash Cryptid on start up
if not G.jokers then return false end
if #G.jokers.cards > 1 then
for i=1, #G.jokers.cards do
if G.jokers.cards[i].edition then
return true
end
end
end
return false
end,
}
--Projection
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_projection',
config = {extra = {odds_break = 4}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card and card.ability.extra.odds_break or self.config.extra.odds_break}}
end,
can_use = function(self, card)
if G.hand and #G.jokers.cards > 1 and #G.jokers.highlighted ==1 and G.jokers.highlighted[1] then
--Check if there is at least one of the selected jokers have edition and is not the same
local joker = G.jokers.highlighted[1]
local position = nil
for i = 1, #G.jokers.cards do
if G.jokers.cards[i] == joker then position = i; break end
end
--Selected Joker is at the rightmost
if position==#G.jokers.cards then
return false
end
local nextjoker = G.jokers.cards[position+1]
--If both has edition, see if it's the same
if joker.edition and nextjoker.edition then
return joker.edition.key ~= nextjoker.edition.key
end
--Return true if at least one of them has edition
return joker.edition or nextjoker.edition
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local joker = G.jokers.highlighted[1]
local position = nil
for i = 1, #G.jokers.cards do
if G.jokers.cards[i] == joker then position = i; break end
end
local nextjoker = G.jokers.cards[position+1]
local firstEdition = joker.edition
local nextEdition = nextjoker.edition
local joker_list = {joker, nextjoker}
--Animation from Basegame Tarot
for i=1, #joker_list do
local percent = 1.15 - (i-0.999)/(#joker_list-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() joker_list[i]:flip();play_sound('card1', percent);joker_list[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Set the new edition
for i=1, #joker_list do
event({trigger = 'after',delay = 0.1,func = function()
joker:set_edition(nextEdition, true, true)
nextjoker:set_edition(firstEdition, true, true)
return true end })
end
for i=1, #joker_list do
local percent = 0.85 + (i-0.999)/(#joker_list-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() joker_list[i]:flip();play_sound('tarot2', percent, 0.6);joker_list[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.5)
--Destroy one of the joker
if pseudorandom('projection'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_break then
local target = pseudorandom_element(joker_list, pseudoseed('projection'))
event({func = function()
target:start_dissolve({HEX("57ecab")}, nil, 1.6)
return true end })
forced_message("Failed...", target, G.C.BLACK, true)
end
end,
pos = get_coordinates(7),
--Can spawn only if there is at least more than 1 Joker with distinct edition
in_pool = function(self, args)
--For some reason, not checking this crash Cryptid on start up
if not G.jokers then return false end
local prev_edition = nil
if #G.jokers.cards > 1 then
--Set the first one as a pivot check
prev_edition = G.jokers.cards[1].edition
--Iterate through the rest
for i=2, #G.jokers.cards do
--If there is one Joker with different edition, breaks and return true
if prev_edition ~= G.jokers.cards[i].edition then
return true
end
end
end
return false
end,
}
unstb_global.siphon_blacklist = {}
unstb_global.siphon_blacklist['e_negative'] = true
--Global register function, just in case more modded edition can blackList them
function unstb_global.register_siphon_blacklist(edition_list)
for i=1, #edition_list do
unstb_global.siphon_blacklist[edition_list[i]] = true
end
end
--Siphon
SMODS.Consumable{
set = 'Spectral', atlas = 'spectral',
key = 'spc_siphon',
config = {extra = {count = 4}},
--discovered = true,
loc_vars = function(self, info_queue, card)
return {vars = {card and card.ability.extra.count or self.config.extra.count}}
end,
can_use = function(self, card)
if G.hand and #G.jokers.cards >= 1 and #G.jokers.highlighted ==1 and G.jokers.highlighted[1] then
return G.jokers.highlighted[1].edition and not unstb_global.siphon_blacklist[G.jokers.highlighted[1].edition.key]
end
return false
end,
use = function(self, card)
event({trigger = 'after', delay = 0.4, func = function()
play_sound('tarot1')
card:juice_up(0.3, 0.5)
return true end })
local edition_card = {}
local temp_hand = {}
for k, v in ipairs(G.hand.cards) do temp_hand[#temp_hand+1] = v end
table.sort(temp_hand, function (a, b) return not a.playing_card or not b.playing_card or a.playing_card < b.playing_card end)
pseudoshuffle(temp_hand, pseudoseed('siphon'))
for i = 1, card.ability.extra.count do edition_card[#edition_card+1] = temp_hand[i] end
local joker = G.jokers.highlighted[1]
local edition = joker.edition
--Destroy the joker
event({func = function()
joker:start_dissolve({HEX("57ecab")}, nil, 1.6)
return true end })
delay(0.5)
--Upgrade the card
event({trigger = 'after',delay = 0.1,func = function()
for i=1, #edition_card do
edition_card[i]:set_edition(edition, true, i==1)
end
return true end })
end,
pos = get_coordinates(8),
--Can spawn only if there is one joker or more with edition
in_pool = function(self, args)
--For some reason, not checking this crash Cryptid on start up
if not G.jokers then return false end
if #G.jokers.cards >= 1 then
for i=1, #G.jokers.cards do
if G.jokers.cards[i].edition then
return true
end
end
end
return false
end,
}
end
--Vouchers
if unstb_config.gameplay.c_aux then
SMODS.Voucher({
object_type = "Voucher",
key = "aux1",
atlas = "voucher",
pos = { x = 0, y = 0 },
unlocked = true,
--discovered = true,
redeem = function(self)
event({
func = function()
G.GAME.auxiliary_rate = (G.GAME.auxiliary_rate or 0) + 2
return true
end,
})
end,
unredeem = function(self)
event({
func = function()
G.GAME.auxiliary_rate = (G.GAME.auxiliary_rate or 2) - 2
return true
end,
})
end
})
SMODS.Voucher({
object_type = "Voucher",
key = "aux2",
atlas = "voucher",
pos = { x = 1, y = 0 },
unlocked = true,
--discovered = true,
requires = { "v_unstb_aux1" },
})
end
-------- Joker Code Starts Here ------
--Basic Jokers
--Lunar Calendar
create_joker({
name = 'Lunar Calendar', id = 40,
rarity = 'Common', cost = 5,
blueprint = true, eternal = true, perishable = true,
vars = { {odds_spawn = 4} },
custom_vars = function(self, info_queue, card)
local suit = card and card.ability.extra and card.ability.extra.suit or 'Spades'
local suit_name = localize(suit, 'suits_singular')
local suit_color = G.C.SUITS[suit]
return {vars = {suit_name, G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_spawn, colours = {suit_color}}}
end,
set_ability = function(self, card, initial, delay_sprites)
--Initialize variables
card.ability.extra.suit = pseudorandom_element(SMODS.Suits, pseudoseed('lunarcalendar')).key --All suits, not just strictly in the deck
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
--Main Context
if context.other_card:is_suit(card.ability.extra.suit) then
local isActivated = pseudorandom('lunarcalendar'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_spawn
if isActivated then
if #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then
G.GAME.consumeable_buffer = G.GAME.consumeable_buffer + 1
event({func = function()
local created_card = create_card('Planet', G.consumeables, nil, nil, nil, nil, nil, nil)
created_card:add_to_deck()
G.consumeables:emplace(created_card)
G.GAME.consumeable_buffer = 0
return true end
})
forced_message("Planet", context.blueprint_card or card, G.C.SECONDARY_SET.Planet, true)
end
end
end
end
--End-of-round Randomize
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
card.ability.extra.suit = pseudorandom_element(SMODS.Suits, pseudoseed('lunarcalendar')).key
return{
message = localize(card.ability.extra.suit, 'suits_singular'),
}
end
end,
})
--Dragon Hoard
create_joker({
name = 'Dragon Hoard', id = 42,
rarity = 'Common', cost = 4,
blueprint = true, eternal = true, perishable = true,
vars = {{mult_rate = 15}, {held_amount = 2}, {mult_current = 0}},
custom_vars = function(self, info_queue, card)
local held_amount = G.consumeables and #G.consumeables.cards or 0
local mult_current = card.ability.extra.mult_rate * math.floor(held_amount/card.ability.extra.held_amount)
return { vars = {card.ability.extra.mult_rate, card.ability.extra.held_amount, mult_current}}
end,
calculate = function(self, card, context)
--Main context
if context.joker_main then
card.ability.extra.mult_current = card.ability.extra.mult_rate * math.floor(#G.consumeables.cards/card.ability.extra.held_amount)
if card.ability.extra.mult_current > 0 then
return {
mult_mod = card.ability.extra.mult_current,
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.mult_current } }
}
end
end
end,
})
--Card Dealer
create_joker({
name = 'Card Dealer', id = 41,
rarity = 'Common', cost = 5,
blueprint = true, eternal = true, perishable = true,
vars = { {chip_rate = 15}, {chips = 0} },
custom_vars = function(self, info_queue, card)
return { vars = {card.ability.extra.chip_rate, card.ability.extra.chips}}
end,
calculate = function(self, card, context)
if context.joker_main then
return {
chip_mod = card.ability.extra.chips,
message = localize { type = 'variable', key = 'a_chips', vars = { card.ability.extra.chips } }
}
end
if context.before and context.full_hand and not context.blueprint then
card.ability.extra.chips = card.ability.extra.chips + #context.full_hand * card.ability.extra.chip_rate
return {
message = 'Upgraded!',
colour = G.C.CHIPS,
card = card
}
end
--End-of-round Resets
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
card.ability.extra.chips = 0
return{
message = localize('k_reset'),
colour = G.C.RED
}
end
end,
})
--Match Three
create_joker({
name = 'Match Three', id = 67,
rarity = 'Common', cost = 6,
blueprint = true, eternal = true, perishable = true,
vars = { {mult = 15}, {count = 3} },
custom_vars = function(self, info_queue, card)
return { vars = {card.ability.extra.mult, card.ability.extra.count}}
end,
calculate = function(self, card, context)
if context.joker_main then
--Not enough card
if #context.scoring_hand < card.ability.extra.count then
return
end
local card_count = 1
local max_card_count = 0
local current_suit = context.scoring_hand[1].base.suit
for i=2, #context.scoring_hand do
if context.scoring_hand[i]:is_suit(current_suit) then
card_count = card_count + 1
else
--reset count
if card_count > max_card_count then
max_card_count = card_count
end
card_count = 1
current_suit = context.scoring_hand[i].base.suit
end
end
if card_count > max_card_count then
max_card_count = card_count
end
--print(max_card_count)
--print(card.ability.extra.count)
if max_card_count >= card.ability.extra.count then
return {
mult_mod = card.ability.extra.mult,
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.mult } }
}
end
end
end,
})
create_joker({
name = 'Furry Joker', id = 44,
rarity = 'Uncommon', cost = 7,
blueprint = true, eternal = true, perishable = true,
vars = { {odds_poly = 8} },
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_wild']
info_queue[#info_queue+1] = G.P_CENTERS['e_polychrome']
return { vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_poly}}
end,
calculate = function(self, card, context)
if context.after and context.scoring_hand then
local polyAmount = 0
for i=1, #context.scoring_hand do
local current_card = context.scoring_hand[i]
if current_card.config.center == G.P_CENTERS.m_wild and (current_card.edition or {}).key ~= 'e_polychrome' and not current_card.becoming_poly then
local isActivated = pseudorandom('furry'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_poly
if isActivated then
current_card.becoming_poly = true
event({trigger = 'after', delay = 0.4, func = function()
big_juice(context.blueprint_card or card)
current_card:set_edition("e_polychrome", true, false)
current_card.becoming_poly = nil
return true end
})
polyAmount = polyAmount+1
end
end
end
if polyAmount>0 then
delay(2.5)
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one wild card in the deck
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_wild then return true end
end
return false
end
})
--Hook set_cost to allow messing with prices
local ref_set_cost = Card.set_cost
function Card.set_cost(self)
ref_set_cost(self)
for k,v in ipairs(SMODS.find_card("j_unstb_luxurious_handbag")) do
self.cost = self.cost + v.ability.extra.inflation
end
end
create_joker({
name = 'Luxurious Handbag', id = 43,
rarity = 'Uncommon', cost = 6,
blueprint = false, eternal = true, perishable = true,
vars = { {add_slot = 4}, {inflation = 2} },
custom_vars = function(self, info_queue, card)
return { vars = {card.ability.extra.add_slot, card.ability.extra.inflation}}
end,
add_to_deck = function(self, card, from_debuff)
G.consumeables:change_size(card.ability.extra.add_slot)
--Recalculate all prices in the shop
event({func = function()
for k, v in pairs(G.I.CARD) do
if v.set_cost then v:set_cost() end
end
return true end
})
end,
remove_from_deck = function(self, card, from_debuff)
G.consumeables:change_size(-card.ability.extra.add_slot)
--Recalculate all prices in the shop
event({func = function()
for k, v in pairs(G.I.CARD) do
if v.set_cost then v:set_cost() end
end
return true end
})
end,
--Actual functionality is in Card.set_cost hook mostly
--[[
calculate = function(self, card, context)
end,]]
})
--Portal
create_joker({
name = 'Portal', id = 39,
rarity = 'Uncommon', cost = 7,
blueprint = true, eternal = true, perishable = true,
--vars = { {times = 1}},
--Actual ability is in ustb_get_straight
--[[calculate = function(self, card, context)
end,]]
})
--Suit Seal Support Jokers
--Vainglorious Joker
create_joker({
name = 'Vainglorious Joker', id = 32,
rarity = 'Common', cost = 5,
gameplay_tag = {'seal_suit'},
blueprint = true, eternal = true, perishable = true,
vars = { {mult = 5} },
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'suit_seal'}
return {vars = {card.ability.extra.mult}}
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.seal and SMODS.Seals[context.other_card.seal] and SMODS.Seals[context.other_card.seal].suit_seal then
--big_juice(context.blueprint_card or card)
return {
mult = card.ability.extra.mult,
card = card
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one card with suit seal
for _, v in pairs(G.playing_cards) do
if v.seal and SMODS.Seals[v.seal] and SMODS.Seals[v.seal].suit_seal then return true end
end
return false
end
})
--Acedia Joker
create_joker({
name = 'Acedia Joker', id = 31,
rarity = 'Common', cost = 5,
gameplay_tag = {'seal_suit'},
blueprint = true, eternal = true, perishable = true,
vars = { {mult = 10} },
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'suit_seal'}
return {vars = {card.ability.extra.mult}}
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.seal and SMODS.Seals[context.other_card.seal] and SMODS.Seals[context.other_card.seal].suit_seal then
local is_activate = false
--If same suit, returns true immediately. This should also handle Wild Card cases and other Joker-related calculation probably?
--I hope so, if there's a case that slips by I'll be crying
if context.other_card:is_suit(SMODS.Seals[context.other_card.seal].suit_seal, nil, nil, true) then
is_activate = true
--Otherwise, checks suit group
elseif get_suit_group(context.other_card.base.suit) == get_suit_group(SMODS.Seals[context.other_card.seal].suit_seal) then
is_activate = true
end
if is_activate then
return {
mult = card.ability.extra.mult,
card = card
}
end
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one card with suit seal
for _, v in pairs(G.playing_cards) do
if v.seal and SMODS.Seals[v.seal] and SMODS.Seals[v.seal].suit_seal then return true end
end
return false
end
})
--Cinnabar
create_joker({
name = 'Cinnabar', id = 33,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'seal_suit', 'c_aux'},
blueprint = true, eternal = true, perishable = true,
vars = { {odds = 6} },
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'suit_seal'}
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds}}
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.seal and SMODS.Seals[context.other_card.seal] and SMODS.Seals[context.other_card.seal].suit_seal then
local isActivated = pseudorandom('cinnabar'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds
if isActivated then
local created_card = unstb_global.SUIT_SEAL[SMODS.Seals[context.other_card.seal].suit_seal].aux_key
if #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then
G.GAME.consumeable_buffer = G.GAME.consumeable_buffer + 1
event({func = function()
local created_card = create_card('Auxiliary', G.consumeables, nil, nil, nil, nil, created_card, nil)
created_card:add_to_deck()
G.consumeables:emplace(created_card)
G.GAME.consumeable_buffer = 0
return true end
})
forced_message("Auxiliary", context.blueprint_card or card, G.C.UNSTB_AUX, true)
end
end
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one card with suit seal
for _, v in pairs(G.playing_cards) do
if v.seal and SMODS.Seals[v.seal] and SMODS.Seals[v.seal].suit_seal then return true end
end
return false
end
})
--Auxiliary Support
--Free Trial
create_joker({
name = 'Free Trial', id = 54,
rarity = 'Uncommon', cost = 4,
gameplay_tag = {'c_aux'},
vars = {{odds = 4}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {key = 'e_negative_consumable', set = 'Edition', config = {extra = 1}}
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.using_consumeable then
if context.consumeable.ability.set == "Auxiliary" then
if pseudorandom('freetrial'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds then
event({trigger = 'after', delay = 0.4, func = function()
if G.consumeables.config.card_limit >= #G.consumeables.cards then
play_sound('timpani')
local c = create_card('Auxiliary', G.consumeables, nil, nil, nil, nil, nil, 'freetrial')
c:set_edition({negative = true}, true)
c:add_to_deck()
G.consumeables:emplace(c)
big_juice(context.blueprint_card or card)
card_eval_status_text(context.blueprint_card or card,'extra',nil, nil, nil,{message = "Free Trial", colour = G.C.UNSTB_AUX, instant = true})
--forced_message("Free Trial!", context.blueprint_card or card, G.C.UNSTB_AUX, )
end
return true end })
end
end
end
end
})
--Extended Warranty
create_joker({
name = 'Extended Warranty', id = 55,
rarity = 'Rare', cost = 8,
gameplay_tag = {'c_aux'},
vars = {{xmult = 1.5}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.xmult}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.joker_main then
for i=1, #G.consumeables.cards do
if G.consumeables.cards[i].ability.set == "Auxiliary" then
event({trigger = 'after', func = function()
(context.blueprint_card or card):juice_up(0.5, 0.5)
return true end
})
SMODS.calculate_effect({xmult = card.ability.extra.xmult}, G.consumeables.cards[i])
end
end
end
end
})
--Technician
create_joker({
name = 'Technician', id = 56,
rarity = 'Common', cost = 6,
gameplay_tag = {'c_aux'},
vars = {{chip_rate = 15}, {chips = 0}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.chip_rate, card.ability.extra.chips}}
end,
blueprint = true, eternal = true, perishable = true,
set_ability = function(self, card, initial, delay_sprites)
if G.GAME and G.GAME.consumeable_usage_total then
card.ability.extra.chips = (G.GAME.consumeable_usage_total.auxiliary or 0) * card.ability.extra.chip_rate
else
card.ability.extra.chips = 0
end
end,
calculate = function(self, card, context)
--Upgrades
if context.using_consumeable and not context.blueprint then
if context.consumeable.ability.set == "Auxiliary" then
card.ability.extra.chips = card.ability.extra.chips + card.ability.extra.chip_rate
event({
func = function()
big_juice(card)
forced_message("Upgraded!", card, G.C.CHIPS, true)
--card_eval_status_text(card,'extra',nil, nil, nil,{message = "Upgraded", colour = G.C.MULT, instant = true})
return true end})
end
end
--Main context
if context.joker_main then
if card.ability.extra.chips > 0 then
return {
chip_mod = card.ability.extra.chips,
message = localize { type = 'variable', key = 'a_chips', vars = { card.ability.extra.chips } }
}
end
end
end
})
--Season Pass
create_joker({
name = 'Season Pass', id = 57,
rarity = 'Uncommon', cost = 5,
gameplay_tag = {'c_aux'},
vars = {{odds = 4}},
custom_vars = function(self, info_queue, card)
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds}}
end,
blueprint = true, eternal = true,
calculate = function(self, card, context)
if context.discard then
if not context.other_card.debuff and context.other_card:is_face() and pseudorandom('season_pass'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds then
--Create consumable
if #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then
G.GAME.consumeable_buffer = G.GAME.consumeable_buffer + 1
event({func = function()
local created_card = create_card('Auxiliary', G.consumeables, nil, nil, nil, nil, nil, 'season_pass')
created_card:add_to_deck()
G.consumeables:emplace(created_card)
G.GAME.consumeable_buffer = 0
return true end
})
forced_message("Auxiliary", context.blueprint_card or card, G.C.UNSTB_AUX, 0.5)
end
end
end
end
})
--BlackJack + Question Mark Line Jokers
--Black Jack
create_joker({
name = 'Black Jack', id = 15,
rarity = 'Common', cost = 5,
gameplay_tag = {'rank_21', 'j_alter'},
blueprint = true, eternal = true, perishable = false,
vars = {{maxRank = 21}, {chips = 0}},
calculate = function(self, card, context)
--This part handles the chip reward
if context.joker_main then
return {
chip_mod = card.ability.extra.chips,
message = localize { type = 'variable', key = 'a_chips', vars = { card.ability.extra.chips } }
}
end
--This part handles the scaling
if context.before and context.scoring_hand and not context.blueprint then
local totalRank = blackJack_evalrank(context.scoring_hand, card.ability.extra.maxRank)
if totalRank < card.ability.extra.maxRank then
card.ability.extra.chips = card.ability.extra.chips + totalRank
return {
message = 'Upgraded!',
colour = G.C.CHIPS,
card = card
}
elseif totalRank == card.ability.extra.maxRank then
card.ability.extra.chips = (card.ability.extra.chips + totalRank) * 2
local popup_msg = 'Black Jack!'
if card.ability.extra.maxRank ~= 21 then
popup_msg = 'Black Jack...?'
else
end
event({ trigger = 'after', delay = 0.2, func = function()
play_sound('multhit1')
return true end })
return {
message = popup_msg,
colour = G.C.RED,
card = card
}
else
card.ability.extra.chips = 0
event({ trigger = 'after', delay = 0.2, func = function()
play_sound('tarot1')
return true end })
return {
message = 'Busted...',
colour = G.C.BLACK,
card = card
}
end
end
end
})
--What
create_joker({
name = 'What', id = 14,
rarity = 'Rare', cost = 6,
gameplay_tag = {'rank_21'},
blueprint = true, eternal = true, perishable = true,
vars = { {chips = 420}, {mult = 69} },
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.base.value == 'unstb_???' then
return {
chips = card.ability.extra.chips,
mult = card.ability.extra.mult,
card = card
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one rank ??? card
for _, v in pairs(G.playing_cards) do
if v.base.value == 'unstb_???' then return true end
end
return false
end
})
--Decimal-line Jokers
create_joker({
name = 'Floating Point Error', id = 34,
rarity = 'Uncommon', cost = 4,
gameplay_tag = {'rank_decimal'},
blueprint = true, eternal = true, perishable = true,
--vars = { {bonus = 10}},
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
local currentCard = context.other_card
if is_decimal(currentCard) then
--big_juice(card)
currentCard.ability.perma_bonus = (currentCard.ability.perma_bonus or 0) + SMODS.Ranks[currentCard.base.value].nominal
event({ trigger = 'after', func = function()
big_juice(card)
return true end })
return {
extra = {message = "Upgrade!", colour = G.C.CHIPS},
colour = G.C.CHIPS,
card = currentCard
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one decimal card
for _, v in pairs(G.playing_cards) do
if is_decimal(v) then return true end
end
return false
end
})
--Academic Journal
create_joker({
name = 'Academic Journal', id = 35,
rarity = 'Uncommon', cost = 6,
gameplay_tag = {'rank_decimal'},
blueprint = true, eternal = true, perishable = true,
vars = { {times_max = 5}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.times_max, card.ability.extra.times_max - card.ability.extra.times_current}}
end,
add_to_deck = function(self, card, from_debuff)
--Enable all decimal ranks card in pools
if not from_debuff then
setPoolRankFlagEnable('unstb_0.5', true);
setPoolRankFlagEnable('unstb_r2', true);
setPoolRankFlagEnable('unstb_e', true);
setPoolRankFlagEnable('unstb_Pi', true);
end
end,
set_ability = function(self, card, initial, delay_sprites)
--Initialize variables
card.ability.extra.times_current = 0
end,
calculate = function(self, card, context)
if context.before and not context.blueprint then
local is_active = true
if card.ability.extra.times_current < card.ability.extra.times_max then
for i=1, #context.scoring_hand do
if context.scoring_hand[i]:is_face() then
is_active = false
break
end
end
if is_active then
card.ability.extra.times_current = (card.ability.extra.times_current or 0) + 1
end
else
is_active = false
end
card.ability.extra.is_active = is_active
end
if context.after then
--Create Card if it activates
if card.ability.extra.is_active then
--Adds a card
event({trigger = 'after', func = function()
local rank = SMODS.Ranks[pseudorandom_element({'unstb_0.5', 'unstb_r2', 'unstb_e', 'unstb_Pi'}, pseudoseed('researchpaper')..G.SEED)].card_key
local suit = pseudorandom_element(SMODS.Suits, pseudoseed('researchpaper')..G.SEED).card_key
--Pooling Enhancements
local cen_pool = {}
for k, v in pairs(G.P_CENTER_POOLS["Enhanced"]) do
if not v.replace_base_card and not v.disenhancement then
cen_pool[#cen_pool+1] = v
end
end
local _card = Card(G.play.T.x + G.play.T.w/2, G.play.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[suit..'_'..rank], pseudorandom_element(cen_pool, pseudoseed('researchpaper')..G.SEED), {playing_card = G.playing_card})
_card:start_materialize({G.C.SECONDARY_SET.Enhanced})
G.play:emplace(_card)
table.insert(G.playing_cards, _card)
big_juice(context.blueprint_card or card)
return true end
})
delay(1.5)
event({trigger = 'after', func = function()
G.deck.config.card_limit = G.deck.config.card_limit + 1
draw_card(G.play,G.deck, 90,'up', nil)
return true end
})
playing_card_joker_effects({true})
end
end
--End of round check, make sure it's checked absolutely once per round
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
if card.ability.extra.times_current > 0 then
card.ability.extra.times_current = 0
return {
message = 'Reset!'
}
end
end
end,
})
--Engineer
create_joker({
name = 'Engineer', id = 37,
rarity = 'Common', cost = 5,
gameplay_tag = {'rank_decimal'},
blueprint = false, eternal = true, perishable = true,
--vars = { {times = 1}},
--Engineer's actual ability is in Card:get_id hook above
--[[calculate = function(self, card, context)
end,]]
custom_in_pool = function(self, args)
--Spawns if there is at least one decimal card
for _, v in pairs(G.playing_cards) do
if is_decimal(v) then return true end
end
return false
end
})
--Thesis Proposal
create_joker({
name = 'Thesis Proposal', id = 36,
rarity = 'Common', cost = 6,
gameplay_tag = {'rank_decimal'},
blueprint = true, eternal = true, perishable = true,
vars = {{ repetitions = 2 }},
calculate = function(self, card, context)
if context.cardarea == G.play and context.repetition and not context.repetition_only then
if is_decimal(context.other_card) then
return {
message = 'Again!',
repetitions = card.ability.extra.repetitions,
card = context.blueprint_card or card
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one decimal card
for _, v in pairs(G.playing_cards) do
if is_decimal(v) then return true end
end
return false
end
})
--Rainbow Flag
create_joker({
name = 'Rainbow Flag', id = 38,
rarity = 'Uncommon', cost = 5,
gameplay_tag = {'rank_decimal'},
blueprint = true, eternal = true, perishable = true,
vars = { {odds_poly = 6} },
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['e_polychrome']
return { vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_poly}}
end,
calculate = function(self, card, context)
if context.after and next(context.poker_hands['Straight']) and context.scoring_hand then
--Check if the Joker should be activated or not
local is_activate = false
for i=1, #context.scoring_hand do
if is_decimal(context.scoring_hand[i]) then
is_activate = true
break
end
end
if is_activate then
local eligible_card = {}
--Populate the list of eligible card
for i=1, #context.scoring_hand do
if (context.scoring_hand[i].edition or {}).key ~= 'e_polychrome' and not context.scoring_hand[i].becoming_poly then
eligible_card[#eligible_card+1] = context.scoring_hand[i]
end
end
--If there is eligible card, and the random chance hits
if #eligible_card > 0 and pseudorandom('rainbowflag'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_poly then
local targetCard = pseudorandom_element(eligible_card, pseudoseed('rainbowflag')..G.SEED)
targetCard.becoming_poly = true
event({trigger = 'after', delay = 0.4, func = function()
big_juice(context.blueprint_card or card)
targetCard:set_edition("e_polychrome", true, false)
targetCard.becoming_poly = nil
return true end
})
delay(2.5)
end
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one decimal card
for _, v in pairs(G.playing_cards) do
if is_decimal(v) then return true end
end
return false
end
})
--Binary-line Jokers
local chipsAbilityMatch = {
m_stone = 50,
m_unstb_resource = 0,
m_unstb_radioactive = 13,
m_unstb_biohazard = 0
}
create_joker({
name = 'Dummy Data', id = 6,
rarity = 'Uncommon', cost = 6,
gameplay_tag = {'rank_binary'},
vars = {{odds = 2}, {unscored_card = {}}},
custom_vars = function(self, info_queue, card)
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds}}
end,
blueprint = false, eternal = true, perishable = true,
add_to_deck = function(self, card, from_debuff)
--Enable rank 0 card in pools
if not from_debuff then
setPoolRankFlagEnable('unstb_0', true);
setPoolRankFlagEnable('unstb_1', true);
end
end,
calculate = function(self, card, context)
if context.before and context.scoring_hand and not context.blueprint then
card.ability.extra.unscored_card = {}
for k, v in pairs(context.full_hand) do
local unscoring = true
for _k,_v in pairs(context.scoring_hand) do
if v == _v then
unscoring = false
break
end
end
if unscoring and not v.debuff then
card.ability.extra.unscored_card[#card.ability.extra.unscored_card+1] = v
end
end
end
if context.after and not context.blueprint then
for i = 1, #card.ability.extra.unscored_card do
local isTurning = pseudorandom('dummy'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds
if isTurning then
local currentCard = card.ability.extra.unscored_card[i]
currentCard.ability.perma_bonus = (currentCard.ability.perma_bonus or 0) + SMODS.Ranks[currentCard.base.value].nominal
--Flipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('card1', 1); currentCard:juice_up(0.3, 0.3); return true end })
--Changing Card Property
event({trigger = 'after', delay = 0.05, func = function()
local suit_data = SMODS.Suits[currentCard.base.suit]
local suit_prefix = suit_data.card_key
currentCard:set_base(G.P_CARDS[suit_prefix .. '_unstb_0' ])
--Un-stoned the stone card
if currentCard.config.center.key == 'm_unstb_slop' or chipsAbilityMatch[currentCard.config.center.key] then
currentCard:set_ability(G.P_CENTERS.c_base)
end
return true end })
--Unflipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('tarot2', 1, 0.6); big_juice(card); currentCard:juice_up(0.3, 0.3); return true end })
forced_message("Zero!", currentCard, G.C.GREY, true)
end
end
end
end
})
create_joker({
name = 'Micro SD Card', id = 4,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'rank_binary', 'j_alter', 'j_powerful'},
vars = {{odds_current = 0}, {odds_destroy = 512}, {stored_chips = 0}},
custom_vars = function(self, info_queue, card)
local activate_text = 'Inactive'
local activate_color = G.C.RED
if G.jokers and G.jokers.cards[1] == card then
activate_text = 'Active'
activate_color = G.C.GREEN
end
return {vars = {G.GAME and G.GAME.probabilities.normal * card.ability.extra.odds_current or 0, card.ability.extra.odds_destroy, G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.stored_chips, activate_text,
colours = {activate_color} }}
end,
blueprint = true, eternal = false, perishable = false,
--Set sprites and hitbox
set_sprites = function(self, card, front)
local w_scale, h_scale = 41/71, 59/95
card.children.center.scale.y = card.children.center.scale.y * h_scale
card.children.center.scale.x = card.children.center.scale.x * w_scale
end,
set_ability = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 41/71, 59/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
load = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 41/71, 59/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
add_to_deck = function(self, card, from_debuff)
--Enable rank 0 card in pools
setPoolRankFlagEnable('unstb_0', true);
setPoolRankFlagEnable('unstb_1', true);
end,
calculate = function(self, card, context)
--This part handles the chip reward
if context.joker_main then
return {
chip_mod = card.ability.extra.stored_chips,
message = localize { type = 'variable', key = 'a_chips', vars = { card.ability.extra.stored_chips } }
}
end
--The scaling part is not copyable by Blueprint
if context.discard and not context.blueprint then
--Check if the joker is on the leftmost slot
if G.jokers.cards[1] == card then
local currentCard = context.other_card
--Not debuffed, and isn't face card, and is base card
if not currentCard:is_face() and not currentCard.debuff and currentCard.config.center == G.P_CENTERS.c_base then
local bonusChip = currentCard.ability.perma_bonus or 0
local baseChip = SMODS.Ranks[currentCard.base.value].nominal
local totalChip = baseChip + bonusChip
if totalChip>0 then
card.ability.extra.stored_chips = (card.ability.extra.stored_chips or 0) + totalChip
card.ability.extra.odds_current = (card.ability.extra.odds_current or 0) + totalChip
--Change card
event({trigger = 'after', delay = 0.02, func = function()
local suit_data = SMODS.Suits[currentCard.base.suit]
local suit_prefix = suit_data.card_key
currentCard:juice_up(0.3, 0.3);
currentCard:set_base(G.P_CARDS[suit_prefix .. '_unstb_0' ])
return true end })
return {
message = localize { type = 'variable', key = 'a_chips', vars = { totalChip } },
colour = G.C.CHIPS,
card = card
}
end
end
end
end
--End of round check, make sure it's checked absolutely once per round
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
if pseudorandom('sdcard'..G.SEED) < G.GAME.probabilities.normal * card.ability.extra.odds_current / card.ability.extra.odds_destroy then
event({func = function()
play_sound('tarot1')
card.T.r = -0.2
card:juice_up(0.3, 0.4)
card.states.drag.is = true
card.children.center.pinch.x = true
--Destroy Card
event({trigger = 'after', delay = 0.3, func = function()
G.jokers:remove_card(card)
card:remove()
card = nil
return true end })
return true end })
return {
message = 'Corrupted...',
colour = G.C.BLACK
}
else
return {
message = 'Safe!'
}
end
end
end
})
create_joker({
name = 'Social Experiment', id = 65,
rarity = 'Rare', cost = 9,
gameplay_tag = {'rank_binary', 'j_powerful'},
blueprint = false, eternal = true, perishable = true,
add_to_deck = function(self, card, from_debuff)
--Enable rank 0 card in pools
setPoolRankFlagEnable('unstb_0', true);
setPoolRankFlagEnable('unstb_1', true);
end,
calculate = function(self, card, context)
if context.after and context.scoring_hand and #context.scoring_hand > 1 and not context.blueprint then
local totalChipCount = 0
for i = 1, #context.scoring_hand do
if i<#context.scoring_hand and not (context.scoring_hand[i]:is_face() and not (context.scoring_hand[i].config.center.key == 'm_unstb_slop' or context.scoring_hand[i].config.center.no_rank)) then --context.scoring_hand[i].config.center ~= G.P_CENTERS.m_stone then --Check if it is not a Stone card
local currentCard = context.scoring_hand[i]
local bonusChip = currentCard.ability.perma_bonus or 0
local baseChip = SMODS.Ranks[currentCard.base.value].nominal
if currentCard.config.center.key == 'm_unstb_slop' then
baseChip = 0
bonusChip = bonusChip + currentCard.ability.extra.chips
elseif chipsAbilityMatch[currentCard.config.center.key] then
baseChip = 0
bonusChip = bonusChip + chipsAbilityMatch[currentCard.config.center.key]
end
if bonusChip + baseChip > 0 then
context.scoring_hand[i+1].ability.perma_bonus = (context.scoring_hand[i+1].ability.perma_bonus or 0) + (bonusChip + baseChip)*2
totalChipCount = totalChipCount + bonusChip + baseChip
currentCard.ability.perma_bonus = 0
--Flipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('card1', 1); currentCard:juice_up(0.3, 0.3); return true end })
--Changing Card Property
event({trigger = 'after', delay = 0.05, func = function()
local suit_data = SMODS.Suits[currentCard.base.suit]
local suit_prefix = suit_data.card_key
currentCard:set_base(G.P_CARDS[suit_prefix .. '_unstb_0' ])
--Un-stoned the stone card
--print(currentCard.config.center.key)
--print(chipsAbilityMatch[currentCard.config.center.key])
if currentCard.config.center.key == 'm_unstb_slop' or chipsAbilityMatch[currentCard.config.center.key] then
currentCard:set_ability(G.P_CENTERS.c_base)
end
return true end })
--Unflipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('tarot2', 1, 0.6); big_juice(card); currentCard:juice_up(0.3, 0.3); return true end })
forced_message("Double It!", currentCard, G.C.CHIPS, true)
end
else
if totalChipCount > 0 then
local currentCard = context.scoring_hand[i]
event({ trigger = 'after', delay = 0.2, func = function()
big_juice(currentCard)
play_sound('multhit1')
return true end })
forced_message("Take!", currentCard, G.C.CHIPS, true)
totalChipCount = 0
end
end
end
end
end
})
create_joker({
name = 'Power of One', id = 13,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'rank_binary'},
blueprint = true, eternal = true, perishable = true,
vars = {{mult_rate = '4'}, {mult = '0'}},
custom_vars = function(self, info_queue, card)
--Check the number of 1 in the deck
local one_count=0
if (G.playing_cards) then
for _, v in pairs(G.playing_cards) do
if v.base.value == 'unstb_1' then one_count = one_count + 1 end
end
return { vars = {card.ability.extra.mult_rate, one_count*card.ability.extra.mult_rate}}
else
return { vars = {card.ability.extra.mult_rate, 0}}
end
end,
calculate = function(self, card, context)
--Main context
if context.joker_main then
local one_count=0
for _, v in pairs(G.playing_cards) do
if v.base.value == 'unstb_1' then one_count = one_count + 1 end
end
card.ability.extra.mult = one_count*card.ability.extra.mult_rate
return {
mult_mod = card.ability.extra.mult,
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.mult } }
}
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one rank 1 card
for _, v in pairs(G.playing_cards) do
if v.base.value == 'unstb_1' then return true end
end
return false
end
})
local binary_rank = {'unstb_0', 'unstb_1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace', 'unstb_???'}
create_joker({
name = 'Binary Number', id = 5,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'rank_binary'},
blueprint = true, eternal = true, perishable = true,
vars = {},
calculate = function(self, card, context)
--Main context
if context.after then
local hand = context.full_hand
--Parse the hand and check if it's eligible in one go
--Ineligible hand
if #hand > 4 then return end
local is_binary = true
local final_rank = 0
local suit_list = {}
for i=1, #hand do
local rank = hand[i].base.value
if rank == 'unstb_1' then
final_rank = final_rank + 2 ^ (#hand-i)
suit_list[#suit_list+1] = hand[1].base.suit
elseif rank == 'unstb_0' then
--Do nothing, really
suit_list[#suit_list+1] = hand[1].base.suit
else
is_binary = false
break
end
end
--If the hand is binary number, create card accordingly
if is_binary then
local target_rank = binary_rank[final_rank+1] or 'unstb_???'
--print(final_rank)
--print(target_rank)
--Create Card
event({func = function()
local rank = SMODS.Ranks[target_rank].card_key
local suit = SMODS.Suits[pseudorandom_element(suit_list, pseudoseed('binary')..G.SEED)].card_key
local _card = Card(G.play.T.x + G.play.T.w/2, G.play.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[suit..'_'..rank], G.P_CENTERS.c_base, {playing_card = G.playing_card})
--Juice up the Joker
card:juice_up(0.3, 0.3)
_card:start_materialize({G.C.SECONDARY_SET.Enhanced})
G.play:emplace(_card)
table.insert(G.playing_cards, _card)
return true end
})
event({func = function()
G.deck.config.card_limit = G.deck.config.card_limit + 1
return true end
})
delay(1)
event({func = function()
draw_card(G.play,G.deck, 90,'up', nil)
return true end
})
playing_card_joker_effects({true})
end
end
end,
custom_in_pool = function(self, args)
--If rank 0 and 1 is unlocked this run
return getPoolRankFlagEnable('unstb_0') and getPoolRankFlagEnable('unstb_1')
end
})
--Enhancement-line Jokers
--Quintuplets
create_joker({
name = 'Quintuplets', id = 2,
rarity = 'Uncommon', cost = 6,
blueprint = true, eternal = true, perishable = true,
vars = {{scoring_name = ''}, {scoring_hand = {}}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_TAGS.tag_negative
end,
calculate = function(self, card, context)
--Keep track of scoring hand
--[[if context.after and context.scoring_name ~= nil and context.scoring_hand and not context.blueprint then
card.ability.extra.scoring_name = context.scoring_name
card.ability.extra.scoring_hand = context.scoring_hand
end
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and card.ability.extra.scoring_name == 'Flush Five' then
local isActivated = true
for i = 1, #card.ability.extra.scoring_hand do
if card.ability.extra.scoring_hand[i].config.center == G.P_CENTERS.c_base then
isActivated = false
break
end
end
if isActivated then
if not context.blueprint then
card.ability.extra.scoring_name = ''
end
event({ trigger = 'after', delay = 0.5, func = function()
card:juice_up(0.3, 0.3)
add_tag(Tag('tag_negative'))
play_sound('generic1', 0.9 + math.random()*0.1, 0.8)
play_sound('holo1', 1.2 + math.random()*0.1, 0.4)
return true end
}
)
end
end]]
if context.after and next(context.poker_hands['Five of a Kind']) then
event({ trigger = 'after', delay = 0.5, func = function()
(context.blueprint_card or card):juice_up(0.3, 0.3)
add_tag(Tag('tag_negative'))
play_sound('generic1', 0.9 + math.random()*0.1, 0.8)
play_sound('holo1', 1.2 + math.random()*0.1, 0.4)
return true end
}
)
end
end
})
--Edition-line Jokers
--Graphic Card
create_joker({
name = 'Graphic Card', id = 69,
rarity = 'Rare', cost = 8,
blueprint = true, eternal = true, perishable = true,
vars = {{ count = 5 }, {current_count = 0}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_TAGS.tag_double
return {vars = {card.ability.extra.count, card.ability.extra.current_count}}
end,
--Set sprites and hitbox
set_sprites = function(self, card, front)
local w_scale, h_scale = 48/71, 95/95
card.children.center.scale.y = card.children.center.scale.y * h_scale
card.children.center.scale.x = card.children.center.scale.x * w_scale
end,
set_ability = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 48/71, 95/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
load = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 48/71, 95/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.repetition then
if context.other_card.edition then
card.ability.extra.current_count = card.ability.extra.current_count + 1
end
end
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
if card.ability.extra.current_count > 0 then
if card.ability.extra.current_count >= card.ability.extra.count then
event({ trigger = 'after', delay = 0.5, func = function()
add_tag(Tag('tag_double'))
play_sound('generic1', 0.9 + math.random()*0.1, 0.8)
play_sound('holo1', 1.2 + math.random()*0.1, 0.4)
return true end
}
)
forced_message("Tag!", card, G.C.SECONDARY_SET.Enhanced, true, false)
else
forced_message("Reset", card, G.C.SECONDARY_SET.Enhanced, true, false)
end
end
card.ability.extra.current_count = 0
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one card with edition
for _, v in pairs(G.playing_cards) do
if v.edition then return true end
end
return false
end
})
--Connoiseur
create_joker({
name = 'Connoiseur', id = 45,
rarity = 'Rare', cost = 8,
gameplay_tag = {'j_powerful'},
blueprint = true, eternal = true, perishable = true,
vars = {{ repetitions = 1 }},
calculate = function(self, card, context)
if context.cardarea == G.play and context.repetition and not context.repetition_only then
if context.other_card.edition then
return {
message = 'Again!',
repetitions = card.ability.extra.repetitions,
card = context.blueprint_card or card
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one card with edition
for _, v in pairs(G.playing_cards) do
if v.edition then return true end
end
return false
end
})
--Jeweler
create_joker({
name = 'Jeweler', id = 0,
rarity = 'Uncommon', cost = 8,
gameplay_tag = {'edition_upgrade'},
vars = {{odds = 4}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'upgrade_edition'}
local vars
if G.GAME and G.GAME.probabilities.normal then
vars = {G.GAME.probabilities.normal, card.ability.extra.odds}
else
vars = {1, card.ability.extra.odds}
end
return {vars = vars}
end,
blueprint = false, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.before and not context.blueprint then
if pseudorandom('jeweler'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds then
forced_message("Upgrade!", card, G.C.PURPLE, true, false)
local hand_name = context.scoring_name
if to_big(G.GAME.hands[hand_name].level) > to_big(0) then
level_up_hand(card, hand_name, false, -1)
end
for i = 1, #context.scoring_hand do
local current_card = context.scoring_hand[i]
edition_upgrade(context.scoring_hand[i])
--[[event({delay = 0, trigger = 'before',
func = function()
edition_upgrade(current_card)
return true end}
)]]
end
end
end
end
})
--New Enhancements Support Stuff
--Slop Card Lines
--Joker Diffusion
create_joker({
name = 'Joker Diffusion', id = 26,
rarity = 'Uncommon', cost = 6,
gameplay_tag = {'enh_custom'},
blueprint = true, eternal = true, perishable = true,
vars = {{count = '3'}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_slop']
return { vars = {card.ability.extra.count}}
end,
calculate = function(self, card, context)
if context.after then
local is_activate = #context.scoring_hand ~= #context.full_hand
--Terminate if the condition does not met
if not is_activate then return end
--Populate Candidate Card
local eligible_card = {}
local converted_card = {}
for i=1, #G.hand.cards do
if G.hand.cards[i].config.center == G.P_CENTERS.c_base and not G.hand.cards[i].to_convert then eligible_card[#eligible_card+1] = G.hand.cards[i] end
end
table.sort(eligible_card, function (a, b) return not a.playing_card or not b.playing_card or a.playing_card < b.playing_card end)
pseudoshuffle(eligible_card, pseudoseed('jokerdiffusion'..G.SEED))
for i = 1, card.ability.extra.count do converted_card[#converted_card+1] = eligible_card[i] end
if #converted_card > 0 then
--Animation from Basegame Tarot
for i=1, #converted_card do
converted_card[i].to_convert = true
local percent = 1.15 - (i-0.999)/(#converted_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() big_juice(context.blueprint_card or card); converted_card[i]:flip();play_sound('card1', percent);converted_card[i]:juice_up(0.3, 0.3);return true end })
end
delay(0.2)
--Handle the conversion
for i=1, #converted_card do
event({trigger = 'after',delay = 0.1,func = function()
converted_card[i]:set_ability(G.P_CENTERS.m_unstb_slop)
return true end })
end
for i=1, #converted_card do
local percent = 0.85 + (i-0.999)/(#converted_card-0.998)*0.3
event({trigger = 'after',delay = 0.15,func = function() converted_card[i]:flip();play_sound('tarot2', percent, 0.6);converted_card[i]:juice_up(0.3, 0.3); converted_card[i].to_convert = nil; return true end })
end
delay(0.5)
end
end
end,
})
--Non-Fungible Joker
create_joker({
name = 'NonFungible Joker', id = 27,
rarity = 'Uncommon', cost = 6,
gameplay_tag = {'enh_custom'},
blueprint = false, eternal = false, perishable = true,
vars = {{count = '1'}, {max_payout = 10}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_slop']
return { vars = {card.ability.extra.count, card.ability.extra.max_payout}}
end,
calculate = function(self, card, context)
if context.pre_discard and not context.blueprint then
local slop_count = 0
for i=1, #context.full_hand do
if context.full_hand[i].config.center == G.P_CENTERS.m_unstb_slop then slop_count = slop_count + 1 end
end
card.ability.extra.is_activate = slop_count==1
end
if context.discard and not context.other_card.debuff and not context.blueprint then
if card.ability.extra.is_activate and context.other_card.config.center == G.P_CENTERS.m_unstb_slop then
ease_dollars(pseudorandom('nfj'..G.SEED, 0, card.ability.extra.max_payout), true)
return {
--play_sound('whoosh1', math.random()*0.1 + 0.6,0.3),
message = 'Sold!',
colour = G.C.GOLD,
remove = true,
card = card
}
end
end
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
if card.ability.extra.max_payout > 0 or card.sell_cost > 0 then
if card.ability.extra.max_payout>0 then
card.ability.extra.max_payout = card.ability.extra.max_payout - 1
end
--Reduce its own selling price too, for the funny
if card.sell_cost > 0 then
card.ability.extra_value = (card.ability.extra_value or 0) - 1
card:set_cost()
end
return{
message = "Price Dropped",
colour = G.C.RED,
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one slop card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_slop then return true end
end
return false
end
})
--Prompt
create_joker({
name = 'Prompt', id = 28,
rarity = 'Common', cost = 7,
gameplay_tag = {'enh_custom'},
blueprint = true, eternal = true, perishable = true,
--vars = { {times = 1}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_slop']
return { vars = {}}
end,
--Prompt's actual ability is in Slop Card's after_play function
--[[calculate = function(self, card, context)
end,]]
custom_in_pool = function(self, args)
--Spawns if there is at least one slop card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_slop then return true end
end
return false
end
})
--Uninterested Primate
create_joker({
name = 'Uninterested Primate', id = 29,
rarity = 'Common', cost = 5,
gameplay_tag = {'enh_custom'},
blueprint = true, eternal = false, perishable = false,
vars = { {chips = 50}, {chips_rate = 10}, {slop_scored = 0}, {slop_cycle = 3}, {chance_destroy = 8}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_slop']
return { vars = {card.ability.extra.chips_rate, card.ability.extra.slop_cycle, G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.chance_destroy, card.ability.extra.chips, card.ability.extra.slop_cycle - card.ability.extra.slop_scored}}
end,
calculate = function(self, card, context)
--Main Context
if context.joker_main then
return {
chip_mod = card.ability.extra.chips,
message = localize { type = 'variable', key = 'a_chips', vars = { card.ability.extra.chips } }
}
end
--Scaling, cannot copy via blueprint
if context.individual and context.cardarea == G.play and not context.blueprint then
if context.other_card.config.center == G.P_CENTERS.m_unstb_slop then
card.ability.extra.slop_scored = card.ability.extra.slop_scored + 1
if card.ability.extra.slop_scored >= card.ability.extra.slop_cycle then
card.ability.extra.slop_scored = 0
card.ability.extra.chips = card.ability.extra.chips + card.ability.extra.chips_rate
forced_message("+"..card.ability.extra.chips_rate, card, G.C.CHIPS, true)
end
end
end
--End of round check, make sure it's checked absolutely once per round
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
--Reduce its own selling price, for the funny
if card.sell_cost > 0 then
card.ability.extra_value = (card.ability.extra_value or 0) - 1
card:set_cost()
end
if pseudorandom('primate'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.chance_destroy then
event({func = function()
play_sound('tarot1')
card.T.r = -0.2
card:juice_up(0.3, 0.4)
card.states.drag.is = true
card.children.center.pinch.x = true
--Destroy Card
event({trigger = 'after', delay = 0.3, func = function()
G.jokers:remove_card(card)
card:remove()
card = nil
return true end })
return true end })
G.GAME.pool_flags.primate_gone = true
return {
message = 'My Primates Gone'
}
else
return {
message = 'Safe!'
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one slop card + primate is not gone yet
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_slop then return not G.GAME.pool_flags.primate_gone end
end
return false
end
})
--Lethargic Lion
create_joker({
name = 'Lethargic Lion', id = 30,
rarity = 'Common', cost = 5,
gameplay_tag = {'enh_custom'},
blueprint = true, eternal = false, perishable = false,
vars = { {xmult = 2}, {xmult_rate = 0.02}, {slop_scored = 0}, {slop_cycle = 3}, {chance_destroy = 8}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_slop']
return { vars = {card.ability.extra.xmult_rate, card.ability.extra.slop_cycle, G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.chance_destroy, card.ability.extra.xmult, card.ability.extra.slop_cycle - card.ability.extra.slop_scored}}
end,
calculate = function(self, card, context)
--Main Context
if context.joker_main then
return {
Xmult_mod = card.ability.extra.xmult,
message = localize { type = 'variable', key = 'a_xmult', vars = { card.ability.extra.xmult } }
}
end
--Scaling, cannot copy via blueprint
if context.individual and context.cardarea == G.play and not context.blueprint then
if context.other_card.config.center == G.P_CENTERS.m_unstb_slop then
card.ability.extra.slop_scored = card.ability.extra.slop_scored + 1
if card.ability.extra.slop_scored >= card.ability.extra.slop_cycle then
card.ability.extra.slop_scored = 0
card.ability.extra.xmult = card.ability.extra.xmult + card.ability.extra.xmult_rate
forced_message("+"..card.ability.extra.xmult_rate, card, G.C.MULT, true)
end
end
end
--End of round check, make sure it's checked absolutely once per round
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
--Reduce its own selling price, for the funny
if card.sell_cost > 0 then
card.ability.extra_value = (card.ability.extra_value or 0) - 1
card:set_cost()
end
if pseudorandom('primate'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.chance_destroy then
event({func = function()
play_sound('tarot1')
card.T.r = -0.2
card:juice_up(0.3, 0.4)
card.states.drag.is = true
card.children.center.pinch.x = true
--Destroy Card
event({trigger = 'after', delay = 0.3, func = function()
G.jokers:remove_card(card)
card:remove()
card = nil
return true end })
return true end })
return {
message = 'My Lions Gone'
}
else
return {
message = 'Safe!'
}
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one slop card + primate is gone
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_slop then return G.GAME.pool_flags.primate_gone end
end
return false
end
})
--Vintage Joker
create_joker({
name = 'Vintage Joker', id = 23,
rarity = 'Uncommon', cost = 6,
gameplay_tag = {'enh_custom'},
blueprint = true, eternal = true, perishable = true,
vars = {{chance_fix = '4'}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_vintage']
return { vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.chance_fix}}
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.config.center == G.P_CENTERS.m_unstb_vintage then
if pseudorandom('vintagejoker'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.chance_fix then
context.other_card.ability.extra.current_odd = 0 --math.ceil(context.other_card.ability.extra.current_odd * 0.5)
event({trigger = 'after', func = function()
play_sound('tarot2', 1, 0.6);
big_juice(context.blueprint_card or card)
return true end })
forced_message("Restored!", context.other_card, G.C.GREEN, true)
end
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one vintage card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_vintage then return true end
end
return false
end
})
--Rules Errata
create_joker({
name = 'Rules Errata', id = 24,
rarity = 'Uncommon', cost = 5,
gameplay_tag = {'enh_custom'},
blueprint = false, eternal = true, perishable = true,
--vars = {{}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'acorn_tooltip'}
return { vars = {}}
end,
--This Joker's effect is in Acorn Mark Card's code itself
--[[calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
end
end,]]
custom_in_pool = function(self, args)
--Spawns if there is at least one acorn card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_acorn then return true end
end
return false
end
})
--Auction Winner
create_joker({
name = 'Auction Winner', id = 25,
rarity = 'Common', cost = 6,
gameplay_tag = {'enh_custom'},
blueprint = true, eternal = true, perishable = true,
--vars = {{}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_promo']
return { vars = {}}
end,
calculate = function(self, card, context)
if context.remove_playing_cards then
local money = 0
for _, v in pairs(context.removed) do
if v.config.center == G.P_CENTERS.m_unstb_promo then
money = money + v.ability.extra.gold
end
end
if money>0 then
event({trigger = 'after', func = function()
ease_dollars(money, true)
big_juice(context.blueprint_card or card)
return true end })
forced_message('$'..money, context.blueprint_card or card, G.C.GOLD, true)
end
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one promo card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_promo then return true end
end
return false
end
})
create_joker({
name = 'Joker Island', id = 7,
rarity = 'Uncommon', cost = 6,
gameplay_tag = {'enh_custom'},
vars = {{target_rank = 2}, {odds_ticket = 6}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'resource_tooltip'}
return {vars = {localize(card.ability.extra.target_rank, 'ranks'), G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_ticket}}
end,
blueprint = true, eternal = true, perishable = true,
set_ability = function(self, card, initial, delay_sprites)
--Random possible rank
local rank = '2'
if G.playing_cards then
rank = get_valid_card_from_deck('jokerisland'..G.SEED).rank --pseudorandom_element(G.playing_cards, pseudoseed('jokerisland')..G.SEED).base.value
end
card.ability.extra.target_rank = rank
end,
add_to_deck = function(self, card, from_debuff)
if not G.GAME.pool_flags.catan_enabled then
G.GAME.pool_flags.catan_enabled = true
end
end,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
local currentCard = context.other_card
if currentCard.base.value == card.ability.extra.target_rank and not currentCard.config.center.no_rank then
local isActivated = pseudorandom('jokerisland'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_ticket
if isActivated then
event({func = function()
local rank = pseudorandom_element(SMODS.Ranks, pseudoseed('jokerisland')..G.SEED).card_key
local suit = SMODS.Suits[currentCard.base.suit].card_key
local _card = Card(G.play.T.x + G.play.T.w/2, G.play.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[suit..'_'..rank], G.P_CENTERS.m_unstb_resource, {playing_card = G.playing_card})
_card:start_materialize({G.C.SECONDARY_SET.Enhanced})
G.play:emplace(_card)
table.insert(G.playing_cards, _card)
return true end
})
event({func = function()
G.deck.config.card_limit = G.deck.config.card_limit + 1
draw_card(G.play,G.deck, 90,'up', nil)
return true end
})
playing_card_joker_effects({true})
end
end
end
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
card.ability.extra.target_rank = get_valid_card_from_deck('jokerisland'..G.SEED).rank --pseudorandom_element(G.playing_cards, pseudoseed('jokerisland')..G.SEED).base.value --pseudorandom_element(SMODS.Ranks, pseudoseed('jokerisland')..G.SEED).key
return{
message = "Randomize"
}
end
end
})
--New Anti-Enhancement Stuff
create_joker({
name = 'Kaiju', id = 17,
rarity = 'Uncommon', cost = 8,
gameplay_tag = {'enh_disenh'},
vars = {{add_slot = 4}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_radioactive']
return {vars = {card.ability.extra.add_slot}}
end,
blueprint = false, eternal = true, perishable = true,
add_to_deck = function(self, card, from_debuff)
if not G.GAME.pool_flags.radioactive_enabled then
G.GAME.pool_flags.radioactive_enabled = true
end
G.jokers.config.card_limit = G.jokers.config.card_limit + card.ability.extra.add_slot
end,
remove_from_deck = function(self, card, from_debuff)
G.jokers.config.card_limit = G.jokers.config.card_limit - card.ability.extra.add_slot
end,
calculate = function(self, card, context)
if context.first_hand_drawn then
event({delay = 0.4, trigger = 'after',
func = function()
local eligible_list={}
for i=1, #G.hand.cards do
if G.hand.cards[i].config.center ~= G.P_CENTERS.m_unstb_radioactive then table.insert(eligible_list,G.hand.cards[i]) end
end
if #eligible_list>0 then
local enhanced_card = pseudorandom_element(eligible_list, pseudoseed('kaiju'..G.SEED))
enhanced_card:set_disenhancement(G.P_CENTERS.m_unstb_radioactive , nil, true)
play_sound('tarot1')
enhanced_card:juice_up()
end
return true end}
)
end
end
})
create_joker({
name = 'Poison the Well', id = 18,
rarity = 'Uncommon', cost = 8,
gameplay_tag = {'enh_disenh'},
vars = {{discard_size = 4}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'poison_tooltip'}
return {vars = {card.ability.extra.discard_size}}
end,
blueprint = false, eternal = true, perishable = true,
add_to_deck = function(self, card, from_debuff)
if not G.GAME.pool_flags.poison_enabled then
G.GAME.pool_flags.poison_enabled = true
end
G.GAME.round_resets.discards = G.GAME.round_resets.discards + card.ability.extra.discard_size
ease_discard(3)
end,
remove_from_deck = function(self, card, from_debuff)
G.GAME.round_resets.discards = G.GAME.round_resets.discards - card.ability.extra.discard_size
ease_discard(-3)
end,
calculate = function(self, card, context)
if context.pre_discard and not context.blueprint then
local target_card = pseudorandom_element(G.hand.highlighted, pseudoseed(seed or 'poisonwell'..G.GAME.round_resets.ante))
event({trigger = 'after', func = function()
play_sound('generic1')
target_card:juice_up(0.3, 0.3);
target_card:set_disenhancement(G.P_CENTERS.m_unstb_poison , nil, true)
return true end })
end
end
})
create_joker({
name = 'Petri Dish', id = 19,
rarity = 'Uncommon', cost = 8,
gameplay_tag = {'enh_disenh'},
vars = {{adds_hand = 4}, {odds = 4}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_biohazard']
return {vars = {card.ability.extra.adds_hand, G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds}}
end,
blueprint = false, eternal = true, perishable = true,
add_to_deck = function(self, card, from_debuff)
if not G.GAME.pool_flags.biohazard_enabled then
G.GAME.pool_flags.biohazard_enabled = true
end
G.GAME.round_resets.hands = G.GAME.round_resets.hands + card.ability.extra.adds_hand
ease_hands_played(3)
end,
remove_from_deck = function(self, card, from_debuff)
G.GAME.round_resets.hands = G.GAME.round_resets.hands - card.ability.extra.adds_hand
ease_hands_played(-3)
end,
calculate = function(self, card, context)
if context.after and not context.blueprint then
if pseudorandom('petridish'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds then
local target_card = pseudorandom_element(context.full_hand, pseudoseed(seed or 'petridishtarget'..G.SEED))
event({trigger = 'after', func = function()
play_sound('generic1')
target_card:juice_up(0.3, 0.3);
target_card:set_disenhancement(G.P_CENTERS.m_unstb_biohazard)
return true end })
forced_message("Infected", target_card, G.C.RED, true)
end
end
end
})
--Anti-Enhancement Supports
create_joker({
name = 'Geiger Counter', id = 20,
rarity = 'Uncommon', cost = 6,
gameplay_tag = {'enh_disenh'},
blueprint = true, eternal = true, perishable = true,
vars = {{mult_rate = '6'}, {mult = '0'}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_radioactive']
--Check the number of radioactive card in the deck
local count =0
if (G.playing_cards) then
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_radioactive then count = count + 1 end
end
return { vars = {card.ability.extra.mult_rate, count*card.ability.extra.mult_rate}}
else
return { vars = {card.ability.extra.mult_rate, 0}}
end
end,
--Set sprites and hitbox
set_sprites = function(self, card, front)
local w_scale, h_scale = 53/71, 95/95
card.children.center.scale.y = card.children.center.scale.y * h_scale
card.children.center.scale.x = card.children.center.scale.x * w_scale
end,
set_ability = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 53/71, 95/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
load = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 53/71, 95/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
calculate = function(self, card, context)
--Main context
if context.joker_main then
local count=0
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_radioactive then count = count + 1 end
end
card.ability.extra.mult = count*card.ability.extra.mult_rate
return {
mult_mod = card.ability.extra.mult,
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.mult } }
}
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one radioactive card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_radioactive then return true end
end
return false
end
})
create_joker({
name = 'Strych Nine', id = 21,
rarity = 'Uncommon', cost = 5,
gameplay_tag = {'enh_disenh'},
blueprint = true, eternal = true, perishable = true,
vars = {{chip_rate = '9'}, {chip = '0'}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'poison_tooltip'}
--Check the number of poison card in the deck
local count =0
if (G.playing_cards) then
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_poison then count = count + 1 end
end
return { vars = {card.ability.extra.chip_rate, count*card.ability.extra.chip_rate}}
else
return { vars = {card.ability.extra.chip_rate, 0}}
end
end,
calculate = function(self, card, context)
--Main context
if context.joker_main then
local count=0
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_poison then count = count + 1 end
end
card.ability.extra.chip = count*card.ability.extra.chip_rate
return {
chip_mod = card.ability.extra.chip,
message = localize { type = 'variable', key = 'a_chips', vars = { card.ability.extra.chip } }
}
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one poison card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_poison then return true end
end
return false
end
})
create_joker({
name = 'Vaccination Card', id = 22,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'enh_disenh'},
blueprint = true, eternal = true, perishable = true,
vars = {{xmult_rate = '0.5'}, {xmult = '1'}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS['m_unstb_biohazard']
--Check the number of biohazard card on hand
local count = 0
if (G.hand) then
for i = 1, #G.hand.cards do
if G.hand.cards[i].config.center == G.P_CENTERS.m_unstb_biohazard and not G.hand.cards[i].healed then
count = count+1
end
end
return { vars = {card.ability.extra.xmult_rate, 1+count*card.ability.extra.xmult_rate}}
else
return { vars = {card.ability.extra.xmult_rate, 1}}
end
end,
calculate = function(self, card, context)
--Main context
if context.joker_main then
local count=0
for i = 1, #G.hand.cards do
if G.hand.cards[i].config.center == G.P_CENTERS.m_unstb_biohazard and not G.hand.cards[i].healed then
count = count+1
end
end
card.ability.extra.xmult = 1+count*card.ability.extra.xmult_rate
return {
Xmult_mod = card.ability.extra.xmult,
message = localize { type = 'variable', key = 'a_xmult', vars = { card.ability.extra.xmult } }
}
end
end,
custom_in_pool = function(self, args)
--Spawns if there is at least one biohazard card
for _, v in pairs(G.playing_cards) do
if v.config.center == G.P_CENTERS.m_unstb_biohazard then return true end
end
return false
end
})
--Miscellaneous
-- Joker 2
create_joker({
name = 'Joker2', id = 11,
rarity = 'Common', cost = 4,
gameplay_tag = {'j_shitpost'},
vars = {{mult = 4}, {xmult = 2}, {odds_destroy = 4}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.mult, card.ability.extra.xmult, G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_destroy}}
end,
blueprint = true, eternal = false, perishable = true,
calculate = function(self, card, context)
--Main context
if context.joker_main then
return {
mult = card.ability.extra.mult,
xmult = card.ability.extra.xmult,
--message = localize { type = 'variable', key = 'a_xmult', vars = { card.ability.extra.xmult } }
}
end
--End of round check, make sure it's checked absolutely once per round
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
if pseudorandom('joker2'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_destroy then
event({func = function()
play_sound('tarot1')
card.T.r = -0.2
card:juice_up(0.3, 0.4)
card.states.drag.is = true
card.children.center.pinch.x = true
--Destroy Card
event({trigger = 'after', delay = 0.3, func = function()
G.jokers:remove_card(card)
card:remove()
card = nil
return true end })
return true end })
return {
message = 'Flopped',
colour = G.C.ORANGE
}
else
return {
message = 'Safe!',
colour = G.C.GREEN
}
end
end
end
})
-- Joker Stairs
create_joker({
name = 'Joker Stairs', id = 12,
rarity = 'Uncommon', cost = 8,
gameplay_tag = {'j_shitpost'},
vars = {{mult_rate = 4}, {mult = 0}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.mult_rate, card.ability.extra.mult}}
end,
blueprint = true, eternal = true, perishable = false,
calculate = function(self, card, context)
--Shop
if context.buying_card and context.card ~= card then
if unstb_global.name_joker[context.card.config.center.key] then
--print("Joker Stair Triggered!")
card.ability.extra.mult = card.ability.extra.mult + card.ability.extra.mult_rate
event({
func = function()
big_juice(card)
forced_message("Upgraded!", card, G.C.MULT, true)
--card_eval_status_text(card,'extra',nil, nil, nil,{message = "Upgraded", colour = G.C.MULT, instant = true})
return true end})
end
end
--Main context
if context.joker_main then
if card.ability.extra.mult > 0 then
return {
mult_mod = card.ability.extra.mult,
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.mult } }
}
end
end
end
})
--A special function to check for blueprint compat for both sides
G.FUNCS.blueprint_compat_dside_l = function(e)
if e.config.ref_table.ability.blueprint_compat_l ~= e.config.ref_table.ability.blueprint_compat_check_l then
if e.config.ref_table.ability.blueprint_compat_l == 'compatible' then
e.config.colour = mix_colours(G.C.GREEN, G.C.JOKER_GREY, 0.8)
elseif e.config.ref_table.ability.blueprint_compat_l == 'incompatible' then
e.config.colour = mix_colours(G.C.RED, G.C.JOKER_GREY, 0.8)
end
e.config.ref_table.ability.blueprint_compat_ui_l = ' '..localize('k_blueprint_l_'..e.config.ref_table.ability.blueprint_compat_l)..' '
e.config.ref_table.ability.blueprint_compat_check_l = e.config.ref_table.ability.blueprint_compat_l
end
end
G.FUNCS.blueprint_compat_dside_r = function(e)
if e.config.ref_table.ability.blueprint_compat_r ~= e.config.ref_table.ability.blueprint_compat_check_r then
if e.config.ref_table.ability.blueprint_compat_r == 'compatible' then
e.config.colour = mix_colours(G.C.GREEN, G.C.JOKER_GREY, 0.8)
elseif e.config.ref_table.ability.blueprint_compat_r == 'incompatible' then
e.config.colour = mix_colours(G.C.RED, G.C.JOKER_GREY, 0.8)
end
e.config.ref_table.ability.blueprint_compat_ui_r = ' '..localize('k_blueprint_r_'..e.config.ref_table.ability.blueprint_compat_r)..' '
e.config.ref_table.ability.blueprint_compat_check_r = e.config.ref_table.ability.blueprint_compat_r
end
end
--Plagiarism
create_joker({
name = 'Plagiarism', id = 46,
rarity = 'Uncommon', cost = 10,
gameplay_tag = {'j_shitpost'},
vars = {{dir = 0}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.dir==0 and 'Left' or 'Right'}}
end,
blueprint = true, eternal = true, perishable = true,
set_ability = function(self, card, initial, delay_sprites)
--Random direction
card.ability.extra.dir = 0
if pseudorandom('plagiarism'..G.SEED) > 0.5 then
card.ability.extra.dir = 1
end
end,
calculate = function(self, card, context)
--Code based on Familiar's Crimsonotype
--This bit of code runs before hand played, cannot copyable by other blueprint
if context.before and not context.blueprint and not context.repetition and not context.repetition_only then
card.ability.extra.dir = 0
if pseudorandom('plagiarism'..G.SEED) > 0.5 then
card.ability.extra.dir = 1
end
forced_message(card.ability.extra.dir==0 and 'Left' or 'Right', card, G.C.ORANGE, true)
end
local other_joker = nil
for i = 1, #G.jokers.cards do
if G.jokers.cards[i] == card then
if card.ability.extra.dir==0 then
other_joker = G.jokers.cards[i - 1]
else
other_joker = G.jokers.cards[i + 1]
end
end
end
if other_joker and other_joker ~= self then
--local newcontext = context
context.blueprint = (context.blueprint and (context.blueprint + 1)) or 1
context.blueprint_card = context.blueprint_card or card
if context.blueprint > #G.jokers.cards + 1 then
return
end
local other_joker_ret, trig = other_joker:calculate_joker(context)
--Context needs resetting afterwards, otherwise this value keeps persisting
context.blueprint = nil
local eff_card = context.blueprint_card or card
context.blueprint_card = nil
if other_joker_ret or trig then
if not other_joker_ret then
other_joker_ret = {}
end
other_joker_ret.card = eff_card
other_joker_ret.colour = G.C.GREEN
other_joker_ret.no_callback = true
--IDK why these are not applied to the return value, it even worked fine when I commented return line out when it shouldn't
return other_joker_ret
end
end
end
})
--Joker Throwing Card
create_joker({
name = 'Joker Throwing Card', id = 47,
rarity = 'Rare', cost = 8,
vars = {{rate = 2}, {reduce = 10}, {odds_destroy = 4}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.reduce, G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_destroy, card.ability.extra.rate}}
end,
blueprint = false, eternal = true, perishable = false,
calculate = function(self, card, context)
--Reduce Blind Size
if context.setting_blind and not context.blueprint then
local decrease = math.min(card.ability.extra.reduce, 70) --Failsafe, just in case other value-altering joker did the funnies
G.GAME.blind.chips = G.GAME.blind.chips * (1 - decrease * 0.01)
G.GAME.blind.chip_text = number_format(G.GAME.blind.chips)
end
--Check before hand is played
if context.before and context.scoring_hand and not context.blueprint then
local isActivated = pseudorandom('throwingcard'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_destroy
if isActivated and card.ability.extra.reduce < 70 then --Won't activate if the reduction reach the hardcoded limit (to prevent other value-altering joker)
local target_card = pseudorandom_element(context.scoring_hand, pseudoseed('throwingcard'..G.SEED))
target_card.is_destroying = true
end
end
--Handle Card Destroy
if context.destroying_card and not context.blueprint then
if context.destroying_card.is_destroying then
if card.ability.extra.reduce < 70 then
card.ability.extra.reduce = card.ability.extra.reduce + card.ability.extra.rate
card.ability.extra.reduce = math.min(card.ability.extra.reduce, 70)
forced_message("Upgraded!", card, G.C.PURPLE, true)
end
return true --Destroy the card
end
end
end
})
--Jackhammer
create_joker({
name = 'Jackhammer', id = 16,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'j_powerful'},
vars = {{retrigger_times = 5}, {is_activate = false}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.retrigger_times}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
--Pre-hand check
if context.before and not context.blueprint then
card.ability.extra.is_activate = false
local jack_count = 0
for i=1, #context.scoring_hand do
if context.scoring_hand[i].base.value == 'Jack' then
jack_count = jack_count + 1
end
end
if jack_count==1 then
card.ability.extra.is_activate = true
end
end
--Main context
if context.cardarea == G.play and context.repetition and not context.repetition_only and card.ability.extra.is_activate then
if context.other_card.base.value == 'Jack' then
card.ability.extra.target_jack = context.other_card
return {
message = 'Again!',
repetitions = card.ability.extra.retrigger_times,
card = context.blueprint_card or card
}
end
end
if context.destroying_card and not context.blueprint then
--This context is called on every single card in the scoring hand
--Check if the card called is the same as target card
if context.destroying_card == card.ability.extra.target_jack then
card.ability.extra.target_jack = nil
return true --Destroy the card
end
end
end
})
--Jack of All Trades
create_joker({
name = 'Jack of All Trades', id = 60,
rarity = 'Uncommon', cost = 6,
vars = {{chips = 20}, {mult = 3}, {xmult = 1.25}, {money = 1}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.chips, card.ability.extra.mult, card.ability.extra.xmult, card.ability.extra.money}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.base.value == 'Jack' then
ease_dollars(card.ability.extra.money)
return {
chips = card.ability.extra.chips,
mult = card.ability.extra.mult,
x_mult = card.ability.extra.xmult,
card = card
}
end
end
end
})
--Magic Trick Card
create_joker({
name = 'Magic Trick Card', id = 0, ex_art = true,
rarity = 'Uncommon', cost = 4,
vars = {{side = 0}},
custom_vars = function(self, info_queue, card)
local rank1 = localize(card.ability.extra.source_rank, 'ranks')
local suit1 = localize(card.ability.extra.source_suit, 'suits_plural')
local rank2 = localize(card.ability.extra.target_rank, 'ranks')
local suit2 = localize(card.ability.extra.target_suit, 'suits_plural')
local colour1 = G.C.SUITS[card.ability.extra.source_suit]
local colour2 = G.C.SUITS[card.ability.extra.target_suit]
return {vars = {rank1..' of '..suit1, rank2..' of '..suit2, colours = {colour1, colour2}}}
end,
blueprint = false, eternal = true, perishable = true,
set_ability = function(self, card, initial, delay_sprites)
if card.ability.extra.side == 0 then
card.ability.extra.source_suit = 'Hearts'
card.ability.extra.source_rank = 'Queen'
card.ability.extra.target_suit = 'Clubs'
card.ability.extra.target_rank = '7'
else
card.ability.extra.source_suit = 'Clubs'
card.ability.extra.source_rank = '7'
card.ability.extra.target_suit = 'Hearts'
card.ability.extra.target_rank = 'Queen'
end
card.children.center:set_sprite_pos({x = card.ability.extra.side, y = 0})
end,
calculate = function(self, card, context)
if context.pre_discard and not context.blueprint then
event({trigger = 'after', delay = 0.3, blockable = false,
func = function()
card:juice_up(1, 1)
if card.ability.extra.side == 0 then
card.ability.extra.side = 1
card.ability.extra.source_suit = 'Clubs'
card.ability.extra.source_rank = '7'
card.ability.extra.target_suit = 'Hearts'
card.ability.extra.target_rank = 'Queen'
else
card.ability.extra.side = 0
card.ability.extra.source_suit = 'Hearts'
card.ability.extra.source_rank = 'Queen'
card.ability.extra.target_suit = 'Clubs'
card.ability.extra.target_rank = '7'
end
card.children.center:set_sprite_pos({x = card.ability.extra.side, y = 0})
return true end})
card_eval_status_text(
card,
'extra',
nil, nil, nil,
{message = 'Flipped!', colour = G.C.ORANGE, instant = true}
)
end
if context.after and not context.blueprint then
for i = 1, #context.scoring_hand do
local currentCard = context.scoring_hand[i]
local is_activate = currentCard:is_suit(card.ability.extra.source_suit) and currentCard.base.value == card.ability.extra.source_rank
if is_activate then
--Flipping Animation
event({trigger = 'after', delay = 0.1, func = function() big_juice(card); currentCard:flip(); play_sound('card1', 1); currentCard:juice_up(0.3, 0.3); return true end })
--Changing Card Property
event({trigger = 'after', delay = 0.05, func = function()
SMODS.change_base(currentCard, card.ability.extra.target_suit, card.ability.extra.target_rank)
return true end })
--Unflipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('tarot2', 1, 0.6); big_juice(card); currentCard:juice_up(0.3, 0.3); return true end })
forced_message("Changed!", currentCard, G.C.ORANGE, true)
end
end
end
end
})
--Queensland
create_joker({
name = 'Queensland', id = 61,
rarity = 'Rare', cost = 7,
gameplay_tag = {'enh_custom'},
vars = {{count_max = 5}, {count = 0}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'resource_tooltip'}
return {vars = {card.ability.extra.count_max, card.ability.extra.count_max - card.ability.extra.count}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.base.value == 'Queen' then
if card.ability.extra.count < card.ability.extra.count_max then
if not context.blueprint then
card.ability.extra.count = card.ability.extra.count + 1
end
local other_card = context.other_card
event({func = function()
local rank = pseudorandom_element(SMODS.Ranks, pseudoseed('queensland')..G.SEED).card_key
local suit = SMODS.Suits[other_card.base.suit].card_key
local _card = Card(G.play.T.x + G.play.T.w/2, G.play.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[suit..'_'..rank], G.P_CENTERS.m_unstb_resource, {playing_card = G.playing_card})
big_juice(context.blueprint_card or card)
_card:start_materialize({G.C.SECONDARY_SET.Enhanced})
G.play:emplace(_card)
table.insert(G.playing_cards, _card)
return true end
})
event({func = function()
G.deck.config.card_limit = G.deck.config.card_limit + 1
draw_card(G.play,G.deck, 90,'up', nil)
return true end
})
playing_card_joker_effects({true})
end
end
end
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
if card.ability.extra.count > 0 then
card.ability.extra.count = 0
return {
message = 'Reset!'
}
end
end
end
})
--King of Pop
create_joker({
name = 'King of Pop', id = 62,
rarity = 'Rare', cost = 8,
gameplay_tag = {'j_powerful'},
vars = {{odds_destroy = 4}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_TAGS.tag_double
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_destroy}}
end,
blueprint = false, eternal = true, perishable = true,
calculate = function(self, card, context)
--Pre-hand check
if context.before and not context.blueprint then
for i=1, #context.scoring_hand do
if context.scoring_hand[i].base.value == 'King' and context.scoring_hand[i].config.center ~= G.P_CENTERS.c_base and not context.scoring_hand[i].config.center.disenhancement then
if pseudorandom('kingofpop'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_destroy then
context.scoring_hand[i].to_destroy = true
end
end
end
end
if context.destroying_card and not context.blueprint then
if context.destroying_card.to_destroy then
event({ trigger = 'after', delay = 0.5, func = function()
add_tag(Tag('tag_double'))
play_sound('generic1', 0.9 + math.random()*0.1, 0.8)
play_sound('holo1', 1.2 + math.random()*0.1, 0.4)
return true end
}
)
forced_message("Tag!", card, G.C.SECONDARY_SET.Enhanced)
return true --Destroy the card
end
end
end
})
--Polychrome Red Seal Steel Joker
create_joker({
name = 'prssj', id = 63,
rarity = 'Rare', cost = 10,
gameplay_tag = {'j_shitpost', 'j_powerful', 'edition_upgrade'},
vars = {{odds_upgrade = 8}, {odds_retrigger = 4}, {odds_hand = 2}, {hand_xmult = 2}},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = {set = 'Other', key = 'upgrade_edition'}
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds_upgrade, card.ability.extra.odds_retrigger, card.ability.extra.odds_hand, card.ability.extra.hand_xmult}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
--Upgrade Part
if context.before then
local upgrade_count = 0
for i = 1, #context.scoring_hand do
local current_card = context.scoring_hand[i]
if not current_card.debuff and current_card.base.value == 'King' and pseudorandom('prssj1'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_upgrade then
upgrade_count = upgrade_count+1
edition_upgrade(current_card)
end
end
if upgrade_count > 0 then
--forced_message("Upgrade!", context.blueprint_card or card, G.C.SECONDARY_SET.Enhanced, true)
return {
message = 'Upgrade!',
colour = G.C.SECONDARY_SET.Enhanced,
card = context.blueprint_card or card,
}
end
end
if context.cardarea == G.play and context.repetition and not context.repetition_only and not context.other_card.debuff and context.other_card.base.value == 'King' then
if pseudorandom('prssj2'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_retrigger then
return {
message = 'Again!',
repetitions = 1,
card = context.blueprint_card or card
}
end
end
if context.individual and context.cardarea == G.hand and not context.end_of_round and context.other_card.base.value == 'King' then
if pseudorandom('prssj3'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_hand then
if context.other_card.debuff then
return {
message = localize('k_debuffed'),
colour = G.C.RED,
card = context.blueprint_card or card,
}
else
return {
x_mult = card.ability.extra.hand_xmult,
card = context.blueprint_card or card
}
end
end
end
end
})
--Master of One
create_joker({
name = 'Master of One', id = 64,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'rank_binary', 'j_alter'},
--vars = {},
custom_vars = function(self, info_queue, card)
return {vars = {}}
end,
blueprint = true, eternal = true, perishable = true,
add_to_deck = function(self, card, from_debuff)
--Enable rank 1 card in pools
if not from_debuff then
setPoolRankFlagEnable('unstb_1', true);
end
end,
calculate = function(self, card, context)
if context.setting_blind then
--Adds a card
event({trigger = 'after', func = function()
local rank = 'unstb_1'
local suit = pseudorandom_element(SMODS.Suits, pseudoseed('masterofone')..G.SEED).card_key
--Pooling Enhancements
local cen_pool = {}
for k, v in pairs(G.P_CENTER_POOLS["Enhanced"]) do
if not v.replace_base_card and not v.disenhancement then
cen_pool[#cen_pool+1] = v
end
end
local _card = Card(G.play.T.x + G.play.T.w/2, G.play.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[suit..'_'..rank], pseudorandom_element(cen_pool, pseudoseed('masterofone')..G.SEED), {playing_card = G.playing_card})
_card:start_materialize({G.C.SECONDARY_SET.Enhanced})
G.play:emplace(_card)
table.insert(G.playing_cards, _card)
big_juice(context.blueprint_card or card)
card_eval_status_text(context.blueprint_card or card, 'extra', nil, nil, nil, {message = 'One!', colour = G.C.SECONDARY_SET.Enhanced})
return true end
})
event({trigger = 'after', func = function()
G.deck.config.card_limit = G.deck.config.card_limit + 1
draw_card(G.play,G.deck, 90,'up', nil)
return true end
})
playing_card_joker_effects({true})
end
end
})
--Spectre
create_joker({
name = 'Spectre', id = 9,
rarity = 'Uncommon', cost = 7,
vars = {{xmult_rate = 0.25}, {xmult = 1}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.xmult_rate, card.ability.extra.xmult}}
end,
blueprint = true, eternal = true, perishable = true,
set_ability = function(self, card, initial, delay_sprites)
if G.GAME and G.GAME.consumeable_usage_total then
card.ability.extra.xmult = 1 + G.GAME.consumeable_usage_total.spectral * card.ability.extra.xmult_rate
else
card.ability.extra.xmult = 1
end
end,
calculate = function(self, card, context)
--Upgrades
if context.using_consumeable and not context.blueprint then
if context.consumeable.ability.set == "Spectral" then
card.ability.extra.xmult = card.ability.extra.xmult + card.ability.extra.xmult_rate
event({
func = function()
big_juice(card)
forced_message("Upgraded!", card, G.C.MULT, true)
--card_eval_status_text(card,'extra',nil, nil, nil,{message = "Upgraded", colour = G.C.MULT, instant = true})
return true end})
end
end
--Main context
if context.joker_main then
return {
Xmult_mod = card.ability.extra.xmult,
message = localize { type = 'variable', key = 'a_xmult', vars = { card.ability.extra.xmult } }
}
end
end
})
--Library Card
create_joker({
name = 'Library Card', id = 52,
rarity = 'Uncommon', cost = 8,
vars = {{chips_rate = 5}, {mult_rate = 2}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.chips_rate, card.ability.extra.mult_rate}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if not context.other_card.config.center.no_suit and unstb_global.name_suit[context.other_card.base.suit] then
local suit_info = unstb_global.name_suit[context.other_card.base.suit]
local totalChips = card.ability.extra.chips_rate * suit_info.count_consonant
local totalMult = card.ability.extra.mult_rate * suit_info.count_vowel
return {
chips = totalChips,
mult = totalMult,
card = card
}
end
end
end
})
--Collector's Album
create_joker({
name = 'Collector Album', id = 53,
rarity = 'Uncommon', cost = 8,
vars = {{chips_rate = 120}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.chips_rate, card.ability.extra.mult_rate}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.other_joker then
--trigger on only joker with "Card" in the name
if unstb_global.name_card[context.other_joker.config.center.key] then
event({
func = function()
context.other_joker:juice_up(0.5, 0.5)
return true
end
})
return {
message = localize{type='variable',key='a_chips',vars={card.ability.extra.chips_rate}},
chip_mod = card.ability.extra.chips_rate
}
end
end
end
})
local function get_random_hand(current_hand, seed)
local _poker_hands = {}
--Populate list
for k, v in pairs(G.GAME.hands) do
if v.visible then _poker_hands[#_poker_hands+1] = k end
end
local new_hand = nil
while not new_hand do
new_hand = pseudorandom_element(_poker_hands, pseudoseed(seed or 'rand_hand'))
if new_hand == current_hand then new_hand = nil end
end
return new_hand
end
--Throwing Hands
create_joker({
name = 'Throwing Hands', id = 8,
rarity = 'Common', cost = 5,
vars = {{target_hand = 'High Card'}, {odds_base = 2}, {odds_destroy = 3}, {xmult = 4}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.xmult, card.ability.extra.odds_base * (G.GAME and G.GAME.probabilities.normal or 1), card.ability.extra.odds_destroy, localize(card.ability.extra.target_hand, 'poker_hands')}}
end,
blueprint = true, eternal = true, perishable = true,
set_ability = function(self,card, initial, delay_sprites)
--Random Poker Hand
card.ability.extra.target_hand = get_random_hand(card.ability.extra.target_hand, 'throwinghands'..G.SEED)
end,
calculate = function(self, card, context)
if context.joker_main and context.scoring_name ~= nil and context.scoring_hand then
local is_destroyed = false;
if context.scoring_name ~= card.ability.extra.target_hand then
if pseudorandom('throwinghands'..G.SEED) < G.GAME.probabilities.normal * card.ability.extra.odds_base / card.ability.extra.odds_destroy then
is_destroyed = true
end
end
if not is_destroyed then
return {
xmult = card.ability.extra.xmult,
}
else
if not context.blueprint then
event({func = function()
play_sound('tarot1')
card.T.r = -0.2
card:juice_up(0.3, 0.4)
card.states.drag.is = true
card.children.center.pinch.x = true
--Destroy Card
event({trigger = 'after', delay = 0.3, func = function()
G.jokers:remove_card(card)
card:remove()
card = nil
return true end })
return true end })
return {
message = 'Bye',
}
end
end
end
if context.end_of_round and not (context.individual or context.repetition or context.blueprint) then
card.ability.extra.target_hand = get_random_hand(card.ability.extra.target_hand, 'throwinghands'..G.SEED)
return {
message = card.ability.extra.target_hand
}
end
end
})
--Imperial Bower
create_joker({
name = 'Imperial Bower', id = 58,
rarity = 'Common', cost = 7,
blueprint = true, eternal = true, perishable = true,
vars = { {xmult = 3}},
custom_vars = function(self, info_queue, card)
return { vars = {card.ability.extra.xmult}}
end,
calculate = function(self, card, context)
if context.joker_main and next(context.poker_hands['Straight']) then
local face_count = 0
for i=1, #context.scoring_hand do
if context.scoring_hand[i]:is_face() then
face_count = face_count+1
end
end
if face_count > 0 then
return {
Xmult_mod = card.ability.extra.xmult,
message = localize { type = 'variable', key = 'a_xmult', vars = { card.ability.extra.xmult } }
}
end
end
end,
})
--The Jolly Joker
create_joker({
name = 'The Jolly Joker', id = 59,
rarity = 'Uncommon', cost = 6,
blueprint = true, eternal = true, perishable = false,
vars = { {mult_rate = 8}, {mult = 0}},
custom_vars = function(self, info_queue, card)
return { vars = {card.ability.extra.mult_rate, card.ability.extra.mult}}
end,
calculate = function(self, card, context)
if context.before and context.scoring_hand and not context.blueprint then
if next(context.poker_hands['Pair']) then
card.ability.extra.mult = card.ability.extra.mult + card.ability.extra.mult_rate
return {
message = 'Upgraded!',
colour = G.C.MULT,
card = card
}
elseif card.ability.extra.mult > 0 then
card.ability.extra.mult = 0
event({ trigger = 'after', delay = 0.2, func = function()
play_sound('tarot1')
return true end })
return {
message = 'Reset',
card = card
}
end
end
if context.joker_main then
if card.ability.extra.mult > 0 then
return {
mult_mod = card.ability.extra.mult,
message = localize { type = 'variable', key = 'a_mult', vars = { card.ability.extra.mult } }
}
end
end
end,
})
--Get Out of Jail Free Card
create_joker({
name = 'Get Out of Jail Free Card', id = 48,
rarity = 'Rare', cost = 10,
blueprint = false, eternal = false, perishable = true,
--vars = { {times = 1}},
custom_vars = function(self, info_queue, card)
local activate_text = 'Inactive'
local activate_color = G.C.RED
if G.STATE == G.STATES.SELECTING_HAND then
activate_text = 'Active'
activate_color = G.C.GREEN
end
return {vars = {activate_text,
colours = {activate_color} }}
end,
--Set sprites and hitbox
set_sprites = function(self, card, front)
local w_scale, h_scale = 64/71, 93/95
card.children.center.scale.y = card.children.center.scale.y * h_scale
card.children.center.scale.x = card.children.center.scale.x * w_scale
end,
set_ability = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 64/71, 93/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
load = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 64/71, 93/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
add_to_deck = function(self, card, from_debuff)
--Set the flag to true immediately once this joker has been picked up, it can't spawn again for the rest of the session
G.GAME.pool_flags.jailfree_get = true
end,
calculate = function(self, card, context)
--Referenced from DebugPlus's "Win Blind" function
if context.selling_self and not context.blueprint then
if G.STATE ~= G.STATES.SELECTING_HAND then
return
end
G.GAME.chips = G.GAME.blind.chips
G.STATE = G.STATES.HAND_PLAYED
G.STATE_COMPLETE = true
end_round()
end
end,
custom_in_pool = function(self, args)
--Spawns if this card has not been picked before
return not G.GAME.pool_flags.jailfree_get
end
})
--Tanzaku
create_joker({
name = 'Tanzaku', id = 70,
rarity = 'Rare', cost = 8,
gameplay_tag = {'j_powerful'},
blueprint = true, eternal = true, perishable = true,
vars = {{ repetitions = 0 }, { repetition_rate = 1 } },
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.repetitions, card.ability.extra.repetition_rate}}
end,
--Set sprites and hitbox
set_sprites = function(self, card, front)
local w_scale, h_scale = 50/71, 80/95
card.children.center.scale.y = card.children.center.scale.y * h_scale
card.children.center.scale.x = card.children.center.scale.x * w_scale
end,
set_ability = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 50/71, 80/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
load = function(self, card, initial, delay_sprites)
local w_scale, h_scale = 50/71, 80/95
card.T.h = card.T.h * h_scale
card.T.w = card.T.w * w_scale
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.repetition and not context.repetition_only then
if context.other_card.seal then
return {
message = 'Again!',
repetitions = card.ability.extra.repetitions,
card = context.blueprint_card or card
}
end
end
--The scaling part is not copyable by Blueprint
if context.discard and not context.blueprint then
local currentCard = context.other_card
--Not debuffed, and has seal
if not currentCard.debuff and currentCard.seal then
card.ability.extra.repetitions = card.ability.extra.repetitions + card.ability.extra.repetition_rate
--forced_message('+'..card.ability.extra.repetition_rate, card, G.C.ORANGE, true)
return {
message = '+'..card.ability.extra.repetition_rate,
card = card
}
end
end
--End of round check, make sure it's checked absolutely once per round
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
if card.ability.extra.repetitions > 0 then
card.ability.extra.repetitions = 0
return {
message = 'Reset!'
}
end
end
end
})
--Glass Cannon
create_joker({
name = 'Glass Cannon', id = 68,
rarity = 'Rare', cost = 8,
gameplay_tag = {'j_powerful'},
blueprint = true, eternal = true, perishable = true,
vars = {{ repetitions = 1 }},
custom_vars = function(self, info_queue, card)
info_queue[#info_queue+1] = G.P_CENTERS.m_glass
return {vars = {card.ability.extra.repetitions}}
end,
calculate = function(self, card, context)
if context.cardarea == G.play and context.repetition and not context.repetition_only then
if context.other_card.config.center == G.P_CENTERS.m_glass then
return {
message = 'Again!',
repetitions = card.ability.extra.repetitions,
card = context.blueprint_card or card
}
end
end
if context.destroying_card and not context.blueprint then
if context.destroying_card.config.center == G.P_CENTERS.m_glass then
return true --Destroy the card
end
end
end
})
--Pity Rate Drop
create_joker({
name = 'Pity Rate Drop', id = 71,
rarity = 'Rare', cost = 8,
blueprint = true, eternal = true, perishable = true,
vars = {{ odds = 8 }, {odds_rate = 1}, {odds_current = 1}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.odds_current * (G.GAME and G.GAME.probabilities.normal or 1), card.ability.extra.odds, card.ability.extra.odds_rate * (G.GAME and G.GAME.probabilities.normal or 1), G.GAME and G.GAME.probabilities.normal or 1}}
end,
calculate = function(self, card, context)
if context.setting_blind and #G.jokers.cards + G.GAME.joker_buffer < G.jokers.config.card_limit then
if pseudorandom('pity_rate_drop'..G.SEED) < G.GAME.probabilities.normal * card.ability.extra.odds_current / card.ability.extra.odds then
if context.blueprint then
card.ability.extra.blueprint_created = true
end
card.ability.extra.odds_current = 1
local jokers_to_create = math.min(1, G.jokers.config.card_limit - (#G.jokers.cards + G.GAME.joker_buffer))
G.GAME.joker_buffer = G.GAME.joker_buffer + jokers_to_create
event({
func = function()
for i = 1, jokers_to_create do
local card = create_card('Joker', G.jokers, nil, 1, nil, nil, nil, 'pity_rate_drop')
card:add_to_deck()
G.jokers:emplace(card)
card:start_materialize()
end
G.GAME.joker_buffer = 0
return true
end})
card_eval_status_text(context.blueprint_card or card, 'extra', nil, nil, nil, {message = 'Create!', colour = G.C.RED})
else
--Odds increments part are not copyable by Blueprint
if not context.blueprint then
--Increase odds, only if a card has not been created by blueprint prior
if not card.ability.extra.blueprint_created then
card.ability.extra.odds_current = card.ability.extra.odds_current + card.ability.extra.odds_rate
else
card.ability.extra.blueprint_created = nil
end
play_nope_sound()
card_eval_status_text(card, 'extra', nil, nil, nil, {message = 'Nope', colour = G.C.RED})
end
end
end
end
})
--Salmon Run
create_joker({
name = 'Salmon Run', id = 10,
rarity = 'Uncommon', cost = 7,
vars = {{odds = 7}},
custom_vars = function(self, info_queue, card)
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
local currentCard = context.other_card
if not currentCard.config.center.no_rank and currentCard.base.value == '7' then
local isActivated = pseudorandom('salmonrun'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds
if isActivated then
event({func = function()
--Copy Card
local _card = copy_card(currentCard, nil, nil, G.playing_card)
_card:start_materialize({G.C.SECONDARY_SET.Enhanced})
G.play:emplace(_card)
table.insert(G.playing_cards, _card)
return true end
})
event({func = function()
G.deck.config.card_limit = G.deck.config.card_limit + 1
draw_card(G.play,G.deck, 90,'up', nil)
return true end
})
playing_card_joker_effects({true})
end
end
end
end
})
--Cool S
create_joker({
name = 'Cool S', id = 66,
rarity = 'Common', cost = 8,
vars = {},
custom_vars = function(self, info_queue, card)
return {vars = {}}
end,
blueprint = false, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.after and not context.blueprint then
for i = 1, #context.scoring_hand do
local currentCard = context.scoring_hand[i]
if currentCard.base.value == '8' then
--Pooling Enhancements
local cen_pool = {}
for k, v in pairs(G.P_CENTER_POOLS["Enhanced"]) do
if not v.replace_base_card and not v.disenhancement then
cen_pool[#cen_pool+1] = v
end
end
--Flipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('card1', 1); currentCard:juice_up(0.3, 0.3); return true end })
--Changing Card Property
event({trigger = 'after', delay = 0.05, func = function()
currentCard:set_ability(pseudorandom_element(cen_pool, pseudoseed('cool_s'..G.SEED)))
return true end })
--Unflipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('tarot2', 1, 0.6); big_juice(card); currentCard:juice_up(0.3, 0.3); return true end })
delay(0.1)
end
end
end
end
})
--Memoriam Photo
create_joker({
name = 'Memoriam Photo', id = 72,
rarity = 'Uncommon', cost = 6,
vars = {{chips = 0}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.chips}}
end,
blueprint = true, eternal = true, perishable = false,
calculate = function(self, card, context)
--Upgrades
if context.remove_playing_cards and not context.blueprint then
local added_total = 0
for i=1, #context.removed do
if not context.removed[i].config.center.no_rank then
added_total = added_total + context.removed[i].base.nominal
end
end
if added_total>0 then
card.ability.extra.chips = card.ability.extra.chips + (2 * added_total)
event({
func = function()
big_juice(card)
forced_message("Upgraded!", card, G.C.CHIPS, true)
--card_eval_status_text(card,'extra',nil, nil, nil,{message = "Upgraded", colour = G.C.MULT, instant = true})
return true end
})
end
end
--Main context
if context.joker_main then
if card.ability.extra.chips > 0 then
return {
chip_mod = card.ability.extra.chips,
message = localize { type = 'variable', key = 'a_chips', vars = { card.ability.extra.chips } }
}
end
end
end,
})
--Schrodinger Cat
create_joker({
name = 'Schrodinger Cat', id = 73,
rarity = 'Uncommon', cost = 8,
vars = {{odds = 3}},
custom_vars = function(self, info_queue, card)
return {vars = {G.GAME and G.GAME.probabilities.normal or 1, card.ability.extra.odds}}
end,
blueprint = true, eternal = true, perishable = true,
calculate = function(self, card, context)
--Upgrades
if context.remove_playing_cards then
for i=1, #context.removed do
local isActivated = pseudorandom('schrodingercat'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds
if isActivated then
event({func = function()
--Copy Card
G.playing_card = (G.playing_card and G.playing_card + 1) or 1
local _card = copy_card(context.removed[i], nil, nil, G.playing_card)
--Special interaction: if it is a vintage card, resets the odds
if _card.config.center.key == 'm_unstb_vintage' then
_card.ability.extra.current_odd = 0
end
_card:start_materialize({G.C.SECONDARY_SET.Enhanced})
_card:add_to_deck()
G.deck:emplace(_card)
table.insert(G.playing_cards, _card)
return true end
})
event({func = function()
G.deck.config.card_limit = G.deck.config.card_limit + 1
return true end
})
playing_card_joker_effects({true})
forced_message(localize('k_copied_ex'), context.blueprint_card or card, G.C.ORANGE, 0.25)
end
end
end
end,
})
--Cashback Card
create_joker({
name = 'Cashback Card', id = 51,
rarity = 'Rare', cost = 9,
vars = {{payout = 0}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.payout}}
end,
blueprint = false, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.using_consumeable and not context.blueprint then
card.ability.extra.payout = card.ability.extra.payout + 1
event({
func = function()
big_juice(card)
forced_message("Upgrade!", card, G.C.GOLD, true)
--card_eval_status_text(card,'extra',nil, nil, nil,{message = "Upgraded", colour = G.C.MULT, instant = true})
return true end})
end
end,
calc_dollar_bonus = function(self, card)
if G.GAME.blind.boss then
local bonus = card.ability.extra.payout
card.ability.extra.payout = 0
if bonus > 0 then return bonus end
end
end
})
--Raffle
create_joker({
name = 'Raffle', id = 50,
rarity = 'Common', cost = 8,
vars = {{odds = 20}, {prize = 20}, {current_odds = 0}},
custom_vars = function(self, info_queue, card)
return {vars = {(G.GAME and G.GAME.probabilities.normal or 1)*card.ability.extra.current_odds, card.ability.extra.odds, card.ability.extra.prize, G.GAME and G.GAME.probabilities.normal or 1, }}
end,
blueprint = false, eternal = true, perishable = true,
calculate = function(self, card, context)
--All upgraded part
if context.buying_card and not context.blueprint then
card.ability.extra.current_odds = card.ability.extra.current_odds + 1
forced_message('Upgrade!', card, G.C.ORANGE, true)
end
if context.reroll_shop and not context.blueprint then
card.ability.extra.current_odds = card.ability.extra.current_odds + 1
forced_message('Upgrade!', card, G.C.ORANGE, true)
end
if context.open_booster and not context.blueprint then
card.ability.extra.current_odds = card.ability.extra.current_odds + 1
forced_message('Upgrade!', card, G.C.ORANGE, true)
end
--Payout
if context.ending_shop and not context.blueprint then
if pseudorandom('raffle'..G.SEED) < G.GAME.probabilities.normal * card.ability.extra.current_odds / card.ability.extra.odds then
event({trigger = 'after', func = function()
ease_dollars(card.ability.extra.prize, true)
big_juice(context.blueprint_card or card)
return true end })
forced_message('$'..card.ability.extra.prize, card, G.C.GOLD, true)
else
play_nope_sound()
forced_message('Nope', card, G.C.RED, true)
end
card.ability.extra.current_odds = 0
end
--Make sure the chance resets again when round ends, cause booster opening from tags currently still trigger it and idk how to prevent that
if context.end_of_round and not context.other_card and not context.repetition and not context.game_over and not context.blueprint then
card.ability.extra.current_odds = 0
end
end
})
--ease_dollars hook, for IC Card
local ref_ease_dollars = ease_dollars
function ease_dollars(mod, instant)
if to_big(mod) < to_big(0) then
local iccard_list = SMODS.find_card('j_unstb_ic_card')
if next(iccard_list) then
for i=1, #iccard_list do
if iccard_list[i].ability.extra.balance > 0 then
--Note: Spend amount is negative
local spendamount = math.max(mod, -iccard_list[i].ability.extra.balance)
mod = mod - spendamount
iccard_list[i].ability.extra.balance = iccard_list[i].ability.extra.balance + spendamount
G.GAME.virtual_dollars = G.GAME.virtual_dollars + spendamount
if not instant then
forced_message("-$"..math.abs(spendamount), iccard_list[i], G.C.red, 0.5)
end
if mod >= 0 then
return
end
end
end
end
end
ref_ease_dollars(mod, instant)
end
--IC Card
create_joker({
name = 'IC Card', id = 49,
rarity = 'Uncommon', cost = 6,
vars = {{money_rate = 3},{balance = 0}, {round_max = 9}},
custom_vars = function(self, info_queue, card)
return {vars = {card.ability.extra.money_rate, card.ability.extra.round_max, card.ability.extra.balance, card.ability.extra.round_left}}
end,
blueprint = false, eternal = false, perishable = false,
set_ability = function(self, card, initial, delay_sprites)
card.ability.extra.round_left = card.ability.extra.round_max
if G.STATE ~= G.STATES.SHOP then --Joker obtained not during the shop
card.ability.extra.start_counting = true
end
end,
add_to_deck = function(self, card, from_debuff)
G.GAME.virtual_dollars = (G.GAME.virtual_dollars or 0) + card.ability.extra.balance
end,
remove_from_deck = function(self, card, from_debuff)
G.GAME.virtual_dollars = (G.GAME.virtual_dollars or 0) - card.ability.extra.balance
end,
calculate = function(self, card, context)
--When entering the round, start counting the round remaining
--(If the Joker is bought in the shop, the first shop leave will not be counted down)
if context.first_hand_drawn then
card.ability.extra.start_counting = true
end
if context.before and context.scoring_hand and not context.blueprint then
card.ability.extra.balance = card.ability.extra.balance + card.ability.extra.money_rate
--Update the game's global virtual money at the same time
G.GAME.virtual_dollars = (G.GAME.virtual_dollars or 0) + card.ability.extra.money_rate
forced_message("$"..card.ability.extra.money_rate, card, G.C.GOLD, true)
end
if context.ending_shop and not context.blueprint then
if card.ability.extra.start_counting then
card.ability.extra.round_left = card.ability.extra.round_left - 1
if card.ability.extra.round_left <= 0 then
event({func = function()
play_sound('tarot1')
card.T.r = -0.2
card:juice_up(0.3, 0.4)
card.states.drag.is = true
card.children.center.pinch.x = true
--Destroy Card
event({trigger = 'after', delay = 0.3, func = function()
G.jokers:remove_card(card)
card:remove()
card = nil
return true end })
return true end })
card_eval_status_text(card,'extra',nil, nil, nil,{message = 'Expired', colour = G.C.ORANGE, instant = true})
else
forced_message(card.ability.extra.round_left..' Round', card, G.C.ORANGE, true)
end
end
card.ability.extra.start_counting = false
end
end
})
--local rank_2048 = { unstb_0 = true, unstb_1 = true, ['2'] = true, ['4'] = true, ['8'] = true}
--2048
create_joker({
name = 'j2048', id = 3,
rarity = 'Uncommon', cost = 7,
gameplay_tag = {'j_alter'},
vars = {{card_to_destroy = {}}},
custom_vars = function(self, info_queue, card)
--[[local key = self.key
if getPoolRankFlagEnable('unstb_0') or getPoolRankFlagEnable('unstb_1') then
key = self.key..'_ex'
end]]
return { vars = {} }
end,
blueprint = false, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.before and context.scoring_hand and not context.blueprint then
local card_list = {}
local card_to_destroy = {}
for i = 1, #context.scoring_hand do
local c = context.scoring_hand[i]
local key = c.base.value
--print(rank_2048[c.base.value])
if card_list[key] then
--print('found the card list: '..c.base.value)
local prev_card = card_list[key]
card_to_destroy[#card_to_destroy+1] = prev_card
--Transfer chips
local bonusChip = prev_card.ability.perma_bonus or 0
local baseChip = SMODS.Ranks[prev_card.base.value].nominal
--Delay the chip adding, so it does not get evaluated
event({delay = 0.1, func = function()
c.ability.perma_bonus = (c.ability.perma_bonus or 0) + baseChip + bonusChip
return true end
})
--Erase the slot, if there's the next one then it counts as a new pair
card_list[key] = nil
else
--print('adding to card list: '..c.base.value)
card_list[key] = c
end
end
--print('done')
--print(inspectDepth(card_to_destroy, nil, 1))
card.ability.extra.card_to_destroy = card_to_destroy
end
if context.destroying_card and not context.blueprint then
--print('check card to destroy main')
for i = 1, #card.ability.extra.card_to_destroy do
--print('check card to destroy')
--print(context.destroying_card == card.ability.extra.card_to_destroy[i])
if context.destroying_card == card.ability.extra.card_to_destroy[i] then
return true
end
end
end
end
})
--Inductor
create_joker({
name = 'Inductor', id = 1,
rarity = 'Rare', cost = 8,
gameplay_tag = {'j_powerful'},
vars = {{odds_en = 2}, {odds_ed = 4}, {odds_s = 8}},
custom_vars = function(self, info_queue, card)
local vars
if G.GAME and G.GAME.probabilities.normal then
vars = {G.GAME.probabilities.normal, card.ability.extra.odds_en, card.ability.extra.odds_ed, card.ability.extra.odds_s}
else
vars = {1, card.ability.extra.odds_en, card.ability.extra.odds_ed, card.ability.extra.odds_s}
end
return {vars = vars}
end,
blueprint = false, eternal = true, perishable = true,
calculate = function(self, card, context)
if context.after and context.scoring_hand and #context.scoring_hand > 1 and not context.blueprint then
local sourceCard = {}
for i = 1, #context.scoring_hand do
if not (context.scoring_hand[i].config.center == G.P_CENTERS.m_stone or context.scoring_hand[i].config.center.replace_base_card) then --Check if it is not a Stone card or have any weird enhancement
if sourceCard[context.scoring_hand[i].base.value..context.scoring_hand[i].base.suit] then --targetCard exists
local currentCard = context.scoring_hand[i]
local targetCard = sourceCard[context.scoring_hand[i].base.value..context.scoring_hand[i].base.suit]
local isCopyEnhancement = pseudorandom('prop_enh'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_en
local isCopyEdition = pseudorandom('prop_ed'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_ed
local isCopySeal = pseudorandom('prop_s'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds_s
--Extra check, if the current card and target card have the same status, don't play animation
if currentCard.config.center == targetCard.config.center then
isCopyEnhancement = false
end
if (currentCard.edition or {}).key == (targetCard.edition or {}).key then
isCopyEdition = false
end
if currentCard.seal == targetCard.seal then
isCopySeal = false
end
local isPlayingAnimation = isCopyEnhancement or isCopyEdition or isCopySeal
if isPlayingAnimation then
--Flipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('card1', 1); currentCard:juice_up(0.3, 0.3); return true end })
--Changing Card Property
event({trigger = 'after', delay = 0.05, func = function()
--Copy enhancement
if isCopyEnhancement then
currentCard:set_ability(targetCard.config.center)
end
--Copy edition
if isCopyEdition then
currentCard:set_edition(targetCard.edition, true, true)
end
--Copy seal
if isCopySeal then
currentCard:set_seal(targetCard.seal, true, true)
end
return true end })
--Unflipping Animation
event({trigger = 'after', delay = 0.1, func = function() currentCard:flip(); play_sound('tarot2', 1, 0.6); big_juice(card); currentCard:juice_up(0.3, 0.3); return true end })
forced_message("Copied!", currentCard, G.C.RED, true)
end
else --set the target card to the following
sourceCard[context.scoring_hand[i].base.value..context.scoring_hand[i].base.suit] = context.scoring_hand[i]
end
end
end
end
end
})
--Decks
if unstb_config.gameplay.c_aux then
SMODS.Back{ -- Utility Deck
key = "utility",
pos = {x = 0, y = 0},
unlocked = true,
config = {vouchers = {'v_unstb_aux1'}, aux_amount = 1, aux_chance = 2},
loc_vars = function(self)
return {vars = {}}
end,
apply = function(self)
G.GAME.auxiliary_rate = (G.GAME.auxiliary_rate or 0) + self.config.aux_chance
event({
func = function()
for i = 1, self.config.aux_amount do
local card = create_card('Auxiliary', G.consumeables, nil, nil, nil, nil, 'c_unstb_aux_random', 'utildeck')
card:add_to_deck()
G.consumeables:emplace(card)
end
return true
end
})
end,
atlas = 'unstb_deck'
}
end
if check_enable_taglist({'rank_binary', 'rank_decimal'}) then
unstb.lowkey_rankList = {'2', '3', '4', '5', 'unstb_0', 'unstb_0.5', 'unstb_1', 'unstb_r2', 'unstb_e', 'unstb_Pi'}
unstb.lowkey_blacklisted = {}
unstb.lowkey_ranklist = {}
--Populate the Lowkey Deck rank blacklist, called at the Splash Screen to support modded ranks as well
function init_lowkey_blacklist()
for k, v in pairs(SMODS.Ranks) do
if not table_has_value(unstb.lowkey_rankList, k) then
unstb.lowkey_blacklisted[k] = true
end
end
end
SMODS.Back{ -- Lowkey Deck
key = "lowkey",
pos = {x = 1, y = 0},
unlocked = true,
config = {},
loc_vars = function(self)
return {vars = {}}
end,
apply = function(self)
--Enable all the pool flags
setPoolRankFlagEnable('unstb_0', true);
setPoolRankFlagEnable('unstb_0.5', true);
setPoolRankFlagEnable('unstb_1', true);
setPoolRankFlagEnable('unstb_r2', true);
setPoolRankFlagEnable('unstb_e', true);
setPoolRankFlagEnable('unstb_Pi', true);
--Notice: used card_key version and not standard key
local added_rank = {'unstb_0', 'unstb_0.5', 'unstb_1', 'unstb_R', 'unstb_E', 'unstb_P'}
local all_suit = {}
for k, v in pairs(SMODS.Suits) do
--If has in_pool, check in_pool
if type(v) == 'table' and type(v.in_pool) == 'function' and v.in_pool then
if v:in_pool({initial_deck = true}) then
all_suit[#all_suit+1] = v.card_key
end
else --Otherwise, added by default
all_suit[#all_suit+1] = v.card_key
end
end
--print(inspect(all_suit))
local extra_cards = {}
for i=1, #all_suit do
for j=1, #added_rank do
extra_cards[#extra_cards+1] = {s = all_suit[i], r = added_rank[j]}
end
end
G.GAME.starting_params.extra_cards = extra_cards
G.GAME.starting_params.blacklisted_ranks = unstb.lowkey_blacklisted
end,
atlas = 'unstb_deck'
}
end
--Compatibility / Tweaks / Rework Stuff
--CardSleeves Support
if CardSleeves then
--Sleeves
SMODS.Atlas {
-- Key for code to find it with
key = "sleeve",
path = "sleeve.png",
px = 73,
py = 95
}
if unstb_config.gameplay.c_aux then
--Utility Sleeve
CardSleeves.Sleeve({
name = "Utility Sleeve",
key="utility",
atlas="sleeve",
pos = { x = 0, y = 0 },
unlocked = true,
loc_vars = function(self)
local key
if self.get_current_deck_key() ~= "b_unstb_utility" then
key = self.key
self.config = { voucher = 'v_unstb_aux1', aux_chance = 1}
else
key = self.key .. "_alt"
self.config = { voucher = 'v_unstb_aux2', aux_chance = 1}
end
return { key = key }
end,
apply = function(self)
--Call main apply function from CardSleeve first to apply vouchers
CardSleeves.Sleeve.apply(self)
G.GAME.auxiliary_rate = (G.GAME.auxiliary_rate or 0) + self.config.aux_chance
event({
func = function()
if self.get_current_deck_key() ~= "b_unstb_utility" then
local card = create_card('Auxiliary', G.consumeables, nil, nil, nil, nil, 'c_unstb_aux_random', 'utildeck')
card:add_to_deck()
G.consumeables:emplace(card)
else
local card = create_card('Joker', G.jokers, nil, nil, nil, nil, 'j_unstb_free_trial', nil)
card:set_edition({negative = true}, true)
card:add_to_deck()
G.jokers:emplace(card)
end
return true
end
})
end
})
end
if check_enable_taglist({'rank_binary', 'rank_decimal'}) then
--Lowkey Sleeve
CardSleeves.Sleeve({
name = "Lowkey Sleeve",
key="lowkey",
atlas="sleeve",
pos = { x = 1, y = 0 },
unlocked = true,
loc_vars = function(self)
local key
if self.get_current_deck_key() ~= "b_unstb_lowkey" then
key = self.key
self.config = { remove_ranks = true }
else
key = self.key .. "_alt"
self.config = { prevent_ranks = true }
end
return { key = key }
end,
apply = function(self)
--Call main apply function from CardSleeve first to apply vouchers
CardSleeves.Sleeve.apply(self)
--Regular sleeve effects
if self.config.remove_ranks then
--Enable all the pool flags
setPoolRankFlagEnable('unstb_0', true);
setPoolRankFlagEnable('unstb_0.5', true);
setPoolRankFlagEnable('unstb_1', true);
setPoolRankFlagEnable('unstb_r2', true);
setPoolRankFlagEnable('unstb_e', true);
setPoolRankFlagEnable('unstb_Pi', true);
--Notice: used card_key version and not standard key
local added_rank = {'unstb_0', 'unstb_0.5', 'unstb_1', 'unstb_R', 'unstb_E', 'unstb_P'}
local all_suit = {}
for k, v in pairs(SMODS.Suits) do
--If has in_pool, check in_pool
if type(v) == 'table' and type(v.in_pool) == 'function' and v.in_pool then
if v:in_pool({initial_deck = true}) then
all_suit[#all_suit+1] = v.card_key
end
else --Otherwise, added by default
all_suit[#all_suit+1] = v.card_key
end
end
--print(inspect(all_suit))
local extra_cards = {}
for i=1, #all_suit do
for j=1, #added_rank do
extra_cards[#extra_cards+1] = {s = all_suit[i], r = added_rank[j]}
end
end
G.GAME.starting_params.extra_cards = extra_cards
G.GAME.starting_params.blacklisted_ranks = unstb.lowkey_blacklisted
end
--Set the game flags to prevent high rank, this affact premium booster
if self.config.prevent_ranks then
G.GAME.prevent_high_rank = true
end
--Code based on Abandoned Sleeve from CardSleeve itself, for special effect
if self.config.prevent_ranks and self.allowed_card_centers == nil then
self.allowed_card_centers = {}
self.skip_trigger_effect = true
for _, card_center in pairs(G.P_CARDS) do
local card_instance = Card(0, 0, 0, 0, card_center, G.P_CENTERS.c_base)
if not unstb.lowkey_blacklisted[card_instance.base.value] then
self.allowed_card_centers[#self.allowed_card_centers+1] = card_center
end
card_instance:remove()
end
-- Make it loops around to 0 immediately after using strength on 5
self.get_rank_after_5 = function() return "unstb_0" end
self.skip_trigger_effect = false
end
end,
trigger_effect = function(self, args)
--Code based on Abandoned Sleeve from CardSleeve itself
if not self.config.prevent_ranks then
return
end
if self.skip_trigger_effect then
return
end
if self.allowed_card_centers == nil then
self:apply()
end
-- handle Strength, Ouija, Grim, Familiar
local card = args.context and args.context.card
local is_playing_card = card and (card.ability.set == "Default" or card.ability.set == "Enhanced") and card.config.card_key
if args.context and args.context.before_use_consumable and card then
if card.ability.name == 'Strength' then
self.in_strength = true
elseif card.ability.name == "Ouija" then
self.in_ouija = true
elseif card.ability.name == "Grim" then
self.in_grim = true
elseif card.ability.name == "Familiar" then
self.in_familiar = true
end
elseif args.context and args.context.after_use_consumable then
self.in_strength = nil
self.in_ouija = nil
self.ouija_rank = nil
self.in_grim = nil
self.in_familiar = nil
elseif G.GAME and G.GAME.blind and args.context and (args.context.create_card or args.context.modify_playing_card) and card and is_playing_card then
if unstb.lowkey_blacklisted[card.base.value] then
local initial = G.GAME.blind == nil or args.context.create_card
if self.in_strength then
local base_key = SMODS.Suits[card.base.suit].card_key .. "_" .. self.get_rank_after_5()
card:set_base(G.P_CARDS[base_key], initial)
elseif self.in_grim then --Grim will always create 1 (because Ace)
local base_key = SMODS.Suits[card.base.suit].card_key .. "_unstb_1"
card:set_base(G.P_CARDS[base_key], initial)
elseif self.in_ouija then
if self.ouija_rank == nil then
local random_base = pseudorandom_element(self.allowed_card_centers, pseudoseed("slv"))
local card_instance = Card(0, 0, 0, 0, random_base, G.P_CENTERS.c_base)
self.ouija_rank = SMODS.Ranks[card_instance.base.value]
card_instance:remove()
end
local base_key = SMODS.Suits[card.base.suit].card_key .. "_" .. self.ouija_rank.card_key
card:set_base(G.P_CARDS[base_key], initial)
else
local new_rank = pseudorandom_element(unstb.lowkey_rankList, pseudoseed("slv"))
--Use smods change_base so we can still keep the suit
SMODS.change_base(card, nil, new_rank)
if self.in_familiar and unstb_config.gameplay.seal_suit then --For Familiar, adds face seal to the created card (if the mechanic is enabled)
card:set_seal('unstb_face', true, true)
end
end
end
end
end,
})
end
end
--Reworked Vanilla Joker to support new features
if unstb_config.joker.vanilla then
filesystem.load(unstb.path..'/override/vanilla_joker.lua')()
end
--Suits, supports for Suit Seals, a lot of suit-based Joker, and modded suits support for Smeared
filesystem.load(unstb.path..'/override/suits.lua')()
--JokerDisplay (Partial) Support
if JokerDisplay then
SMODS.load_file("/override/jokerdisplay.lua")()
end
--Manually load localizations and populate necessary info needed for the mod
--Global table entry to handle trigger for name-related stuff
unstb_global.name_joker = {}
unstb_global.name_card = {}
unstb_global.name_suit = {}
function unstb_process_english_loc()
local function populateList(temp_loc)
if not temp_loc or not temp_loc.descriptions then return end
local jokers = temp_loc.descriptions.Joker or {}
for k,v in pairs(jokers) do
if not unstb_global.name_joker[k] and not unstb_global.name_card[k] then
if v.name and string.match(string.lower(v.name), "joker") then
unstb_global.name_joker[k] = v.name
end
if v.name and string.match(string.lower(v.name), "card") then
unstb_global.name_card[k] = v.name
end
end
end
--Process suit loc if exists
local suit = (temp_loc.misc and temp_loc.misc.suits_singular) or {}
for k,v in pairs(suit) do
if not unstb_global.name_suit[k] and v then
local suit_name = string.lower(v)
local vowel = suit_name:gsub("[^aeiou]","")
local consonant = suit_name:gsub("[^bcdfghjklmnpqrstvwxyz]","") --To be safe, list alphabet only, no symbol, space, or numbers
unstb_global.name_suit[k] = {
name = v,
count_vowel = string.len(vowel),
count_consonant = string.len(consonant)
}
end
end
end
local function loc_fallback()
--print("Called loc fallback")
return {}
end
print("Start initializing localization-independent info")
--Basegame Jokers
local temp_loc = assert(loadstring(love.filesystem.read('localization/en-us.lua')))()
populateList(temp_loc)
--Modded Jokers
for k, _ in pairs(SMODS.Mods) do
if SMODS.Mods[k].can_load and SMODS.Mods[k].path then
temp_loc = (SMODS.load_file('/localization/en-us.lua', k) or loc_fallback)()
if temp_loc then
populateList(temp_loc)
end
end
end
--Fallback, handle the rest of the mod without proper localization (eg. uses loc_txt)
--these mods tend to not have proper localization so it should be fine to look at the game's raw table
populateList(G.localization)
print("Finished initializing localization-independent info")
end
--Hook for the game's splash screen, to initialize any data that is sensitive to the mod's order (mainly rank stuff)
local ref_gamesplashscreen = Game.splash_screen
function Game:splash_screen()
ref_gamesplashscreen(self)
--set_consumeable_usage hook to keep track of UnStable's own consumable count
--Certain mod like Aurinko made this hook doesn't work fsr
local set_consumeable_usage_ref = set_consumeable_usage
function set_consumeable_usage(card)
if card.config.center_key and card.ability.consumeable then
if card.config.center.set == 'Auxiliary' then
--Initialize it if not, basically what basegame does but have to put here bc it runs before basegame's
G.GAME.consumeable_usage_total = G.GAME.consumeable_usage_total or {tarot = 0, planet = 0, spectral = 0, tarot_planet = 0, all = 0}
G.GAME.consumeable_usage_total.auxiliary = (G.GAME.consumeable_usage_total.auxiliary or 0) + 1
end
end
return set_consumeable_usage_ref(card)
end
init_prev_rank_data()
if init_lowkey_blacklist then
init_lowkey_blacklist()
end
unstb_process_english_loc()
end