Compare commits

...

17 commits
2.9.12 ... main

34 changed files with 396 additions and 527 deletions

View file

@ -65,4 +65,8 @@
"unused-local": "Error", "unused-local": "Error",
"unused-vararg": "Error", "unused-vararg": "Error",
}, },
"Lua.runtime.version": "LuaJIT",
"Lua.workspace.library": [
"${3rd}/love2d/library"
],
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 77 KiB

BIN
assets/1x/voucher.png Normal file

Binary file not shown.

After

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: 82 KiB

After

Width:  |  Height:  |  Size: 89 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

BIN
assets/2x/voucher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -495,6 +495,22 @@ 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.12", "version": "2.9.28",
"badge_colour": "8BE9FD", "badge_colour": "8BE9FD",
"display_name": "Roland", "display_name": "Roland",
"main_file": "src/main.lua", "main_file": "src/main.lua",

View file

@ -27,9 +27,8 @@ local back = (function()
tbl.atlas = "back" tbl.atlas = "back"
x = x + 1 x = x + 1
local back = q(SMODS.Back(tbl)) local back = q(SMODS.Back(tbl))
local sleeve = (_G["CardSleeves"] or {}).Sleeve
local _ = sleeve and sleeve { local _ = CardSleeves and CardSleeves.Sleeve {
key = key, key = key,
pos = tbl.pos, pos = tbl.pos,
atlas = "sleeve", atlas = "sleeve",
@ -61,7 +60,7 @@ SMODS.Atlas {
py = 95, py = 95,
} }
local _ = _G["CardSleeves"] and SMODS.Atlas { local _ = CardSleeves and SMODS.Atlas {
key = "sleeve", key = "sleeve",
path = "sleeve.png", path = "sleeve.png",
px = 73, px = 73,

View file

@ -23,10 +23,7 @@ SMODS.Atlas {
atlas_table = "ANIMATION_ATLAS", atlas_table = "ANIMATION_ATLAS",
} }
SMODS.Sound { SMODS.Sound {key = "kick", path = "kick.ogg"}
key = "kick",
path = "kick.ogg",
}
local function common_rank() local function common_rank()
local tally, to_name = {}, {} local tally, to_name = {}, {}
@ -55,7 +52,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 not not (e and next(e)) return e and next(e)
end end
local function set_freeze(state) local function set_freeze(state)
@ -242,16 +239,16 @@ blind {
disable = function(self) disable = function(self)
self:defeat() self:defeat()
end, end,
calculate = function(self, b) calculate = function(self, b, context)
return not b.disabled and self.cards() return not b.disabled and
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(function(v) return f(G):where(getmetatable, CardArea):flatmap("cards", ipairs)
return type(v) == "table" and type(v.cards) == "table"
end):flatmap("cards", ipairs)
end, end,
} }
@ -353,6 +350,16 @@ 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
@ -476,15 +483,15 @@ local venerable_visage = blind {
vitriol = function(b) vitriol = function(b)
local vitriol = SMODS.Mods.Roland.config.vitriol local vitriol = SMODS.Mods.Roland.config.vitriol
local resize_to_w, resize_to_h = 320, 200 local resize_to_w, resize_to_h = 320, 200
local is_fullscreen = _G["love"].window.getFullscreen() local is_fullscreen = love.window.getFullscreen()
if vitriol then if vitriol then
_G["love"].window.setFullscreen(false) love.window.setFullscreen(false)
delay(1.5) delay(1.5)
end end
local function jitter() local function jitter()
local x, y = _G["love"].window.getDesktopDimensions() local x, y = love.window.getDesktopDimensions()
return pseudorandom(pseudoseed "RolandVenerableVisageX", 0, x) - x / 2, return pseudorandom(pseudoseed "RolandVenerableVisageX", 0, x) - x / 2,
pseudorandom(pseudoseed "RolandVenerableVisageY", 0, y) - y / 2 pseudorandom(pseudoseed "RolandVenerableVisageY", 0, y) - y / 2
@ -512,15 +519,16 @@ local venerable_visage = blind {
play_sound("gong", v) play_sound("gong", v)
end) end)
---@type number, number, table
local w, h, flags = love.window.getMode()
local len = #G.playing_cards local len = #G.playing_cards
local w, h, flags = _G["love"].window.getMode()
if vitriol then if vitriol then
_G["love"].window.setMode(resize_to_w, resize_to_h) love.window.setMode(resize_to_w, resize_to_h)
_G["love"].resize(resize_to_w, resize_to_h) love.resize(resize_to_w, resize_to_h)
end end
local x, y = _G["love"].window.getPosition() local x, y = love.window.getPosition()
f(G.playing_cards):each(function(v, i) f(G.playing_cards):each(function(v, i)
q { q {
@ -529,7 +537,7 @@ local venerable_visage = blind {
func = function() func = function()
if vitriol then if vitriol then
local x_random, y_random = jitter() local x_random, y_random = jitter()
_G["love"].window.setPosition(x + x_random * i / len, y + y_random * i / len) love.window.setPosition(x + x_random * i / len, y + y_random * i / len)
end end
v:start_dissolve() v:start_dissolve()
@ -543,10 +551,10 @@ local venerable_visage = blind {
trigger = "before", trigger = "before",
delay = 1.5, delay = 1.5,
func = function() func = function()
_G["love"].window.setPosition(x, y) love.window.setPosition(x, y)
_G["love"].window.setMode(w, h, flags) love.window.setMode(w, h, flags)
_G["love"].resize(w, h) love.resize(w, h)
_G["love"].window.setFullscreen(is_fullscreen) love.window.setFullscreen(is_fullscreen)
end, end,
} }

View file

@ -188,7 +188,7 @@ SMODS.Challenge {
end) end)
local mod = G.GAME.modifiers local mod = G.GAME.modifiers
mod.Roland_Eternally_Crimson = not context.setting_blind and mod.Roland_Eternally_Crimson or nil mod.Roland_Eternally_Crimson = context.setting_blind and true or mod.Roland_Eternally_Crimson
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_Roland_violet"}}, jokers = {{id = "j_joker"}, {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].config.center.ability.extra.active then G.Bakery_charm_area.cards[1].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 not card.edition or not card.edition.Roland_frozen then if not (card or {}).Roland_frozen or not (card.edition or {}).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 not card.edition or not card.edition.Roland_frozen then if not (card or {}).Roland_frozen or not (card.edition or {}).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,11 +488,9 @@ joker {
end end
end end
local key = card.config.center.key return SMODS.merge_effects(
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 ~= key return v.config.center.key ~= card.config.center.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)
@ -500,8 +498,6 @@ joker {
return v return v
end):values():table() end):values():table()
) )
return merged
end, end,
} }
@ -652,7 +648,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)
f(G.jokers.cards):each(function(v) local _ = G.jokers and 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)
@ -679,7 +675,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()
f(G.jokers.cards, ipairs_reversed):where(is_frozen, false):each(function(v) local _ = G.jokers and 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
@ -688,11 +684,13 @@ 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
v.ability.Roland_crimson = not not v.debuff local debuff = 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
v:set_debuff(v.ability.Roland_crimson) local 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,
@ -759,7 +757,7 @@ joker {
key = "violet", key = "violet",
pronouns = "she_they", pronouns = "she_they",
idea = "hamester", idea = "hamester",
config = {extra = {before = 0.1, xmult = 9}}, config = {extra = {before = 0.1, xmult = 6}},
pixel_size = {w = 68, h = 68}, pixel_size = {w = 68, h = 68},
attributes = {"xmult"}, attributes = {"xmult"},
cost = 6, cost = 6,
@ -938,10 +936,14 @@ 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
return {card = card, xmult = extra.odds} local n, d = SMODS.get_probability_vars(card, 1, extra.odds, self.key)
return n < d and {card = card, xmult = extra.odds} or nil
end end
for _ = 1, 256 do -- 2^38 is the first iteration displayed as scientific notation.
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
@ -953,9 +955,18 @@ 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,
} }
@ -1247,7 +1258,7 @@ joker {
return {card = card, numerator = extra.probability} return {card = card, numerator = extra.probability}
end end
if context.end_of_round and extra.probability ~= extra.reset then if context.end_of_round and not context.repetition 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,81 +9,91 @@ local f = {}
if not f then if not f then
---@generic I, O ---@generic I, O
---@param first fun(v: I): O ---@param first string|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 fun(v: I): T ---@param first string|fun(v: I): T
---@param second fun(v: T): O ---@param second string|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 fun(v: I): T1 ---@param first string|fun(v: I): T1
---@param second fun(v: T1): T2 ---@param second string|fun(v: T1): T2
---@param third fun(v: T2): O ---@param third string|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 fun(v: I): T1 ---@param first string|fun(v: I): T1
---@param second fun(v: T1): T2 ---@param second string|fun(v: T1): T2
---@param third fun(v: T2): T3 ---@param third string|fun(v: T2): T3
---@param fourth fun(v: T3): O ---@param fourth string|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 fun(v: I): T1 ---@param first string|fun(v: I): T1
---@param second fun(v: T1): T2 ---@param second string|fun(v: T1): T2
---@param third fun(v: T2): T3 ---@param third string|fun(v: T2): T3
---@param fourth fun(v: T3): T4 ---@param fourth string|fun(v: T3): T4
---@param fifth fun(v: T4): O ---@param fifth string|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]: (fun(v: I): O)} ---@param all { [1]: (string|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]: (fun(v: I): T), [2]: (fun(v: T): O)} ---@param all { [1]: (string|fun(v: I): T), [2]: (string|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]: (fun(v: I): T1), [2]: (fun(v: T1): T2), [3]: (fun(v: T2): O)} ---@param all { [1]: (string|fun(v: I): T1), [2]: (string|fun(v: T1): T2), [3]: (string|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]: (fun(v: I): T1), [2]: (fun(v: T1): T2), [3]: (fun(v: T2): T3), [4]: (fun(v: 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) }
---@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]: (fun(v: I): T1), [2]: (fun(v: T1): T2), [3]: (fun(v: T2): T3), [4]: (fun(v: T3): T4), [4]: (fun(v: 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) }
---@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
@ -104,7 +114,7 @@ local none
---@return T ---@return T
---@nodiscard ---@nodiscard
local function autofunc(func) local function autofunc(func)
return type(func) == "string" and f.index(func) or func or f.id return type(func) == "string" and f.indices(func) or func or f.id
end end
---@generic K, V ---@generic K, V
@ -118,13 +128,6 @@ 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
@ -157,6 +160,7 @@ 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
@ -166,6 +170,7 @@ 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
@ -190,6 +195,15 @@ 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
@ -228,17 +242,32 @@ 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(...)
for _, v in ipairs(...) do local ret
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(ret(...)) return vv(copy(...))
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(ret(...)) return v(copy(...))
end or v end or v
end end
end end
@ -255,6 +284,7 @@ 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,
@ -269,12 +299,14 @@ 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,
@ -354,7 +386,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] = is_f(fs[i]) and fs[i] or f.from(fs[i]) fs[i] = f.isf(fs[i]) and fs[i] or f.from(fs[i])
end end
return f.new(function() return f.new(function()
@ -382,12 +414,30 @@ 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 F|fun(v: V, k: K): U ---@param func fun(v: V, k: K): U
---@return F|{ [K]: U } ---@return F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U } ---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
---@nodiscard
function f:map(func) function f:map(func)
func = autofunc(func) func = autofunc(func)
@ -402,61 +452,11 @@ end
---@generic K, V, U ---@generic K, V, U
---@param self F|{ [K]: V } ---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): { [any]: U } ---@param func 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 }
---@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 } ---@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 ---@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 F|fun(v: V, k: K): boolean ---@param func 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 F|fun(v: V, k: K): any ---@param func fun(v: V, k: K): any
---@return boolean|V ---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V ---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
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 F|fun(v: V, k: K): any ---@param func fun(v: V, k: K): any
---@return boolean|V ---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V ---@overload fun(self: F|{ [K]: V }, func: string?): boolean|V
---@nodiscard
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 F|fun(v: V, k: K): any ---@param func fun(v: V, k: K): any
---@return integer ---@return integer
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): integer ---@overload fun(self: F|{ [K]: V }, func: string): integer
---@nodiscard
function f:count(func) function f:count(func)
local ret = 0 local ret = 0
func = autofunc(func) func = autofunc(func)

View file

@ -1,19 +1,8 @@
---@meta ---@meta
---@alias Attributes "mult"|"chips"|"xmult"|"xchips"|"score"|"xscore"|"blindsize"|"xblindsize"|"balance"|"swap"|"retrigger"|"scaling"|"reset"|"suit"|"diamonds"|"hearts"|"spades"|"clubs"|"hand_type"|"rank"|"ace"|"two"|"three"|"four"|"five"|"six"|"seven"|"eight"|"nine"|"ten"|"jack"|"queen"|"king"|"face"|"economy"|"generation"|"destroy_card"|"hands"|"discard"|"hand_size"|"chance"|"joker_slot"|"mod_chance"|"copying"|"full_deck"|"passive"|"joker"|"tarot"|"planet"|"spectral"|"enhancements"|"seals"|"editions"|"tag"|"skip"|"modify_card"|"perma_bonus"|"prevents_death"|"boss_blind"|"reroll"|"on_sell"|"sell_value"|"food"|"space"|"bakery_double_sided"|"bakery_usable"|"bakery_werewolf" ---@alias Attributes "mult"|"chips"|"xmult"|"xchips"|"score"|"xscore"|"blindsize"|"xblindsize"|"balance"|"swap"|"retrigger"|"scaling"|"reset"|"suit"|"diamonds"|"hearts"|"spades"|"clubs"|"hand_type"|"rank"|"ace"|"two"|"three"|"four"|"five"|"six"|"seven"|"eight"|"nine"|"ten"|"jack"|"queen"|"king"|"face"|"economy"|"generation"|"destroy_card"|"hands"|"discard"|"hand_size"|"chance"|"joker_slot"|"mod_chance"|"copying"|"full_deck"|"passive"|"joker"|"tarot"|"planet"|"spectral"|"enhancements"|"seals"|"editions"|"tag"|"skip"|"modify_card"|"perma_bonus"|"prevents_death"|"boss_blind"|"reroll"|"on_sell"|"sell_value"|"food"|"space"|"bakery_double_sided"|"bakery_usable"|"bakery_werewolf"
SMODS.Mods.Roland.config = require "config"
---@type Card[] ---@type Card[]
CardArea.cards = CardArea.cards CardArea.cards = CardArea.cards
---@type userdata|{getWidth: fun(self: self): number}
SMODS.Atlas.image_data = SMODS.Atlas.image_data
--- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) }
_G["Big"] = _G["Big"]
--- @type fun(area: CardArea, ...: ...): Card
create_card_for_shop = create_card_for_shop
--- @overload fun(tbl: SMODS.Joker): SMODS.GameObject --- @overload fun(tbl: SMODS.Joker): SMODS.GameObject
Bakery_API.Charm = Bakery_API.Charm Bakery_API.Charm = Bakery_API.Charm
@ -24,6 +13,38 @@ function Bakery_API.credit(obj)
error(obj) error(obj)
end end
---@type table
Balatest = Balatest
--- @type { constants?: { TEN: table }, new: (fun(self: self, arr?: number[], sign?: number, noNormalize?: boolean): table), pow: (fun(x: number, y: number): number) }
Big = Big
--- @type table?
CardSleeves = CardSleeves
--- @type table|fun(obj: SMODS.Back): SMODS.Back
CardSleeves.Sleeve = CardSleeves.Sleeve
--- @type {aliases: { [string]: [string] }}
Cryptid = Cryptid
--- @type fun(area: CardArea, ...: ...): Card
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"
---@type userdata|{getWidth: fun(self: self): number}
SMODS.Atlas.image_data = SMODS.Atlas.image_data
--- @type table
Talisman = Talisman
--- @type fun(obj: any): number
to_number = to_number
-- This exists to remove the @deprecated warning. -- This exists to remove the @deprecated warning.
---Returns the elements from the given `list`. This function is equivalent to ---Returns the elements from the given `list`. This function is equivalent to
---```lua ---```lua

View file

@ -1,5 +1,59 @@
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)
@ -21,14 +75,13 @@ local function protect_ev(fun)
return Event { return Event {
blocking = false, blocking = false,
no_delete = true, no_delete = true,
func = function() func = protect(function()
if not Bakery_API or not Bakery_API.credit then if not Bakery_API or not Bakery_API.credit then
return false return false
end end
Bakery_API.credit(fun) Bakery_API.credit(fun)
return true end),
end,
} }
else else
fun.func = protect(fun.func or fun[1]) fun.func = protect(fun.func or fun[1])
@ -57,4 +110,35 @@ local function u()
G.STATE ~= G.STATES.PLAY_TAROT) G.STATE ~= G.STATES.PLAY_TAROT)
end end
return {f, q, u} --- Creates one or more cards.
---@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,18 +52,32 @@ 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 SMODS.Mods.DebugPlus and SMODS.Mods.Roland.config.import_funky then if not SMODS.Mods.DebugPlus or not SMODS.Mods.Roland.config.import_funky then
_G.f, _G.q, _G.u = unpack(qol) return
end end
_G.f, _G.q, _G.u, _G.c = unpack(qol)
end, end,
} }
f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"} f {
:each(function(v) "challenge",
"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)
if _G["Balatest"] then if Balatest then
f {"joker", "blind", "spectral"}:each(function(v) f {"joker", "blind", "spectral"}:each(function(v)
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol) assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
end) end)
@ -107,7 +121,7 @@ function SMODS.current_mod.config_tab()
toggle "equinox_assist", toggle "equinox_assist",
SMODS.Mods.DebugPlus and toggle "import_funky", SMODS.Mods.DebugPlus and toggle "import_funky",
G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket", G.P_CENTERS.c_Bakery_Scribe and toggle "scribable_basket",
_G["Talisman"] and toggle "harsh_ante_scaling", Talisman and toggle "harsh_ante_scaling",
}, },
}}, }},
} }

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 u() and not not next(G.hand.cards) return next(G.hand.cards) and u()
end, end,
use = function(_, card) use = function(_, card)
q { q {
trigger = "after", trigger = "after",
delay = 0.4, delay = 0.4,
func = function() func = function()
play_sound("tarot1")
card:juice_up(0.3, 0.5) card:juice_up(0.3, 0.5)
play_sound("tarot1")
end, end,
} }
@ -89,8 +89,8 @@ spectral {
delay = 0.15, delay = 0.15,
func = function() func = function()
v:flip() v:flip()
play_sound("card1", percent)
v:juice_up(0.3, 0.3) v:juice_up(0.3, 0.3)
play_sound("card1", percent)
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()
play_sound("tarot2", percent, 0.6)
v:juice_up(0.3, 0.3) v:juice_up(0.3, 0.3)
play_sound("tarot2", percent, 0.6)
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 #G.playing_cards > 1 and not not u() return next(G.playing_cards) and 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", "xerox"}, category = {"blind", "mitotic"},
name = "xerox", name = "mitotic",
blind = "bl_Roland_xerox", blind = "bl_Roland_mitotic",
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}
end, end,
@ -204,9 +204,9 @@ Balatest.TestPlay {
} }
Balatest.TestPlay { Balatest.TestPlay {
category = {"blind", "xerox"}, category = {"blind", "mitotic"},
name = "xerox_disabled", name = "mitotic_disabled",
blind = "bl_Roland_xerox", blind = "bl_Roland_mitotic",
jokers = {"j_chicot"}, jokers = {"j_chicot"},
execute = function() execute = function()
Balatest.discard {"2S"} Balatest.discard {"2S"}

View file

@ -2,330 +2,6 @@ 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",
@ -334,7 +10,7 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(7 * 2) Balatest.assert_chips(7)
end, end,
} }
@ -347,6 +23,6 @@ Balatest.TestPlay {
Balatest.play_hand {"2S"} Balatest.play_hand {"2S"}
end, end,
assert = function() assert = function()
Balatest.assert_chips(7 * math.pow(2, 32)) Balatest.assert_chips(1 / 0)
end, end,
} }

View file

@ -2,22 +2,6 @@ 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,11 +2,16 @@ 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(...) function Card:set_debuff(should_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, ...) orig_set_debuff(self, should_debuff, ...)
end end
end end
@ -62,7 +67,10 @@ function G.FUNCS.use_card(e, ...)
discover_card(ref.config.center) discover_card(ref.config.center)
ref:use_consumeable(ref.area) ref:use_consumeable(ref.area)
SMODS.calculate_context {area = ref.area, consumeable = ref, using_consumeable = true} SMODS.calculate_context {area = ref.area, consumeable = ref, using_consumeable = true}
q(function()
ref:remove() ref:remove()
end)
if normal then if normal then
return return
@ -94,11 +102,11 @@ local orig_get_blind_amount = get_blind_amount
---@param ante number ---@param ante number
---@return table|number ---@return table|number
local function blind(ante) local function blind(ante)
return ante == 39 and 1e294 or (_G["to_number"] or f.id)(orig_get_blind_amount(ante)) return ante == 39 and 1e294 or (to_number or f.id)(orig_get_blind_amount(ante))
end end
local function no_harsh_ante_scaling() local function no_harsh_ante_scaling()
return not _G["Talisman"] or not SMODS.Mods.Roland.config.harsh_ante_scaling return not Talisman or not SMODS.Mods.Roland.config.harsh_ante_scaling
end end
function get_blind_amount(ante, ...) function get_blind_amount(ante, ...)
@ -112,10 +120,10 @@ function get_blind_amount(ante, ...)
return 1 / 0 return 1 / 0
end end
local big, rem = _G["Big"], tonumber(blind((ante % loop) + 1)) local rem = tonumber(blind((ante % loop) + 1))
return ante / 15 >= loop and big:new(f(blind(ante - (loop * 15))):map(f.const(10)):table()) or return ante / 15 >= loop and Big:new(f(blind(ante - (loop * 15))):map(f.const(10)):table()) or
(ante / 9 >= loop and big:new(f(ante / loop - 8):map(f.const(10)):concat {rem}:table()) or (ante / 9 >= loop and Big:new(f(ante / loop - 8):map(f.const(10)):concat {rem}:table()) or
(ante / 2 >= loop and big:new {rem, ante / loop} or (ante / 2 >= loop and Big:new {rem, ante / loop} or
(big.constants and big.constants.TEN or big:new {10}):pow(rem))) (Big.constants and Big.constants.TEN or Big:new {10}):pow(rem)))
end end

44
src/voucher.lua Normal file
View file

@ -0,0 +1,44 @@
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,
}