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