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

1961 lines
58 KiB
Lua

local unstbex = SMODS.current_mod
local filesystem = NFS or love.filesystem
local path = unstbex.path
local unstbex_config = unstbex.config
--Global Table
unstbex_global = {}
unstbex_global.config = unstbex.config
unstbex_lib = {}
--Library
SMODS.load_file("/lib/suit_compat.lua")()
--Localization Messages
--local loc = filesystem.load(path..'localization.lua')()
-- Debug message
local function print(message)
sendDebugMessage('[UnstableEX] - '..(tostring(message) or '???'))
end
print("Starting UnstableEX")
--Compat List
unstbex_global.compat = {
Bunco = (SMODS.Mods["Bunco"] or {}).can_load,
Familiar = (SMODS.Mods["familiar"] or {}).can_load,
Ortalab = (SMODS.Mods["ortalab"] or {}).can_load,
Six_Suit = (SMODS.Mods["SixSuits"] or {}).can_load,
Inks_Color = (SMODS.Mods["InkAndColor"] or {}).can_load,
Cryptid = (SMODS.Mods["Cryptid"] or {}).can_load,
CustomCards = (SMODS.Mods["CustomCards"] or {}).can_load,
Pokermon = (SMODS.Mods["Pokermon"] or {}).can_load,
KCVanilla = (SMODS.Mods["kcvanilla"] or {}).can_load,
DnDJ = (SMODS.Mods["dndj"] or {}).can_load,
MtJ = (SMODS.Mods["magic_the_jokering"] or {}).can_load,
Minty = (SMODS.Mods["MintysSillyMod"] or {}).can_load,
Cardsauce = (SMODS.Mods["Cardsauce"] or {}).can_load,
Showdown = (SMODS.Mods["showdown"] or {}).can_load,
}
local function check_mod_active(mod_id)
return unstbex_global.compat[mod_id]
end
--Config Stuff
function unstbex.save_config(self)
SMODS.save_mod_config(self)
end
local unstbex_config_tab = function()
--Dynamically Building Config Based on loaded mods
--I'll figure out how to make paginations later
local mod_config_ui_tables = {}
local function create_mod_config(mod_name, conf_list)
if not check_mod_active(mod_name.key) then return end
local ui_nodes = {}
ui_nodes[1] = {n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = mod_name.name or mod_name.key, colour = G.C.ORANGE, scale = 0.5}}}}
for k,v in pairs(conf_list) do
ui_nodes[#ui_nodes+1] = create_toggle({label = v.label, ref_table = unstbex.config[v.ref_table], ref_value = k, callback = function() unstbex:save_config() end})
end
mod_config_ui_tables[#mod_config_ui_tables+1] = {n=G.UIT.R, config={align = "cl"}, nodes = ui_nodes }
end
local function mod_config_ui_init()
--List of ALL possible mod config
create_mod_config({key= "DnDJ", name = "DnDJ"}, {
keep_sprite = {label = "Keep Rank Sprites", ref_table = "dndj"},
})
create_mod_config({key= "Showdown", name = "Showdown"}, {
use_decimal = {label = "Use Decimal Mechanics", ref_table = "showdown"},
replace_zero = {label = "Replace Rank 0", ref_table = "showdown"},
})
end
mod_config_ui_init();
--Rendering
local render_table = {}
local restart_header = nil
if #mod_config_ui_tables > 0 then
for i=1, #mod_config_ui_tables do
render_table[#render_table+1] = mod_config_ui_tables[i]
end
restart_header = {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}}}}
else
render_table = {{n=G.UIT.R, config={align = "cl"}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={{n = G.UIT.T, config = {text = "No configurable options found for the current modlist", scale = 0.5}}}},
}}}
end
return{
{
label = "Config",
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= {restart_header} },
{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 = render_table },
-- Right Side Column
--{n=G.UIT.C, config={align = "cl"}, nodes = render_table},
}}
},
}
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 = {
},
}
end
}]]
}
end
unstbex.extra_tabs = unstbex_config_tab
--Just so the gear icon shows up
unstbex.config_tab = true
--Map config value with a single string keyword
local config_value = {
--["rank_21"] = unstb_config.rank.rank_21,
}
--Utility
--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
-- 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 cards to use
--[[SMODS.Atlas {
-- Key for code to find it with
key = "unstbEX_jokers",
-- The name of the file, for the code to pull the atlas from
path = "unstbEX_jokers.png",
-- Width of each sprite in 1x size
px = 71,
-- Height of each sprite in 1x size
py = 95
}]]
--Updated Enhancement atli to include modded suits
SMODS.Atlas {
key = "enh_slop",
path = "enh_slop.png",
px = 71,
py = 95
}
SMODS.Atlas {
key = "enh_slop_hc",
path = "enh_slop_hc.png",
px = 71,
py = 95
}
SMODS.Atlas {
key = "enh_res",
path = "enh_res.png",
px = 71,
py = 95
}
--Fallback atlas for extra ranks
SMODS.Atlas {
key = "rank_ex_default",
path = "rank_ex/default/rank_ex.png",
px = 71,
py = 95
}
SMODS.Atlas {
key = "rank_ex2_default",
path = "rank_ex/default/rank_ex2.png",
px = 71,
py = 95
}
--Bunco's resprites suit colours
SMODS.Atlas {
key = "rank_ex_hc_b",
path = "rank_ex/bunco/rank_ex_hc_b.png",
px = 71,
py = 95
}
SMODS.Atlas {
key = "rank_ex2_hc_b",
path = "rank_ex/bunco/rank_ex2_hc_b.png",
px = 71,
py = 95
}
SMODS.Atlas {
key = "enh_slop_hc_b",
path = "enh_slop_hc_b.png",
px = 71,
py = 95
}
--Cardsauce Skin
local use_cardsauce_skin = false
if check_mod_active("Cardsauce") then
use_cardsauce_skin = csau_enabled['enableSkins']
SMODS.Atlas {
key = "rank_ex_cs",
path = "rank_ex/cardsauce/rank_ex_cs.png",
px = 71,
py = 95
}
SMODS.Atlas {
key = "rank_ex2_cs",
path = "rank_ex/cardsauce/rank_ex2_cs.png",
px = 71,
py = 95
}
end
--Familiar's Multi-Suit Cards Fallback
--(I don't think it is possible to make all combinations by myself, especially to account for modded suits)
SMODS.Atlas {
key = "rank_ex_multi",
path = "rank_ex_multi.png",
px = 71,
py = 95
}
SMODS.Atlas {
key = "rank_ex2_multi",
path = "rank_ex2_multi.png",
px = 71,
py = 95
}
--Map new atlas to the rank - new ranks now used more than 1 atlas (split off for maintenance reason)
local rank_atlas_map = {['unstb_0'] = 1,
['unstb_0.5'] = 1,
['unstb_1'] = 1,
['unstb_r2'] = 1,
['unstb_e'] = 1,
['unstb_Pi'] = 1,
['unstb_21'] = 1,
['unstb_???'] = 1,
['unstb_11'] = 2,
['unstb_12'] = 2,
['unstb_13'] = 2,
['unstb_25'] = 2,
['unstb_161'] = 2,}
local rank_atlas_name = {'unstbex_rank_ex', 'unstbex_rank_ex2'}
--Swap the hc atli with bunco resprites version to match suit colours
local using_bunco_resprites = false
if check_mod_active("Bunco") then
if BUNCOMOD and BUNCOMOD.content and BUNCOMOD.content.config then
using_bunco_resprites = BUNCOMOD.content.config.fixed_sprites
end
end
local vanilla_suits = {Hearts = true,
Clubs = true,
Diamonds = true,
Spades = true,
}
unstbex_lib.extra_suits = {}
--Init extra suits information based on loaded mod
if check_mod_active("Bunco") then
unstbex_lib.init_suit_compat('bunc_Fleurons', 'bunco')
unstbex_lib.init_suit_compat('bunc_Halberds', 'bunco')
end
if check_mod_active("Six_Suit") then
unstbex_lib.init_suit_compat('six_Stars', 'sixsuits', true)
unstbex_lib.init_suit_compat('six_Moons', 'sixsuits', true)
end
if check_mod_active("Inks_Color") then
unstbex_lib.init_suit_compat('ink_Inks', 'inkscolor', true)
unstbex_lib.init_suit_compat('ink_Colors', 'inkscolor', true)
end
if check_mod_active("MtJ") then
unstbex_lib.init_suit_compat('mtg_Clovers', 'mtj')
end
if check_mod_active("Minty") then
unstbex_lib.init_suit_compat('minty_3s', 'minty', true)
end
--Suit injection code based on Showdown by Mistyk__
local function inject_p_card_suit_compat(suit, rank)
local card = {
name = rank.key .. ' of ' .. suit.key,
value = rank.key,
suit = suit.key,
pos = { x = rank.pos.x, y = rank.suit_map[suit.key] or suit.pos.y },
lc_atlas = rank.suit_map[suit.key] and rank.lc_atlas or suit.lc_atlas,
hc_atlas = rank.suit_map[suit.key] and rank.hc_atlas or suit.hc_atlas,
}
if not vanilla_suits[card.suit] then
if not unstbex_lib.extra_suits[card.suit] then
print("Warning: Unknown suit for "..card.name)
card.lc_atlas = rank_atlas_name[rank_atlas_map[rank.key]]..'_default'
card.hc_atlas = rank_atlas_name[rank_atlas_map[rank.key]]..'_default'
card.pos.y = 0
else
card.lc_atlas = unstbex_lib.extra_suits[card.suit].lc_atlas[rank_atlas_map[rank.key]]
card.hc_atlas = unstbex_lib.extra_suits[card.suit].hc_atlas[rank_atlas_map[rank.key]]
end
end
G.P_CARDS[suit.card_key .. '_' .. rank.card_key] = card
end
local function rank_injection(self)
print("Performing extra rank injection")
for _, suit in pairs(SMODS.Suits) do
inject_p_card_suit_compat(suit, self)
end
end
local function inject_rank_atlas(prefix)
for k,v in pairs(SMODS.Ranks) do
if k:find(prefix) then
local rank = SMODS.Ranks[k]
rank.inject = rank_injection
if use_cardsauce_skin then
rank.lc_atlas = rank_atlas_name[rank_atlas_map[k]]..'_cs'
rank.hc_atlas = rank_atlas_name[rank_atlas_map[k]]..'_cs'
end
if using_bunco_resprites then
rank.hc_atlas = rank_atlas_name[rank_atlas_map[k]]..'_hc_b'
end
--[[rank.lc_atlas = rank_atlas_map[k]
rank.hc_atlas = using_bunco_resprites and rank_atlas_map[k]..'_hc_b' or rank_atlas_map[k]..'_hc'
rank.suit_map = unstbex_global.rank_suit_map]]
print("Injecting the graphic for rank "..rank.key)
end
end
end
inject_rank_atlas('unstb_')
--Register Suits for UnStable suit system
--Modded Suits Code in UnStableEX
--Bunco
--[[register_suit_group("suit_black", "bunc_Halberds")
register_suit_group("suit_red", "bunc_Fleurons")
register_suit_group("suit_black", "six_Moons")
register_suit_group("suit_red", "six_Stars")
register_suit_group("no_smear", "Inks_Inks")
register_suit_group("no_smear", "Inks_Color")]]
--Update extended atlas for Slop and Resource Cards
--Separated from rank suit map now
local enhancement_suit_map = {
Hearts = 0,
Clubs = 1,
Diamonds = 2,
Spades = 3,
bunc_Fleurons = 4,
bunc_Halberds = 5,
six_Stars = 6,
six_Moons = 7,
ink_Inks = 8,
ink_Colors = 9,
}
local center_unstb_slop = SMODS.Centers['m_unstb_slop'] or {}
center_unstb_slop.suit_map = enhancement_suit_map
center_unstb_slop.atlas = 'unstbex_enh_slop'
center_unstb_slop.lc_atlas = 'unstbex_enh_slop'
center_unstb_slop.hc_atlas = using_bunco_resprites and 'unstbex_enh_slop_hc_b' or 'unstbex_enh_slop_hc'
local center_unstb_resource = SMODS.Centers['m_unstb_resource'] or {}
center_unstb_resource.suit_map = enhancement_suit_map
center_unstb_resource.atlas = 'unstbex_enh_res'
--Generic Rank Replacement Utility
--A map of orignal mod's rank, to new rank, and the mod's tag to check if the config is enabled or not
local replace_rank_map = {}
function register_rank_replacement(originalrank, newrank, keepsprite)
replace_rank_map[originalrank] = {new_rank = newrank, keep_sprite = keepsprite}
end
local ref_card_set_base = Card.set_base
function Card:set_base(card, initial)
card = card or {}
ref_card_set_base(self, card, initial)
--Only run this piece of codes when inside the run (so, menu animations aren't interrupted)
if G.GAME and G.GAME.blind then
if self.base and self.base.value and replace_rank_map[self.base.value] then
local replace_rank_data = replace_rank_map[self.base.value]
if replace_rank_data.keep_sprite and vanilla_suits[self.base.suit] then
--Re-assign the rank to UnStable's equivalent
--Doing it this way should keep the sprites unchanged
--Automatically replaced it completely if the suit isn't vanilla for stable reasons
self.base.value = replace_rank_data.new_rank
local rank = SMODS.Ranks[self.base.value] or {}
self.base.nominal = rank.nominal or 0
self.base.face_nominal = rank.face_nominal or 0
self.base.id = rank.id
else
SMODS.change_base(self, nil, replace_rank_map[self.base.value].new_rank)
end
end
end
end
--Blacklist "top" ranks for many jokers
local top_rank_blacklist = {
['Ace'] = true,
['unstb_21'] = true,
['unstb_25'] = true,
['unstb_161'] = true,
['unstb_???'] = true,
}
if check_mod_active("Bunco") then
print("Inject Bunco Jokers")
local bunc_pawn = SMODS.Centers['j_bunc_pawn'] or {}
--Blacklist ranks for Pawn
bunc_pawn.loc_vars = function(self, info_queue, card)
if G.playing_cards and #G.playing_cards > 0 then
local rank = math.huge
local target_rank = 'unstb_???';
for _, deck_card in ipairs(G.playing_cards) do
local newrank = deck_card.base.nominal + (deck_card.base.face_nominal or 0)
if newrank < rank and (not deck_card.config.center.no_rank or deck_card.config.center ~= G.P_CENTERS.m_stone) then
rank = newrank
target_rank = deck_card.base.value
end
end
return {vars = {localize(target_rank, 'ranks')}}
end
return {vars = {localize('2', 'ranks')}}
end
bunc_pawn.calculate = function(self, card, context)
if context.after and context.scoring_hand and not context.blueprint then
for i = 1, #context.scoring_hand do
local condition = false
local other_card = context.scoring_hand[i]
local rank = math.huge
local target_rank = 'unstb_???';
for _, deck_card in ipairs(G.playing_cards) do
local newrank = deck_card.base.nominal + (deck_card.base.face_nominal or 0)
if newrank < rank and (not deck_card.config.center.no_rank or deck_card.config.center ~= G.P_CENTERS.m_stone) then
rank = newrank
target_rank = deck_card.base.value
end
end
if other_card.base.value == target_rank and not top_rank_blacklist[other_card.base.value] then
condition = true
event({trigger = 'after', delay = 0.15, func = function() other_card:flip(); play_sound('card1', 1); other_card:juice_up(0.3, 0.3); return true end })
event({
trigger = 'after',
delay = 0.1,
func = function()
local new_rank = get_next_x_rank(other_card.base.value, 1)
assert(SMODS.change_base(other_card, nil, new_rank))
return true
end
})
event({trigger = 'after', delay = 0.15, func = function() other_card:flip(); play_sound('tarot2', 1, 0.6); big_juice(card); other_card:juice_up(0.3, 0.3); return true end })
end
if condition then delay(0.7 * 1.25) end
end
end
end
local bunc_zero_shapiro = SMODS.Centers['j_bunc_zero_shapiro'] or {}
local zeroshapiro_zerorank = {
['unstb_0'] = true,
['unstb_???'] = true,
['Jack'] = true,
['Queen'] = true,
['King'] = true,
['showdown_Butler'] = true,
['showdown_Princess'] = true,
['showdown_Lord'] = true,
['showdown_Zero'] = true,
}
bunc_zero_shapiro.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
if context.other_card.config.center.no_rank or zeroshapiro_zerorank[context.other_card.base.value] then
if pseudorandom('zero_shapiro'..G.SEED) < G.GAME.probabilities.normal / card.ability.extra.odds then
return {
extra = {message = '+'..localize{type = 'name_text', key = 'tag_d_six', set = 'Tag'}, colour = G.C.GREEN},
card = card,
func = function()
event({func = function()
add_tag(Tag('tag_d_six'))
return true
end})
end
}
end
end
end
end
local bunc_crop_circles = SMODS.Centers['j_bunc_crop_circles'] or {}
local crop_circles_rank_mult = {
['unstb_0'] = 1,
['unstb_0.5'] = 1,
['6'] = 1,
['8'] = 2,
['9'] = 1,
['10'] = 1,
['Queen'] = 1,
['showdown_8.5'] = 2,
['showdown_Butler'] = 2,
['showdown_Princess'] = 1,
['showdown_Zero'] = 1,
}
--Implemented differently than in Bunco, but should yield the same result
bunc_crop_circles.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play then
local other_card = context.other_card
local total_mult = 0
--Check suit
if not other_card.config.center.no_suit then
if other_card.base.suit == 'bunc_Fleurons' then
total_mult = total_mult + 4
elseif other_card.base.suit == 'Clubs' then
total_mult = total_mult + 3
elseif other_card.base.suit == 'mtg_Clovers' then
total_mult = total_mult + 4
end
end
--Check rank
if not other_card.config.center.no_rank then
if crop_circles_rank_mult[other_card.base.value] then
total_mult = total_mult + crop_circles_rank_mult[other_card.base.value]
end
end
--If the amount is greater than 0, grant the bonus w/ animation
if total_mult > 0 then
return {
mult = total_mult,
card = card
}
end
end
end
end
--Hook to Familiar's set_sprite_suits to account for new ranks
local unstb_ranks_pos = {['unstb_0'] = 6,
['unstb_0.5'] = 2,
['unstb_1'] = 5,
['unstb_r2'] = 7,
['unstb_e'] = 3,
['unstb_Pi'] = 4,
['unstb_21'] = 0,
['unstb_???'] = 1,
['unstb_11'] = 0,
['unstb_12'] = 1,
['unstb_13'] = 2,
['unstb_25'] = 3,
['unstb_161'] = 4,}
if check_mod_active("Familiar") then
print('Inject Familiar set_sprite_suits')
local ref_set_sprite_suits = set_sprite_suits
function set_sprite_suits(card, juice)
ref_set_sprite_suits(card, juice)
--If the rank is one of the UnStable Rank, and has one of the ability
if unstb_ranks_pos[card.base.value] and (card.ability.is_spade or card.ability.is_heart or card.ability.is_club or card.ability.is_diamond or card.ability.suitless) then
--print('UnstbEX Set Sprite Suit Hook Active')
local suit_check = {card.base.suit == 'Spades' or card.ability.is_spade or false,
card.base.suit == 'Hearts' or card.ability.is_heart or false,
card.base.suit == 'Clubs' or card.ability.is_club or false,
card.base.suit == 'Diamonds' or card.ability.is_diamond or false}
local suit_count = 0
for i=1, #suit_check do
if suit_check[i] then
suit_count = suit_count+1
end
end
--Suitless, or has more than 1 suits
if card.ability.suitless or suit_count>1 then
--Technically, if anyone wants to make it works properly, this would be where to check
--Unfortunately, I don't think I can write them all because there's a lot of combination + lots of graphic to make
--Hopefully there is a more elegant solution found in the future.
card.children.front.atlas = G.ASSET_ATLAS[rank_atlas_map[card.base.value]..'_multi']
card.children.front:set_sprite_pos({x = unstb_ranks_pos[card.base.value], y = 0})
end
end
end
print("Inject Familiar Vigor Fortune Card")
local familiar_vigor = SMODS.Centers['c_fam_vigor'] or {}
--Reimplemented Familiar Vigor Fortune Card to use get_next_x_rank instead
familiar_vigor.use = function(self, card)
for i = 1, #G.hand.highlighted do
for j = 1, 3 do
G.E_MANAGER:add_event(Event({trigger = 'after',delay = 0.1,func = function()
local card = G.hand.highlighted[i]
local new_rank = get_next_x_rank(card.base.value, 1)
assert(SMODS.change_base(card, nil, new_rank))
card:juice_up(0.3, 0.5)
return true end }))
end
end
end
end
--Re-implementation of Ortalab's Index Card functions to support UNSTB Ranks
--Notice: this rank changes the behavior from vanilla slightly, where rank 0 and 1 is immediately available
local main_rankList = {'unstb_0', 'unstb_1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace'}
--Special UNSTB Rank is pre-defined
local rankMap = { ['unstb_0.5'] = {UP = 'unstb_1', MID = 'unstb_0.5' , DOWN = 'unstb_0'},
['unstb_r2'] = {UP = '2', MID = 'unstb_r2' , DOWN = 'unstb_1'},
unstb_e = {UP = '3', MID = 'unstb_e' , DOWN = '2'},
unstb_Pi = {UP = '4', MID = 'unstb_Pi' , DOWN = '3'},
unstb_21 = {UP = 'unstb_21', MID = 'unstb_21' , DOWN = 'unstb_21'},
unstb_11 = {UP = 'unstb_12', MID = 'unstb_11' , DOWN = '10'},
unstb_12 = {UP = 'unstb_13', MID = 'unstb_12' , DOWN = 'unstb_11'},
unstb_13 = {UP = 'Ace', MID = 'unstb_13' , DOWN = 'unstb_12'},
unstb_25 = {UP = 'unstb_25', MID = 'unstb_25' , DOWN = 'unstb_25'},
unstb_161 = {UP = 'unstb_161', MID = 'unstb_161' , DOWN = 'unstb_161'},
['unstb_???'] = {UP = 'unstb_???', MID = 'unstb_???' , DOWN = 'unstb_???'},
['showdown_2.5'] = {UP = '3', MID = 'showdown_2.5' , DOWN = '2'},
['showdown_5.5'] = {UP = '6', MID = 'showdown_5.5' , DOWN = '5'},
['showdown_8.5'] = {UP = '9', MID = 'showdown_8.5' , DOWN = '8'},
['showdown_Butler'] = {UP = 'Queen', MID = 'showdown_Butler' , DOWN = 'Jack'},
['showdown_Princess'] = {UP = 'King', MID = 'showdown_Princess' , DOWN = 'Queen'},
['showdown_Lord'] = {UP = 'Ace', MID = 'showdown_Lord' , DOWN = 'King'},
['showdown_Zero'] = {UP = 'unstb_1', MID = 'showdown_Zero' , DOWN = 'Ace'},
}
for i=1, #main_rankList do
rankMap[main_rankList[i]] = {UP = main_rankList[i+1] or main_rankList[1], MID = main_rankList[i], DOWN = main_rankList[i-1] or main_rankList[#main_rankList]}
end
--print(inspectDepth(rankMap))
if check_mod_active("Ortalab") then
print('Inject Ortalab Index Card')
--Inject new property into Ortalab index card
local ortalab_index = SMODS.Centers['m_ortalab_index'] or {}
ortalab_index.set_ability = function(self, card, initial, delay_sprites)
print('call set ability')
if card.base and card.ability and card.ability.extra and type(card.ability.extra) == 'table' then
if card.ability.extra.index_state == 'MID' then
card.ability.extra.mainrank = card.base.value
elseif card.ability.extra.index_state == 'UP' then
card.ability.extra.mainrank = rankMap[card.base.value]['DOWN']
elseif card.ability.extra.index_state == 'DOWN' then
card.ability.extra.mainrank = rankMap[card.base.value]['UP']
end
end
end
ortalab_index.set_sprites = function(self, card, front)
if card.ability and card.ability.extra and type(card.ability.extra) == 'table' then
if card.ability.extra.index_state == 'MID' then
card.children.center:set_sprite_pos({x = 2, y = 0})
elseif card.ability.extra.index_state == 'UP' then
--card.ability.extra.mainrank = rankMap[card.base.value]['DOWN']
card.children.center:set_sprite_pos({x = 1, y = 2})
elseif card.ability.extra.index_state == 'DOWN' then
card.children.center:set_sprite_pos({x = 0, y = 2})
end
--print('main card value is '.. card.ability.extra.mainrank)
end
end
ortalab_index.update = function(self, card)
--Jank, handles special case where Tarot like Strength was used
if (card.VT.w <= 0) then
local isCollection = (card.area and card.area.config.collection) or false
if not isCollection then
if card.ability.extra.index_state == 'MID' then
card.ability.extra.mainrank = card.base.value
elseif card.ability.extra.index_state == 'UP' then
card.ability.extra.mainrank = rankMap[card.base.value]['DOWN']
elseif card.ability.extra.index_state == 'DOWN' then
card.ability.extra.mainrank = rankMap[card.base.value]['UP']
end
end
--print('main card value changed to '.. card.ability.extra.mainrank)
end
end
G.FUNCS.increase_index = function(e, mute, nosave)
--print('using unstbex implementation of func')
e.config.button = nil
local card = e.config.ref_table
local area = card.area
local change = 1
if card.ability.extra.index_state == 'DOWN' then change = 2 end
card.ability.extra.index_state = 'UP'
card.children.center:set_sprite_pos({x = 1, y = 2})
SMODS.change_base(card, nil, rankMap[card.ability.extra.mainrank] and rankMap[card.ability.extra.mainrank]['UP'] or 'unstb_???')
end
G.FUNCS.mid_index = function(e, mute, nosave)
--print('using unstbex implementation of func')
e.config.button = nil
local card = e.config.ref_table
local area = card.area
local change = 1
if card.ability.extra.index_state == 'UP' then change = -1 end
card.ability.extra.index_state = 'MID'
card.children.center:set_sprite_pos({x = 2, y = 0})
--card.base.id = card.base.id + change
SMODS.change_base(card, nil, rankMap[card.ability.extra.mainrank] and rankMap[card.ability.extra.mainrank]['MID'] or 'unstb_???')
end
G.FUNCS.decrease_index = function(e, mute, nosave)
--print('using unstbex implementation of func')
e.config.button = nil
local card = e.config.ref_table
local area = card.area
local change = 1
if card.ability.extra.index_state == 'UP' then change = 2 end
card.ability.extra.index_state = 'DOWN'
card.children.center:set_sprite_pos({x = 0, y = 2})
--card.base.id = card.base.id - change
SMODS.change_base(card, nil, rankMap[card.ability.extra.mainrank] and rankMap[card.ability.extra.mainrank]['DOWN'] or 'unstb_???')
end
--More safety check
--[[
G.FUNCS.index_card_increase = function(e)
if not e.config.ref_table.ability.extra or type(e.config.ref_table.ability.extra) ~= 'table' then
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
return
end
if e.config.ref_table.ability.extra.index_state ~= 'UP' then
e.config.colour = G.C.RED
e.config.button = 'increase_index'
else
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
end
end
G.FUNCS.index_card_mid = function(e)
if not e.config.ref_table.ability.extra or type(e.config.ref_table.ability.extra) ~= 'table' then
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
return
end
if e.config.ref_table.ability.extra.index_state ~= 'MID' then
e.config.colour = G.C.RED
e.config.button = 'mid_index'
else
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
end
end
G.FUNCS.index_card_decrease = function(e)
if not e.config.ref_table.ability.extra or type(e.config.ref_table.ability.extra) ~= 'table' then
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
return
end
if e.config.ref_table.ability.extra.index_state ~= 'DOWN' then
e.config.colour = G.C.RED
e.config.button = 'decrease_index'
else
e.config.colour = G.C.UI.BACKGROUND_INACTIVE
e.config.button = nil
end
end]]
print("Inject Ortalab Flag Loteria")
local ortalab_lot_flag = SMODS.Centers['c_ortalab_lot_flag'] or {}
--Reimplementation to use UnStable version of get_next_x_rank
ortalab_lot_flag.use = function(self, card, area, copier)
--print("UnStbEX version")
track_usage(card.config.center.set, card.config.center_key)
local options = {}
for i=1, card.ability.extra.rank_change do
table.insert(options, i)
end
for i=1, #G.hand.highlighted do
local percent = 1.15 - (i-0.999)/(#G.hand.highlighted-0.998)*0.3
G.E_MANAGER:add_event(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
for _, card in pairs(G.hand.highlighted) do
local sign = pseudorandom(pseudoseed('flag_sign')) > 0.5 and 1 or -1
local change = pseudorandom_element(options, pseudoseed('flag_change'))
for i=1, change do
G.E_MANAGER:add_event(Event({trigger = 'after',delay = 0.4,func = function()
local new_rank = get_next_x_rank(card.base.value, sign)
assert(SMODS.change_base(card, nil, new_rank))
return true end }))
end
-- card_eval_status_text(card, 'extra', nil, nil, nil, {message = tostring(sign*change), colour = G.ARGS.LOC_COLOURS.loteria, delay = 0.4})
end
for i=1, #G.hand.highlighted do
local percent = 0.85 + (i-0.999)/(#G.hand.highlighted-0.998)*0.3
G.E_MANAGER:add_event(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
G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.2,func = function() G.hand:unhighlight_all(); return true end }))
delay(0.5)
end
--Inject Ortalab Joker code
--Mathmagician
--Now used the same check from UnStable like Odd Todd and Even Steven
local ortalab_mathmagician = SMODS.Centers['j_ortalab_mathmagician'] or {}
ortalab_mathmagician.calculate = function(self, card, context) --Mathmagician logic
if context.discard and context.other_card == context.full_hand[#context.full_hand] then
local numbered_even = 0
local numbered_odd = 0
for _, v in ipairs(context.full_hand) do
--Hardcoded check for ??? rank
--For that, it counts as both. So it increments whatever needed left
if v.base.value == 'unstb_???' then
if numbered_odd < 2 then
numbered_odd = numbered_odd + 1
else
numbered_even = numbered_even + 1
end
else
--General case, use modulo check from UnStable
if unstb_global.modulo_check(v, 2, 1) then
numbered_odd = numbered_odd + 1
elseif unstb_global.modulo_check(v, 2, 0) then
numbered_even = numbered_even + 1
end
end
end
if numbered_even >= 2 and numbered_odd >= 2 and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then
local choice = pseudorandom('mathmagician') > 0.5 and 'Loteria' or 'Zodiac'
G.E_MANAGER:add_event(Event({
func = (function()
G.E_MANAGER:add_event(Event({
func = function()
local card = create_card(choice, G.consumeables)
card:add_to_deck()
G.consumeables:emplace(card)
G.GAME.consumeable_buffer = G.GAME.consumeable_buffer - 1
return true
end}))
card_eval_status_text(context.blueprint_card or card, 'extra', nil, nil, nil, {message = localize('ortalab_'..string.lower(choice)..'_add'), colour = G.C.SET.Loteria})
return true
end)}))
end
end
end
end
--Cryptid Compat
if check_mod_active("Cryptid") then
--Special interaction w/ Plagiarism and rigged
--[[
local j_plagiarism = SMODS.Centers['j_unstb_plagiarism']
if j_plagiarism then
local ref_j_plagiarism_calculate = j_plagiarism.calculate
j_plagiarism.calculate = function(self, card, context)
--Alternate function entirely if it's rigged
if card.ability.cry_rigged then
--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
forced_message('Both', card, G.C.ORANGE, true)
end
local other_joker = nil
for i = 1, #G.jokers.cards do
if G.jokers.cards[i] == card then
other_joker = {G.jokers.cards[i - 1], G.jokers.cards[i + 1]}
end
end
if other_joker then
for i = 1, #other_joker do
if other_joker[i] and other_joker[i] ~= 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[i]: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
if other_joker_ret then
--Jank, might result in message appear at wrong place idk but at least it should be executed properly
SMODS.calculate_effect(other_joker_ret, context.individual and context.other_card or eff_card)
--return other_joker_ret
end
end
end
end
end
else
return ref_j_plagiarism_calculate(self, card, context)
end
end
end]]
--Add appropiate Jokers to the pool
--Placeholder, there's no food jokers yet in UNSTB and/or EX
--[[
if Cryptid.food then
local food_jokers = {
}
for i = 1, #meme_jokers do
Cryptid.food[#Cryptid.food+1] = food_jokers[i]
end
end]]
if Cryptid.memepack then
--Adds pretty much most shitpost-centric Joker onto it
local meme_jokers = {
"j_unstb_joker2", --Joker 2
"j_unstb_joker_stairs", --Joker Stairs
"j_unstb_plagiarism", --Plagiarism
"j_unstb_prssj", --prssj
"j_unstb_the_jolly_joker", --The Jolly too just because
"j_unstb_what", --69, 420. Unsure if this would break the in_pool tho
}
for i = 1, #meme_jokers do
Cryptid.memepack[#Cryptid.memepack+1] = meme_jokers[i]
end
end
end
-- Hook for is_suit, in case other mods injected into it and it got caught early by the UnStable's hook
-- Currently only used by CustomCards
if check_mod_active("CustomCards") then
local ref_card_is_suit = Card.is_suit
function Card:is_suit(suit, bypass_debuff, flush_calc, bypass_seal)
local result = ref_card_is_suit(self, suit, bypass_debuff, flush_calc, bypass_seal)
--Return early if true (suit seal case)
if result then
return result
end
--If it is a trading card, check its own value
if self.ability and self.ability.trading then
local eval = self:calculate_exotic({bypass_debuff = bypass_debuff, flush_calc = flush_calc, is_suit = suit})
if eval then
return eval
end
end
--Should only return false here at this point?
return result
end
end
--Pokermon Compat
if check_mod_active("Pokermon") then
--Use get_next_x_rank instead
local ref_poke_vary_rank = poke_vary_rank
poke_vary_rank = function(card, decrease, seed)
local new_rank
if decrease then
new_rank = get_next_x_rank(card.base.value, -1)
elseif seed then
new_rank = pseudorandom_element(SMODS.Ranks, pseudoseed(seed)).key
else
new_rank = get_next_x_rank(card.base.value, 1)
end
G.E_MANAGER:add_event(Event({
func = function()
SMODS.change_base(card, nil, new_rank)
return true
end
}))
end
--Inject Jokers Code
--Oddish and Bellsprout Lines now used UnStable method of checking odd and even numbers (same as Odd Todd and Even Steven)
--Oddish Line
local poke_oddish = SMODS.Centers['j_poke_oddish'] or {}
poke_oddish.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 1) then
local value
if pseudorandom('oddish') < .50 then
value = card.ability.extra.mult
else
value = card.ability.extra.mult2
end
return {
message = localize{type = 'variable', key = 'a_mult', vars = {value}},
colour = G.C.MULT,
mult = value,
card = card
}
end
end
return level_evo(self, card, context, "j_poke_gloom")
end
local poke_gloom = SMODS.Centers['j_poke_gloom'] or {}
poke_gloom.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 1) then
local value
if pseudorandom('gloom') < .50 then
value = card.ability.extra.mult
else
value = card.ability.extra.mult2
end
return {
message = localize{type = 'variable', key = 'a_mult', vars = {value}},
colour = G.C.MULT,
mult = value,
card = card
}
end
end
return item_evo(self, card, context)
end
local poke_vileplume = SMODS.Centers['j_poke_vileplume'] or {}
poke_vileplume.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 1) then
if pseudorandom('vileplume') < .50 then
return {
x_mult = card.ability.extra.Xmult_multi,
card = card
}
else
return {
mult = card.ability.extra.mult,
card = card
}
end
end
end
end
local poke_bellossom = SMODS.Centers['j_poke_bellossom'] or {}
poke_bellossom.calculate = function(self, card, context)
if context.before and context.cardarea == G.jokers and not context.blueprint then
local odds = {}
for k, v in ipairs(context.scoring_hand) do
local upgrade = pseudorandom(pseudoseed('bellossom'))
if (unstb_global.modulo_check(v, 2, 1)) and upgrade > .50 then
odds[#odds+1] = v
if v.ability.name == 'Wild Card' and not v.edition then
local edition = {polychrome = true}
v:set_edition(edition, true, true)
end
v:set_ability(G.P_CENTERS.m_wild, nil, true)
G.E_MANAGER:add_event(Event({
func = function()
v:juice_up()
return true
end
}))
else
v.bellossom_score = true
end
end
if #odds > 0 then
return {
message = localize("poke_petal_dance_ex"),
colour = G.C.MULT,
card = card
}
end
end
if context.individual and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 1) then
if context.other_card.bellossom_score then
context.other_card.bellossom_score = nil
return {
message = localize{type = 'variable', key = 'a_mult', vars = {card.ability.extra.mult}},
colour = G.C.MULT,
mult = card.ability.extra.mult,
card = card
}
end
end
end
end
local poke_bellsprout = SMODS.Centers['j_poke_bellsprout'] or {}
poke_bellsprout.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 0) then
return {
message = localize{type = 'variable', key = 'a_chips', vars = {card.ability.extra.chips}},
colour = G.C.CHIPS,
chips = card.ability.extra.chips,
card = card
}
end
end
return level_evo(self, card, context, "j_poke_weepinbell")
end
local poke_weepinbell = SMODS.Centers['j_poke_weepinbell'] or {}
poke_weepinbell.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 0) then
return {
message = localize{type = 'variable', key = 'a_chips', vars = {card.ability.extra.chips}},
colour = G.C.CHIPS,
chips = card.ability.extra.chips,
card = card
}
end
end
return item_evo(self, card, context, "j_poke_victreebel")
end
local poke_victreebel = SMODS.Centers['j_poke_victreebel'] or {}
poke_victreebel.calculate = function(self, card, context)
if context.individual and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 0) then
return {
message = localize{type = 'variable', key = 'a_chips', vars = {card.ability.extra.chips}},
colour = G.C.CHIPS,
chips = card.ability.extra.chips,
card = card
}
end
end
if context.repetition and context.cardarea == G.play and not context.other_card.debuff then
if unstb_global.modulo_check(context.other_card, 2, 0) then
return {
message = localize('k_again_ex'),
repetitions = card.ability.extra.retriggers,
card = card
}
end
end
end
end
--KCVanilla Compat
if check_mod_active("KCVanilla") then
print("Inject KCVanilla Joker")
--Five Days Forecast, now used get_next_x_rank properly
local function unstb_kcv_rank_up_discreetly(card)
-- local newcard = kcv_get_rank_up_pcard(card)
card.kcv_ignore_debuff_check = true
card.kcv_ranked_up_discreetly = true
-- card:set_base(newcard)
local old_rank = SMODS.Ranks[card.base.value]
local new_rank = get_next_x_rank(card.base.value, 1)
card.kcv_display_rank = card.kcv_display_rank and card.kcv_display_rank or old_rank
SMODS.change_base(card, card.base.suit, new_rank) -- Should respect "kcv_ranked_up_discreetly" as it uses card:set_base
end
local kc_5day = SMODS.Centers['j_kcvanilla_5day'] or {}
kc_5day.calculate = function(self, card, context)
if context.kcv_forecast_event and context.scoring_hand then
if next(context.poker_hands["Straight"]) then
for i, other_c in ipairs(context.scoring_hand) do
if not top_rank_blacklist[other_c.base.value] then
unstb_kcv_rank_up_discreetly(other_c)
end
end
end
end
if context.before and context.scoring_hand then
if next(context.poker_hands["Straight"]) then
local targets = {}
for i, other_c in ipairs(context.scoring_hand) do
if other_c.kcv_ranked_up_discreetly then
table.insert(targets, other_c)
end
end
card_eval_status_text(context.blueprint_card or card, 'extra', nil, nil, nil, {
message = localize('k_active_ex'),
colour = G.C.FILTER,
card = context.blueprint_card or card
});
for i_2, other_c_2 in ipairs(targets) do
local percent = 1.15 - (i_2 - 0.999) / (#context.scoring_hand - 0.998) * 0.3
G.E_MANAGER:add_event(Event({
func = function()
if not other_c_2.kcv_ranked_up_discreetly then
-- was complete, but another 5-day joker is targeting this card
return true
end
play_sound('card1', percent)
other_c_2:flip()
return true
end
}))
delay(0.15)
end
delay(0.3)
for i_3, other_c_3 in ipairs(targets) do
local percent = 0.85 + (i_3 - 0.999) / (#context.scoring_hand - 0.998) * 0.3
G.E_MANAGER:add_event(Event({
func = function()
if not other_c_3.kcv_ranked_up_discreetly then
-- was complete, but another 5-day joker is targeting this card
return true
end
-- kcv_log(other_c_3.base.id .. ' - ' .. other_c_3.kcv_display_rank)
other_c_3.kcv_display_rank = SMODS.Ranks[get_next_x_rank(other_c_3.kcv_display_rank.key, 1)]
-- Copying method SMODs uses
local card_suit = SMODS.Suits[other_c_3.base.suit].card_key
local card_rank = other_c_3.kcv_display_rank.card_key
local newcard = G.P_CARDS[('%s_%s'):format(card_suit, card_rank)]
-- set_base again to update sprites that were postponed by kcv_ranked_up_discreetly
other_c_3:set_sprites(nil, newcard)
play_sound('tarot2', percent, 0.6)
other_c_3:flip()
if other_c_3.kcv_display_rank.card_key == SMODS.Ranks[other_c_3.base.value].card_key then
-- cleanup
other_c_3.kcv_ranked_up_discreetly = nil
other_c_3.kcv_ignore_debuff_check = nil
other_c_3.kcv_display_rank = nil
end
return true
end
}))
delay(0.15)
end
delay(0.5)
end
end
end
end
--DnDJ Compat
if check_mod_active("DnDJ") then
--TO DO: Make it a toggle setting if the card from DnDJ contraband pack would keep its rank graphic or not
local dndj_keep_sprite = unstbex.config.dndj.keep_sprite
local dndj_rank_map = {['dndj_0'] = 'unstb_0',
['dndj_0.5'] = 'unstb_0.5',
['dndj_1'] = 'unstb_1',
['dndj_Pi'] = 'unstb_Pi',
['dndj_11'] = 'unstb_11',
['dndj_12'] = 'unstb_12',
['dndj_13'] = 'unstb_13',
['dndj_21'] = 'unstb_21'}
for k, v in pairs(dndj_rank_map) do
register_rank_replacement(k, v, dndj_keep_sprite)
end
end
--Showdown compat
if check_mod_active("Showdown") then
local enable_unstable_decimal = unstbex.config.showdown.use_decimal
local replace_zero = unstbex.config.showdown.replace_zero
if replace_zero then
register_rank_replacement('showdown_Zero', 'unstb_0')
end
--Adds "decimal" compat to all counterpart ranks
local rank_sh_two_half = SMODS.Ranks['showdown_2.5']
rank_sh_two_half.decimal_compat = true
local rank_sh_five_half = SMODS.Ranks['showdown_5.5']
rank_sh_five_half.decimal_compat = true
local rank_sh_eight_half = SMODS.Ranks['showdown_8.5']
rank_sh_eight_half.decimal_compat = true
local rank_sh_butler = SMODS.Ranks['showdown_Butler']
rank_sh_butler.decimal_compat = true
local rank_sh_princess = SMODS.Ranks['showdown_Princess']
rank_sh_princess.decimal_compat = true
local rank_sh_lord = SMODS.Ranks['showdown_Lord']
rank_sh_lord.decimal_compat = true
--Additional information so UnStable's Engineer can work with them
unstb_global.register_decimal_rank_map('showdown_2.5', '3')
unstb_global.register_decimal_rank_map('showdown_5.5', '6')
unstb_global.register_decimal_rank_map('showdown_8.5', '9')
unstb_global.register_decimal_rank_map('showdown_Butler', 'Queen')
unstb_global.register_decimal_rank_map('showdown_Princess', 'King')
unstb_global.register_decimal_rank_map('showdown_Lord', 'Ace')
--If the setting is enabled, add proper UnStable decimal rank mechanics onto the ranks
if enable_unstable_decimal then
--Hook into get_counterpart to erase the UI display for them
local ref_getCounterPart = get_counterpart
function get_counterpart(rank, onlyCounterpart)
--onlyCounterpart is used for UI
if onlyCounterpart then
return nil
end
return ref_getCounterPart(rank, onlyCounterpart)
end
local max_rank_id_number = -1
for _, v in pairs(SMODS.Ranks) do
if v.id > 0 and v.id > max_rank_id_number then
max_rank_id_number = v.id
end
end
rank_sh_two_half.is_decimal = true
rank_sh_two_half.rank_act = {'2', '2.5', '3'}
rank_sh_two_half.next = { 'unstb_e', '3', 'unstb_Pi', '4' }
rank_sh_two_half.prev = { '2' }
rank_sh_two_half.strength_effect = {
fixed = 2,
random = false,
ignore = false
}
rank_sh_two_half.id = max_rank_id_number + 1
rank_sh_five_half.is_decimal = true
rank_sh_five_half.rank_act = {'5', '5.5', '6'}
rank_sh_five_half.next = { '6', '7'}
rank_sh_five_half.prev = { '5' }
rank_sh_five_half.id = max_rank_id_number + 2
rank_sh_eight_half.is_decimal = true
rank_sh_eight_half.rank_act = {'8', '8.5', '9'}
rank_sh_eight_half.next = { '9', '10'}
rank_sh_eight_half.prev = { '8' }
rank_sh_eight_half.id = max_rank_id_number + 3
rank_sh_butler.is_decimal = true
rank_sh_butler.rank_act = {'Jack', 'Butler', 'Queen'}
rank_sh_butler.next = {'Queen', 'showdown_Princess', 'King'}
rank_sh_butler.prev = { 'Jack' }
rank_sh_butler.id = max_rank_id_number + 4
rank_sh_princess.is_decimal = true
rank_sh_princess.rank_act = {'Queen', 'Princess', 'King'}
rank_sh_princess.next = {'King', 'showdown_Lord', 'Ace'}
rank_sh_princess.prev = { 'Queen' }
rank_sh_princess.id = max_rank_id_number + 5
rank_sh_lord.is_decimal = true
rank_sh_lord.rank_act = {'King', 'Lord'}
rank_sh_lord.next = {'Ace'}
rank_sh_lord.prev = { 'King' }
rank_sh_lord.id = max_rank_id_number + 6
--Jank, mostly bc Showdown's rank id is forced to be negative for the counterparts, thus ended up mess with the total rank ID orders
SMODS.Rank.max_id.value = rank_sh_lord.id
--Changes to existing ranks to allow Straight in numerical order
SMODS.Ranks['2'].strength_effect = {
fixed = 3,
random = false,
ignore = false
}
SMODS.Ranks['2'].next = {'showdown_2.5', 'unstb_e', '3', 'unstb_Pi'}
SMODS.Ranks['5'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['5'].next = {'showdown_5.5', '6'}
SMODS.Ranks['8'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['8'].next = {'showdown_8.5', '9'}
SMODS.Ranks['10'].next = {'Jack', 'showdown_Butler', 'unstb_11'}
SMODS.Ranks['Jack'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['Jack'].next = {'showdown_Butler', 'Queen', 'showdown_Princess'}
SMODS.Ranks['Queen'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['Queen'].next = {'showdown_Princess', 'King', 'showdown_Lord'}
SMODS.Ranks['King'].strength_effect = {
fixed = 2,
random = false,
ignore = false
}
SMODS.Ranks['King'].next = {'showdown_Lord', 'Ace'}
end
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)
--Cryptid stuff has to be done on Splash Screen because of its high priority
if check_mod_active("Cryptid") then
print("Inject new nominal code override for Cryptid")
--Make a dedicated table of rank id and the nominal order
--This is because Cryptid randomize nominal chips in Misprint Deck and Glitched Edition
local rank_nominal_order = {}
for key, rank in pairs(SMODS.Ranks) do
rank_nominal_order[key] = rank.nominal
end
--Basically the same code from the basegame, but swap nominal out with the new rank_nominal_order property
function Card:get_nominal(mod)
local mult = 1
local rank_mult = 1
if mod == 'suit' then mult = 30000 end
if self.ability.effect == 'Stone Card' or (self.config.center.no_suit and self.config.center.no_rank) then
mult = -10000
elseif self.config.center.no_suit then
mult = 0
elseif self.config.center.no_rank then
rank_mult = 0
end
--Temporary fix so the card with the lowest nominal can still be sorted properly
local nominal = rank_nominal_order[self.base.value] or 0
if self.base.value == 'unstb_???' then
nominal = 0.3
elseif nominal < 0.4 then
nominal = 0.31 + nominal*0.1
end
--Hardcode this so it's sorted properly
if self.base.value == 'unstb_161' then
nominal = 30
end
return 10*(nominal)*rank_mult + self.base.suit_nominal*mult + (self.base.suit_nominal_original or 0)*0.0001*mult + 10*self.base.face_nominal*rank_mult + 0.000001*self.unique_val
end
--Secret interaction: The "Jolly Joker" (UnStable Joker) counts as Jolly as well
local ref_card_is_jolly = Card.is_jolly
function Card:is_jolly()
if self.config.center.key == 'j_unstb_the_jolly_joker' then
return true
end
return ref_card_is_jolly(self)
end
--Inject Blinds effect
print("Inject Blind effects for Cryptid")
local blind_hammer = SMODS.Blinds['bl_cry_hammer'] or {}
blind_hammer.recalc_debuff = function(self, card, from_blind)
if card.area ~= G.jokers and not G.GAME.blind.disabled then
if
card.ability.effect ~= "Stone Card"
and (
card.base.value == "3"
or card.base.value == "5"
or card.base.value == "7"
or card.base.value == "9"
or card.base.value == "Ace"
or card.base.value == "unstb_1"
or card.base.value == "unstb_21"
or card.base.value == "unstb_11"
or card.base.value == "unstb_13"
or card.base.value == "unstb_25"
or card.base.value == "unstb_161"
or card.base.value == "unstb_???"
)
then
return true
end
return false
end
end
local blind_magic = SMODS.Blinds['bl_cry_magic'] or {}
blind_magic.recalc_debuff = function(self, card, from_blind)
if card.area ~= G.jokers and not G.GAME.blind.disabled then
if
card.ability.effect ~= "Stone Card"
and (
card.base.value == "2"
or card.base.value == "4"
or card.base.value == "6"
or card.base.value == "8"
or card.base.value == "10"
or card.base.value == "unstb_0"
or card.base.value == "unstb_12"
or card.base.value == "unstb_???"
or card.base.value == "showdown_Zero"
)
then
return true
end
return false
end
end
--Override ://VARIABLE Code card's code
--Because the original code has problem with the card with modded rank
--Also, switch over to SMODS.change_base instead of manually building card key,
--which was the cause of the problem
unstbex_global.cryptid_variable_rank = {'', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace', '', '', '', 'unstb_0', 'unstb_21', 'unstb_0.5', 'unstb_r2', 'unstb_e', 'unstb_Pi', 'unstb_1', 'unstb_11', 'unstb_12', 'unstb_13', 'unstb_25', 'unstb_161', 'unstb_???'}
G.FUNCS.variable_apply = function()
local rank_table = {
{},
{ "2", "Two", "II" },
{ "3", "Three", "III" },
{ "4", "Four", "IV" },
{ "5", "Five", "V" },
{ "6", "Six", "VI" },
{ "7", "Seven", "VII" },
{ "8", "Eight", "VIII" },
{ "9", "Nine", "IX" },
{ "10", "1O", "Ten", "X", "T" },
{ "J", "Jack" },
{ "Q", "Queen" },
{ "K", "King" },
{ "A", "Ace"}, --Notably, 1 is now 1 and not Ace :P
{ "M" },
{ "nil" },
{}, --Not sure if I should left it blank but its used for a cheat check below??
--UNSTB Rank
{"0", "O", "Zero"},
{"21", "Twenty-One", "TwentyOne", "XXI", "BJ"},
{"0.5", "O.5", "Half"},
{"1.4", "1.41", "Root2", "Sqrt2", "Root", "Sqrt", "r", "sq"},
{"2.7", "2.71","e", "Euler"},
{"3.1", "3.14", "22/7", "Pi", "P"},
{"1", "One", "I"},
{"11", "Eleven", "XI"},
{"12", "Twelve", "XII"},
{"13", "Thirteen", "XIII"},
{"25", "Twenty-Five", "TwentyFive", "XXV", "quad"},
{"161", "OneHundredSixtyOne", "OneSixOne", "CLXI", "abomination"},
{"?", "???", "Question", "idk"},
}
local rank_suffix = nil
for i, v in pairs(rank_table) do
for j, k in pairs(v) do
if string.lower(G.ENTERED_RANK) == string.lower(k) then
rank_suffix = i
end
end
end
if rank_suffix then
G.PREVIOUS_ENTERED_RANK = G.ENTERED_RANK
G.GAME.USING_CODE = false
if rank_suffix == 15 then
check_for_unlock({ type = "cheat_used" })
local card = create_card("Joker", G.jokers, nil, nil, nil, nil, "j_jolly")
card:add_to_deck()
G.jokers:emplace(card)
elseif rank_suffix == 16 then
check_for_unlock({ type = "cheat_used" })
local card = create_card("Code", G.consumeables, nil, nil, nil, nil, "c_cry_crash")
card:add_to_deck()
G.consumeables:emplace(card)
elseif rank_suffix == 17 then
check_for_unlock({ type = "cheat_used" })
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.4,
func = function()
play_sound("tarot1")
return true
end,
}))
for i = 1, #G.hand.highlighted do
local percent = 1.15 - (i - 0.999) / (#G.hand.highlighted - 0.998) * 0.3
G.E_MANAGER:add_event(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)
for i = 1, #G.hand.highlighted do
local CARD = G.hand.highlighted[i]
local percent = 0.85 + (i - 0.999) / (#G.hand.highlighted - 0.998) * 0.3
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.15,
func = function()
CARD:flip()
CARD:set_ability(
G.P_CENTERS[pseudorandom_element(G.P_CENTER_POOLS.Consumeables, pseudoseed("cry_variable")).key],
true,
nil
)
play_sound("tarot2", percent)
CARD:juice_up(0.3, 0.3)
return true
end,
}))
end
else
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.4,
func = function()
play_sound("tarot1")
return true
end,
}))
for i = 1, #G.hand.highlighted do
local percent = 1.15 - (i - 0.999) / (#G.hand.highlighted - 0.998) * 0.3
G.E_MANAGER:add_event(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)
for i = 1, #G.hand.highlighted do
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.1,
func = function()
local card = G.hand.highlighted[i]
local new_rank = unstbex_global.cryptid_variable_rank[rank_suffix]
--Fallback
if not new_rank or new_rank == '' then
new_rank = 'unstb_???'
end
SMODS.change_base(card, nil, new_rank)
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
G.E_MANAGER:add_event(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
G.E_MANAGER:add_event(Event({
trigger = "after",
delay = 0.2,
func = function()
G.hand:unhighlight_all()
return true
end,
}))
delay(0.5)
end
G.CHOOSE_RANK:remove()
end
end
end
end