Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 148 additions & 1 deletion Core/Brainstorm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Brainstorm.config = {
soul_skip = 1,
inst_observatory = false,
inst_perkeo = false,
joker_key = "",
joker_id = 1,
joker_location = "any",
joker_location_id = 1,
},
ar_prefs = {
spf_id = 3,
Expand Down Expand Up @@ -271,6 +275,137 @@ ffi.cdef([[
const char* brainstorm(const char* seed, const char* voucher, const char* pack, const char* tag, double souls, bool observatory, bool perkeo);
]])

-- Joker search helper: simulate RNG for prediction without modifying game state
local function predict_pseudoseed(key, predict_seed, temp_state)
if not temp_state[key] then
temp_state[key] = pseudohash(key .. (predict_seed or ""))
end
temp_state[key] = math.abs(tonumber(string.format("%.13f", (2.134453429141 + temp_state[key] * 1.72431234) % 1)))
return (temp_state[key] + (pseudohash(predict_seed) or 0)) / 2
end

-- Simulate shop jokers for a given seed, returns true if target joker is found
local function simulate_shop_jokers(predict_seed, target_joker)
local shop_slots = 2
local temp_state = {}

for i = 1, shop_slots do
-- Determine card type for this slot
local type_seed = predict_pseudoseed("cdt1", predict_seed, temp_state)
local type_poll = pseudorandom(type_seed)

-- Default rates from base game
local joker_rate = 20
local total_rate = 20 + 4 + 4 + 4 -- joker + tarot + planet + playing_card
local polled_rate = type_poll * total_rate

if polled_rate <= joker_rate then
-- This slot generates a Joker
local rarity_seed = predict_pseudoseed("rarity1sho", predict_seed, temp_state)
local rarity_roll = pseudorandom(rarity_seed)

local rarity = 1
if rarity_roll > 0.95 then
rarity = 3
elseif rarity_roll > 0.7 then
rarity = 2
end

local joker_seed = predict_pseudoseed("Joker" .. rarity .. "sho1", predict_seed, temp_state)
local joker_center = pseudorandom_element(G.P_JOKER_RARITY_POOLS[rarity], joker_seed)

if joker_center and joker_center.key == target_joker then
return true
end
end
end
return false
end

-- Simulate buffoon pack jokers for a given seed, returns true if target joker is found
local function simulate_buffoon_pack_jokers(pack_key, predict_seed, target_joker)
local joker_count = 2
if string.find(pack_key, "jumbo") or string.find(pack_key, "mega") then
joker_count = 4
end

local temp_state = {}

for i = 1, joker_count do
local rarity_seed = predict_pseudoseed("rarity1buf", predict_seed, temp_state)
local rarity_roll = pseudorandom(rarity_seed)

local rarity = 1
if rarity_roll > 0.95 then
rarity = 3
elseif rarity_roll > 0.7 then
rarity = 2
end

local joker_seed = predict_pseudoseed("Joker" .. rarity .. "buf1", predict_seed, temp_state)
local joker_center = pseudorandom_element(G.P_JOKER_RARITY_POOLS[rarity], joker_seed)

if joker_center and joker_center.key == target_joker then
return true
end
end
return false
end

-- Check if seed contains target joker in buffoon packs available in shop
local function check_buffoon_packs_for_joker(predict_seed, target_joker)
local temp_state = {}

-- Check both shop pack slots
for slot = 1, 2 do
local cume, it, center = 0, 0, nil
for _, v in ipairs(G.P_CENTER_POOLS["Booster"]) do
cume = cume + (v.weight or 1)
end

local poll_seed = predict_pseudoseed("shop_pack" .. slot, predict_seed, temp_state)
local poll = pseudorandom(poll_seed) * cume

for _, v in ipairs(G.P_CENTER_POOLS["Booster"]) do
it = it + (v.weight or 1)
if it >= poll and it - (v.weight or 1) <= poll then
center = v
break
end
end

if center and string.find(center.key, "buffoon") then
if simulate_buffoon_pack_jokers(center.key, predict_seed, target_joker) then
return true
end
end
end
return false
end

-- Main joker search function, called after FFI returns a candidate seed
local function check_seed_for_joker(seed, target_joker, location)
if not target_joker or target_joker == "" then
return true -- No joker filter active
end

local found = false

if location == "shop" or location == "any" then
if simulate_shop_jokers(seed, target_joker) then
found = true
end
end

if not found and (location == "pack" or location == "any") then
if check_buffoon_packs_for_joker(seed, target_joker) then
found = true
end
end

return found
end

function Brainstorm.autoReroll()
local seed_found = random_string(
8,
Expand Down Expand Up @@ -309,7 +444,19 @@ function Brainstorm.autoReroll()
Brainstorm.config.ar_filters.inst_perkeo
)
)
if seed_found then

-- Post-filter: check for joker if joker search is active
if seed_found and seed_found ~= "" then
local target_joker = Brainstorm.config.ar_filters.joker_key
local joker_location = Brainstorm.config.ar_filters.joker_location

if not check_seed_for_joker(seed_found, target_joker, joker_location) then
-- Seed doesn't have the target joker, reject it
return nil
end
end

if seed_found and seed_found ~= "" then
_stake = G.GAME.stake
G:delete_run()
G:start_run({
Expand Down
71 changes: 71 additions & 0 deletions UI/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,46 @@ local pack_list = {
["Jumbo Spectral"] = { "p_spectral_jumbo_1" },
["Mega Spectral"] = { "p_spectral_mega_1" },
}

-- Joker location options for search
local joker_location_list = {
["Any"] = "any",
["Shop"] = "shop",
["Buffoon Pack"] = "pack",
}

local joker_location_keys = { "Any", "Shop", "Buffoon Pack" }

-- Dynamic joker list (populated when game data is available)
local joker_list = { ["None"] = "" }
local joker_keys = { "None" }

-- Build joker list from game data
local function build_joker_list()
if not G or not G.P_CENTER_POOLS or not G.P_CENTER_POOLS.Joker then
return
end

joker_list = { ["None"] = "" }
joker_keys = { "None" }

local temp_jokers = {}
for _, joker in ipairs(G.P_CENTER_POOLS.Joker) do
if joker.key and joker.key ~= "" then
local display_name = localize({ type = "name_text", set = "Joker", key = joker.key }) or joker.key
table.insert(temp_jokers, { name = display_name, key = joker.key })
end
end

-- Sort alphabetically by display name
table.sort(temp_jokers, function(a, b) return a.name < b.name end)

for _, joker in ipairs(temp_jokers) do
joker_list[joker.name] = joker.key
table.insert(joker_keys, joker.name)
end
end

local spf_list = {
["500"] = 500,
["750"] = 750,
Expand Down Expand Up @@ -188,6 +228,18 @@ G.FUNCS.change_suit_ratio = function(x)
Brainstorm.writeConfig()
end

G.FUNCS.change_target_joker = function(x)
Brainstorm.config.ar_filters.joker_id = x.to_key
Brainstorm.config.ar_filters.joker_key = joker_list[x.to_val]
Brainstorm.writeConfig()
end

G.FUNCS.change_joker_location = function(x)
Brainstorm.config.ar_filters.joker_location_id = x.to_key
Brainstorm.config.ar_filters.joker_location = joker_location_list[x.to_val]
Brainstorm.writeConfig()
end

Brainstorm.opt_ref = G.FUNCS.options
G.FUNCS.options = function(e)
Brainstorm.opt_ref(e)
Expand All @@ -196,6 +248,9 @@ end
local ct = create_tabs
function create_tabs(args)
if args and args.tab_h == 7.05 then
-- Build joker list when opening settings (game data now available)
build_joker_list()

args.tabs[#args.tabs + 1] = {
label = "Brainstorm",
tab_definition_function = function()
Expand Down Expand Up @@ -249,6 +304,22 @@ function create_tabs(args)
current_option = Brainstorm.config.ar_filters.soul_skip + 1
or 1,
}),
create_option_cycle({
label = "AR: JOKER SEARCH",
scale = 0.8,
w = 4,
options = joker_keys,
opt_callback = "change_target_joker",
current_option = Brainstorm.config.ar_filters.joker_id or 1,
}),
create_option_cycle({
label = "AR: JOKER LOCATION",
scale = 0.8,
w = 4,
options = joker_location_keys,
opt_callback = "change_joker_location",
current_option = Brainstorm.config.ar_filters.joker_location_id or 1,
}),
},
},
{
Expand Down