From a636126164d98a7bbfd1360340fcbefc8b75ebee Mon Sep 17 00:00:00 2001 From: KyleBastien Date: Sun, 1 Feb 2026 20:05:54 -0800 Subject: [PATCH] Implement Joker Search on v2 of Brainstorm --- Core/Brainstorm.lua | 149 +++++++++++++++++++++++++++++++++++++++++++- UI/ui.lua | 71 +++++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) diff --git a/Core/Brainstorm.lua b/Core/Brainstorm.lua index cc351fe..c13a92b 100644 --- a/Core/Brainstorm.lua +++ b/Core/Brainstorm.lua @@ -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, @@ -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, @@ -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({ diff --git a/UI/ui.lua b/UI/ui.lua index 23af71b..bca742a 100644 --- a/UI/ui.lua +++ b/UI/ui.lua @@ -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, @@ -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) @@ -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() @@ -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, + }), }, }, {