diff --git a/localization/en-us.lua b/localization/en-us.lua index 9ec3197..5047401 100644 --- a/localization/en-us.lua +++ b/localization/en-us.lua @@ -1,13 +1,39 @@ return { descriptions = { + Back = { + b_Roland_blossom = { + name = "Blossom Deck", + text = { + "{C:attention}Small{} and {C:attention}Big Blinds{} are", + "replaced with {C:attention}Boss Blinds", + "that are {C:dark_edition}debuffed #1# times", + }, + }, + b_Roland_go = { + name = "placeholder", + text = {"grr"}, + }, + }, Blind = { bl_Roland_improbable = { name = "The Improbable", - text = {"{C:attention}All probabilities", "cannot happen"}, + text = {"All probabilities", "cannot happen"}, }, bl_Roland_nimble = { name = "The Nimble", - text = {"The first {C:attention}5 cards", "drawn are {C:attention}played"}, + text = {"The first 5 cards", "drawn are played"}, + }, + bl_Roland_divide = { + name = "The Great Divide", + text = {"Half of the deck", "is {C:discard}discarded"}, + }, + bl_Roland_tranquilizer = { + name = "The Tranquilizer", + text = {"#1#s are debuffed", "Changes based on", "most common rank"}, + }, + bl_Roland_xerox = { + name = "The Xerox", + text = {"Discarded cards", "are copied"}, }, }, Joker = { @@ -80,6 +106,9 @@ return { c_Roland_Jokerful = "Jokerful", c_Roland_Pastries = "Sweet Pastries", }, + v_dictionary = { + b_Roland_most_common_card = "(Rank)", + }, v_text = { ch_c_Roland_Jokerful = {"Only the {C:common}default Joker{} can appear in shops"}, ch_c_Roland_Pastries = {"All blinds, cards, and tags are of {C:gold}Bakery{} or {C:blue}Roland"}, diff --git a/lovely.toml b/lovely.toml new file mode 100644 index 0000000..c6e8827 --- /dev/null +++ b/lovely.toml @@ -0,0 +1,66 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -2 + +# Huge shoutouts to the fantastic Cryptid mod and [MathIsFun0](https://github.com/MathIsFun0) +# in particular because this would be so annoying to figure out on your own. +# The following patches are all adapted from: +# https://github.com/SpectralPack/Cryptid/blob/84895c0349971341e4e09a041a8c94c07ebf0f3f/lovely/stake.toml#L210-L284 +[[patches]] +[patches.pattern] +target = "game.lua" +pattern = "self.GAME.round_resets.blind_choices.Boss = get_new_boss()" +position = "before" +payload = ''' +if G.GAME.modifiers.Roland_blossom_deck then + self.GAME.round_resets.blind_choices.Small = get_new_boss() + self.GAME.round_resets.blind_choices.Big = get_new_boss() +end +''' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/common_events.lua" +pattern = "G.GAME.round_resets.blind_choices.Boss = get_new_boss()" +position = "before" +payload = ''' +if G.GAME.modifiers.Roland_blossom_deck then + G.GAME.round_resets.blind_choices.Small = get_new_boss() + G.GAME.round_resets.blind_choices.Big = get_new_boss() +end +''' +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "if G.GAME.blind:get_type() == 'Boss' then" +position = "at" +payload = "if G.GAME.blind_on_deck == 'Boss' then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "if G.GAME.round_resets.ante == G.GAME.win_ante and G.GAME.blind:get_type() == 'Boss' then" +position = "at" +payload = "if G.GAME.round_resets.ante >= G.GAME.win_ante and G.GAME.blind_on_deck == 'Boss' then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "if G.GAME.round_resets.blind == G.P_BLINDS.bl_small then" +position = "at" +payload = "if G.GAME.blind_on_deck == 'Small' then" +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/state_events.lua" +pattern = "elseif G.GAME.round_resets.blind == G.P_BLINDS.bl_big then" +position = "at" +payload = "elseif G.GAME.blind_on_deck == 'Big' then" +match_indent = true diff --git a/manifest.json b/manifest.json index 62c8198..3bffcfd 100644 --- a/manifest.json +++ b/manifest.json @@ -6,7 +6,7 @@ "author": [ "Emik" ], - "version": "1.5.1", + "version": "2.0.0~dev", "badge_colour": "8BE9FD", "main_file": "src/main.lua", "badge_text_colour": "44475A", diff --git a/src/LuaFunctional b/src/LuaFunctional index 4f3eae0..cfb7fca 160000 --- a/src/LuaFunctional +++ b/src/LuaFunctional @@ -1 +1 @@ -Subproject commit 4f3eae046476664b720a96f91b0e04f388709bbb +Subproject commit cfb7fca10b3b694b25587c39074762a4962ca6c1 diff --git a/src/back.lua b/src/back.lua new file mode 100644 index 0000000..c3f8ffc --- /dev/null +++ b/src/back.lua @@ -0,0 +1,66 @@ +local _, q = unpack(... or require "src.functional") + +-- SMODS.Atlas { +-- key = "back", +-- path = "back.png", +-- px = 71, +-- py = 95, +-- } +SMODS.Back { + key = "blossom", + name = "Blossom", + atlas = "void", + pos = {x = 0, y = 0}, + config = {extra = {times = 2}}, + loc_vars = function(self, _, _) + return {vars = {self.config.extra.times}} + end, + apply = function(_, _) + G.GAME.modifiers.Roland_blossom_deck = true + save_run() + end, + calculate = function(self, _, context) + local function disable() + for _ = 1, self.config.extra.times do + G.GAME.blind:disable() + end + + play_sound("timpani") + return true + end + + if not context.setting_blind then + return + end + + local blinds = G.GAME.round_resets.blind_states + + if blinds.Small == "Current" or blinds.Big == "Current" then + q(disable) + end + end, +} + +SMODS.Back { + key = "go", + name = "Go", + atlas = "void", + pos = {x = 0, y = 0}, + apply = function(_, _) + local function add_credit_card() + local card = create_card("Joker", G.jokers, nil, nil, nil, nil, "j_credit_card", "Roland_go") + card:add_to_deck() + card:start_materialize() + G.jokers:emplace(card) + save_run() + return true + end + + q(add_credit_card) + end, + calculate = function(_, _, context) + if context.starting_shop then + ease_dollars(-G.GAME.dollars) + end + end, +} diff --git a/src/blind.lua b/src/blind.lua index 7137fc4..e4b18c9 100644 --- a/src/blind.lua +++ b/src/blind.lua @@ -1,3 +1,5 @@ +local _, q = unpack(... or require "src.functional") + SMODS.Atlas { px = 34, py = 34, @@ -7,8 +9,39 @@ SMODS.Atlas { atlas_table = "ANIMATION_ATLAS", } +local function common_rank() + local tally = {} + local to_name = {} + + --- Tallies up a card area's cards. + ---@param card_area CardArea + local function tally_up(card_area) + for _, v in ipairs(card_area.cards) do + if not SMODS.has_no_rank(v) then + local id = v:get_id() + tally[id] = (tally[id] or 0) + 1 + to_name[id] = v.base.value + end + end + end + + tally_up(G.deck) + tally_up(G.hand) + tally_up(G.discard) + local max_key, max_value = -1 / 0, -1 / 0 + + for k, v in pairs(tally) do + if v > max_value or k > max_key and v == max_value then + max_key = k + max_value = v + end + end + + return max_key, to_name[max_key] +end + local function disable_improbable() - G.GAME.improbable = nil + G.GAME.modifiers.Roland_improbable = nil local orig = (getmetatable(G.GAME.probabilities) or {}).orig if orig then @@ -16,12 +49,16 @@ local function disable_improbable() end end +local function is_locked() + return G.STATE ~= G.STATES.SELECTING_HAND or G.CONTROLLER.locked or (G.GAME.STOP_USE and G.GAME.STOP_USE > 0) +end + if cry_prob then local orig_prob = cry_prob ---@diagnostic disable-next-line: lowercase-global cry_prob = function(owned, den, rigged) - return G.GAME.improbable and 0 or orig_prob(owned, den, rigged) + return G.GAME.modifiers.Roland_improbable and 0 or orig_prob(owned, den, rigged) end end @@ -32,7 +69,7 @@ function Game:update(dt) orig_update(self, dt) local orig = G.GAME.probabilities - if not G.GAME.improbable or getmetatable(orig) then + if not G.GAME.modifiers.Roland_improbable or getmetatable(orig) then return end @@ -59,13 +96,13 @@ SMODS.Blind { boss_colour = HEX("009966"), atlas = "blind", pos = {x = 0, y = 0}, - pronouns = "any_all", + pronouns = "it_its", mult = 2, dollars = 5, defeat = disable_improbable, disable = disable_improbable, set_blind = function(_) - G.GAME.improbable = true + G.GAME.modifiers.Roland_improbable = true end, } @@ -86,7 +123,7 @@ SMODS.Blind { end, drawn_to_hand = function(self) local function force_hand() - if G.STATE ~= G.STATES.SELECTING_HAND or G.CONTROLLER.locked or (G.GAME.STOP_USE and G.GAME.STOP_USE > 0) then + if is_locked() then return false end @@ -99,15 +136,146 @@ SMODS.Blind { end G.FUNCS.play_cards_from_highlighted(nil) - return true end if not self.disabled then self.disabled = true - G.E_MANAGER:add_event(Event({func = force_hand, blocking = false})) + q {func = force_hand, blocking = false} end end, set_blind = function(self) self.disabled = false end, } + +SMODS.Blind { + key = "divide", + boss = {min = 4, max = 10}, + boss_colour = HEX("0291fb"), + atlas = "blind", + pos = {x = 0, y = 1}, + pronouns = "they_them", + mult = 2, + dollars = 5, + disable = function() + local function disable() + for i = 1, #G.discard.cards do + draw_card(G.discard, G.hand, i / #G.discard.cards * 100, "up", false, G.discard.cards[i], nil, nil, true) + end + + for i = 1, #G.discard.cards do + draw_card(G.hand, G.deck, i / #G.discard.cards * 100, "up", false, G.discard.cards[i]) + end + end + + q(disable) + end, + set_blind = function() + local function force_hand() + local length = (#G.deck.cards - 2) / 2 + + for i = 0, length do + local card = G.deck.cards[#G.deck.cards - i] + draw_card(G.deck, G.hand, i / length * 100, "down", false, card, nil, nil, true) + end + + for i = 0, length do + local card = G.deck.cards[#G.deck.cards - i] + draw_card(G.hand, G.discard, i / length * 100, "down", false, card, nil, nil, true) + end + end + + -- Allows the background to ease in first before drawing cards. + q(force_hand) + end, +} + +SMODS.Blind { + key = "tranquilizer", + boss = {min = 4, max = 10}, + boss_colour = HEX("0291fb"), + atlas = "blind", + pos = {x = 0, y = 1}, + pronouns = "they_them", + mult = 2, + dollars = 5, + collection_loc_vars = function(_) + return { + vars = {localize { + type = "variable", key = "b_Roland_most_common_card", + }}, + } + end, + loc_vars = function(_) + local _, name = common_rank() + return {vars = {localize(name or "Ace", "ranks")}} + end, + disable = function(self) + self.disabled = true + end, + calculate = function(self, _, context) + if not context.card_added and + not context.drawing_cards and + not context.playing_card_added and + not context.pre_discard and + not context.press_play then + return + end + + local needs_text_change = false + + local function process(card_area) + for _, v in pairs(card_area.cards) do + local debuff = v.debuff + v:set_debuff(self:recalc_debuff(v, false)) + needs_text_change = needs_text_change or debuff ~= v.debuff + end + end + + process(G.deck) + process(G.hand) + process(G.discard) + + if needs_text_change then + G.GAME.blind:wiggle() + G.GAME.blind:set_text() + end + end, + recalc_debuff = function(self, card) + local id, _ = common_rank() + return not self.disabled and id == card:get_id() + end, +} + +SMODS.Blind { + key = "xerox", + boss = {min = 2, max = 10}, + boss_colour = HEX("0291fb"), + atlas = "blind", + pos = {x = 0, y = 1}, + pronouns = "they_them", + mult = 2, + dollars = 5, + disable = function(self) + self.disabled = true + end, + calculate = function(_, blind, context) + if blind.disabled or not context.pre_discard then + return + end + + local cards_added = {} + local length = #G.hand.highlighted + + for i = 1, length do + local copy = copy_card(G.hand.highlighted[i]) + copy:add_to_deck() + table.insert(G.hand, copy) + table.insert(cards_added, copy) + table.insert(G.playing_cards, copy) + draw_card(G.hand, G.discard, i / length * 100, "down", false, copy, nil, nil, true) + end + + playing_card_joker_effects(cards_added) + end, +} diff --git a/src/challenge.lua b/src/challenge.lua index cf47676..fce9721 100644 --- a/src/challenge.lua +++ b/src/challenge.lua @@ -1,3 +1,4 @@ +local f, q = unpack(... or require "src.functional") local jokerful = {banned_cards = {}} local pastries = {banned_cards = {}, banned_tags = {}, banned_other = {}} @@ -34,13 +35,16 @@ SMODS.Challenge { pronouns = "she_them", } -G.E_MANAGER:add_event(Event { +q { trigger = "immediate", func = function() - F.foreach(F.filter(G.P_CENTERS, is_joker), adder(jokerful.banned_cards)) - F.foreach(F.filter(G.P_TAGS, is_banned_from_pastry), adder(pastries.banned_tags)) - F.foreach(F.filter(G.P_BLINDS, is_banned_from_pastry), adder(pastries.banned_other)) - F.foreach(F.filter(G.P_CENTERS, is_center_banned_from_pastry), adder(pastries.banned_cards)) - return true + f { + {G.P_CENTERS, is_joker, jokerful.banned_cards}, + {G.P_TAGS, is_banned_from_pastry, pastries.banned_tags}, + {G.P_BLINDS, is_banned_from_pastry, pastries.banned_other}, + {G.P_CENTERS, is_center_banned_from_pastry, pastries.banned_cards}, + }:foreach(function(v) + f(v[1]):filter(v[2]):foreach(adder(v[3])) + end) end, -}) +} diff --git a/src/functional.lua b/src/functional.lua new file mode 100644 index 0000000..7fd9874 --- /dev/null +++ b/src/functional.lua @@ -0,0 +1,81 @@ +local function protect(fun) + return function() + local res, ret = pcall(fun) + + if not res then + sendErrorMessage(tostring(ret), "Roland") + end + + return not res or ret ~= false + end +end + +local function protect_ev(fun) + if type(fun) == "table" then + fun.func = protect(fun.func) + fun = getmetatable(fun) == Event and fun or Event(fun) + elseif type(fun) == "function" then + fun = Event {func = protect(fun)} + else + error("Expected a function or event, got a " .. type(fun), 3) + end + + return fun +end + +local luaf = assert(SMODS.load_file("src/LuaFunctional/functional.lua"))() + +if false then + ---Returns the elements from the given `list`. This function is equivalent to + ---```lua + --- return list[i], list[i+1], ยทยทยท, list[j] + ---``` + --- + --- + ---[View documents](command:extension.lua.doc?["en-us/52/manual.html/pdf-unpack"]) + --- + ---@generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 + ---@param list { + --- [1]?: T1, + --- [2]?: T2, + --- [3]?: T3, + --- [4]?: T4, + --- [5]?: T5, + --- [6]?: T6, + --- [7]?: T7, + --- [8]?: T8, + --- [9]?: T9, + --- [10]?: T10, + ---} + ---@param i? integer + ---@param j? integer + ---@return T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 + ---@nodiscard + function unpack(list, i, j) + error({list, i, j}) + end + + luaf = require "src.LuaFunctional.functional" +end + +--- Creates a non-numerically-indexed query from the given table. +---@param obj table +---@param f_pairs? fun(t: table): unknown +---@param numeric boolean? +---@return Query +local function f(obj, f_pairs, numeric) + return f_pairs and luaf.from(obj, f_pairs, numeric ~= false) or luaf.pairs(obj) +end + +--- Queues an event to be run. +--- Note that events added this way implicitly `return true` unless you explicitly `return false`, unlike the vanilla ones. +--- @param fun (fun():boolean?)|Event The event or a function to run turn into an event. +--- @param front boolean|nil `true` to add the event to the front of the queue, rather than the end. +local function q(fun, front) + G.E_MANAGER:add_event(protect_ev(fun), nil, front) +end + +---@class Query: metatable +local _ = luaf._proto + +return {f, q} diff --git a/src/joker.lua b/src/joker.lua index 2fb7e3c..25e8a79 100644 --- a/src/joker.lua +++ b/src/joker.lua @@ -1,3 +1,5 @@ +local f, q = unpack(... or require "src.functional") + local function can_use() return not ((G.play and #G.play.cards > 0 or G.CONTROLLER.locked or @@ -8,14 +10,12 @@ local function can_use() end local function common_hand() - return (G.GAME or {}).current_round and F.reduce( - G.GAME.hands, + return (G.GAME or {}).current_round and f(G.GAME.hands):reduce( {name = "High Card", order = -1 / 0, played = 0}, function(a, v, k) return (a.played < v.played or (a.played == v.played) and (a.order > v.order)) and {name = k, order = v.order, played = v.played} or a - end, - pairs + end ).name or "High Card" end @@ -70,7 +70,7 @@ SMODS.Joker { local quotes = loc_self.quotes local merge = G.jokers - and #F.filter(G.jokers.cards, is_mergeable_with(card)) > 0 + and f(G.jokers.cards):filter(is_mergeable_with(card)):any() and loc_self.merge or {} local normal = (merge[1] or sinister) and {} or @@ -96,15 +96,17 @@ SMODS.Joker { end, calculate = function(_, _, context) if type(G.escapey_debugger) == "function" then - G.escapey_debugger(F.reduce(context, "", function(acc, _, next) return acc .. ", " .. next end, pairs):sub(2)) + G.escapey_debugger(f(context):reduce("", function(acc, _, next) + return acc .. ", " .. next + end):sub(2)) end end, ---@param card Card Bakery_can_use = function(_, card) return not card.debuff and can_use() and ( #G.GAME.tags ~= 0 or - F.any(G.consumeables.cards, destructible) or - F.any(F.filter(G.jokers.cards, is_mergeable_with(card))) + f(G.consumeables.cards):filter(destructible):any() or + f(G.jokers.cards):filter(is_mergeable_with(card)):any() ) end, ---@param card Card @@ -113,8 +115,8 @@ SMODS.Joker { return "DEBUFFED" end - return #G.GAME.tags == 0 and #F.filter(G.consumeables.cards, destructible) == 0 and - F.any(F.filter(G.jokers.cards, is_mergeable_with(card))) and "FUSE" or "ESCAPE" + 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, ---@param card Card Bakery_use_joker = function(_, card) @@ -122,7 +124,7 @@ SMODS.Joker { return end - local consumables = F.filter(G.consumeables.cards, destructible) + local consumables = f(G.consumeables.cards):filter(destructible):into() local consumable_count = #consumables local tag_count = #G.GAME.tags @@ -130,15 +132,12 @@ SMODS.Joker { local level_sum, sell_sum = 0, 0 local any_carbon = is_carbon(card) - F.foreach( - F.filter(G.jokers.cards, is_mergeable_with(card)), - function(v) - any_carbon = any_carbon or is_carbon(v) - level_sum = level_sum + v.ability.extra.level_up_by * (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 - ) + 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.level_up_by * (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.level_up_by = card.ability.extra.level_up_by + level_sum @@ -178,7 +177,6 @@ SMODS.Joker { play_sound("cancel", 1.66, 0.5) v.HUD_tag.states.visible = false v:remove() - return true end end @@ -201,27 +199,21 @@ SMODS.Joker { local trigger = #G.GAME.tags >= 30 and "immediate" or "before" local delay = #G.GAME.tags >= 30 and 0 or 1 / #G.GAME.tags - F.foreach( - G.GAME.tags, - function(v) - destroyed = destroyed + 1 + f(G.GAME.tags):foreach(function(v) + q { + trigger = trigger, + blocking = #G.GAME.tags < 30, + delay = delay, + func = fast_delete(v), + } - G.E_MANAGER:add_event(Event { - trigger = trigger, - blocking = #G.GAME.tags < 30, - delay = delay, - func = fast_delete(v), - }) - end - ) + destroyed = destroyed + 1 + end) else - F.foreach( - consumables, - function(v) - destroyed = destroyed + 1 - v:start_dissolve({HEX("57ecab")}, nil, 1.6) - end - ) + f(consumables):foreach(function(v) + v:start_dissolve({HEX("57ecab")}, nil, 1.6) + destroyed = destroyed + 1 + end) end level_up_hand(card, hand, nil, destroyed * card.ability.extra.level_up_by) @@ -250,7 +242,7 @@ SMODS.Joker { local vars = {normal} for i = #vars, 9 do - vars[i + 1] = math.pow(odds, i) + table.insert(vars, math.pow(odds, i)) end return {vars = vars} @@ -262,13 +254,16 @@ SMODS.Joker { local extra = card.ability.extra local xmult = extra.odds + local numerator = 1 for _ = 1, 31 do if SMODS.pseudorandom_probability(card, "j_Roland_martingale", 1, extra.odds, "Martingale") then break end - SMODS.calculate_effect({card = card, repetitions = 1, message = "1/" .. number_format(xmult)}, card) + 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 diff --git a/src/main.lua b/src/main.lua index 7eda99a..cce87dc 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,13 +1,13 @@ -F = assert(SMODS.load_file("src/LuaFunctional/functional.lua"))() -assert(SMODS.load_file("src/challenge.lua"))() -assert(SMODS.load_file("src/spectral.lua"))() -assert(SMODS.load_file("src/blind.lua"))() -assert(SMODS.load_file("src/joker.lua"))() +local qol = assert(SMODS.load_file("src/functional.lua"))() or require "src.functional" + +qol[1] {"challenge", "spectral", "blind", "joker", "back", "seal"}:foreach(function(v) + assert(SMODS.load_file("src/" .. v .. ".lua"))(qol) +end) if Balatest then - assert(SMODS.load_file("src/tests/joker.tests.lua"))() - assert(SMODS.load_file("src/tests/blind.tests.lua"))() - assert(SMODS.load_file("src/tests/spectral.tests.lua"))() + qol[1] {"joker", "blind", "spectral"}:foreach(function(v) + assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol) + end) end SMODS.Joker:take_ownership("joker", {cost = 1}, true) diff --git a/src/seal.lua b/src/seal.lua new file mode 100644 index 0000000..817ffc8 --- /dev/null +++ b/src/seal.lua @@ -0,0 +1,34 @@ +-- SMODS.Atlas { +-- px = 71, +-- py = 95, +-- key = "seal", +-- path = "seal.png", +-- } +SMODS.Seal { + name = "Frost", + key = "frost", + badge_colour = HEX("12f254"), + atlas = "void", + pos = {x = 0, y = 0}, + calculate = function(_, card, context) + if not context.blind_defeated or card.area ~= G.deck then + return + end + + local tag = Tag(get_next_tag_key("Roland_frost")) + + if tag.name == "Orbital Tag" then + local hands = {} + + for k, v in pairs(G.GAME.hands) do + if v.visible then + table.insert(hands, k) + end + end + + tag.ability.orbital_hand = pseudorandom_element(hands, pseudoseed("Roland_frost")) + end + + add_tag(tag) + end, +} diff --git a/src/spectral.lua b/src/spectral.lua index 0a914d7..860f88a 100644 --- a/src/spectral.lua +++ b/src/spectral.lua @@ -1,3 +1,5 @@ +local f, q = unpack(... or require "src.functional") + local function can_use() return not ((G.play and #G.play.cards > 0 or G.CONTROLLER.locked or @@ -39,24 +41,22 @@ SMODS.Consumable { end, use = function(_, card, _, _) for _, v in ipairs(Bakery_API.get_highlighted()) do - G.E_MANAGER:add_event(Event { + q { delay = 0.1, func = function() v:set_edition({negative = true}) v:juice_up(0.5, 0.5) - return true end, - }) + } end - G.E_MANAGER:add_event(Event { + q { delay = 0.1, func = function() G.hand:change_size(card.ability.extra.hand) Bakery_API.unhighlight_all() - return true end, - }) + } end, } @@ -92,14 +92,14 @@ SMODS.Consumable { end local function void() - local cards = F.filter(G.playing_cards, destructible) + local cards = f(G.playing_cards):filter(destructible):into() local function calculate_joker(v) v:calculate_joker({remove_playing_cards = true, removed = cards}) end - F.foreach(cards, destroy) - F.foreach(G.jokers.cards, calculate_joker) + f(cards):foreach(destroy) + f(G.jokers.cards):foreach(calculate_joker) for _ = 1, card.ability.extra.amount do local cryptid = create_card( @@ -117,17 +117,15 @@ SMODS.Consumable { cryptid:add_to_deck() G.consumeables:emplace(cryptid) end - - return true end play_sound("Roland_void", 1, 0.7) - G.E_MANAGER:add_event(Event({ + q { delay = 0.27, timer = "REAL", trigger = "after", func = void, - })) + } end, } diff --git a/src/tests/blind.tests.lua b/src/tests/blind.tests.lua index c6d786f..393ee1e 100644 --- a/src/tests/blind.tests.lua +++ b/src/tests/blind.tests.lua @@ -32,6 +32,20 @@ Balatest.TestPlay { end, } +Balatest.TestPlay { + category = {"blind", "improbable"}, + name = "improbable_disabled", + blind = "bl_Roland_improbable", + jokers = {"j_oops", "j_oops", "j_oops", "j_chicot"}, + deck = {cards = {{s = "S", r = "2", e = "m_lucky"}, {s = "S", r = "3"}}}, + execute = function() + Balatest.play_hand {"2S"} + end, + assert = function() + Balatest.assert_chips(7 * 21) + end, +} + Balatest.TestPlay { category = {"blind", "nimble"}, name = "nimble", @@ -59,3 +73,143 @@ Balatest.TestPlay { Balatest.assert_chips(2720) end, } + +Balatest.TestPlay { + category = {"blind", "nimble"}, + name = "nimble_disabled", + blind = "bl_Roland_nimble", + jokers = {"j_chicot"}, + execute = function() + Balatest.wait_for_input() + end, + assert = function() + Balatest.assert_chips(0) + end, +} + +Balatest.TestPlay { + category = {"blind", "divide"}, + name = "divide", + blind = "bl_Roland_divide", + execute = function() + Balatest.wait_for_input() + end, + assert = function() + Balatest.assert_eq(#G.discard.cards, 26) + end, +} + +Balatest.TestPlay { + category = {"blind", "divide"}, + name = "divide_odd", + blind = "bl_Roland_divide", + deck = {cards = { + {s = "S", r = "2"}, + {s = "S", r = "2"}, + {s = "S", r = "2"}, + {s = "S", r = "2"}, + {s = "S", r = "2"}, + }}, + execute = function() + Balatest.wait_for_input() + end, + assert = function() + Balatest.assert_eq(#G.discard.cards, 2) + end, +} + +Balatest.TestPlay { + category = {"blind", "divide"}, + name = "divide_disabled", + blind = "bl_Roland_divide", + jokers = {"j_chicot"}, + execute = function() + Balatest.wait_for_input() + end, + assert = function() + Balatest.assert_eq(#G.discard.cards, 0) + end, +} + +Balatest.TestPlay { + category = {"blind", "tranquilizer"}, + name = "tranquilizer", + blind = "bl_Roland_tranquilizer", + execute = function() + Balatest.wait_for_input() + end, + assert = function() + for k, v in pairs(G.hand.cards) do + local message = v.debuff and + "Card " .. k .. " should not be debuffed" or + "Card " .. k .. " should be debuffed" + + Balatest.assert(v.debuff == (v.base.value == "Ace"), message) + end + end, +} + +Balatest.TestPlay { + category = {"blind", "tranquilizer"}, + name = "tranquilizer_change", + blind = "bl_Roland_tranquilizer", + jokers = {"j_dna", "j_dna"}, + deck = {cards = { + {s = "S", r = "2"}, + {s = "S", r = "3"}, + {s = "S", r = "3"}, + }}, + execute = function() + Balatest.wait_for_input() + Balatest.play_hand {"2S"} + end, + assert = function() + for k, v in pairs(G.hand.cards) do + local message = v.debuff and + "Card " .. k .. " should not be debuffed" or + "Card " .. k .. " should be debuffed" + + Balatest.assert(v.debuff == (v.base.value == "2"), message) + end + end, +} + +Balatest.TestPlay { + category = {"blind", "tranquilizer"}, + name = "tranquilizer_disabled", + blind = "bl_Roland_tranquilizer", + jokers = {"j_chicot"}, + execute = function() + Balatest.wait_for_input() + end, + assert = function() + for k, v in pairs(G.hand.cards) do + Balatest.assert(not v.debuff, "Card " .. k .. " should not be debuffed") + end + end, +} + +Balatest.TestPlay { + category = {"blind", "xerox"}, + name = "xerox", + blind = "bl_Roland_xerox", + execute = function() + Balatest.discard {"2S"} + end, + assert = function() + Balatest.assert_eq(#G.playing_cards, 53) + end, +} + +Balatest.TestPlay { + category = {"blind", "xerox"}, + name = "xerox_disabled", + blind = "bl_Roland_xerox", + jokers = {"j_chicot"}, + execute = function() + Balatest.discard {"2S"} + end, + assert = function() + Balatest.assert_eq(#G.playing_cards, 52) + end, +} diff --git a/src/tests/joker.tests.lua b/src/tests/joker.tests.lua index b9dd613..a084706 100644 --- a/src/tests/joker.tests.lua +++ b/src/tests/joker.tests.lua @@ -2,31 +2,6 @@ if not Balatest then return end -Balatest.TestPlay { - category = {"joker", "martingale"}, - name = "martingale_oops", - jokers = {"j_Roland_martingale", "j_oops"}, - execute = function() - Balatest.play_hand {"2S"} - end, - assert = function() - Balatest.assert_chips(7 * 2) - end, -} - -Balatest.TestPlay { - category = {"joker", "martingale"}, - name = "martingale_improbable", - blind = "bl_Roland_improbable", - jokers = {"j_Roland_martingale"}, - execute = function() - Balatest.play_hand {"2S"} - end, - assert = function() - Balatest.assert_chips(7 * math.pow(2, 32)) - end, -} - Balatest.TestPlay { category = {"joker", "escapey", "escape"}, name = "escapey_none", @@ -350,3 +325,28 @@ Balatest.TestPlay { Balatest.assert_eq(G.jokers.cards[1].ability.extra.level_up_by, 1) end, } + +Balatest.TestPlay { + category = {"joker", "martingale"}, + name = "martingale_oops", + jokers = {"j_Roland_martingale", "j_oops"}, + execute = function() + Balatest.play_hand {"2S"} + end, + assert = function() + Balatest.assert_chips(7 * 2) + end, +} + +Balatest.TestPlay { + category = {"joker", "martingale"}, + name = "martingale_improbable", + blind = "bl_Roland_improbable", + jokers = {"j_Roland_martingale"}, + execute = function() + Balatest.play_hand {"2S"} + end, + assert = function() + Balatest.assert_chips(7 * math.pow(2, 32)) + end, +}