Add 2 new jokers, add credit, fix credit
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
|
@ -355,6 +355,18 @@ return {
|
|||
"{C:attention}#2# {}scored cards",
|
||||
},
|
||||
},
|
||||
j_Roland_violet = {
|
||||
name = "Violet Vessel",
|
||||
text = {
|
||||
"{X:mult,C:white}X#1#{} Mult,",
|
||||
"but {X:mult,C:white}X#2#{} Mult",
|
||||
"{C:attention}before {}scoring",
|
||||
},
|
||||
},
|
||||
j_Roland_venerable = {
|
||||
name = "Venerable Visage",
|
||||
text = {"{X:red,C:white}X#1#{} discards each round"},
|
||||
},
|
||||
j_Roland_yard = {
|
||||
name = "Yard Sale",
|
||||
text = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"id": "Roland",
|
||||
"name": "Roland",
|
||||
"prefix": "Roland",
|
||||
"version": "2.8.36",
|
||||
"version": "2.9.0",
|
||||
"badge_colour": "8BE9FD",
|
||||
"display_name": "Roland",
|
||||
"main_file": "src/main.lua",
|
||||
|
|
|
|||
|
|
@ -3,14 +3,20 @@ local f, q = unpack(... or require "lib.shared")
|
|||
local blind = (function()
|
||||
local y = 0
|
||||
|
||||
---@param tbl SMODS.Blind
|
||||
---@param tbl SMODS.Blind|{idea?: string}
|
||||
---@return SMODS.Blind
|
||||
return function(tbl)
|
||||
tbl.pos = {x = 0, y = y}
|
||||
tbl.atlas = "blind"
|
||||
local ret = SMODS.Blind(tbl)
|
||||
tbl.idea = tbl.idea and "Roland_" .. tbl.idea or nil
|
||||
local blind = SMODS.Blind(tbl)
|
||||
y = y + 1
|
||||
return ret
|
||||
|
||||
q(function()
|
||||
Bakery_API.credit(blind)
|
||||
end)
|
||||
|
||||
return blind
|
||||
end
|
||||
end)()
|
||||
|
||||
|
|
@ -237,6 +243,7 @@ blind {
|
|||
|
||||
blind {
|
||||
key = "blizzard",
|
||||
idea = "redstoad",
|
||||
boss = {min = 3},
|
||||
boss_colour = HEX "102a41ff",
|
||||
pronouns = "it_its",
|
||||
|
|
|
|||
264
src/joker.lua
|
|
@ -22,10 +22,11 @@ local joker = (function()
|
|||
return ret
|
||||
end
|
||||
|
||||
---@param tbl SMODS.Joker|{artist?: string, Bakery_can_use: (fun(self: self, card: Card): boolean?), Bakery_use_button_text: (fun(self: self, card: Card): string|table|nil), Bakery_use_joker: fun(self: self, card: Card), attributes?: Attributes[]}
|
||||
---@param tbl SMODS.Joker|{artist?: string, idea?: string, Bakery_can_use: (fun(self: self, card: Card): boolean?), Bakery_use_button_text: (fun(self: self, card: Card): string|table|nil), Bakery_use_joker: fun(self: self, card: Card), attributes?: Attributes[]}
|
||||
return function(tbl)
|
||||
tbl.pos = inc()
|
||||
tbl.atlas = "joker"
|
||||
tbl.idea = tbl.idea and "Roland_" .. tbl.idea or nil
|
||||
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
|
||||
|
||||
if ((tbl.config or {}).extra or {}).flipped ~= nil then
|
||||
|
|
@ -38,6 +39,8 @@ local joker = (function()
|
|||
q(function()
|
||||
Bakery_API.credit(joker)
|
||||
end)
|
||||
|
||||
return joker
|
||||
end
|
||||
end)()
|
||||
|
||||
|
|
@ -161,6 +164,7 @@ joker {
|
|||
key = "mrsbones",
|
||||
pronouns = "she_her",
|
||||
artist = "ghostlyfield",
|
||||
idea = "redstoad",
|
||||
config = {extra = {xmult = 4, requirement = 4}},
|
||||
attributes = {"xmult"},
|
||||
cost = G.P_CENTERS.j_mr_bones.cost - 1,
|
||||
|
|
@ -197,6 +201,7 @@ joker {
|
|||
key = "sunny",
|
||||
pronouns = "it_its",
|
||||
artist = "char",
|
||||
idea = "redstoad",
|
||||
attributes = {"food", "on_sell"},
|
||||
cost = 2,
|
||||
rarity = 1,
|
||||
|
|
@ -510,6 +515,7 @@ joker {
|
|||
key = "sapling",
|
||||
pronouns = "they_them",
|
||||
artist = "char",
|
||||
idea = "redstoad",
|
||||
attributes = {"mult", "suit"},
|
||||
cost = 4,
|
||||
rarity = 1,
|
||||
|
|
@ -580,103 +586,6 @@ joker {
|
|||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "bulldozer",
|
||||
pronouns = "she_her",
|
||||
attributes = {"xmult"},
|
||||
cost = 6,
|
||||
rarity = 2,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(self, _, card)
|
||||
return {vars = {self:xmult_frozen(card)}}
|
||||
end,
|
||||
calculate = function(self, card, context)
|
||||
return (context.joker_main or context.forcetrigger) and {card = card, xmult = self:xmult_frozen(card)} or nil
|
||||
end,
|
||||
xmult_frozen = function(self, card)
|
||||
if not is_frozen(card) then
|
||||
return self.xmult()
|
||||
end
|
||||
|
||||
local ability = card.Roland_frozen_ability or {}
|
||||
card.Roland_frozen_ability = ability
|
||||
ability.Roland_frozen_xmult = ability.Roland_frozen_xmult or self.xmult()
|
||||
return ability.Roland_frozen_xmult
|
||||
end,
|
||||
xmult = function()
|
||||
local function mult(id)
|
||||
local next = {Select = true, Upcoming = true}
|
||||
local states = G.GAME.round_resets.blind_states
|
||||
local choices = G.GAME.round_resets.blind_choices
|
||||
return next[states[id]] and G.P_BLINDS[choices[id]].mult
|
||||
end
|
||||
|
||||
local m = ((G.GAME or {}).blind or {}).mult
|
||||
return m and m ~= 0 and m or mult "Small" or mult "Big" or mult "Boss" or 1
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "phytoestrogens",
|
||||
pronouns = "she_her",
|
||||
artist = "ghostlyfield",
|
||||
config = {extra = {xmult = 0.3}},
|
||||
attributes = {"mult", "xmult"},
|
||||
cost = 8,
|
||||
rarity = 3,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(_, _, card)
|
||||
return {vars = {card.ability.extra.xmult}}
|
||||
end,
|
||||
calculate = function(_, card, context)
|
||||
if not context.joker_main and not context.forcetrigger then
|
||||
return
|
||||
end
|
||||
|
||||
SMODS.calculate_effect({mult = hand_chips}, card)
|
||||
SMODS.calculate_effect({xmult = card.ability.extra.xmult}, card)
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "nilly",
|
||||
pronouns = "any_all",
|
||||
artist = "ghostlyfield",
|
||||
config = {extra = {flipped = false}},
|
||||
attributes = {"xmult", "bakery_double_sided"},
|
||||
cost = 0,
|
||||
rarity = 2,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(_, _, card)
|
||||
local key = card.ability.extra.flipped and "b_Roland_enabled" or "b_Roland_disabled"
|
||||
return {vars = {localize {type = "variable", key = key}}}
|
||||
end,
|
||||
calculate = function(_, card, context)
|
||||
if (not context.joker_main or not card.ability.extra.flipped) and not context.forcetrigger then
|
||||
return
|
||||
end
|
||||
|
||||
mult = mod_mult(0)
|
||||
update_hand_text({delay = 0}, {chips = hand_chips, mult = mult})
|
||||
return {sound = "Roland_nilly", message = "0X " .. localize "k_mult", colour = G.C.MULT, message_card = card}
|
||||
end,
|
||||
Bakery_can_use = function(_, card)
|
||||
return not card.debuff
|
||||
end,
|
||||
Bakery_use_button_text = function(_, card)
|
||||
return localize {type = "variable", key = card.debuff and "b_Roland_debuffed" or "b_Roland_toggle"}
|
||||
end,
|
||||
Bakery_use_joker = function(_, card)
|
||||
local _ = card.debuff or Bakery_API.flip_double_sided(card)
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "amber",
|
||||
pronouns = "they_them",
|
||||
|
|
@ -731,7 +640,7 @@ joker {
|
|||
pixel_size = {w = 68, h = 68},
|
||||
attributes = {"xmult"},
|
||||
cost = 6,
|
||||
rarity = 2,
|
||||
rarity = 3,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
|
|
@ -852,9 +761,163 @@ function CardArea:unhighlight_all(...)
|
|||
return orig_unhighlight_all(self, ...)
|
||||
end
|
||||
|
||||
joker {
|
||||
key = "violet",
|
||||
pronouns = "she_they",
|
||||
idea = "hamester",
|
||||
config = {extra = {before = 1 / 3, xmult = 9}},
|
||||
pixel_size = {w = 68, h = 68},
|
||||
attributes = {"xmult"},
|
||||
cost = 6,
|
||||
rarity = 3,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(_, _, card)
|
||||
local extra = card.ability.extra
|
||||
return {vars = {extra.xmult, extra.before}}
|
||||
end,
|
||||
calculate = function(_, card, context)
|
||||
local extra = card.ability.extra
|
||||
|
||||
return (context.joker_main or context.forcetrigger) and
|
||||
{card = card, xmult = extra.xmult} or
|
||||
(context.before and {card = card, xmult = extra.before} or nil)
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "venerable",
|
||||
pronouns = "any_all",
|
||||
config = {extra = {xdiscard = 3}},
|
||||
pixel_size = {w = 68, h = 68},
|
||||
attributes = {"discard", "passive"},
|
||||
cost = 6,
|
||||
rarity = 3,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = false,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(_, _, card)
|
||||
local extra = card.ability.extra
|
||||
return {vars = {extra.xdiscard}}
|
||||
end,
|
||||
add_to_deck = function(self, card)
|
||||
self:venerable(math.ceil(G.GAME.round_resets.discards * card.ability.extra.xdiscard))
|
||||
end,
|
||||
remove_from_deck = function(self, card)
|
||||
self:venerable(math.floor(G.GAME.round_resets.discards / card.ability.extra.xdiscard))
|
||||
end,
|
||||
venerable = function(_, amount)
|
||||
local round_resets = G.GAME.round_resets
|
||||
local discards = round_resets.discards
|
||||
round_resets.discards = amount
|
||||
ease_discard(round_resets.discards - discards)
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "bulldozer",
|
||||
pronouns = "she_her",
|
||||
idea = "redstoad",
|
||||
attributes = {"xmult"},
|
||||
cost = 6,
|
||||
rarity = 2,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(self, _, card)
|
||||
return {vars = {self:xmult_frozen(card)}}
|
||||
end,
|
||||
calculate = function(self, card, context)
|
||||
return (context.joker_main or context.forcetrigger) and {card = card, xmult = self:xmult_frozen(card)} or nil
|
||||
end,
|
||||
xmult_frozen = function(self, card)
|
||||
if not is_frozen(card) then
|
||||
return self.xmult()
|
||||
end
|
||||
|
||||
local ability = card.Roland_frozen_ability or {}
|
||||
card.Roland_frozen_ability = ability
|
||||
ability.Roland_frozen_xmult = ability.Roland_frozen_xmult or self.xmult()
|
||||
return ability.Roland_frozen_xmult
|
||||
end,
|
||||
xmult = function()
|
||||
local function mult(id)
|
||||
local next = {Select = true, Upcoming = true}
|
||||
local states = G.GAME.round_resets.blind_states
|
||||
local choices = G.GAME.round_resets.blind_choices
|
||||
return next[states[id]] and G.P_BLINDS[choices[id]].mult
|
||||
end
|
||||
|
||||
local m = ((G.GAME or {}).blind or {}).mult
|
||||
return m and m ~= 0 and m or mult "Small" or mult "Big" or mult "Boss" or 1
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "phytoestrogens",
|
||||
pronouns = "she_her",
|
||||
artist = "ghostlyfield",
|
||||
config = {extra = {xmult = 0.3}},
|
||||
attributes = {"mult", "xmult"},
|
||||
cost = 8,
|
||||
rarity = 3,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(_, _, card)
|
||||
return {vars = {card.ability.extra.xmult}}
|
||||
end,
|
||||
calculate = function(_, card, context)
|
||||
if not context.joker_main and not context.forcetrigger then
|
||||
return
|
||||
end
|
||||
|
||||
SMODS.calculate_effect({mult = hand_chips}, card)
|
||||
SMODS.calculate_effect({xmult = card.ability.extra.xmult}, card)
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "nilly",
|
||||
pronouns = "any_all",
|
||||
artist = "ghostlyfield",
|
||||
idea = "redstoad",
|
||||
config = {extra = {flipped = false}},
|
||||
attributes = {"xmult", "bakery_double_sided"},
|
||||
cost = 0,
|
||||
rarity = 2,
|
||||
eternal_compat = true,
|
||||
blueprint_compat = true,
|
||||
perishable_compat = true,
|
||||
loc_vars = function(_, _, card)
|
||||
local key = card.ability.extra.flipped and "b_Roland_enabled" or "b_Roland_disabled"
|
||||
return {vars = {localize {type = "variable", key = key}}}
|
||||
end,
|
||||
calculate = function(_, card, context)
|
||||
if (not context.joker_main or not card.ability.extra.flipped) and not context.forcetrigger then
|
||||
return
|
||||
end
|
||||
|
||||
mult = mod_mult(0)
|
||||
update_hand_text({delay = 0}, {chips = hand_chips, mult = mult})
|
||||
return {sound = "Roland_nilly", message = "0X " .. localize "k_mult", colour = G.C.MULT, message_card = card}
|
||||
end,
|
||||
Bakery_can_use = function(_, card)
|
||||
return not card.debuff
|
||||
end,
|
||||
Bakery_use_button_text = function(_, card)
|
||||
return localize {type = "variable", key = card.debuff and "b_Roland_debuffed" or "b_Roland_toggle"}
|
||||
end,
|
||||
Bakery_use_joker = function(_, card)
|
||||
local _ = card.debuff or Bakery_API.flip_double_sided(card)
|
||||
end,
|
||||
}
|
||||
|
||||
joker {
|
||||
key = "martingale",
|
||||
pronouns = "he_him",
|
||||
idea = "redstoad",
|
||||
config = {extra = {odds = 2}},
|
||||
attributes = {"xmult", "chance"},
|
||||
cost = 8,
|
||||
|
|
@ -1033,6 +1096,8 @@ end
|
|||
joker {
|
||||
key = "artemis",
|
||||
pronouns = "any_all",
|
||||
artist = "hamester",
|
||||
idea = "redstoad",
|
||||
cost = 6,
|
||||
rarity = 2,
|
||||
config = {extra = {
|
||||
|
|
@ -1097,6 +1162,7 @@ joker {
|
|||
joker {
|
||||
key = "excalibur",
|
||||
pronouns = "he_him",
|
||||
idea = "redstoad",
|
||||
cost = 8,
|
||||
rarity = 3,
|
||||
config = {extra = {
|
||||
|
|
|
|||
|
|
@ -6,31 +6,19 @@ local function protect(fun)
|
|||
sendErrorMessage(tostring(ret), "Roland")
|
||||
end
|
||||
|
||||
return not res or ret ~= false
|
||||
return not res or ret ~= false and not (SMODS.current_mod and ret ~= true)
|
||||
end
|
||||
end
|
||||
|
||||
local function protect_ev(fun, retries)
|
||||
if type(fun) == "function" and retries then
|
||||
local orig_fun = fun
|
||||
|
||||
fun = function(...)
|
||||
local ret = orig_fun(...)
|
||||
|
||||
if ret == true then
|
||||
retries = retries - 1
|
||||
return retries <= 0
|
||||
else
|
||||
return ret
|
||||
end
|
||||
end
|
||||
end
|
||||
local function protect_ev(fun)
|
||||
local no_delete = not not SMODS.current_mod
|
||||
|
||||
if type(fun) == "table" then
|
||||
fun.func = protect(fun.func)
|
||||
fun.no_delete = no_delete
|
||||
fun = getmetatable(fun) == Event and fun or Event(fun)
|
||||
elseif type(fun) == "function" then
|
||||
fun = Event {func = protect(fun)}
|
||||
fun = Event {func = protect(fun), no_delete = no_delete}
|
||||
else
|
||||
error("Expected a function or event, got a " .. type(fun), 3)
|
||||
end
|
||||
|
|
@ -89,11 +77,12 @@ end
|
|||
local f = assert(SMODS.load_file "src/lib/funky.lua")() or require "lib.funky"
|
||||
|
||||
--- Queues an event to be run.
|
||||
--- Note that events added this way implicitly `return true` unless you explicitly `return false`, unlike the vanilla ones.
|
||||
--- @param fun (fun():boolean?)|Event The event or a function to run turn into an event.
|
||||
--- @param front? boolean `true` to add the event to the front of the queue, rather than the end.
|
||||
local function q(fun, front)
|
||||
G.E_MANAGER:add_event(protect_ev(fun, SMODS.current_mod and 100), nil, front)
|
||||
--- Note that events added this way implicitly `return true` unless you explicitly `return false`.
|
||||
--- For `front`; boolean `true` to add the event to the front of the queue, rather than the end.
|
||||
--- @param fun fun():boolean?|{front?: boolean}|Event The event or a function to run turn into an event.
|
||||
local function q(fun)
|
||||
local ev = protect_ev(fun)
|
||||
G.E_MANAGER:add_event(ev, nil, ev.front)
|
||||
end
|
||||
|
||||
--- Determines if a center is allowed to be usable.
|
||||
|
|
|
|||
44
src/main.lua
|
|
@ -1,17 +1,6 @@
|
|||
local qol = assert(SMODS.load_file "src/lib/shared.lua")() or require "lib.shared"
|
||||
local f, q = unpack(qol)
|
||||
|
||||
f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"}
|
||||
:each(function(v)
|
||||
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
|
||||
end)
|
||||
|
||||
if _G["Balatest"] then
|
||||
f {"joker", "blind", "spectral"}:each(function(v)
|
||||
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
|
||||
end)
|
||||
end
|
||||
|
||||
q(function()
|
||||
local contributors = (Bakery_API or {}).contributors
|
||||
|
||||
|
|
@ -20,7 +9,7 @@ q(function()
|
|||
end
|
||||
|
||||
-- Special shoutout to all contributors. <3
|
||||
f {
|
||||
local credits = {
|
||||
aster = {
|
||||
name = "asterSSH",
|
||||
fg = HEX "f8f8f2ff",
|
||||
|
|
@ -41,14 +30,37 @@ q(function()
|
|||
fg = HEX "ffffffff",
|
||||
bg = HEX "b290e6ff",
|
||||
},
|
||||
}:each(function(v, k)
|
||||
hamester = {
|
||||
name = "Hamester",
|
||||
fg = HEX "ffffffff",
|
||||
bg = HEX "ffa100ff",
|
||||
},
|
||||
redstoad = {
|
||||
name = "RedsToad",
|
||||
fg = HEX "ffffffff",
|
||||
bg = HEX "da4044ff",
|
||||
},
|
||||
}
|
||||
|
||||
f(credits):each(function(v, k)
|
||||
contributors["Roland_" .. k] = v
|
||||
end)
|
||||
|
||||
if SMODS.Mods.DebugPlus and SMODS.Mods.Roland.config.import_funky then
|
||||
_G.f, _G.q, _G.u = unpack(qol)
|
||||
end
|
||||
end, true)
|
||||
end)
|
||||
|
||||
f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "tarot", "back", "seal", "tag"}
|
||||
:each(function(v)
|
||||
assert(SMODS.load_file("src/" .. v .. ".lua"))(qol)
|
||||
end)
|
||||
|
||||
if _G["Balatest"] then
|
||||
f {"joker", "blind", "spectral"}:each(function(v)
|
||||
assert(SMODS.load_file("src/tests/" .. v .. ".tests.lua"))(qol)
|
||||
end)
|
||||
end
|
||||
|
||||
local function toggle(id)
|
||||
return create_toggle {
|
||||
|
|
@ -71,8 +83,6 @@ SMODS.Atlas {
|
|||
atlas_table = animated and "ANIMATION_ATLAS" or "ASSET_ATLAS",
|
||||
}
|
||||
|
||||
SMODS.current_mod.qol = qol
|
||||
|
||||
function SMODS.current_mod.config_tab()
|
||||
return {
|
||||
n = G.UIT.ROOT,
|
||||
|
|
@ -93,3 +103,5 @@ function SMODS.current_mod.config_tab()
|
|||
}},
|
||||
}
|
||||
end
|
||||
|
||||
SMODS.current_mod.qol = qol
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ local spectral = (function()
|
|||
tbl.atlas = "spectral"
|
||||
tbl.pos = {x = x, y = 0}
|
||||
tbl.artist = tbl.artist and "Roland_" .. tbl.artist or nil
|
||||
local ret = SMODS.Consumable(tbl)
|
||||
local spectral = SMODS.Consumable(tbl)
|
||||
x = x + 1
|
||||
|
||||
q(function()
|
||||
Bakery_API.credit(ret)
|
||||
Bakery_API.credit(spectral)
|
||||
end)
|
||||
|
||||
return ret
|
||||
return spectral
|
||||
end
|
||||
end)()
|
||||
|
||||
|
|
|
|||