Compare commits

..

No commits in common. "main" and "2.9.14" have entirely different histories.
main ... 2.9.14

32 changed files with 491 additions and 339 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -495,22 +495,6 @@ return {
}, },
}, },
}, },
Voucher = {
v_Roland_ceres = {
name = "Ceres",
text = {
"Level up {C:attention}Flush House",
"by {C:attention}#1# {}when it is played",
},
},
v_Roland_neptune = {
name = "Neptune",
text = {
"Level up {C:attention}Straight Flush",
"by {C:attention}#1# {}when it is played",
},
},
},
}, },
misc = { misc = {
challenge_names = { challenge_names = {

View file

@ -3,7 +3,7 @@
"id": "Roland", "id": "Roland",
"name": "Roland", "name": "Roland",
"prefix": "Roland", "prefix": "Roland",
"version": "2.9.28", "version": "2.9.14",
"badge_colour": "8BE9FD", "badge_colour": "8BE9FD",
"display_name": "Roland", "display_name": "Roland",
"main_file": "src/main.lua", "main_file": "src/main.lua",

View file

@ -23,7 +23,10 @@ SMODS.Atlas {
atlas_table = "ANIMATION_ATLAS", atlas_table = "ANIMATION_ATLAS",
} }
SMODS.Sound {key = "kick", path = "kick.ogg"} SMODS.Sound {
key = "kick",
path = "kick.ogg",
}
local function common_rank() local function common_rank()
local tally, to_name = {}, {} local tally, to_name = {}, {}
@ -52,7 +55,7 @@ end
local function has_enhancement(card) local function has_enhancement(card)
local e = SMODS.get_enhancements(card) local e = SMODS.get_enhancements(card)
return e and next(e) return not not (e and next(e))
end end
local function set_freeze(state) local function set_freeze(state)
@ -239,16 +242,16 @@ blind {
disable = function(self) disable = function(self)
self:defeat() self:defeat()
end, end,
calculate = function(self, b, context) calculate = function(self, b)
return not b.disabled and return not b.disabled and self.cards()
context.hand_drawn and
self.cards()
:where("Roland_blizzard", false) :where("Roland_blizzard", false)
:where("facing", "front") :where("facing", "front")
:each(set_freeze(true)) or nil :each(set_freeze(true)) or nil
end, end,
cards = function() cards = function()
return f(G):where(getmetatable, CardArea):flatmap("cards", ipairs) return f(G):where(function(v)
return type(v) == "table" and type(v.cards) == "table"
end):flatmap("cards", ipairs)
end, end,
} }
@ -350,16 +353,6 @@ function SMODS.current_mod:calculate(context)
local _ = not str and type(G.calc[1]) == "function" and G.calc[1](f(context):keys():string()) local _ = not str and type(G.calc[1]) == "function" and G.calc[1](f(context):keys():string())
end end
local _ = context.before and
f {"v_Roland_ceres", "v_Roland_neptune"}
:where(f.index_into(G.GAME.used_vouchers))
:map(f.index_into(G.P_CENTERS))
:where("config.extra.hand_type", context.scoring_name)
:pun "SMODS.Voucher"
:each(function(v, _)
SMODS.smart_level_up_hand(nil, v.config.extra.hand_type, nil, v.config.extra.amount)
end)
local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities local improbable, orig = G.GAME.modifiers.Roland_improbable, G.GAME.probabilities
local _ = context.end_of_round and local _ = context.end_of_round and

View file

@ -188,7 +188,7 @@ SMODS.Challenge {
end) end)
local mod = G.GAME.modifiers local mod = G.GAME.modifiers
mod.Roland_Eternally_Crimson = context.setting_blind and true or mod.Roland_Eternally_Crimson mod.Roland_Eternally_Crimson = not context.setting_blind and mod.Roland_Eternally_Crimson or nil
local debuff = context.debuff_card local debuff = context.debuff_card
local cards = G.jokers.cards local cards = G.jokers.cards
@ -262,7 +262,7 @@ SMODS.Challenge {
SMODS.Challenge { SMODS.Challenge {
key = "Eternally_Violet", key = "Eternally_Violet",
rules = {custom = {{id = "Roland_Eternally_Violet"}, {id = "Roland_Showdown_Violet"}}}, rules = {custom = {{id = "Roland_Eternally_Violet"}, {id = "Roland_Showdown_Violet"}}},
jokers = {{id = "j_joker"}, {id = "j_Roland_violet"}}, jokers = {{id = "j_Roland_violet"}},
restrictions = violet, restrictions = violet,
pronouns = "she_they", pronouns = "she_they",
calculate = function(_, context) calculate = function(_, context)

View file

@ -261,7 +261,7 @@ local orig_apply_to_run = Tag.apply_to_run
function Tag:apply_to_run(...) function Tag:apply_to_run(...)
if G.GAME.Bakery_charm == "BakeryCharm_Roland_wii" and if G.GAME.Bakery_charm == "BakeryCharm_Roland_wii" and
G.Bakery_charm_area.cards[1].ability.extra.active then G.Bakery_charm_area.cards[1].config.center.ability.extra.active then
return return
end end

View file

@ -13,7 +13,7 @@ end
frozen_sound "_click" frozen_sound "_click"
local frozen_blocklist = {CardSleeve = true} local frozen_blocklist = {CardSleeve = true}
local frozen_sounds = f(4):map(frozen_sound):map "key":table() local frozen_sounds = f(4):map(frozen_sound):map("key"):table()
local needs_chip_mult_override = { local needs_chip_mult_override = {
Bull = true, Bull = true,
@ -144,7 +144,7 @@ local function hook_proxy()
local orig_calculate = proxy.calculate local orig_calculate = proxy.calculate
function proxy:calculate(card, context, ...) function proxy:calculate(card, context, ...)
if not (card or {}).Roland_frozen or not (card.edition or {}).Roland_frozen then if not card or not card.edition or not card.edition.Roland_frozen then
return orig_calculate(self, card, context, ...) return orig_calculate(self, card, context, ...)
else else
return SMODS.blueprint_effect(card, get_proxied_joker(card), context) return SMODS.blueprint_effect(card, get_proxied_joker(card), context)
@ -154,7 +154,7 @@ local function hook_proxy()
local orig_loc_vars = proxy.loc_vars local orig_loc_vars = proxy.loc_vars
function proxy:loc_vars(info_queue, card, ...) function proxy:loc_vars(info_queue, card, ...)
if not (card or {}).Roland_frozen or not (card.edition or {}).Roland_frozen then if not card or not card.edition or not card.edition.Roland_frozen then
return orig_loc_vars(self, info_queue, card, ...) return orig_loc_vars(self, info_queue, card, ...)
end end

View file

@ -216,9 +216,9 @@ joker {
SMODS.calculate_context {drawing_cards = true, draw = {G.deck.cards}} SMODS.calculate_context {drawing_cards = true, draw = {G.deck.cards}}
SMODS.calculate_context { SMODS.calculate_context {
hand_drawn = facing_blind and {G.deck.cards[1]} and true,
other_drawn = not facing_blind and {G.deck.cards[1]} and true,
first_hand_drawn = not current_round.any_hand_drawn and facing_blind or nil, first_hand_drawn = not current_round.any_hand_drawn and facing_blind or nil,
hand_drawn = facing_blind and {G.deck.cards[1]} --[[@as true]],
other_drawn = not facing_blind and {G.deck.cards[1]},
} }
if type(facing_blind) == "table" then if type(facing_blind) == "table" then
@ -488,9 +488,11 @@ joker {
end end
end end
return SMODS.merge_effects( local key = card.config.center.key
local merged = SMODS.merge_effects(
f(G.jokers.cards):where(is_frozen):where(function(v) f(G.jokers.cards):where(is_frozen):where(function(v)
return v.config.center.key ~= card.config.center.key return v.config.center.key ~= key
end):map(function(v) end):map(function(v)
return SMODS.blueprint_effect(card, v, context) return SMODS.blueprint_effect(card, v, context)
end):where(type, "table"):map(function(v) end):where(type, "table"):map(function(v)
@ -498,6 +500,8 @@ joker {
return v return v
end):values():table() end):values():table()
) )
return merged
end, end,
} }
@ -648,7 +652,7 @@ joker {
local _ = card.area == G.jokers and self:cerulean(false) local _ = card.area == G.jokers and self:cerulean(false)
end, end,
cerulean = function(_, value) cerulean = function(_, value)
local _ = G.jokers and f(G.jokers.cards):each(function(v) f(G.jokers.cards):each(function(v)
f {"click", "drag", "focus", "hover"}:map(f.index_into(v.states)):each(function(s) f {"click", "drag", "focus", "hover"}:map(f.index_into(v.states)):each(function(s)
s.can = value or v.config.center.key == "j_Roland_cerulean" s.can = value or v.config.center.key == "j_Roland_cerulean"
end) end)
@ -675,7 +679,7 @@ joker {
{card = card, xmult = card.ability.extra.xmult} or nil {card = card, xmult = card.ability.extra.xmult} or nil
end, end,
crimson = function() crimson = function()
local _ = G.jokers and f(G.jokers.cards, ipairs_reversed):where(is_frozen, false):each(function(v) f(G.jokers.cards, ipairs_reversed):where(is_frozen, false):each(function(v)
local right = G.jokers.cards[v.rank + 1] local right = G.jokers.cards[v.rank + 1]
local debuffed_by_crimson = right and local debuffed_by_crimson = right and
@ -684,13 +688,11 @@ joker {
right.config.center.key == "j_Roland_crimson" right.config.center.key == "j_Roland_crimson"
if debuffed_by_crimson and v.ability.Roland_crimson == nil then if debuffed_by_crimson and v.ability.Roland_crimson == nil then
local debuff = not not v.debuff v.ability.Roland_crimson = not not v.debuff
v:set_debuff(true) v:set_debuff(true)
v.ability.Roland_crimson = debuff
elseif not debuffed_by_crimson and v.ability.Roland_crimson ~= nil then elseif not debuffed_by_crimson and v.ability.Roland_crimson ~= nil then
local debuff = v.ability.Roland_crimson v:set_debuff(v.ability.Roland_crimson)
v.ability.Roland_crimson = nil v.ability.Roland_crimson = nil
v:set_debuff(debuff)
end end
end) end)
end, end,
@ -757,7 +759,7 @@ joker {
key = "violet", key = "violet",
pronouns = "she_they", pronouns = "she_they",
idea = "hamester", idea = "hamester",
config = {extra = {before = 0.1, xmult = 6}}, config = {extra = {before = 0.1, xmult = 9}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
attributes = {"xmult"}, attributes = {"xmult"},
cost = 6, cost = 6,
@ -936,14 +938,10 @@ joker {
local extra, numerator, xmult = card.ability.extra, 1, 1 local extra, numerator, xmult = card.ability.extra, 1, 1
if G.GAME.blind.name == "bl_mp_nemesis" then if G.GAME.blind.name == "bl_mp_nemesis" then
local n, d = SMODS.get_probability_vars(card, 1, extra.odds, self.key) return {card = card, xmult = extra.odds}
return n < d and {card = card, xmult = extra.odds} or nil
end end
-- 2^38 is the first iteration displayed as scientific notation. for _ = 1, 256 do
local max, overflown = 38, false
for i = 1, max do
local key = "RolandMartingale" .. tostring(G.GAME.modifiers.Roland_martingale_seed or "") local key = "RolandMartingale" .. tostring(G.GAME.modifiers.Roland_martingale_seed or "")
if SMODS.pseudorandom_probability(card, self.key, 1, extra.odds, key) then if SMODS.pseudorandom_probability(card, self.key, 1, extra.odds, key) then
@ -955,18 +953,9 @@ joker {
xmult = xmult * extra.odds xmult = xmult * extra.odds
local message = number_format(numerator) .. "/" .. number_format(xmult) local message = number_format(numerator) .. "/" .. number_format(xmult)
SMODS.calculate_effect({card = card, repetitions = 1, message = message, message_card = card}, card) SMODS.calculate_effect({card = card, repetitions = 1, message = message, message_card = card}, card)
overflown = overflown or i == max
end end
if overflown then
SMODS.calculate_effect({card = card, score = 1 / 0}, card)
q(function()
card:shatter()
end)
else
SMODS.calculate_effect({card = card, xmult = xmult}, card) SMODS.calculate_effect({card = card, xmult = xmult}, card)
end
end, end,
} }
@ -1258,7 +1247,7 @@ joker {
return {card = card, numerator = extra.probability} return {card = card, numerator = extra.probability}
end end
if context.end_of_round and not context.repetition and extra.probability ~= extra.reset then if context.end_of_round and extra.probability ~= extra.reset then
extra.probability = extra.reset extra.probability = extra.reset
return {message = localize "k_reset", colour = G.C.RED, message_card = card, repetitions = 0} return {message = localize "k_reset", colour = G.C.RED, message_card = card, repetitions = 0}
end end

View file

@ -9,91 +9,81 @@ local f = {}
if not f then if not f then
---@generic I, O ---@generic I, O
---@param first string|fun(v: I): O ---@param first fun(v: I): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(first) function f.chain(first)
error {first} error {first}
end end
---@generic I, T, O ---@generic I, T, O
---@param first string|fun(v: I): T ---@param first fun(v: I): T
---@param second string|fun(v: T): O ---@param second fun(v: T): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(first, second) function f.chain(first, second)
error {first, second} error {first, second}
end end
---@generic I, T1, T2, O ---@generic I, T1, T2, O
---@param first string|fun(v: I): T1 ---@param first fun(v: I): T1
---@param second string|fun(v: T1): T2 ---@param second fun(v: T1): T2
---@param third string|fun(v: T2): O ---@param third fun(v: T2): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third) function f.chain(first, second, third)
error {first, second, third} error {first, second, third}
end end
---@generic I, T1, T2, T3, O ---@generic I, T1, T2, T3, O
---@param first string|fun(v: I): T1 ---@param first fun(v: I): T1
---@param second string|fun(v: T1): T2 ---@param second fun(v: T1): T2
---@param third string|fun(v: T2): T3 ---@param third fun(v: T2): T3
---@param fourth string|fun(v: T3): O ---@param fourth fun(v: T3): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third, fourth) function f.chain(first, second, third, fourth)
error {first, second, third, fourth} error {first, second, third, fourth}
end end
---@generic I, T1, T2, T3, T4, O ---@generic I, T1, T2, T3, T4, O
---@param first string|fun(v: I): T1 ---@param first fun(v: I): T1
---@param second string|fun(v: T1): T2 ---@param second fun(v: T1): T2
---@param third string|fun(v: T2): T3 ---@param third fun(v: T2): T3
---@param fourth string|fun(v: T3): T4 ---@param fourth fun(v: T3): T4
---@param fifth string|fun(v: T4): O ---@param fifth fun(v: T4): O
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(first, second, third, fourth, fifth) function f.chain(first, second, third, fourth, fifth)
error {first, second, third, fourth, fifth} error {first, second, third, fourth, fifth}
end end
---@generic I, O ---@generic I, O
---@param all { [1]: (string|fun(v: I): O) } ---@param all {[1]: (fun(v: I): O)}
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(all) function f.chain(all)
error(all) error(all)
end end
---@generic I, T, O ---@generic I, T, O
---@param all { [1]: (string|fun(v: I): T), [2]: (string|fun(v: T): O) } ---@param all {[1]: (fun(v: I): T), [2]: (fun(v: T): O)}
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(all) function f.chain(all)
error(all) error(all)
end end
---@generic I, T1, T2, O ---@generic I, T1, T2, O
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): O) } ---@param all {[1]: (fun(v: I): T1), [2]: (fun(v: T1): T2), [3]: (fun(v: T2): O)}
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(all) function f.chain(all)
error(all) error(all)
end end
---@generic I, T1, T2, T3, O ---@generic I, T1, T2, T3, O
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): T3), [4]: (string|fun(v: T3): O) } ---@param all {[1]: (fun(v: I): T1), [2]: (fun(v: T1): T2), [3]: (fun(v: T2): T3), [4]: (fun(v: T3): O)}
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(all) function f.chain(all)
error(all) error(all)
end end
---@generic I, T1, T2, T3, T4, O ---@generic I, T1, T2, T3, T4, O
---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|fun(v: T2): T3), [4]: (string|fun(v: T3): T4), [4]: (string|fun(v: T4): O) } ---@param all {[1]: (fun(v: I): T1), [2]: (fun(v: T1): T2), [3]: (fun(v: T2): T3), [4]: (fun(v: T3): T4), [4]: (fun(v: T4): O)}
---@return fun(v: I): O ---@return fun(v: I): O
---@nodiscard
function f.chain(all) function f.chain(all)
error(all) error(all)
end end
@ -114,7 +104,7 @@ local none
---@return T ---@return T
---@nodiscard ---@nodiscard
local function autofunc(func) local function autofunc(func)
return type(func) == "string" and f.indices(func) or func or f.id return type(func) == "string" and f.index(func) or func or f.id
end end
---@generic K, V ---@generic K, V
@ -128,6 +118,13 @@ local function autopairs(tbl, fpairs)
return (fpairs or (tbl[#tbl] and ipairs or pairs))(tbl) return (fpairs or (tbl[#tbl] and ipairs or pairs))(tbl)
end end
---@param any any
---@return boolean
---@nodiscard
local function is_f(any)
return type(any) == "table" and any.from == f.from and any.new == f.new
end
--- Always returns nil. --- Always returns nil.
---@return nil ---@return nil
---@nodiscard ---@nodiscard
@ -160,7 +157,6 @@ end
---@generic T ---@generic T
---@param value T ---@param value T
---@return fun(T): boolean ---@return fun(T): boolean
---@nodiscard
function f.eq(value) function f.eq(value)
return function(v) return function(v)
return value == v return value == v
@ -170,7 +166,6 @@ end
---@generic T ---@generic T
---@param value T ---@param value T
---@return fun(T): boolean ---@return fun(T): boolean
---@nodiscard
function f.nq(value) function f.nq(value)
return function(v) return function(v)
return value ~= v return value ~= v
@ -195,15 +190,6 @@ function f.const(v)
end end
end end
---@param i integer
---@return fun(...: any): any
---@nodiscard
function f.arg(i)
return function(...)
return ({...})[i]
end
end
---@generic K, V ---@generic K, V
---@param v K ---@param v K
---@return fun(x: { [K]: V }): V ---@return fun(x: { [K]: V }): V
@ -242,32 +228,17 @@ function f.indices(v)
end end
end end
---@param any any
---@return boolean
---@nodiscard
function f.isf(any)
return type(any) == "table" and any.from == f.from and any.new == f.new
end
f[true and "chain"] = function(...) f[true and "chain"] = function(...)
local ret for _, v in ipairs(...) do
for _, v in ipairs {...} do
if type(v) == "table" then if type(v) == "table" then
for _, vv in ipairs(v) do for _, vv in ipairs(v) do
local copy = ret
vv = autofunc(vv)
ret = ret and function(...) ret = ret and function(...)
return vv(copy(...)) return vv(ret(...))
end or vv end or vv
end end
else else
local copy = ret
v = autofunc(v)
ret = ret and function(...) ret = ret and function(...)
return v(copy(...)) return v(ret(...))
end or v end or v
end end
end end
@ -284,7 +255,6 @@ function f.new(fnext)
return { return {
all = f.all, all = f.all,
any = f.any, any = f.any,
arg = f.arg,
chain = f.chain, chain = f.chain,
concat = f.concat, concat = f.concat,
const = f.const, const = f.const,
@ -299,14 +269,12 @@ function f.new(fnext)
index = f.index, index = f.index,
index_into = f.index_into, index_into = f.index_into,
indices = f.indices, indices = f.indices,
isf = f.isf,
keys = f.keys, keys = f.keys,
map = f.map, map = f.map,
new = f.new, new = f.new,
next = fnext or f.noop, next = fnext or f.noop,
noop = f.noop, noop = f.noop,
nq = f.nq, nq = f.nq,
peek = f.peek,
pun = f.pun, pun = f.pun,
skip = f.skip, skip = f.skip,
slice = f.slice, slice = f.slice,
@ -386,7 +354,7 @@ function f:concat(...)
local sum, last = 0, 0 local sum, last = 0, 0
for i = 1, #fs do for i = 1, #fs do
fs[i] = f.isf(fs[i]) and fs[i] or f.from(fs[i]) fs[i] = is_f(fs[i]) and fs[i] or f.from(fs[i])
end end
return f.new(function() return f.new(function()
@ -414,30 +382,12 @@ function f:concat(...)
end) end)
end end
---@generic K, V
---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any
---@return F|{ [K]: V }
---@nodiscard
function f:peek(func)
func = autofunc(func)
return f.new(function()
local k, v = self:next()
if k ~= nil then
func(v, k)
return k, v
end
end)
end
---@generic K, V, U ---@generic K, V, U
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): U ---@param func F|fun(v: V, k: K): U
---@return F|{ [K]: U } ---@return F|{ [K]: U }
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
function f:map(func) function f:map(func)
func = autofunc(func) func = autofunc(func)
@ -452,11 +402,61 @@ end
---@generic K, V, U ---@generic K, V, U
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): { [any]: U } ---@param func F|fun(v: V, k: K): { [any]: U }
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V) ---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return F|{ [K]: U } ---@return F|{ [K]: U }
---@overload fun(self: F|{ [K]: V }, func: string, fpairs?: fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)): F|{ [K]: U }
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string, fpairs?: fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)): F|{ [K]: U }
function f:flatmap(func, fpairs)
-- local i = 0
local vt, vk, vv, vp
func = autofunc(func)
return f.new(function()
if vk then
vk, vv = vp(vt, vk)
if vk ~= nil then
-- i = i + 1
-- return i, vv
return vk, vv
end
end
while true do
local k, v = self:next()
if k == nil then
return
end
v = func(v, k)
if type(v) ~= "table" then
-- i = i + 1
-- return i, v
return k, v
end
vp, vt, vk = autopairs(v, fpairs)
vk, vv = vp(vt, vk)
if vk ~= nil then
-- i = i + 1
-- return i, vv
return vk, vv
end
end
end)
end
---@generic K, V, U
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): { [any]: U }
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string, fpairs?: fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)): F|{ [K]: U }
function f:flatmap(func, fpairs) function f:flatmap(func, fpairs)
-- local i = 0 -- local i = 0
local vt, vk, vv, vp local vt, vk, vv, vp
@ -502,7 +502,7 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): boolean ---@param func F|fun(v: V, k: K): boolean
---@param is? any ---@param is? any
---@return F|{ [K]: V } ---@return F|{ [K]: V }
---@nodiscard ---@nodiscard
@ -665,10 +665,10 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any ---@param func F|fun(v: V, k: K): any
---@return boolean|V ---@return boolean|V
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
function f:any(func) function f:any(func)
func = autofunc(func) func = autofunc(func)
@ -683,10 +683,10 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any ---@param func F|fun(v: V, k: K): any
---@return boolean|V ---@return boolean|V
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
function f:all(func) function f:all(func)
func = autofunc(func) func = autofunc(func)
@ -701,10 +701,10 @@ end
---@generic K, V ---@generic K, V
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func fun(v: V, k: K): any ---@param func F|fun(v: V, k: K): any
---@return integer ---@return integer
---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard ---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): integer
function f:count(func) function f:count(func)
local ret = 0 local ret = 0
func = autofunc(func) func = autofunc(func)

View file

@ -19,21 +19,15 @@ Balatest = Balatest
--- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) } --- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) }
Big = Big Big = Big
--- @type table? --- @type table
CardSleeves = CardSleeves CardSleeves = CardSleeves
--- @type table|fun(obj: SMODS.Back): SMODS.Back --- @type fun(obj: SMODS.Back): SMODS.Back
CardSleeves.Sleeve = CardSleeves.Sleeve CardSleeves.Sleeve = CardSleeves.Sleeve
--- @type {aliases: { [string]: [string] }}
Cryptid = Cryptid
--- @type fun(area: CardArea, ...: ...): Card --- @type fun(area: CardArea, ...: ...): Card
create_card_for_shop = create_card_for_shop create_card_for_shop = create_card_for_shop
--- @type boolean|table
G.Bakery_charm_area.cards[1].ability.extra = G.Bakery_charm_area.cards[1].ability.extra
SMODS.Mods.Roland.config = require "config" SMODS.Mods.Roland.config = require "config"
---@type userdata|{getWidth: fun(self: self): number} ---@type userdata|{getWidth: fun(self: self): number}

View file

@ -1,59 +1,5 @@
local f = assert(SMODS.load_file "src/lib/funky.lua")() or require "lib.funky" local f = assert(SMODS.load_file "src/lib/funky.lua")() or require "lib.funky"
---@param v string
---@return Card|Tag
local function add(v)
if not G.P_TAGS[v] then
return SMODS.add_card {no_edition = true, key = v}
end
local tag = Tag(v)
if tag.name == "Orbital Tag" then
local hands = f(G.GAME.hands):where "visible":keys():table()
tag.ability.orbital_hand = pseudorandom_element(hands, pseudoseed "Roland_c_orbital_tag")
end
add_tag(tag)
return tag
end
local function simplify(x)
return type(x) == "string" and x:lower():gsub("%s", ""):gsub("_", ""):gsub("^the", "") or x
end
local function flatten(v)
return type(v) ~= "table" and {v} or (f.isf(v) and v:table() or v)
end
---@param it string
local function find(it)
if (G.P_CENTERS[it] or {}).config then
return it
end
local match = simplify(it)
local pool = f(G.P_CENTER_POOLS):any(f.chain(f.arg(2), simplify, f.eq(match)))
if type(pool) == "table" and next(pool) then
return pseudorandom_element(pool, pseudoseed "Roland_c").key
end
if Cryptid and Cryptid.aliases and Cryptid.aliases[it] then
return Cryptid.aliases[it]
end
return f(G.localization.descriptions)
:flatmap(flatten)
:where(type, "table")
:where(f.chain(f.arg(2), f.index_into(G.P_CENTERS), "config"))
:where(function(v, k)
return match == simplify(k) or match == simplify(v.name)
end)
:keys()
:any()
end
local function protect(fun) local function protect(fun)
return function() return function()
local res, ret = pcall(fun) local res, ret = pcall(fun)
@ -110,35 +56,4 @@ local function u()
G.STATE ~= G.STATES.PLAY_TAROT) G.STATE ~= G.STATES.PLAY_TAROT)
end end
--- Creates one or more cards. return {f, q, u}
---@param ... any
---@return Card|Tag|(Card|Tag[])
local function c(...)
local cards = f {...}
:flatmap(flatten)
:where(type, "string")
:map(string.lower)
:map(find)
:where(f.id)
:map(add)
:values()
:table()
return #cards > 1 and cards or cards[1]
end
return {f, q, u, setmetatable({}, {
__call = function(_, ...)
return c(...)
end,
__index = function(_, k)
return c(k)
end,
__newindex = function(_, k, v)
local n = tonumber(v)
for _ = 1, type(n) == "number" and n or 1 do
c(k)
end
end,
})}

View file

@ -52,28 +52,14 @@ q {
-- G.ARGS.LOC_COLOURS["Bakery_credit_bg_Roland_" .. k] = v.bg -- G.ARGS.LOC_COLOURS["Bakery_credit_bg_Roland_" .. k] = v.bg
end) end)
if not SMODS.Mods.DebugPlus or not SMODS.Mods.Roland.config.import_funky then if SMODS.Mods.DebugPlus and SMODS.Mods.Roland.config.import_funky then
return _G.f, _G.q, _G.u = unpack(qol)
end end
_G.f, _G.q, _G.u, _G.c = unpack(qol)
end, end,
} }
f { f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"}
"challenge", :each(function(v)
"spectral",
"edition",
"tweaks",
"blind",
"charm",
"joker",
"tarot",
"back",
"seal",
"tag",
"voucher",
}:each(function(v)
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol) assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
end) end)

View file

@ -69,15 +69,15 @@ spectral {
table.insert(info_queue, G.P_CENTERS.m_stone) table.insert(info_queue, G.P_CENTERS.m_stone)
end, end,
can_use = function() can_use = function()
return next(G.hand.cards) and u() return u() and not not next(G.hand.cards)
end, end,
use = function(_, card) use = function(_, card)
q { q {
trigger = "after", trigger = "after",
delay = 0.4, delay = 0.4,
func = function() func = function()
card:juice_up(0.3, 0.5)
play_sound("tarot1") play_sound("tarot1")
card:juice_up(0.3, 0.5)
end, end,
} }
@ -89,8 +89,8 @@ spectral {
delay = 0.15, delay = 0.15,
func = function() func = function()
v:flip() v:flip()
v:juice_up(0.3, 0.3)
play_sound("card1", percent) play_sound("card1", percent)
v:juice_up(0.3, 0.3)
return true return true
end, end,
} }
@ -110,8 +110,8 @@ spectral {
delay = 0.15, delay = 0.15,
func = function() func = function()
v:flip() v:flip()
v:juice_up(0.3, 0.3)
play_sound("tarot2", percent, 0.6) play_sound("tarot2", percent, 0.6)
v:juice_up(0.3, 0.3)
return true return true
end, end,
} }
@ -155,7 +155,7 @@ spectral {
return {vars = {card.ability.extra.amount}} return {vars = {card.ability.extra.amount}}
end, end,
can_use = function() can_use = function()
return next(G.playing_cards) and u() return #G.playing_cards > 1 and not not u()
end, end,
use = function(_, card) use = function(_, card)
local function destructible(v) local function destructible(v)

View file

@ -192,9 +192,9 @@ Balatest.TestPlay {
} }
Balatest.TestPlay { Balatest.TestPlay {
category = {"blind", "mitotic"}, category = {"blind", "xerox"},
name = "mitotic", name = "xerox",
blind = "bl_Roland_mitotic", blind = "bl_Roland_xerox",
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}
end, end,
@ -204,9 +204,9 @@ Balatest.TestPlay {
} }
Balatest.TestPlay { Balatest.TestPlay {
category = {"blind", "mitotic"}, category = {"blind", "xerox"},
name = "mitotic_disabled", name = "xerox_disabled",
blind = "bl_Roland_mitotic", blind = "bl_Roland_xerox",
jokers = {"j_chicot"}, jokers = {"j_chicot"},
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}

View file

@ -2,6 +2,330 @@ if not Balatest then
return return
end end
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_none",
jokers = {"j_Roland_escapey"},
execute = function() end,
assert = function()
Balatest.assert(not G.jokers.cards[1].config.center:Bakery_can_use(G.jokers.cards[1]))
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_one_consumable",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_two_consumables",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength", "c_strength"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 3)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_one_tag",
jokers = {"j_Roland_escapey"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_two_tags",
jokers = {"j_Roland_escapey"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 3)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_consumables_and_tags",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_partial_selected_consumables_and_tags",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength", "c_strength"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.consumeables:add_to_highlighted(G.consumeables.cards[1])
end)
Balatest.wait()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "escape"},
name = "escapey_full_selected_consumables_and_tags",
jokers = {"j_Roland_escapey"},
consumeables = {"c_strength"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.consumeables:add_to_highlighted(G.consumeables.cards[1])
end)
Balatest.wait()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(G.GAME.hands["High Card"].level, 3)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_fusion",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_consumable_takes_precedence",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
consumeables = {"c_strength"},
execute = function()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 2)
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_tag_takes_precedence",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
no_auto_start = true,
execute = function()
Balatest.skip_blind("tag_investment")
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 2)
Balatest.assert_eq(G.GAME.hands["High Card"].level, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_fusion_takes_precedence",
jokers = {"j_Roland_escapey", "j_Roland_escapey"},
consumeables = {"c_strength"},
execute = function()
Balatest.q(function()
G.consumeables:add_to_highlighted(G.consumeables.cards[1])
end)
Balatest.wait()
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 2)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_scribe_fusion",
jokers = {"j_Roland_escapey"},
execute = function()
if not G.P_CENTERS.c_Bakery_Scribe then
sendWarnMessage("escapey_scribe_fusion cannot run without c_Bakery_Scribe, skipping test.")
return
end
Balatest.q(function()
local scribe = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_Bakery_Scribe", "balatest")
scribe:add_to_deck()
G.consumeables:emplace(scribe)
end)
Balatest.wait()
Balatest.q(function()
G.jokers:add_to_highlighted(G.jokers.cards[1])
end)
Balatest.wait()
Balatest.use(function() return G.consumeables.cards[1] end)
Balatest.q(function()
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[1]}}
end)
Balatest.wait()
end,
assert = function()
if not G.P_CENTERS.c_Bakery_Scribe then
return
end
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 1)
end,
}
Balatest.TestPlay {
category = {"joker", "escapey", "fuse"},
name = "escapey_scribe_fusion_alt",
jokers = {"j_Roland_escapey"},
execute = function()
if not G.P_CENTERS.c_Bakery_Scribe then
sendWarnMessage("escapey_scribe_fusion_alt cannot run without c_Bakery_Scribe, skipping test.")
return
end
Balatest.q(function()
local scribe = create_card(nil, G.consumeables, nil, nil, nil, nil, "c_Bakery_Scribe", "balatest")
scribe:add_to_deck()
G.consumeables:emplace(scribe)
end)
Balatest.wait()
Balatest.q(function()
G.jokers:add_to_highlighted(G.jokers.cards[1])
end)
Balatest.wait()
Balatest.use(function() return G.consumeables.cards[1] end)
Balatest.q(function()
G.jokers:add_to_highlighted(G.jokers.cards[2])
G.FUNCS.Bakery_use_joker {config = {ref_table = G.jokers.cards[2]}}
end)
Balatest.wait()
end,
assert = function()
if not G.P_CENTERS.c_Bakery_Scribe then
return
end
Balatest.assert_eq(#G.jokers.cards, 1)
Balatest.assert_eq(G.jokers.cards[1].sell_cost, 8)
Balatest.assert_eq(G.jokers.cards[1].ability.extra.levels, 1)
end,
}
Balatest.TestPlay { Balatest.TestPlay {
category = {"joker", "martingale"}, category = {"joker", "martingale"},
name = "martingale_oops", name = "martingale_oops",
@ -10,7 +334,7 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(7) Balatest.assert_chips(7 * 2)
end, end,
} }
@ -23,6 +347,6 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(1 / 0) Balatest.assert_chips(7 * math.pow(2, 32))
end, end,
} }

View file

@ -2,6 +2,22 @@ if not Balatest then
return return
end end
Balatest.TestPlay {
category = {"spectral", "afterimage"},
name = "afterimage",
consumeables = {"c_Roland_afterimage"},
deck = {cards = {{s = "S", r = "2"}}},
execute = function()
Balatest.highlight({"2S"})
Balatest.use(G.consumeables.cards[1])
Balatest.end_round()
end,
assert = function()
Balatest.assert_eq(G.hand.config.card_limit, 51)
Balatest.assert(G.deck.cards[1].edition.negative)
end,
}
Balatest.TestPlay { Balatest.TestPlay {
category = {"spectral", "void"}, category = {"spectral", "void"},
name = "spectral", name = "spectral",

View file

@ -2,16 +2,11 @@ local f, q = (... or require "lib.shared")[1], (... or require "lib.shared")[2]
SMODS.Joker:take_ownership("joker", {cost = 1}, true) SMODS.Joker:take_ownership("joker", {cost = 1}, true)
local orig_set_debuff = Card.set_debuff local orig_set_debuff = Card.set_debuff
function Card:set_debuff(should_debuff, ...) function Card:set_debuff(...)
if self.ability.Roland_crimson ~= nil then
self.ability.Roland_crimson = not not should_debuff
return
end
if SMODS.get_enhancements(self).m_wild and SMODS.Mods.Roland.config.no_wild_debuff then if SMODS.get_enhancements(self).m_wild and SMODS.Mods.Roland.config.no_wild_debuff then
self.debuff = false self.debuff = false
else else
orig_set_debuff(self, should_debuff, ...) orig_set_debuff(self, ...)
end end
end end

View file

@ -1,44 +0,0 @@
local voucher = (function()
local x = 0
---@param tbl SMODS.Voucher|{attributes: Attributes[]}
---@return SMODS.Voucher
return function(tbl)
tbl.pos = {x = x, y = 0}
tbl.atlas = "voucher"
tbl.cost = 10
x = x + 1
return SMODS.Voucher(tbl)
end
end)()
SMODS.Atlas {
px = 71,
py = 95,
key = "voucher",
path = "voucher.png",
}
voucher {
key = "ceres",
pronouns = "it_its",
config = {extra = {amount = 1, hand_type = "Flush House"}},
attributes = {"planet", "passive", "hand_type", "space"},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.amount}}
end,
in_pool = function(self)
return G.GAME.hands[self.config.extra.hand_type].visible
end,
}
voucher {
key = "neptune",
pronouns = "it_its",
requires = {"v_Roland_ceres"},
config = {extra = {amount = 2, hand_type = "Straight Flush"}},
attributes = {"planet", "passive", "hand_type", "space"},
loc_vars = function(_, _, card)
return {vars = {card.ability.extra.amount}}
end,
}