balatro-mods/lovely/dump/functions/misc_functions.lua
2025-02-11 01:31:20 +08:00

2238 lines
82 KiB
Lua

LOVELY_INTEGRITY = '7af2e0e0690a3e982bc378e0f2810cf02634e614841ab27a2036b576c0fdc825'
--Updates all display information for all displays for a given screenmode. Returns the key for the resolution option cycle
--
---@param screenmode string optional arg for one of 'Windowed', 'Fullscreen', or 'Borderless'
---@param display number optional arg to match fullscreen resolution with only the current display
function GET_DISPLAYINFO(screenmode, display)
--default the display to the current display, otherwise we need to return all available modes for the selected display
display = display or G.SETTINGS.WINDOW.selcted_display or 1
--default to current mode/'windowed', otherwise return all available options for specified mode
screenmode = screenmode or G.SETTINGS.WINDOW.screenmode or 'Windowed'
--Get a count of the connected displays and iterate through them to update our window setting options accordingly
local display_count = love.window.getDisplayCount()
local res_option = 1 --This is the mode that the fullscreen option will default to for resolution, if we can find it in the list for the current monitor
local realw, realh = love.window.getMode() --the actual render resolution the window is currently using
local current_res_values = {w = realw, h = realh}
--reset names to populate for displays
G.SETTINGS.WINDOW.display_names = {}
for i = 1, display_count do
--reset the screen resolution table for this display and set the name
G.SETTINGS.WINDOW.DISPLAYS[i] = {}
G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions = {
strings = {},
values = {},
}
G.SETTINGS.WINDOW.display_names[i] = ''..i
--Get the render resolution and desktop resolution, this is a workaround to a known OpenGL issue where windows doesn't supply the correct DPI scaling factor
local render_w, render_h = love.window.getDesktopDimensions(i)
local unscaled_dims = love.window.getFullscreenModes(i)[1]
--determine the DPI scale here, this can be used to determine the actual render resolution per display
G.SETTINGS.WINDOW.DISPLAYS[i].DPI_scale = 1--math.floor((0.5*unscaled_dims.width/render_w + 0.5*unscaled_dims.height/render_h)*500 + 0.5)/500
G.SETTINGS.WINDOW.DISPLAYS[i].MONITOR_DIMS = unscaled_dims
if screenmode == 'Fullscreen' then --Iterate through all possible screenmodes and populate the screen_resolutions table
--if the real resolution of the window is found, make sure we return that value
for _, v in ipairs(love.window.getFullscreenModes(i)) do
local _w, _h = v.width*G.SETTINGS.WINDOW.DISPLAYS[i].DPI_scale, v.height*G.SETTINGS.WINDOW.DISPLAYS[i].DPI_scale
if _w <= G.SETTINGS.WINDOW.DISPLAYS[i].MONITOR_DIMS.width and _h <= G.SETTINGS.WINDOW.DISPLAYS[i].MONITOR_DIMS.height then
G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.strings[#G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.strings+1] = ''..v.width..' X '..v.height
G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.values[#G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.values+1] = {w = v.width, h = v.height}
if i == G.SETTINGS.WINDOW.selected_display and i == display and current_res_values.w == v.width and current_res_values.h == v.height then
res_option = #G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.values
end
end
end
elseif screenmode == 'Windowed' then
G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.strings[1] = '-'
G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.values[1] = {w = 1280, h = 720}
elseif screenmode == 'Borderless' then
G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.strings[1] =
''..G.SETTINGS.WINDOW.DISPLAYS[i].MONITOR_DIMS.width/G.SETTINGS.WINDOW.DISPLAYS[i].DPI_scale..' X '
..G.SETTINGS.WINDOW.DISPLAYS[i].MONITOR_DIMS.height/G.SETTINGS.WINDOW.DISPLAYS[i].DPI_scale
G.SETTINGS.WINDOW.DISPLAYS[i].screen_resolutions.values[1] = current_res_values
end
end
return res_option
end
function timer_checkpoint(label, type, reset)
G.PREV_GARB = G.PREV_GARB or 0
if not G.F_ENABLE_PERF_OVERLAY then return end
G.check = G.check or {
draw = {
checkpoint_list = {},
checkpoints = 0,
last_time = 0,
},
update = {
checkpoint_list = {},
checkpoints = 0,
last_time = 0,
}
}
local cp = G.check[type]
if reset then
cp.last_time = love.timer.getTime()
cp.checkpoints = 0
return
end
cp.checkpoint_list[cp.checkpoints+1] = cp.checkpoint_list[cp.checkpoints+1] or {}
cp.checkpoints = cp.checkpoints+1
cp.checkpoint_list[cp.checkpoints].label = label..': '..(collectgarbage( "count" ) - G.PREV_GARB)
cp.checkpoint_list[cp.checkpoints].time = love.timer.getTime()
cp.checkpoint_list[cp.checkpoints].TTC = cp.checkpoint_list[cp.checkpoints].time - cp.last_time
cp.checkpoint_list[cp.checkpoints].trend = cp.checkpoint_list[cp.checkpoints].trend or {}
cp.checkpoint_list[cp.checkpoints].states = cp.checkpoint_list[cp.checkpoints].states or {}
table.insert(cp.checkpoint_list[cp.checkpoints].trend, 1, cp.checkpoint_list[cp.checkpoints].TTC)
table.insert(cp.checkpoint_list[cp.checkpoints].states, 1, G.STATE)
cp.checkpoint_list[cp.checkpoints].trend[401] = nil
cp.checkpoint_list[cp.checkpoints].states[401] = nil
cp.last_time = cp.checkpoint_list[cp.checkpoints].time
G.PREV_GARB = collectgarbage( "count" )
local av = 0
for k, v in ipairs(cp.checkpoint_list[cp.checkpoints].trend) do
av = av + v/#cp.checkpoint_list[cp.checkpoints].trend
end
cp.checkpoint_list[cp.checkpoints].average = av
end
function boot_timer(_label, _next, progress)
progress = progress or 0
G.LOADING = G.LOADING or {
font = love.graphics.setNewFont("resources/fonts/m6x11plus.ttf", 20),
love.graphics.dis
}
local realw, realh = love.window.getMode()
love.graphics.setCanvas()
love.graphics.push()
love.graphics.setShader()
love.graphics.clear(0,0,0,1)
love.graphics.setColor(0.6, 0.8, 0.9,1)
if progress > 0 then love.graphics.rectangle('fill', realw/2 - 150, realh/2 - 15, progress*300, 30, 5) end
love.graphics.setColor(1, 1, 1,1)
love.graphics.setLineWidth(3)
love.graphics.rectangle('line', realw/2 - 150, realh/2 - 15, 300, 30, 5)
if G.F_VERBOSE and not _RELEASE_MODE then love.graphics.print("LOADING: ".._next, realw/2 - 150, realh/2 +40) end
love.graphics.pop()
love.graphics.present()
G.ARGS.bt = G.ARGS.bt or love.timer.getTime()
G.ARGS.bt = love.timer.getTime()
end
function EMPTY(t)
if not t then return {} end
for k, v in pairs(t) do
t[k] = nil
end
return t
end
function interp(per, max, min)
min = min or 0
if per and max then
return per*(max - min) + min
end
end
function remove_all(t)
for i=#t, 1, -1 do
local v=t[i]
table.remove(t, i)
if v and v.children then
remove_all(v.children)
end
if v then v:remove() end
v = nil
end
for _, v in pairs(t) do
if v.children then remove_all(v.children) end
v:remove()
v = nil
end
end
function Vector_Dist(trans1, trans2, mid)
local x = trans1.x - trans2.x + (mid and 0.5*(trans1.w-trans2.w) or 0)
local y = trans1.y - trans2.y + (mid and 0.5*(trans1.h-trans2.h) or 0)
return math.sqrt((x)*(x) + (y)*(y))
end
function Vector_Len(trans1)
return math.sqrt((trans1.x)*(trans1.x) + (trans1.y)*(trans1.y))
end
function Vector_Sub(trans1, trans2)
return {x = trans1.x - trans2.x, y = trans1.y - trans2.y}
end
function get_index(t, val)
local index = nil
for i, v in pairs(t) do
if v == val then
index = i
end
end
return index
end
function table_length(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
function remove_nils(t)
local ans = {}
for _,v in pairs(t) do
ans[ #ans+1 ] = v
end
return ans
end
function SWAP(t, i, j)
if not t or not i or not j then return end
local temp = t[i]
t[i] = t[j]
t[j] = temp
end
function pseudoshuffle(list, seed)
if seed then math.randomseed(seed) end
if list[1] and list[1].sort_id then
table.sort(list, function (a, b) return (a.sort_id or 1) < (b.sort_id or 2) end)
end
for i = #list, 2, -1 do
local j = math.random(i)
list[i], list[j] = list[j], list[i]
end
end
function generate_starting_seed()
if G.GAME.stake >= #G.P_CENTER_POOLS['Stake'] then
local r_leg, r_tally = {}, 0
local g_leg, g_tally = {}, 0
for k, v in pairs(G.P_JOKER_RARITY_POOLS[4]) do
local win_ante = get_joker_win_sticker(v, true)
if win_ante and (win_ante >= 8) or (v.in_pool and type(v.in_pool) == 'function' and not v:in_pool()) then
g_leg[v.key] = true
g_tally = g_tally + 1
else
r_leg[v.key] = true
r_tally = r_tally + 1
end
end
if r_tally > 0 and g_tally > 0 then
local seed_found = nil
local extra_num = 0
while not seed_found do
extra_num = extra_num + 0.561892350821
seed_found = random_string(8, extra_num + G.CONTROLLER.cursor_hover.T.x*0.33411983 + G.CONTROLLER.cursor_hover.T.y*0.874146 + 0.412311010*G.CONTROLLER.cursor_hover.time)
if not r_leg[get_first_legendary(seed_found)] then seed_found = nil end
end
return seed_found
end
end
return random_string(8, G.CONTROLLER.cursor_hover.T.x*0.33411983 + G.CONTROLLER.cursor_hover.T.y*0.874146 + 0.412311010*G.CONTROLLER.cursor_hover.time)
end
function get_first_legendary(_key)
local _t, key = pseudorandom_element(G.P_JOKER_RARITY_POOLS[4], pseudoseed('Joker4', _key))
return _t.key
end
function pseudorandom_element(_t, seed, args)
-- TODO special cases for now
-- Preserves reverse nominal order for Suits, nominal+face_nominal order for Ranks
-- for vanilla RNG
if _t == SMODS.Suits then
_t = SMODS.Suit:obj_list(true)
end
if _t == SMODS.Ranks then
_t = SMODS.Rank:obj_list()
end
if seed then math.randomseed(seed) end
local keys = {}
for k, v in pairs(_t) do
local keep = true
local in_pool_func =
args and args.in_pool
or type(v) == 'table' and type(v.in_pool) == 'function' and v.in_pool
or _t == G.P_CARDS and function(c)
--Handles special case for Erratic Deck
local initial_deck = args and args.starting_deck or false
return not (
type(SMODS.Ranks[c.value].in_pool) == 'function' and not SMODS.Ranks[c.value]:in_pool({initial_deck = initial_deck, suit = c.suit})
or type(SMODS.Suits[c.suit].in_pool) == 'function' and not SMODS.Suits[c.suit]:in_pool({initial_deck = initial_deck, rank = c.value})
)
end
if in_pool_func then
keep = in_pool_func(v, args)
end
if keep then
keys[#keys+1] = {k = k,v = v}
end
end
if keys[1] and keys[1].v and type(keys[1].v) == 'table' and keys[1].v.sort_id then
table.sort(keys, function (a, b) return a.v.sort_id < b.v.sort_id end)
else
table.sort(keys, function (a, b) return a.k < b.k end)
end
if #keys == 0 then return nil, nil end
local key = keys[math.random(#keys)].k
return _t[key], key
end
function random_string(length, seed)
if seed then math.randomseed(seed) end
local ret = ''
for i = 1, length do
ret = ret..string.char(math.random() > 0.7 and math.random(string.byte('1'),string.byte('9')) or (math.random() > 0.45 and math.random(string.byte('A'),string.byte('N')) or math.random(string.byte('P'),string.byte('Z'))))
end
return string.upper(ret)
end
function pseudohash(str)
if true then
local num = 1
for i=#str, 1, -1 do
num = ((1.1239285023/num)*string.byte(str, i)*math.pi + math.pi*i)%1
end
return num
else
str = string.sub(string.format("%-16s",str), 1, 24)
local h = 0
for i=#str, 1, -1 do
h = bit.bxor(h, bit.lshift(h, 7) + bit.rshift(h, 3) + string.byte(str, i))
end
return tonumber(string.format("%.13f",math.sqrt(math.abs(h))%1))
end
end
function pseudoseed(key, predict_seed)
if key == 'seed' then return math.random() end
if predict_seed then
local _pseed = pseudohash(key..(predict_seed or ''))
_pseed = math.abs(tonumber(string.format("%.13f", (2.134453429141+_pseed*1.72431234)%1)))
return (_pseed + (pseudohash(predict_seed) or 0))/2
end
if not G.GAME.pseudorandom[key] then
G.GAME.pseudorandom[key] = pseudohash(key..(G.GAME.pseudorandom.seed or ''))
end
G.GAME.pseudorandom[key] = math.abs(tonumber(string.format("%.13f", (2.134453429141+G.GAME.pseudorandom[key]*1.72431234)%1)))
return (G.GAME.pseudorandom[key] + (G.GAME.pseudorandom.hashed_seed or 0))/2
end
function pseudorandom(seed, min, max)
if type(seed) == 'string' then seed = pseudoseed(seed) end
math.randomseed(seed)
if min and max then return math.random(min, max)
else return math.random() end
end
function tprint(tbl, indent)
if not indent then indent = 0 end
local toprint = string.rep(" ", indent) .. "{\r\n"
indent = indent + 2
for k, v in pairs(tbl) do
toprint = toprint .. string.rep(" ", indent)
if (type(k) == "number") then
toprint = toprint .. "[" .. k .. "] = "
elseif (type(k) == "string") then
toprint = toprint .. k .. "= "
end
if (type(v) == "number") then
toprint = toprint .. v .. ",\r\n"
elseif (type(v) == "string") then
toprint = toprint .. "\"" .. v .. "\",\r\n"
elseif (type(v) == "table") then
if indent>=10 then
toprint = toprint .. tostring(v) .. ",\r\n"
else
toprint = toprint .. tostring(v) .. tprint(v, indent + 1) .. ",\r\n"
end
else
toprint = toprint .. "\"" .. tostring(v) .. "\",\r\n"
end
end
toprint = toprint .. string.rep(" ", indent-2) .. "}"
return toprint
end
function sortingFunction(e1, e2)
return e1.order < e2.order
end
function HEX(hex)
if #hex <= 6 then hex = hex.."FF" end
local _,_,r,g,b,a = hex:find('(%x%x)(%x%x)(%x%x)(%x%x)')
local color = {tonumber(r,16)/255,tonumber(g,16)/255,tonumber(b,16)/255,tonumber(a,16)/255 or 255}
return color
end
function get_blind_main_colour(blind) --either in the form of the blind key for the P_BLINDS table or type
local disabled = false
blind = blind or ''
if blind == 'Boss' or blind == 'Small' or blind == 'Big' then
G.GAME.round_resets.blind_states = G.GAME.round_resets.blind_states or {}
if G.GAME.round_resets.blind_states[blind] == 'Defeated' or G.GAME.round_resets.blind_states[blind] == 'Skipped' then disabled = true end
blind = G.GAME.round_resets.blind_choices[blind]
end
return (disabled or not G.P_BLINDS[blind]) and G.C.BLACK or
G.P_BLINDS[blind].boss_colour or
(blind == 'bl_small' and mix_colours(G.C.BLUE, G.C.BLACK, 0.6) or
blind == 'bl_big' and mix_colours(G.C.ORANGE, G.C.BLACK, 0.6)) or G.C.BLACK
end
function evaluate_poker_hand(hand)
local results = {
["Flush Five"] = {},
["Flush House"] = {},
["Five of a Kind"] = {},
["Straight Flush"] = {},
["Four of a Kind"] = {},
["Full House"] = {},
["Flush"] = {},
["Straight"] = {},
["Three of a Kind"] = {},
["Two Pair"] = {},
["Pair"] = {},
["High Card"] = {},
top = nil
}
for _,v in ipairs(SMODS.PokerHand.obj_buffer) do
results[v] = {}
end
local parts = {
_5 = get_X_same(5,hand),
_4 = get_X_same(4,hand),
_3 = get_X_same(3,hand),
_2 = get_X_same(2,hand),
_flush = get_flush(hand),
_straight = get_straight(hand),
_highest = get_highest(hand)
}
for _,_hand in pairs(SMODS.PokerHands) do
if _hand.atomic_part and type(_hand.atomic_part) == 'function' then
parts[_hand.key] = _hand.atomic_part(hand)
end
end
if next(parts._5) and next(parts._flush) then
results["Flush Five"] = parts._5
if not results.top then results.top = results["Flush Five"] end
end
if next(parts._3) and next(parts._2) and next(parts._flush) then
local fh_hand = {}
local fh_3 = parts._3[1]
local fh_2 = parts._2[1]
for i=1, #fh_3 do
fh_hand[#fh_hand+1] = fh_3[i]
end
for i=1, #fh_2 do
fh_hand[#fh_hand+1] = fh_2[i]
end
table.insert(results["Flush House"], fh_hand)
if not results.top then results.top = results["Flush House"] end
end
if next(parts._5) then
results["Five of a Kind"] = parts._5
if not results.top then results.top = results["Five of a Kind"] end
end
if next(parts._flush) and next(parts._straight) then
local _s, _f, ret = parts._straight, parts._flush, {}
for _, v in ipairs(_f[1]) do
ret[#ret+1] = v
end
for _, v in ipairs(_s[1]) do
local in_straight = nil
for _, vv in ipairs(_f[1]) do
if vv == v then in_straight = true end
end
if not in_straight then ret[#ret+1] = v end
end
results["Straight Flush"] = {ret}
if not results.top then results.top = results["Straight Flush"] end
end
if next(parts._4) then
results["Four of a Kind"] = parts._4
if not results.top then results.top = results["Four of a Kind"] end
end
if next(parts._3) and next(parts._2) then
local fh_hand = {}
local fh_3 = parts._3[1]
local fh_2 = parts._2[1]
for i=1, #fh_3 do
fh_hand[#fh_hand+1] = fh_3[i]
end
for i=1, #fh_2 do
fh_hand[#fh_hand+1] = fh_2[i]
end
table.insert(results["Full House"], fh_hand)
if not results.top then results.top = results["Full House"] end
end
if next(parts._flush) then
results["Flush"] = parts._flush
if not results.top then results.top = results["Flush"] end
end
if next(parts._straight) then
results["Straight"] = parts._straight
if not results.top then results.top = results["Straight"] end
end
if next(parts._3) then
results["Three of a Kind"] = parts._3
if not results.top then results.top = results["Three of a Kind"] end
end
if (#parts._2 == 2) or (#parts._3 == 1 and #parts._2 == 1) then
local fh_hand = {}
local r = parts._2
local fh_2a = r[1]
local fh_2b = r[2]
if not fh_2b then
fh_2b = parts._3[1]
end
for i=1, #fh_2a do
fh_hand[#fh_hand+1] = fh_2a[i]
end
for i=1, #fh_2b do
fh_hand[#fh_hand+1] = fh_2b[i]
end
table.insert(results["Two Pair"], fh_hand)
if not results.top then results.top = results["Two Pair"] end
end
if next(parts._2) then
results["Pair"] = parts._2
if not results.top then results.top = results["Pair"] end
end
if next(parts._highest) then
results["High Card"] = parts._highest
if not results.top then results.top = results["High Card"] end
end
if results["Five of a Kind"][1] then
results["Four of a Kind"] = {results["Five of a Kind"][1], results["Five of a Kind"][2], results["Five of a Kind"][3], results["Five of a Kind"][4]}
end
if results["Four of a Kind"][1] then
results["Three of a Kind"] = {results["Four of a Kind"][1], results["Four of a Kind"][2], results["Four of a Kind"][3]}
end
if results["Three of a Kind"][1] then
results["Pair"] = {results["Three of a Kind"][1], results["Three of a Kind"][2]}
end
for _,_hand in pairs(SMODS.PokerHands) do
if _hand.composite and type(_hand.composite) == 'function' then
local other_hands
results[_hand.key], other_hands = _hand.composite(parts)
results[_hand.key] = results[_hand.key] or {}
if other_hands and type(other_hands) == 'table' then
for k, v in pairs(other_hands) do
results[k] = v
end
end
else
results[_hand.key] = parts[_hand.key]
end
end
results.top = nil
for _, v in ipairs(G.handlist) do
if not results.top and results[v] then
results.top = results[v]
break
end
end
return results
end
function get_flush(hand)
local ret = {}
local four_fingers = next(find_joker('Four Fingers'))
local suits = SMODS.Suit.obj_buffer
if #hand < (5 - (four_fingers and 1 or 0)) then return ret else
for j = 1, #suits do
local t = {}
local suit = suits[j]
local flush_count = 0
for i=1, #hand do
if hand[i]:is_suit(suit, nil, true) then flush_count = flush_count + 1; t[#t+1] = hand[i] end
end
if flush_count >= (5 - (four_fingers and 1 or 0)) then
table.insert(ret, t)
return ret
end
end
return {}
end
end
function get_straight(hand)
local ret = {}
local four_fingers = next(find_joker('Four Fingers'))
if #hand > 5 or #hand < (5 - (four_fingers and 1 or 0)) then return ret else
local t = {}
local IDS = {}
for i=1, #hand do
local id = hand[i]:get_id()
if id > 1 and id < 15 then
if IDS[id] then
IDS[id][#IDS[id]+1] = hand[i]
else
IDS[id] = {hand[i]}
end
end
end
local straight_length = 0
local straight = false
local can_skip = next(find_joker('Shortcut'))
local skipped_rank = false
for j = 1, 14 do
if IDS[j == 1 and 14 or j] then
straight_length = straight_length + 1
skipped_rank = false
for k, v in ipairs(IDS[j == 1 and 14 or j]) do
t[#t+1] = v
end
elseif can_skip and not skipped_rank and j ~= 14 then
skipped_rank = true
else
straight_length = 0
skipped_rank = false
if not straight then t = {} end
if straight then break end
end
if straight_length >= (5 - (four_fingers and 1 or 0)) then straight = true end
end
if not straight then return ret end
table.insert(ret, t)
return ret
end
end
function get_X_same(num, hand, or_more)
local vals = {}
for i = 1, SMODS.Rank.max_id.value do
vals[i] = {}
end
for i=#hand, 1, -1 do
local curr = {}
table.insert(curr, hand[i])
for j=1, #hand do
if hand[i]:get_id() == hand[j]:get_id() and i ~= j then
table.insert(curr, hand[j])
end
end
if or_more and (#curr >= num) or (#curr == num) then
vals[curr[1]:get_id()] = curr
end
end
local ret = {}
for i=#vals, 1, -1 do
if next(vals[i]) then table.insert(ret, vals[i]) end
end
return ret
end
function get_highest(hand)
local highest = nil
for k, v in ipairs(hand) do
if not highest or v:get_nominal() > highest:get_nominal() then
highest = v
end
end
if #hand > 0 then return {{highest}} else return {} end
end
function reset_drawhash()
G.DRAW_HASH = EMPTY(G.DRAW_HASH)
end
--Copyright 2021 Max Cahill (Zlib license)
--
--This software is provided 'as-is', without any express or implied
--warranty. In no event will the authors be held liable for any damages
--arising from the use of this software.
--
--Permission is granted to anyone to use this software for any purpose,
--including commercial applications, and to alter it and redistribute it
--freely, subject to the following restrictions:
--
--1. The origin of this software must not be misrepresented; you must not
-- claim that you wrote the original software. If you use this software
-- in a product, an acknowledgment in the product documentation would be
-- appreciated but is not required.
--2. Altered source versions must be plainly marked as such, and must not be
-- misrepresented as being the original software.
--3. This notice may not be removed or altered from any source distribution.
--This function was slightly modified from it's original state
function nuGC(time_budget, memory_ceiling, disable_otherwise)
time_budget = time_budget or 3e-4
memory_ceiling = memory_ceiling or 300
local max_steps = 1000
local steps = 0
local start_time = love.timer.getTime()
while
love.timer.getTime() - start_time < time_budget and
steps < max_steps
do
collectgarbage("step", 1)
steps = steps + 1
end
--safety net
if collectgarbage("count") / 1024 > memory_ceiling then
collectgarbage("collect")
end
--don't collect gc outside this margin
if disable_otherwise then
collectgarbage("stop")
end
end
--The drawhash is a hash table of all drawn nodes and all nodes that may be invisible but still collide with the cursor
function add_to_drawhash(obj)
if obj then
G.DRAW_HASH[#G.DRAW_HASH+1] = obj
end
end
function mix_colours(C1, C2, proportionC1)
return {
(C1[1] or 0.5)*proportionC1 + (C2[1] or 0.5)*(1-proportionC1),
(C1[2] or 0.5)*proportionC1 + (C2[2] or 0.5)*(1-proportionC1),
(C1[3] or 0.5)*proportionC1 + (C2[3] or 0.5)*(1-proportionC1),
(C1[4] or 1)*proportionC1 + (C2[4] or 1)*(1-proportionC1),
}
end
function mod_chips(_chips)
if G.GAME.modifiers.chips_dollar_cap then
_chips = math.min(_chips, math.max(G.GAME.dollars, 0))
end
return _chips
end
function mod_mult(_mult)
return _mult
end
function play_sound(sound_code, per, vol)
if G.F_MUTE then return end
if sound_code and G.SETTINGS.SOUND.volume > 0.001 then
G.ARGS.play_sound = G.ARGS.play_sound or {}
G.ARGS.play_sound.type = 'sound'
G.ARGS.play_sound.time = G.TIMERS.REAL
G.ARGS.play_sound.crt = G.SETTINGS.GRAPHICS.crt
G.ARGS.play_sound.sound_code = sound_code
G.ARGS.play_sound.per = per
G.ARGS.play_sound.vol = vol
G.ARGS.play_sound.pitch_mod = G.PITCH_MOD
G.ARGS.play_sound.state = G.STATE
G.ARGS.play_sound.music_control = G.SETTINGS.music_control
G.ARGS.play_sound.sound_settings = G.SETTINGS.SOUND
G.ARGS.play_sound.splash_vol = G.SPLASH_VOL
G.ARGS.play_sound.overlay_menu = not (not G.OVERLAY_MENU)
if G.F_SOUND_THREAD then
G.SOUND_MANAGER.channel:push(G.ARGS.play_sound)
else
PLAY_SOUND(G.ARGS.play_sound)
end
end
end
function modulate_sound(dt)
--volume of the splash screen is set here
G.SPLASH_VOL = 2*dt*(G.STATE == G.STATES.SPLASH and 1 or 0) + (G.SPLASH_VOL or 1)*(1-2*dt)
--Control the music here
local desired_track =
G.video_soundtrack or
(G.STATE == G.STATES.SPLASH and '') or
SMODS.Sound:get_current_music() or
(G.booster_pack_sparkles and not G.booster_pack_sparkles.REMOVED and 'music2') or
(G.booster_pack_meteors and not G.booster_pack_meteors.REMOVED and 'music3') or
(G.booster_pack and not G.booster_pack.REMOVED and 'music2') or
(G.shop and not G.shop.REMOVED and 'music4') or
(G.GAME.blind and G.GAME.blind.boss and 'music5') or
('music1')
G.PITCH_MOD = (G.PITCH_MOD or 1)*(1 - dt) + dt*((not G.normal_music_speed and G.STATE == G.STATES.GAME_OVER) and 0.5 or 1)
--For ambient sound control
G.SETTINGS.ambient_control = G.SETTINGS.ambient_control or {}
G.ARGS.score_intensity = G.ARGS.score_intensity or {}
if not is_number(G.GAME.current_round.current_hand.chips) or not is_number(G.GAME.current_round.current_hand.mult) then
G.ARGS.score_intensity.earned_score = 0
else
G.ARGS.score_intensity.earned_score = G.GAME.current_round.current_hand.chips*G.GAME.current_round.current_hand.mult
end
G.ARGS.score_intensity.required_score = G.GAME.blind and G.GAME.blind.chips or 0
if G.cry_flame_override and G.cry_flame_override['duration'] > 0 then
G.cry_flame_override['duration'] = math.max(0, G.cry_flame_override['duration'] - dt)
end
G.ARGS.score_intensity.flames = math.min(1, (G.STAGE == G.STAGES.RUN and 1 or 0)*(
(G.ARGS.chip_flames and (G.ARGS.chip_flames.real_intensity + G.ARGS.chip_flames.change) or 0))/10)
G.ARGS.score_intensity.organ = G.video_organ or to_big(G.ARGS.score_intensity.required_score) > to_big(0) and math.max(math.min(0.4, 0.1*math.log(G.ARGS.score_intensity.earned_score/(G.ARGS.score_intensity.required_score+1), 5)),0.) or 0
local AC = G.SETTINGS.ambient_control
G.ARGS.ambient_sounds = G.ARGS.ambient_sounds or {
ambientFire2 = {volfunc = function(_prev_volume) return _prev_volume*(1 - dt) + dt*0.9*((G.ARGS.score_intensity.flames > 0.3) and 1 or G.ARGS.score_intensity.flames/0.3) end},
ambientFire1 = {volfunc = function(_prev_volume) return _prev_volume*(1 - dt) + dt*0.8*((G.ARGS.score_intensity.flames > 0.3) and (G.ARGS.score_intensity.flames-0.3)/0.7 or 0) end},
ambientFire3 = {volfunc = function(_prev_volume) return _prev_volume*(1 - dt) + dt*0.4*((G.ARGS.chip_flames and G.ARGS.chip_flames.change or 0) + (G.ARGS.mult_flames and G.ARGS.mult_flames.change or 0)) end},
ambientOrgan1 = {volfunc = function(_prev_volume) return _prev_volume*(1 - dt) + dt*0.6*(G.SETTINGS.SOUND.music_volume + 100)/200*(G.ARGS.score_intensity.organ) end},
}
for k, v in pairs(G.ARGS.ambient_sounds) do
AC[k] = AC[k] or {}
AC[k].per = (k == 'ambientOrgan1') and 0.7 or (k == 'ambientFire1' and 1.1) or (k == 'ambientFire2' and 1.05) or 1
AC[k].vol = Cartomancer.handle_flames_volume((not G.video_organ and G.STATE == G.STATES.SPLASH) and 0 or AC[k].vol and v.volfunc(AC[k].vol) or 0)
end
G.ARGS.push = G.ARGS.push or {}
G.ARGS.push.type = 'modulate'
G.ARGS.push.pitch_mod = G.PITCH_MOD
G.ARGS.push.state = G.STATE
G.ARGS.push.time = G.TIMERS.REAL
G.ARGS.push.dt = dt
G.ARGS.push.desired_track = desired_track
G.ARGS.push.sound_settings = G.SETTINGS.SOUND
G.ARGS.push.splash_vol = G.SPLASH_VOL
G.ARGS.push.overlay_menu = not (not G.OVERLAY_MENU)
G.ARGS.push.ambient_control = G.SETTINGS.ambient_control
if SMODS.remove_replace_sound and SMODS.remove_replace_sound ~= desired_track then
SMODS.Sound.replace_sounds[SMODS.remove_replace_sound] = nil
SMODS.remove_replace_sound = nil
end
local replace_sound = SMODS.Sound.replace_sounds[desired_track]
if replace_sound then
local replaced_track = desired_track
desired_track = replace_sound.key
G.ARGS.push.desired_track = desired_track
if SMODS.previous_track ~= desired_track then
if replace_sound.times > 0 then replace_sound.times = replace_sound.times - 1 end
if replace_sound.times == 0 then SMODS.remove_replace_sound = replaced_track end
end
end
local stop_sound = SMODS.Sound.stop_sounds[desired_track]
if SMODS.Sound.stop_sounds[desired_track] then
if SMODS.previous_track ~= '' and stop_sound > 0 then stop_sound = stop_sound - 1 end
SMODS.Sound.stop_sounds[desired_track] = stop_sound ~= 0 and stop_sound or nil
SMODS.previous_track = ''
return
end
if G.F_SOUND_THREAD then
G.SOUND_MANAGER.channel:push(G.ARGS.push)
SMODS.previous_track = SMODS.previous_track or ''
local in_sync = (SMODS.Sounds[desired_track] or {}).sync
local out_sync = (SMODS.Sounds[SMODS.previous_track] or {}).sync
local should_sync = true
if (type(in_sync) == 'table' and not in_sync[SMODS.previous_track]) or in_sync == false then should_sync = false end
if (type(out_sync) == 'table' and not out_sync[desired_track]) or out_sync == false then should_sync = false end
if
SMODS.previous_track and SMODS.previous_track ~= desired_track and
not should_sync
then
G.ARGS.push.type = 'restart_music'
G.SOUND_MANAGER.channel:push(G.ARGS.push)
end
SMODS.previous_track = desired_track
else
MODULATE(G.ARGS.push)
end
end
function count_of_suit(area, suit)
local num = 0
for _,c in pairs(area.cards) do
if c.base.suit == suit then
num = num +1
end
end
return num
end
function prep_draw(moveable, scale, rotate, offset)
if Big and G.STATE == G.STATES.MENU then moveable.VT.x = to_big(moveable.VT.x):to_number()
moveable.VT.y = to_big(moveable.VT.y):to_number()
moveable.VT.w = to_big(moveable.VT.w):to_number()
moveable.VT.h = to_big(moveable.VT.h):to_number() end
love.graphics.push()
love.graphics.scale(G.TILESCALE*G.TILESIZE)
love.graphics.translate(
moveable.VT.x+moveable.VT.w/2 + (offset and offset.x or 0) + ((moveable.layered_parallax and moveable.layered_parallax.x) or ((moveable.parent and moveable.parent.layered_parallax and moveable.parent.layered_parallax.x)) or 0),
moveable.VT.y+moveable.VT.h/2 + (offset and offset.y or 0) + ((moveable.layered_parallax and moveable.layered_parallax.y) or ((moveable.parent and moveable.parent.layered_parallax and moveable.parent.layered_parallax.y)) or 0))
if moveable.VT.r ~= 0 or moveable.juice or rotate then love.graphics.rotate(moveable.VT.r + (rotate or 0)) end
love.graphics.translate(
-scale*moveable.VT.w*(moveable.VT.scale)/2,
-scale*moveable.VT.h*(moveable.VT.scale)/2)
love.graphics.scale(moveable.VT.scale*scale)
end
function get_chosen_triangle_from_rect(x, y, w, h, vert)
local scale = 2
if vert then
x = x + math.min(0.6*math.sin(G.TIMERS.REAL*9)*scale+0.2, 0)
return {
x-3.5*scale, y+h/2 - 1.5*scale,
x-0.5*scale, y+h/2 + 0,
x-3.5*scale,y+h/2 + 1.5*scale
}
else
y = y + math.min(0.6*math.sin(G.TIMERS.REAL*9)*scale+0.2, 0)
return {
x+w/2 - 1.5*scale, y-4*scale,
x+w/2 + 0, y-1.1*scale,
x+w/2 + 1.5*scale, y-4*scale
}
end
end
function point_translate(_T, delta)
_T.x = (_T.x + delta.x) or 0
_T.y = (_T.y + delta.y) or 0
end
function point_rotate(_T, angle)
local _cos, _sin, _ox, _oy = math.cos(angle+math.pi/2), math.sin(angle+math.pi/2), _T.x , _T.y
_T.x = -_oy*_cos + _ox*_sin
_T.y = _oy*_sin + _ox*_cos
end
function lighten(colour, percent, no_tab)
if no_tab then
return
colour[1]*(1-percent)+percent,
colour[2]*(1-percent)+percent,
colour[3]*(1-percent)+percent,
colour[4]
end
return {
colour[1]*(1-percent)+percent,
colour[2]*(1-percent)+percent,
colour[3]*(1-percent)+percent,
colour[4]
}
end
function darken(colour, percent, no_tab)
if no_tab then
return
colour[1]*(1-percent),
colour[2]*(1-percent),
colour[3]*(1-percent),
colour[4]
end
return {
colour[1]*(1-percent),
colour[2]*(1-percent),
colour[3]*(1-percent),
colour[4]
}
end
function adjust_alpha(colour, new_alpha, no_tab)
if no_tab then
return
colour[1],
colour[2],
colour[3],
new_alpha
end
return {
colour[1],
colour[2],
colour[3],
new_alpha
}
end
function alert_no_space(card, area)
G.CONTROLLER.locks.no_space = true
attention_text({
scale = 0.9, text = localize('k_no_space_ex'), hold = 0.9, align = 'cm',
cover = area, cover_padding = 0.1, cover_colour = adjust_alpha(G.C.BLACK, 0.7)
})
card:juice_up(0.3, 0.2)
for i = 1, #area.cards do
area.cards[i]:juice_up(0.15)
end
G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.06*G.SETTINGS.GAMESPEED, blockable = false, blocking = false, func = function()
play_sound('tarot2', 0.76, 0.4);return true end}))
play_sound('tarot2', 1, 0.4)
G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.5*G.SETTINGS.GAMESPEED, blockable = false, blocking = false,
func = function()
G.CONTROLLER.locks.no_space = nil
return true end}))
end
function find_joker(name, non_debuff)
local jokers = {}
if not G.jokers or not G.jokers.cards then return {} end
for k, v in pairs(G.jokers.cards) do
if v and type(v) == 'table' and v.ability.name == name and (non_debuff or not v.debuff) then
table.insert(jokers, v)
end
end
for k, v in pairs(G.consumeables.cards) do
if v and type(v) == 'table' and v.ability.name == name and (non_debuff or not v.debuff) then
table.insert(jokers, v)
end
end
return jokers
end
function get_blind_amount(ante)
if G.GAME.modifiers.scaling and G.GAME.modifiers.scaling > 3 then return SMODS.get_blind_amount(ante) end
local k = 0.75
if not G.GAME.modifiers.scaling or G.GAME.modifiers.scaling == 1 then
local amounts = {
300, 800, 2000, 5000, 11000, 20000, 35000, 50000
}
if ante < 1 then return 100 end
if ante <= 8 then return amounts[ante] end
local a, b, c, d = amounts[8],1.6,ante-8, 1 + 0.2*(ante-8)
local amount = math.floor(a*(b+(k*c)^d)^c)
amount = amount - amount%(10^math.floor(math.log10(amount)-1))
return amount
elseif G.GAME.modifiers.scaling == 2 then
local amounts = {
300, 900, 2600, 8000, 20000, 36000, 60000, 100000
--300, 900, 2400, 7000, 18000, 32000, 56000, 90000
}
if ante < 1 then return 100 end
if ante <= 8 then return amounts[ante] end
local a, b, c, d = amounts[8],1.6,ante-8, 1 + 0.2*(ante-8)
local amount = math.floor(a*(b+(k*c)^d)^c)
amount = amount - amount%(10^math.floor(math.log10(amount)-1))
return amount
elseif G.GAME.modifiers.scaling == 3 then
local amounts = {
300, 1000, 3200, 9000, 25000, 60000, 110000, 200000
--300, 1000, 3000, 8000, 22000, 50000, 90000, 180000
}
if ante < 1 then return 100 end
if ante <= 8 then return amounts[ante] end
local a, b, c, d = amounts[8],1.6,ante-8, 1 + 0.2*(ante-8)
local amount = math.floor(a*(b+(k*c)^d)^c)
amount = amount - amount%(10^math.floor(math.log10(amount)-1))
return amount
end
end
function number_format(num, e_switch_point)
if type(num) ~= 'number' then return num end
local sign = (num >= 0 and "") or "-"
num = math.abs(num)
G.E_SWITCH_POINT = G.E_SWITCH_POINT or 100000000000
if not num or type(num) ~= 'number' then return num or '' end
if num >= (e_switch_point or G.E_SWITCH_POINT) then
local x = string.format("%.4g",num)
local fac = math.floor(math.log(tonumber(x), 10))
if num == math.huge then
return sign.."naneinf"
end
local mantissa = round_number(x/(10^fac), 3)
if mantissa >= 10 then
mantissa = mantissa / 10
fac = fac + 1
end
return sign..(string.format(fac >= 100 and "%.1fe%i" or fac >= 10 and "%.2fe%i" or "%.3fe%i", mantissa, fac))
end
local formatted
if num ~= math.floor(num) and num < 100 then
formatted = string.format(num >= 10 and "%.1f" or "%.2f", num)
if formatted:sub(-1) == "0" then
formatted = formatted:gsub("%.?0+$", "")
end
-- Return already to avoid comas being added
if num < 0.01 then return tostring(num) end
else
formatted = string.format("%.0f", num)
end
return sign..(formatted:reverse():gsub("(%d%d%d)", "%1,"):gsub(",$", ""):reverse())
end
function score_number_scale(scale, amt)
G.E_SWITCH_POINT = G.E_SWITCH_POINT or 100000000000
if type(amt) ~= 'number' then return 0.7*(scale or 1) end
if amt >= G.E_SWITCH_POINT then
return 0.7*(scale or 1)
elseif amt >= 1000000 then
return 14*0.75/(math.floor(math.log(amt))+4)*(scale or 1)
else
return 0.75*(scale or 1)
end
end
function copy_table(O)
local O_type = type(O)
local copy
if O_type == 'table' then
copy = {}
for k, v in next, O, nil do
copy[copy_table(k)] = copy_table(v)
end
setmetatable(copy, copy_table(getmetatable(O)))
else
copy = O
end
return copy
end
function send_score(_score)
if G.F_HTTP_SCORES and G.SETTINGS.COMP and G.F_STREAMER_EVENT then
G.HTTP_MANAGER.out_channel:push({
set_score = true,
score = _score,
username = G.SETTINGS.COMP.name,
uid = tostring(G.STEAM.user.getSteamID()),
version = G.VERSION
})
end
end
function send_name()
if G.F_HTTP_SCORES and G.SETTINGS.COMP and G.F_STREAMER_EVENT then
G.HTTP_MANAGER.out_channel:push({
set_name = true,
username = G.SETTINGS.COMP.name,
uid = tostring(G.STEAM.user.getSteamID()),
version = G.VERSION
})
end
end
function check_and_set_high_score(score, amt)
if not amt or type(amt) ~= 'number' then return end
if G.GAME.round_scores[score] and math.floor(amt) > (G.GAME.round_scores[score].amt or 0) then
G.GAME.round_scores[score].amt = math.floor(amt)
end
if G.GAME.seeded then return end
if score == 'hand' and G.SETTINGS.COMP and ((not G.SETTINGS.COMP.score) or (G.SETTINGS.COMP.score < math.floor(amt))) then
G.SETTINGS.COMP.score = amt
send_score(math.floor(amt))
end
if G.PROFILES[G.SETTINGS.profile].high_scores[score] and math.floor(amt) > G.PROFILES[G.SETTINGS.profile].high_scores[score].amt then
if G.GAME.round_scores[score] then G.GAME.round_scores[score].high_score = true end
G.PROFILES[G.SETTINGS.profile].high_scores[score].amt = math.floor(amt)
G:save_settings()
end
end
function set_joker_usage()
for k, v in pairs(G.jokers.cards) do
if v.config.center_key and v.ability.set == 'Joker' then
if G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] then
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].count = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].count + 1
else
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = {count = 1, order = v.config.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}
end
end
end
G:save_settings()
end
function set_joker_win()
for k, v in pairs(G.jokers.cards) do
if v.config.center_key and v.ability.set == 'Joker' then
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] or {count = 1, order = v.config.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}
if G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] then
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins or {}
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins[G.GAME.stake] or 0) + 1
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins_by_key[SMODS.stake_from_index(G.GAME.stake)] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].wins_by_key[SMODS.stake_from_index(G.GAME.stake)] or 0) + 1
end
end
end
G:save_settings()
end
function get_joker_win_sticker(_center, index)
if G.PROFILES[G.SETTINGS.profile].joker_usage[_center.key] and
G.PROFILES[G.SETTINGS.profile].joker_usage[_center.key].wins then
local _w = 0
for k, v in pairs(G.PROFILES[G.SETTINGS.profile].joker_usage[_center.key].wins) do
_w = math.max(k, _w)
end
if index then return _w end
if _w > 0 then return G.sticker_map[_w] end
end
if index then return 0 end
end
function set_joker_loss()
for k, v in pairs(G.jokers.cards) do
if v.config.center_key and v.ability.set == 'Joker' then
if G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key] then
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses = G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses or {}
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses[G.GAME.stake] or 0) + 1
G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses_by_key[SMODS.stake_from_index(G.GAME.stake)] = (G.PROFILES[G.SETTINGS.profile].joker_usage[v.config.center_key].losses_by_key[SMODS.stake_from_index(G.GAME.stake)] or 0) + 1
end
end
end
G:save_settings()
end
function set_deck_usage()
if G.GAME.selected_back and G.GAME.selected_back.effect and G.GAME.selected_back.effect.center and G.GAME.selected_back.effect.center.key then
local deck_key = G.GAME.selected_back.effect.center.key
if G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then
G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].count = G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].count + 1
else
G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}}
end
G:save_settings()
end
end
function set_deck_win()
if G.GAME.selected_back and G.GAME.selected_back.effect and G.GAME.selected_back.effect.center and G.GAME.selected_back.effect.center.key then
local deck_key = G.GAME.selected_back.effect.center.key
if not G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}} end
if G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then
G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].wins[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].wins[G.GAME.stake] or 0) + 1
for i = 1,
(G.P_CENTER_POOLS["Stake"][G.GAME.stake].unlocked_stake) and
(G.P_STAKES[G.P_CENTER_POOLS["Stake"][G.GAME.stake].unlocked_stake].stake_level-1) or (G.GAME.stake-1)
do
G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].wins[i] = (G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].wins[i] or 1)
end
end
set_challenge_unlock()
G:save_settings()
end
end
function set_challenge_unlock()
if G.PROFILES[G.SETTINGS.profile].all_unlocked then return end
if G.PROFILES[G.SETTINGS.profile].challenges_unlocked then
local _ch_comp, _ch_tot = 0,#G.CHALLENGES
for k, v in ipairs(G.CHALLENGES) do
if v.id and G.PROFILES[G.SETTINGS.profile].challenge_progress.completed[v.id or ''] then
_ch_comp = _ch_comp + 1
end
end
G.PROFILES[G.SETTINGS.profile].challenges_unlocked = math.min(_ch_tot, _ch_comp+5)
else
local deck_wins = 0
for k, v in pairs(G.PROFILES[G.SETTINGS.profile].deck_usage) do
if v.wins and v.wins[1] then
deck_wins = deck_wins + 1
end
end
if deck_wins >= G.CHALLENGE_WINS and not G.PROFILES[G.SETTINGS.profile].challenges_unlocked then
G.PROFILES[G.SETTINGS.profile].challenges_unlocked = 5
notify_alert('b_challenge', "Back")
end
end
end
function get_deck_win_stake(_deck_key)
if not _deck_key then
local _w, _w_low = 0, nil
local deck_count = 0
for _, deck in pairs(G.PROFILES[G.SETTINGS.profile].deck_usage) do
local deck_won_with = nil
for k, v in pairs(deck.wins) do
deck_won_with = true
_w = math.max(k, _w)
end
if deck_won_with then deck_count = deck_count + 1 end
_w_low = _w_low and (math.min(_w_low, _w)) or _w
end
return _w, ((deck_count >= #G.P_CENTER_POOLS.Back) and _w_low or 0)
end
if G.PROFILES[G.SETTINGS.profile].deck_usage[_deck_key] and
G.PROFILES[G.SETTINGS.profile].deck_usage[_deck_key].wins then
local _w = 0
for k, v in pairs(G.PROFILES[G.SETTINGS.profile].deck_usage[_deck_key].wins) do
_w = math.max(k, _w)
end
return _w
end
return 0
end
function get_deck_win_sticker(_center)
if G.PROFILES[G.SETTINGS.profile].deck_usage[_center.key] and
G.PROFILES[G.SETTINGS.profile].deck_usage[_center.key].wins then
local _w = -1
for k, v in pairs(G.PROFILES[G.SETTINGS.profile].deck_usage[_center.key].wins) do
_w = math.max(k, _w)
end
if _w > 0 then return G.sticker_map[_w] end
end
end
function set_deck_loss()
if G.GAME.selected_back and G.GAME.selected_back.effect and G.GAME.selected_back.effect.center and G.GAME.selected_back.effect.center.key then
local deck_key = G.GAME.selected_back.effect.center.key
if not G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] = {count = 1, order = G.GAME.selected_back.effect.center.order, wins = {}, losses = {}, wins_by_key = {}, losses_by_key = {}} end
if G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key] then
G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].losses[G.GAME.stake] = (G.PROFILES[G.SETTINGS.profile].deck_usage[deck_key].losses[G.GAME.stake] or 0) + 1
end
G:save_settings()
end
end
function set_consumeable_usage(card)
if card.config.center_key and card.ability.consumeable then
if G.PROFILES[G.SETTINGS.profile].consumeable_usage[card.config.center_key] then
G.PROFILES[G.SETTINGS.profile].consumeable_usage[card.config.center_key].count = G.PROFILES[G.SETTINGS.profile].consumeable_usage[card.config.center_key].count + 1
else
G.PROFILES[G.SETTINGS.profile].consumeable_usage[card.config.center_key] = {count = 1, order = card.config.center.order}
end
if G.GAME.consumeable_usage[card.config.center_key] then
G.GAME.consumeable_usage[card.config.center_key].count = G.GAME.consumeable_usage[card.config.center_key].count + 1
else
G.GAME.consumeable_usage[card.config.center_key] = {count = 1, order = card.config.center.order, set = card.ability.set}
end
G.GAME.consumeable_usage_total = G.GAME.consumeable_usage_total or {tarot = 0, planet = 0, spectral = 0, tarot_planet = 0, all = 0}
if card.config.center.set == 'Tarot' then
G.GAME.consumeable_usage_total.tarot = G.GAME.consumeable_usage_total.tarot + 1
G.GAME.consumeable_usage_total.tarot_planet = G.GAME.consumeable_usage_total.tarot_planet + 1
elseif card.config.center.set == 'Planet' then
G.GAME.consumeable_usage_total.planet = G.GAME.consumeable_usage_total.planet + 1
G.GAME.consumeable_usage_total.tarot_planet = G.GAME.consumeable_usage_total.tarot_planet + 1
elseif card.config.center.set == 'Spectral' then G.GAME.consumeable_usage_total.spectral = G.GAME.consumeable_usage_total.spectral + 1
end
G.GAME.consumeable_usage_total.all = G.GAME.consumeable_usage_total.all + 1
if not card.config.center.discovered then
discover_card(card)
end
if card.config.center.set == 'Tarot' or card.config.center.set == 'Planet' then
G.E_MANAGER:add_event(Event({
trigger = 'immediate',
func = function()
G.E_MANAGER:add_event(Event({
trigger = 'immediate',
func = function()
G.GAME.last_tarot_planet = card.config.center_key
return true
end
}))
return true
end
}))
end
end
G:save_settings()
end
function set_voucher_usage(card)
if card.config.center_key and card.ability.set == 'Voucher' then
if G.PROFILES[G.SETTINGS.profile].voucher_usage[card.config.center_key] then
G.PROFILES[G.SETTINGS.profile].voucher_usage[card.config.center_key].count = G.PROFILES[G.SETTINGS.profile].voucher_usage[card.config.center_key].count + 1
else
G.PROFILES[G.SETTINGS.profile].voucher_usage[card.config.center_key] = {count = 1, order = card.config.center.order}
end
end
G:save_settings()
end
function set_hand_usage(hand)
local hand_label = hand
hand = hand:gsub("%s+", "")
if G.PROFILES[G.SETTINGS.profile].hand_usage[hand] then
G.PROFILES[G.SETTINGS.profile].hand_usage[hand].count = G.PROFILES[G.SETTINGS.profile].hand_usage[hand].count + 1
else
G.PROFILES[G.SETTINGS.profile].hand_usage[hand] = {count = 1, order = hand_label}
end
if G.GAME.hand_usage[hand] then
G.GAME.hand_usage[hand].count = G.GAME.hand_usage[hand].count + 1
else
G.GAME.hand_usage[hand] = {count = 1, order = hand_label}
end
G:save_settings()
end
function set_profile_progress()
G.PROGRESS = G.PROGRESS or {
joker_stickers = {tally = 0, of = 0},
deck_stakes = {tally = 0, of = 0},
challenges = {tally = 0, of = 0},
}
for _, v in pairs(G.PROGRESS) do
if type(v) == 'table' then
v.tally = 0
v.of = 0
end
end
for _, v in pairs(G.P_CENTERS) do
if v.set == 'Back' and not v.omit then
G.PROGRESS.deck_stakes.of = G.PROGRESS.deck_stakes.of + #G.P_CENTER_POOLS.Stake
G.PROGRESS.deck_stakes.tally = G.PROGRESS.deck_stakes.tally + get_deck_win_stake(v.key)
end
if v.set == 'Joker' then
G.PROGRESS.joker_stickers.of = G.PROGRESS.joker_stickers.of + #G.P_CENTER_POOLS.Stake
G.PROGRESS.joker_stickers.tally = G.PROGRESS.joker_stickers.tally + get_joker_win_sticker(v, true)
end
end
for _, v in pairs(G.CHALLENGES) do
G.PROGRESS.challenges.of = G.PROGRESS.challenges.of + 1
if G.PROFILES[G.SETTINGS.profile].challenge_progress.completed[v.id] then
G.PROGRESS.challenges.tally = G.PROGRESS.challenges.tally + 1
end
end
G.PROFILES[G.SETTINGS.profile].progress.joker_stickers = copy_table(G.PROGRESS.joker_stickers)
G.PROFILES[G.SETTINGS.profile].progress.deck_stakes = copy_table(G.PROGRESS.deck_stakes)
G.PROFILES[G.SETTINGS.profile].progress.challenges = copy_table(G.PROGRESS.challenges)
end
function set_discover_tallies()
G.DISCOVER_TALLIES = G.DISCOVER_TALLIES or {
blinds = {tally = 0, of = 0},
tags = {tally = 0, of = 0},
jokers = {tally = 0, of = 0},
consumeables = {tally = 0, of = 0},
vouchers = {tally = 0, of = 0},
boosters = {tally = 0, of = 0},
editions = {tally = 0, of = 0},
backs = {tally = 0, of = 0},
total = {tally = 0, of = 0},
}
for _, v in ipairs(SMODS.ConsumableType.ctype_buffer) do
G.DISCOVER_TALLIES[v:lower()..'s'] = {tally = 0, of = 0}
end for _, v in pairs(G.DISCOVER_TALLIES) do
v.tally = 0
v.of = 0
end
for _, v in pairs(G.P_CENTERS) do
if not v.omit and not v.no_collection then
if v.set and ((v.set == 'Joker') or v.consumeable or (v.set == 'Edition') or (v.set == 'Voucher') or (v.set == 'Back') or (v.set == 'Booster')) then
G.DISCOVER_TALLIES.total.of = G.DISCOVER_TALLIES.total.of+1
if v.discovered then
G.DISCOVER_TALLIES.total.tally = G.DISCOVER_TALLIES.total.tally+1
end
end
if v.set and v.set == 'Joker' then
G.DISCOVER_TALLIES.jokers.of = G.DISCOVER_TALLIES.jokers.of+1
if v.discovered then
G.DISCOVER_TALLIES.jokers.tally = G.DISCOVER_TALLIES.jokers.tally+1
end
end
if v.set and v.set == 'Back' then
G.DISCOVER_TALLIES.backs.of = G.DISCOVER_TALLIES.backs.of+1
if v.unlocked then
G.DISCOVER_TALLIES.backs.tally = G.DISCOVER_TALLIES.backs.tally+1
end
end
if v.set and v.consumeable then
G.DISCOVER_TALLIES.consumeables.of = G.DISCOVER_TALLIES.consumeables.of+1
if v.discovered then
G.DISCOVER_TALLIES.consumeables.tally = G.DISCOVER_TALLIES.consumeables.tally+1
end
local tally = G.DISCOVER_TALLIES[v.set:lower()..'s']
if tally then
tally.of = tally.of + 1
if v.discovered then
tally.tally = tally.tally + 1
end
end
end
if v.set and v.set == 'Voucher' then
G.DISCOVER_TALLIES.vouchers.of = G.DISCOVER_TALLIES.vouchers.of+1
if v.discovered then
G.DISCOVER_TALLIES.vouchers.tally = G.DISCOVER_TALLIES.vouchers.tally+1
end
end
if v.set and v.set == 'Booster' then
G.DISCOVER_TALLIES.boosters.of = G.DISCOVER_TALLIES.boosters.of+1
if v.discovered then
G.DISCOVER_TALLIES.boosters.tally = G.DISCOVER_TALLIES.boosters.tally+1
end
end
if v.set and v.set == 'Edition' then
G.DISCOVER_TALLIES.editions.of = G.DISCOVER_TALLIES.editions.of+1
if v.discovered then
G.DISCOVER_TALLIES.editions.tally = G.DISCOVER_TALLIES.editions.tally+1
end
end
end
end
for _, v in pairs(G.P_BLINDS) do
if not v.no_collection then
G.DISCOVER_TALLIES.total.of = G.DISCOVER_TALLIES.total.of+1
G.DISCOVER_TALLIES.blinds.of = G.DISCOVER_TALLIES.blinds.of+1
if v.discovered then
G.DISCOVER_TALLIES.blinds.tally = G.DISCOVER_TALLIES.blinds.tally+1
G.DISCOVER_TALLIES.total.tally = G.DISCOVER_TALLIES.total.tally+1
end
end
end
for _, v in pairs(G.P_TAGS) do
if not v.no_collection then
G.DISCOVER_TALLIES.total.of = G.DISCOVER_TALLIES.total.of+1
G.DISCOVER_TALLIES.tags.of = G.DISCOVER_TALLIES.tags.of+1
if v.discovered then
G.DISCOVER_TALLIES.tags.tally = G.DISCOVER_TALLIES.tags.tally+1
G.DISCOVER_TALLIES.total.tally = G.DISCOVER_TALLIES.total.tally+1
end
end
end
G.PROFILES[G.SETTINGS.profile].high_scores.collection.amt = G.DISCOVER_TALLIES.total.tally
G.PROFILES[G.SETTINGS.profile].high_scores.collection.tot = G.DISCOVER_TALLIES.total.of
G.PROFILES[G.SETTINGS.profile].progress.discovered = copy_table(G.DISCOVER_TALLIES.total)
if check_for_unlock then check_for_unlock({type = 'discover_amount',
amount = G.DISCOVER_TALLIES.total.tally,
planet_count = G.DISCOVER_TALLIES.planets.tally,
tarot_count = G.DISCOVER_TALLIES.tarots.tally})
end
end
function stop_use()
G.GAME.STOP_USE = (G.GAME.STOP_USE or 0) + 1
dec_stop_use(6)
end
function dec_stop_use(_depth)
if _depth > 0 then
G.E_MANAGER:add_event(Event({
blocking = false,
no_delete = true,
func = (function()
dec_stop_use(_depth - 1)
return true end)}))
else
G.E_MANAGER:add_event(Event({
blocking = false,
no_delete = true,
func = (function()
G.GAME.STOP_USE = math.max(G.GAME.STOP_USE - 1, 0)
return true end)}))
end
end
function inc_career_stat(stat, mod)
if G.GAME.seeded or G.GAME.challenge then return end
if not G.PROFILES[G.SETTINGS.profile].career_stats[stat] then G.PROFILES[G.SETTINGS.profile].career_stats[stat] = 0 end
G.PROFILES[G.SETTINGS.profile].career_stats[stat] = G.PROFILES[G.SETTINGS.profile].career_stats[stat] + (mod or 0)
G:save_settings()
end
function recursive_table_cull(t)
local ret_t = {}
for k, v in pairs(t) do
if type(v) == 'table' then
if v.is and v:is(Object) then ret_t[k] = [["]].."MANUAL_REPLACE"..[["]]
else ret_t[k] = recursive_table_cull(v)
end
else ret_t[k] = v end
end
return ret_t
end
function save_with_action(action)
G.action = action
save_run()
G.action = nil
end
function save_run()
if G.F_NO_SAVING == true then return end
local cardAreas = {}
for k, v in pairs(G) do
if (type(v) == "table") and v.is and v:is(CardArea) then
local cardAreaSer = v:save()
if cardAreaSer then cardAreas[k] = cardAreaSer end
end
end
local tags = {}
for k, v in ipairs(G.GAME.tags) do
if (type(v) == "table") and v.is and v:is(Tag) then
local tagSer = v:save()
if tagSer then tags[k] = tagSer end
end
end
G.culled_table = recursive_table_cull{
cardAreas = cardAreas,
tags = tags,
GAME = G.GAME,
STATE = G.STATE,
ACTION = G.action or nil,
BLIND = G.GAME.blind:save(),
BACK = G.GAME.selected_back:save(),
VERSION = G.VERSION
}
G.ARGS.save_run = G.culled_table
G.FILE_HANDLER = G.FILE_HANDLER or {}
G.FILE_HANDLER.run = true
G.FILE_HANDLER.update_queued = true
end
function remove_save()
love.filesystem.remove(G.SETTINGS.profile..'/save.jkr')
G.SAVED_GAME = nil
G.FILE_HANDLER.run = nil
end
function loc_colour(_c, _default)
G.ARGS.LOC_COLOURS = G.ARGS.LOC_COLOURS or {
red = G.C.RED,
mult = G.C.MULT,
blue = G.C.BLUE,
chips = G.C.CHIPS,
green = G.C.GREEN,
money = G.C.MONEY,
gold = G.C.GOLD,
attention = G.C.FILTER,
purple = G.C.PURPLE,
white = G.C.WHITE,
inactive = G.C.UI.TEXT_INACTIVE,
spades = G.C.SUITS.Spades,
hearts = G.C.SUITS.Hearts,
clubs = G.C.SUITS.Clubs,
diamonds = G.C.SUITS.Diamonds,
tarot = G.C.SECONDARY_SET.Tarot,
planet = G.C.SECONDARY_SET.Planet,
spectral = G.C.SECONDARY_SET.Spectral,
edition = G.C.EDITION,
dark_edition = G.C.DARK_EDITION,
legendary = G.C.RARITY[4],
enhanced = G.C.SECONDARY_SET.Enhanced
}
for _, v in ipairs(SMODS.Rarity.obj_buffer) do
G.ARGS.LOC_COLOURS[v:lower()] = G.C.RARITY[v]
end
for _, v in ipairs(SMODS.ConsumableType.ctype_buffer) do
G.ARGS.LOC_COLOURS[v:lower()] = G.C.SECONDARY_SET[v]
end
for _, v in ipairs(SMODS.Suit.obj_buffer) do
G.ARGS.LOC_COLOURS[v:lower()] = G.C.SUITS[v]
end
return G.ARGS.LOC_COLOURS[_c] or _default or G.C.UI.TEXT_DARK
end
function init_localization()
G.localization.misc.v_dictionary_parsed = {}
for k, v in pairs(G.localization.misc.v_dictionary) do
if type(v) == 'table' then
G.localization.misc.v_dictionary_parsed[k] = {multi_line = true}
for kk, vv in ipairs(v) do
G.localization.misc.v_dictionary_parsed[k][kk] = loc_parse_string(vv)
end
else
G.localization.misc.v_dictionary_parsed[k] = loc_parse_string(v)
end
end
G.localization.misc.v_text_parsed = {}
for k, v in pairs(G.localization.misc.v_text) do
G.localization.misc.v_text_parsed[k] = {}
for kk, vv in ipairs(v) do
G.localization.misc.v_text_parsed[k][kk] = loc_parse_string(vv)
end
end
G.localization.tutorial_parsed = {}
for k, v in pairs(G.localization.misc.tutorial) do
G.localization.tutorial_parsed[k] = {multi_line = true}
for kk, vv in ipairs(v) do
G.localization.tutorial_parsed[k][kk] = loc_parse_string(vv)
end
end
G.localization.quips_parsed = {}
for k, v in pairs(G.localization.misc.quips or {}) do
G.localization.quips_parsed[k] = {multi_line = true}
for kk, vv in ipairs(v) do
G.localization.quips_parsed[k][kk] = loc_parse_string(vv)
end
end
for g_k, group in pairs(G.localization) do
if g_k == 'descriptions' then
for _, set in pairs(group) do
for _, center in pairs(set) do
center.text_parsed = {}
if not center.text then else
for _, line in ipairs(center.text) do
center.text_parsed[#center.text_parsed+1] = loc_parse_string(line)
end
center.name_parsed = {}
for _, line in ipairs(type(center.name) == 'table' and center.name or {center.name}) do
center.name_parsed[#center.name_parsed+1] = loc_parse_string(line)
end
if center.unlock then
center.unlock_parsed = {}
for _, line in ipairs(center.unlock) do
center.unlock_parsed[#center.unlock_parsed+1] = loc_parse_string(line)
end
end
end
end
end
end
end
end
function playing_card_joker_effects(cards)
for i = 1, #G.jokers.cards do
G.jokers.cards[i]:calculate_joker({playing_card_added = true, cards = cards})
end
end
function convert_save_to_meta()
if love.filesystem.getInfo(G.SETTINGS.profile..'/'..'unlocked_jokers.jkr') then
local _meta = {
unlocked = {},
alerted = {},
discovered = {}
}
if love.filesystem.getInfo(G.SETTINGS.profile..'/'..'unlocked_jokers.jkr') then
for line in string.gmatch( (get_compressed(G.SETTINGS.profile..'/'..'unlocked_jokers.jkr') or '').. "\n", "([^\n]*)\n") do
local key = line:gsub("%s+", "")
if key and (key ~= '') then
_meta.unlocked[key] = true
end
end
end
if love.filesystem.getInfo(G.SETTINGS.profile..'/'..'discovered_jokers.jkr') then
for line in string.gmatch( (get_compressed(G.SETTINGS.profile..'/'..'discovered_jokers.jkr') or '').. "\n", "([^\n]*)\n") do
local key = line:gsub("%s+", "")
if key and (key ~= '') then
_meta.discovered[key] = true
end
end
end
if love.filesystem.getInfo(G.SETTINGS.profile..'/'..'alerted_jokers.jkr') then
for line in string.gmatch( (get_compressed(G.SETTINGS.profile..'/'..'alerted_jokers.jkr') or '').. "\n", "([^\n]*)\n") do
local key = line:gsub("%s+", "")
if key and (key ~= '') then
_meta.alerted[key] = true
end
end
end
love.filesystem.remove(G.SETTINGS.profile..'/'..'unlocked_jokers.jkr')
love.filesystem.remove(G.SETTINGS.profile..'/'..'discovered_jokers.jkr')
love.filesystem.remove(G.SETTINGS.profile..'/'..'alerted_jokers.jkr')
compress_and_save( G.SETTINGS.profile..'/'..'meta.jkr', STR_PACK(_meta))
end
end
function card_from_control(control)
G.playing_card = (G.playing_card and G.playing_card + 1) or 1
local _card = Card(G.deck.T.x, G.deck.T.y, G.CARD_W, G.CARD_H, G.P_CARDS[control.s..'_'..control.r], G.P_CENTERS[control.e or 'c_base'], {playing_card = G.playing_card})
if control.d then _card:set_edition({[control.d] = true}, true, true) end
if control.g then _card:set_seal(control.g, true, true) end
G.deck:emplace(_card)
table.insert(G.playing_cards, _card)
end
function loc_parse_string(line)
local parsed_line = {}
local control = {}
local _c, _c_name, _c_val, _c_gather = nil, nil, nil, nil
local _s_gather, _s_ref = nil, nil
local str_parts, str_it = {}, 1
for i = 1, #line do
local char = line:sub(i,i)
if char == '{' then --Start of a control section, extract all controls
if str_parts[1] then parsed_line[#parsed_line+1] = {strings = str_parts, control = control or {}} end
str_parts, str_it = {}, 1
control, _c, _c_name, _c_val, _c_gather = {}, nil, nil, nil, nil
_s_gather, _s_ref = nil, nil
_c = true
elseif _c and not (char == ':' or char == '}') and not _c_gather then _c_name = (_c_name or '')..char
elseif _c and char == ':' then _c_gather = true
elseif _c and not (char == ',' or char == '}') and _c_gather then _c_val = (_c_val or '')..char
elseif _c and (char == ',' or char == '}') then _c_gather = nil; if _c_name then control[_c_name] = _c_val end; _c_name = nil; _c_val = nil; if char == '}' then _c = nil end
elseif not _c and char ~= '#' and not _s_gather then str_parts[str_it] = (str_parts[str_it] or '')..(control['X'] and char:gsub("%s+", "") or char)
elseif not _c and char == '#' and not _s_gather then _s_gather = true; if str_parts[str_it] then str_it = str_it + 1 end
elseif not _c and char == '#' and _s_gather then _s_gather = nil; if _s_ref then str_parts[str_it] = {_s_ref}; str_it = str_it + 1; _s_ref = nil end
elseif not _c and _s_gather then _s_ref = (_s_ref or '')..char
end
if i == #line then
if str_parts[1] then parsed_line[#parsed_line+1] = {strings = str_parts, control = control or {}} end
return parsed_line
end
end
end
--UTF8 handler for special characters, from https://github.com/blitmap/lua-utf8-simple
utf8 = {pattern = '[%z\1-\127\194-\244][\128-\191]*'}
utf8.map =
function (s, f, no_subs)
local i = 0
if no_subs then
for b, e in s:gmatch('()' .. utf8.pattern .. '()') do
i = i + 1
local c = e - b
f(i, c, b)
end
else
for b, c in s:gmatch('()(' .. utf8.pattern .. ')') do
i = i + 1
f(i, c, b)
end
end
end
utf8.chars =
function (s, no_subs)
return coroutine.wrap(function () return utf8.map(s, coroutine.yield, no_subs) end)
end
function localize(args, misc_cat)
if args and args.vars then
local reset = {}
for i, j in pairs(args.vars) do
if type(j) == 'table' then
if (j.new and type(j.new) == "function") and ((j.m and j.e) or (j.array and j.sign and (type(j.array) == "table"))) then
reset[i] = number_format(j)
end
end
end
for i, j in pairs(reset) do
args.vars[i] = j
end
end
if args and not (type(args) == 'table') then
if misc_cat and G.localization.misc[misc_cat] then return G.localization.misc[misc_cat][args] or 'ERROR' end
return G.localization.misc.dictionary[args] or 'ERROR'
end
local loc_target = nil
local ret_string = nil
if args.type == 'other' then
loc_target = G.localization.descriptions.Other[args.key]
elseif args.type == 'descriptions' or args.type == 'unlocks' then
loc_target = G.localization.descriptions[args.set][args.key]
elseif args.type == 'tutorial' then
loc_target = G.localization.tutorial_parsed[args.key]
elseif args.type == 'quips' then
loc_target = G.localization.quips_parsed[args.key]
elseif args.type == 'raw_descriptions' then
loc_target = G.localization.descriptions[args.set][args.key]
local multi_line = {}
if loc_target then
for _, lines in ipairs(args.type == 'unlocks' and loc_target.unlock_parsed or args.type == 'name' and loc_target.name_parsed or args.type == 'text' and loc_target or loc_target.text_parsed) do
local final_line = ''
for _, part in ipairs(lines) do
local assembled_string = ''
for _, subpart in ipairs(part.strings) do
assembled_string = assembled_string..(type(subpart) == 'string' and subpart or format_ui_value(args.vars[tonumber(subpart[1])]) or 'ERROR')
end
final_line = final_line..assembled_string
end
multi_line[#multi_line+1] = final_line
end
end
return multi_line
elseif args.type == 'text' then
loc_target = G.localization.misc.v_text_parsed[args.key]
elseif args.type == 'variable' then
loc_target = G.localization.misc.v_dictionary_parsed[args.key]
if not loc_target then return 'ERROR' end
if loc_target.multi_line then
local assembled_strings = {}
for k, v in ipairs(loc_target) do
local assembled_string = ''
for _, subpart in ipairs(v[1].strings) do
assembled_string = assembled_string..(type(subpart) == 'string' and subpart or format_ui_value(args.vars[tonumber(subpart[1])]))
end
assembled_strings[k] = assembled_string
end
return assembled_strings or {'ERROR'}
else
local assembled_string = ''
for _, subpart in ipairs(loc_target[1].strings) do
assembled_string = assembled_string..(type(subpart) == 'string' and subpart or format_ui_value(args.vars[tonumber(subpart[1])]))
end
ret_string = assembled_string or 'ERROR'
end
elseif args.type == 'name_text' then
if pcall(function() ret_string = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name end) then
else ret_string = "ERROR" end
elseif args.type == 'name' then
loc_target = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key]
end
if ret_string then return ret_string end
if loc_target then
for _, lines in ipairs(args.type == 'unlocks' and loc_target.unlock_parsed or args.type == 'name' and loc_target.name_parsed or (args.type == 'text' or args.type == 'tutorial' or args.type == 'quips') and loc_target or loc_target.text_parsed) do
local final_line = {}
for _, part in ipairs(lines) do
local assembled_string = ''
for _, subpart in ipairs(part.strings) do
assembled_string = assembled_string..(type(subpart) == 'string' and subpart or format_ui_value(args.vars[tonumber(subpart[1])]) or 'ERROR')
end
local desc_scale = G.LANG.font.DESCSCALE
if G.F_MOBILE_UI then desc_scale = desc_scale*1.5 end
if args.type == 'name' then
final_line[#final_line+1] = {n=G.UIT.O, config={
object = DynaText({string = {assembled_string},
colours = {(part.control.V and args.vars.colours[tonumber(part.control.V)]) or (part.control.C and loc_colour(part.control.C)) or args.text_colour or G.C.UI.TEXT_LIGHT},
bump = true,
silent = true,
pop_in = 0,
pop_in_rate = 4,
maxw = 5,
shadow = true,
y_offset = -0.6,
spacing = math.max(0, 0.32*(17 - #assembled_string)),
scale = (0.55 - 0.004*#assembled_string)*(part.control.s and tonumber(part.control.s) or args.scale or 1)
})
}}
elseif part.control.E then
local _float, _silent, _pop_in, _bump, _spacing = nil, true, nil, nil, nil
if part.control.E == '1' then
_float = true; _silent = true; _pop_in = 0
elseif part.control.E == '2' then
_bump = true; _spacing = 1
end
final_line[#final_line+1] = {n=G.UIT.O, config={
object = DynaText({string = {assembled_string}, colours = {part.control.V and args.vars.colours[tonumber(part.control.V)] or loc_colour(part.control.C or nil)},
float = _float,
silent = _silent,
pop_in = _pop_in,
bump = _bump,
spacing = _spacing,
scale = 0.32*(part.control.s and tonumber(part.control.s) or args.scale or 1)*desc_scale})
}}
elseif part.control.X then
final_line[#final_line+1] = {n=G.UIT.C, config={align = "m", colour = loc_colour(part.control.X), r = 0.05, padding = 0.03, res = 0.15}, nodes={
{n=G.UIT.T, config={
text = assembled_string,
colour = loc_colour(part.control.C or nil),
scale = 0.32*(part.control.s and tonumber(part.control.s) or args.scale or 1)*desc_scale}},
}}
else
final_line[#final_line+1] = {n=G.UIT.T, config={
detailed_tooltip = part.control.T and (G.P_CENTERS[part.control.T] or G.P_TAGS[part.control.T]) or nil,
text = assembled_string,
shadow = args.shadow,
colour = part.control.V and args.vars.colours[tonumber(part.control.V)] or not part.control.C and args.text_colour or loc_colour(part.control.C or nil, args.default_col),
scale = 0.32*(part.control.s and tonumber(part.control.s) or args.scale or 1)*desc_scale},}
end
end
if args.type == 'name' or args.type == 'text' then return final_line end
args.nodes[#args.nodes+1] = final_line
end
end
end
function get_stake_sprite(_stake, _scale)
_stake = _stake or 1
_scale = _scale or 1
local stake_sprite = Sprite(0,0,_scale*1,_scale*1,G.ASSET_ATLAS[G.P_CENTER_POOLS.Stake[_stake].atlas], G.P_CENTER_POOLS.Stake[_stake].pos)
stake_sprite.states.drag.can = false
if G.P_CENTER_POOLS['Stake'][_stake].shiny then
stake_sprite.draw = function(_sprite)
_sprite.ARGS.send_to_shader = _sprite.ARGS.send_to_shader or {}
_sprite.ARGS.send_to_shader[1] = math.min(_sprite.VT.r*3, 1) + G.TIMERS.REAL/(18) + (_sprite.juice and _sprite.juice.r*20 or 0) + 1
_sprite.ARGS.send_to_shader[2] = G.TIMERS.REAL
Sprite.draw_shader(_sprite, 'dissolve')
Sprite.draw_shader(_sprite, 'voucher', nil, _sprite.ARGS.send_to_shader)
end
end
return stake_sprite
end
function get_front_spriteinfo(_front)
if _front and _front.suit and G.SETTINGS.CUSTOM_DECK and G.SETTINGS.CUSTOM_DECK.Collabs then
local collab = G.SETTINGS.CUSTOM_DECK.Collabs[_front.suit]
if collab then
local deckSkin = SMODS.DeckSkins[collab]
if deckSkin then
if deckSkin.outdated then
local hasRank = false
for i = 1, #deckSkin.ranks do
if deckSkin.ranks[i] == _front.value then hasRank = true break end
end
if hasRank then
local atlas = G.ASSET_ATLAS[G.SETTINGS.colour_palettes[_front.suit] == 'hc' and deckSkin.hc_atlas or deckSkin.lc_atlas]
if atlas then
if deckSkin.pos_style == 'collab' then
return atlas, G.COLLABS.pos[_front.value]
elseif deckSkin.pos_style == 'suit' then
return atlas, { x = _front.pos.x, y = 0}
elseif deckSkin.pos_style == 'deck' then
return atlas, _front.pos
elseif deckSkin.pos_style == 'ranks' or nil then
for i, rank in ipairs(deckSkin.ranks) do
if rank == _front.value then
return atlas, { x = i - 1, y = 0}
end
end
end
end
end
return G.ASSET_ATLAS[G.SETTINGS.colour_palettes[_front.suit] == 'hc' and _front.hc_atlas or _front.lc_atlas or {}] or G.ASSET_ATLAS[_front.atlas] or G.ASSET_ATLAS["cards_"..(G.SETTINGS.colour_palettes[_front.suit] == 'hc' and 2 or 1)], _front.pos
else
local palette = deckSkin.palette_map and deckSkin.palette_map[G.SETTINGS.colour_palettes[_front.suit] or ''] or (deckSkin.palettes or {})[1]
local hasRank = false
for i = 1, #palette.ranks do
if palette.ranks[i] == _front.value then hasRank = true break end
end
if hasRank then
local atlas = G.ASSET_ATLAS[palette.atlas]
if type(palette.pos_style) == "table" then
if palette.pos_style[_front.value] then
if palette.pos_style[_front.value].atlas then
atlas = G.ASSET_ATLAS[palette.pos_style[_front.value].atlas]
end
if palette.pos_style[_front.value].pos then
return atlas, palette.pos_style[_front.value].pos
end
elseif palette.pos_style.fallback_style then
if palette.pos_style.fallback_style == 'collab' then
return atlas, G.COLLABS.pos[_front.value]
elseif palette.pos_style.fallback_style == 'suit' then
return atlas, { x = _front.pos.x, y = 0}
elseif palette.pos_style.fallback_style == 'deck' then
return atlas, _front.pos
end
end
elseif palette.pos_style == 'collab' then
return atlas, G.COLLABS.pos[_front.value]
elseif palette.pos_style == 'suit' then
return atlas, { x = _front.pos.x, y = 0}
elseif palette.pos_style == 'deck' then
return atlas, _front.pos
elseif palette.pos_style == 'ranks' or nil then
for i, rank in ipairs(palette.ranks) do
if rank == _front.value then
return atlas, { x = i - 1, y = 0}
end
end
end
end
return G.ASSET_ATLAS[palette.hc_default and _front.hc_atlas or _front.lc_atlas or {}] or G.ASSET_ATLAS[_front.atlas] or G.ASSET_ATLAS["cards_"..(palette.hc_default and 2 or 1)], _front.pos
end
end
end
end
return G.ASSET_ATLAS[G.SETTINGS.colourblind_option and _front.hc_atlas or _front.lc_atlas or {}] or G.ASSET_ATLAS[_front.atlas] or G.ASSET_ATLAS["cards_"..(G.SETTINGS.colourblind_option and 2 or 1)], _front.pos
end
function get_stake_col(_stake)
G.C.STAKES = G.C.STAKES or {
G.C.WHITE,
G.C.RED,
G.C.GREEN,
G.C.BLACK,
G.C.BLUE,
G.C.PURPLE,
G.C.ORANGE,
G.C.GOLD
}
return G.C.STAKES[_stake]
end
function get_challenge_int_from_id(_id)
for k, v in pairs(G.CHALLENGES) do
if v.id == _id then return k end
end
return 0
end
function get_starting_params()
return {
dollars = 4,
hand_size = 8,
discards = 3,
hands = 4,
reroll_cost = 5,
joker_slots = 5,
ante_scaling = 1,
consumable_slots = 2,
no_faces = false,
erratic_suits_and_ranks = false,
}
end
function get_challenge_rule(_challenge, _type, _id)
if _challenge and _challenge.rules and _challenge.rules[_type] then
for k, v in ipairs(_challenge.rules[_type]) do
if _id == v.id then return v.value end
end
end
end
--SOUND
function PLAY_SOUND(args)
args.per = args.per or 1
args.vol = args.vol or 1
SOURCES[args.sound_code] = SOURCES[args.sound_code] or {}
local should_stream = (string.find(args.sound_code,'music') or string.find(args.sound_code,'ambient'))
local s = {sound = love.audio.newSource("resources/sounds/"..args.sound_code..'.ogg', should_stream and "stream" or 'static')}
table.insert(SOURCES[args.sound_code], s)
s.sound_code = args.sound_code
s.original_pitch = args.per or 1
s.original_volume = args.vol or 1
s.created_on_pause = (args.overlay_menu and true or false)
s.created_on_state = args.state
s.sfx_handled = 0
s.transition_timer = 0
SET_SFX(s, args)
love.audio.play(s.sound)
return s
end
function STOP_AUDIO()
for _, source in pairs(SOURCES) do
for _, s in pairs(source) do
if s.sound:isPlaying() then
s.sound:stop()
end
end
end
end
function SET_SFX(s, args)
if string.find(s.sound_code,'music') then
if s.sound_code == args.desired_track then
s.current_volume = s.current_volume or 1
s.current_volume = 1*(args.dt*3) + (1-(args.dt*3))*s.current_volume
else
s.current_volume = s.current_volume or 0
s.current_volume = 0*(args.dt*3) + (1-(args.dt*3))*s.current_volume
end
s.sound:setVolume(s.current_volume*s.original_volume*(args.sound_settings.volume/100.0)*(args.sound_settings.music_volume/100.0))
s.sound:setPitch(s.original_pitch*args.pitch_mod)
else
if s.temp_pitch ~= s.original_pitch then
s.sound:setPitch(s.original_pitch)
s.temp_pitch = s.original_pitch
end
local sound_vol = s.original_volume*(args.sound_settings.volume/100.0)*(args.sound_settings.game_sounds_volume/100.0)
if s.created_on_state == 13 then sound_vol = sound_vol*args.splash_vol end
if sound_vol <= 0 then
s.sound:stop()
else
s.sound:setVolume(sound_vol)
end
end
end
function MODULATE(args)
for k, v in pairs(SOURCES) do
if (string.find(k,'music') and (args.desired_track ~= '')) then
if v[1] and v[1].sound and v[1].sound:isPlaying() then
else
RESTART_MUSIC(args)
break;
end
end
end
for k, v in pairs(SOURCES) do
local i=1
while i <= #v do
if not v[i].sound:isPlaying() then
table.remove(v, i)
else
i = i + 1
end
end
for i, s in ipairs(v) do
if s.sound and s.sound:isPlaying() and s.original_volume then
SET_SFX(s, args)
end
end
end
end
function RESTART_MUSIC(args)
for k, v in pairs(SOURCES) do
if string.find(k,'music') then
for i, s in ipairs(v) do
s.sound:stop()
end
SOURCES[k] = {}
args.per = 0.7
args.vol = 0.6
args.sound_code = k
local s = PLAY_SOUND(args)
s.initialized = true
end
end
end
function AMBIENT(args)
for k, v in pairs(SOURCES) do
if args.ambient_control[k] then
local start_ambient = args.ambient_control[k].vol > 0
for i, s in ipairs(v) do
if s.sound and s.sound:isPlaying() and s.original_volume then
s.original_volume = args.ambient_control[k].vol
SET_SFX(s, args)
start_ambient = false
end
end
if start_ambient then
args.sound_code = k
args.vol = args.ambient_control[k].vol
args.per = args.ambient_control[k].per
PLAY_SOUND(args)
end
end
end
end
function RESET_STATES(state)
for k, v in pairs(SOURCES) do
for i, s in ipairs(v) do
s.created_on_state = state
end
end
end