Roland/src/joker.lua
2026-02-10 23:42:57 +01:00

549 lines
16 KiB
Lua

local f, q, u = unpack(... or require "src.functional")
local joker = (function()
local z = 0
---@return {x: number, y: number}
local function inc()
local ret = {x = z % 6, y = math.floor(z / 6)}
z = z + 1
return ret
end
---@param tbl SMODS.Joker|{sinis?: boolean|{x: number, y: number}, soul_pos?: boolean|{x: number, y: number}}
return function(tbl)
tbl.pos = inc()
tbl.atlas = "joker"
tbl.soul_pos = tbl.soul_pos and inc() or nil
tbl.sinis = tbl.sinis and inc() or nil
SMODS.Joker(tbl)
end
end)()
local function destructible(card)
return not card.highlighted and not (card.ability or {}).eternal
end
local function is_carbon(card)
return card.edition and card.edition.key == "e_Bakery_Carbon"
end
local function is_mergeable_with(x)
return function(y)
return x.rank ~= y.rank and y.label == "j_Roland_escapey" and not (y.ability or {}).eternal
end
end
local function level_up(hand, by, card)
local function update(handname, chips, mult, level, pitch)
update_hand_text(
{sound = "button", volume = 0.7, pitch = pitch or 0.8, delay = 0.3},
{handname = handname, chips = chips, level = level, mult = mult}
)
end
if by <= 0 then
return
end
hand = hand or "NO_HAND_SPECIFIED"
local hand_obj = G.GAME.hands[hand]
if hand == "all" or hand == "allhands" or hand == "all_hands" then
update(localize "k_all_hands", "...", "...", "")
f(G.GAME.hands):keys():foreach(function(k)
level_up_hand(card, k, nil, by)
end)
elseif hand_obj then
update(localize(hand, "poker_hands"), hand_obj.chips, hand_obj.mult, hand_obj.level)
level_up_hand(card, hand, nil, by)
end
update("", 0, 0, "", 1.1)
end
SMODS.Atlas {
key = "joker",
path = "joker.png",
px = 71,
py = 95,
}
joker {
key = "escapey",
pronouns = "they_them",
config = {extra = {hands = 2}},
cost = 4,
rarity = 2,
sinis = true,
soul_pos = true,
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, _, card)
local loc_self = G.localization.descriptions.Joker.j_Roland_escapey
---@diagnostic disable-next-line: undefined-global
local sinister = (Jen or {}).sinister or G.escapey_sinister
local quotes = loc_self.quotes
local merge = G.jokers
and f(G.jokers.cards):any(is_mergeable_with(card))
and loc_self.merge or {}
local normal = (merge[1] or sinister) and "" or
pseudorandom_element(quotes.normal, pseudoseed "EscapeyQuotes") or ""
local scared = (merge[1] or not sinister) and "" or
pseudorandom_element(quotes.scared, pseudoseed "EscapeyQuotes") or ""
return {
vars = {
card.ability.extra.hands,
normal,
scared,
merge[1] or "",
merge[1] and loc_self.name or "",
merge[2] or "",
},
}
end,
calculate = function(_, _, context)
if type(G.escapey_debugger) == "function" then
G.escapey_debugger(f(context):reduce("", function(acc, _, next)
return acc .. ", " .. next
end):sub(2))
end
end,
Bakery_can_use = function(_, card)
return not card.debuff and u() and (
#G.GAME.tags ~= 0 or
f(G.consumeables.cards):any(destructible) or
f(G.jokers.cards):any(is_mergeable_with(card))
)
end,
Bakery_use_button_text = function(_, card)
if card.debuff then
return "DEBUFFED"
end
return #G.GAME.tags == 0 and not f(G.consumeables.cards):filter(destructible):any() and
f(G.jokers.cards):filter(is_mergeable_with(card)):any() and "FUSE" or "ESCAPE"
end,
Bakery_use_joker = function(_, card)
if card.debuff then
return
end
local consumables = f(G.consumeables.cards):filter(destructible):into()
local consumable_count = #consumables
local tag_count = #G.GAME.tags
if consumable_count == 0 and tag_count == 0 then
local level_sum, sell_sum = 0, 0
local any_carbon = is_carbon(card)
f(G.jokers.cards):filter(is_mergeable_with(card)):foreach(function(v)
any_carbon = any_carbon or is_carbon(v)
level_sum = level_sum + v.ability.extra.hands * (v.getEvalQty and v:getEvalQty() or 1)
sell_sum = sell_sum + v.sell_cost * (v.getEvalQty and v:getEvalQty() or 1)
v:start_dissolve({HEX "57ecab"}, nil, 1.6)
end)
if not any_carbon then
card.ability.extra.hands = card.ability.extra.hands + level_sum
end
card.sell_cost = card.sell_cost + sell_sum
return
end
local function fast_delete(v)
return function()
attention_text {
scale = 0.7,
align = "cm",
text = "ESC",
cover = v.HUD_tag,
colour = G.C.WHITE,
cover_colour = G.C.BLACK,
hold = 0.3 / G.SETTINGS.GAMESPEED,
}
play_sound("cancel", 1.66, 0.5)
v.HUD_tag.states.visible = false
v:remove()
end
end
local destroyed = 0
if consumable_count == 0 then
local trigger = #G.GAME.tags >= 30 and "immediate" or "before"
local delay = #G.GAME.tags >= 30 and 0 or 1 / #G.GAME.tags
f(G.GAME.tags):foreach(function(v)
q {
trigger = trigger,
blocking = #G.GAME.tags < 30,
delay = delay,
func = fast_delete(v),
}
destroyed = destroyed + 1
end)
else
f(consumables):foreach(function(v)
v:start_dissolve({HEX "57ecab"}, nil, 1.6)
destroyed = destroyed + 1
end)
end
local hands = f(G.GAME.hands):filter(function(v, _)
return v.visible
end):keys():into()
pseudoshuffle(hands, pseudoseed "RolandEscapey")
local levels = destroyed * card.ability.extra.hands
level_up("all", math.floor(levels / #hands), card)
f(hands):take(levels % #hands):foreach(function(v)
level_up(v, 1, card)
end)
end,
debug_export = function(self, obj)
local to = obj or self
to.f, to.q, to.u = f, q, u
end,
}
joker {
key = "msjoker", -- Blue bow
pronouns = "she_her",
cost = 1,
rarity = 1,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
config = {extra = {chips = 30}},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.chips}}
end,
calculate = function(_, card, context)
return context.joker_main and {card = card, chips = card.ability.extra.chips} or nil
end,
}
joker {
key = "mrsbones",
pronouns = "she_her",
config = {extra = {xmult = 4, requirement = 4}},
cost = 4,
rarity = 2,
eternal_compat = false,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.xmult, card.ability.extra.requirement * 100}}
end,
calculate = function(_, card, context)
if context.blueprint then
return
end
if context.joker_main then
return {xmult = card.ability.extra.xmult}
end
if context.blind_defeated and G.GAME.chips / card.ability.extra.requirement < G.GAME.blind.chips then
q(function()
G.hand_text_area.blind_chips:juice_up()
G.hand_text_area.game_chips:juice_up()
play_sound("tarot1")
card:start_dissolve()
end)
end
end,
}
joker {
key = "estrogen",
pronouns = "she_her",
config = {extra = {division = 4}},
cost = 8,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.division}}
end,
calculate = function(_, card, context)
return context.joker_main and {mult = hand_chips / card.ability.extra.division} or nil
end,
}
joker {
key = "hexagon",
pronouns = "it_its",
config = {extra = {money = 6}},
cost = 6,
rarity = 1,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.money}}
end,
calculate = function(_, card, context)
if not context.joker_main or
context.scoring_name ~= "Four of a Kind" then
return
end
card.sell_cost = card.sell_cost + card.ability.extra.money
end,
}
joker {
key = "hexagoner",
pronouns = "it_its",
config = {extra = {mult = 0}},
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = false,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.mult}}
end,
calculate = function(_, card, context)
if context.joker_main then
return {mult = card.ability.extra.mult}
end
if context.individual and
context.cardarea == "unscored" and
context.scoring_name == "Four of a Kind" then
card.ability.extra.mult = card.ability.extra.mult + context.other_card.base.nominal
end
end,
}
joker {
key = "hexagonest",
pronouns = "it_its",
config = {extra = {mult = 0}},
cost = 6,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
calculate = function(_, _, context)
if not context.individual or
context.cardarea ~= "unscored" or
context.scoring_name ~= "Four of a Kind" then
return
end
q(function()
playing_card_joker_effects(f(G.play):take(#G.play):map(function(v)
G.playing_card = (G.playing_card or 0) + 1
G.deck.config.card_limit = G.deck.config.card_limit + 1
local copy = copy_card(v, nil, nil, G.playing_card)
copy:add_to_deck()
G.hand:emplace(copy)
copy:start_materialize()
table.insert(G.playing_cards, copy)
return copy
end):into())
end)
end,
}
joker {
key = "sunny", -- Cracked egg
pronouns = "they_them",
cost = 2,
rarity = 1,
eternal_compat = false,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
local last = ((G.deck or {}).cards or {})[1]
if not last or card.area ~= G.jokers then
return {
vars = {
"",
localize {type = "variable", key = "b_Roland_na"},
"",
colours = {G.C.JOKER_GREY, G.C.JOKER_GREY},
},
}
end
local suit = last.base.suit
local value = last.base.value
local name = last.ability.name or ""
local no_rank = SMODS.has_no_rank(last)
local no_suit = SMODS.has_no_suit(last)
return {
vars = {
value and not no_rank and localize(value or 14, "ranks") or name,
not no_rank and not no_suit and localize {type = "variable", key = "b_Roland_of"} or "",
no_suit and "" or localize(suit, "suits_plural"),
colours = {G.C.IMPORTANT, G.C.SUITS[suit] or G.C.JOKER_GREY},
},
}
end,
calculate = function(_, _, context)
local _ = context.selling_self and
draw_card(G.deck, G.hand, 100, "up", false, G.deck.cards[1])
end,
}
joker {
key = "yard",
pronouns = "he_him",
config = {extra = {money = 2}},
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.money}}
end,
calculate = function(_, card, context)
return context.remove_playing_cards and
{dollars = card.ability.extra.money * #context.removed, card = card} or nil
end,
}
joker {
key = "misfortune",
pronouns = "she_they",
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
}
joker {
key = "temple",
pronouns = "any_all",
config = {extra = {xmult = 1.5}},
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.xmult}}
end,
calculate = function(_, card, context)
if context.individual and
context.cardarea == "unscored" and
SMODS.has_enhancement(context.other_card, "m_wild") then
return {xmult = card.ability.extra.xmult}
end
end,
}
joker {
key = "bulldozer",
pronouns = "it_its",
cost = 6,
rarity = 2,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(self, _, _)
return {vars = {self.xmult()}}
end,
calculate = function(self, card, context)
return context.joker_main and {card = card, xmult = self.xmult()} or nil
end,
xmult = function()
return ((G.GAME or {}).blind or {}).mult or 1
end,
}
joker {
key = "oops", -- Slot machine
pronouns = "he_they",
cost = 8,
rarity = 3,
config = {extra = {probability = 1, delta = 1}},
eternal_compat = true,
blueprint_compat = false,
perishable_compat = true,
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.probability}}
end,
calculate = function(_, card, context)
if context.blueprint or not card.ability then
return
end
local extra = card.ability.extra
if context.mod_probability then
return {numerator = extra.probability}
end
if context.blind_defeated then
extra.probability = extra.delta
elseif context.after then
extra.probability = extra.probability + extra.delta
end
end,
}
joker {
key = "martingale",
pronouns = "he_him",
config = {extra = {odds = 2, times = 0}},
cost = 7,
rarity = 3,
eternal_compat = true,
blueprint_compat = true,
perishable_compat = true,
loc_vars = function(_, _, card)
local normal = G.GAME.probabilities.normal
local odds = card.ability.extra.odds
local vars = {normal}
for i = #vars, 9 do
table.insert(vars, math.pow(odds, i))
end
return {vars = vars}
end,
calculate = function(_, card, context)
if not context.joker_main then
return
end
local extra = card.ability.extra
local xmult = extra.odds
local numerator = 1
for _ = 1, 63 do
if SMODS.pseudorandom_probability(card, "j_Roland_martingale", 1, extra.odds, "Martingale") then
break
end
numerator = numerator * (extra.odds - G.GAME.probabilities.normal)
local message = number_format(numerator) .. "/" .. number_format(xmult)
SMODS.calculate_effect({card = card, repetitions = 1, message = message}, card)
xmult = xmult * extra.odds
end
SMODS.calculate_effect({card = card, xmult = xmult}, card)
end,
}