diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..14cb8a5 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,25 @@ +# Brainstorm Copilot Instructions + +- **Purpose**: Balatro mod that automates rerolls to find seeds matching user-selected filters (tags, vouchers, packs) and restarts runs accordingly. Main logic lives in [Core/Brainstorm.lua](Core/Brainstorm.lua); a working options tab is in [UI/ui.lua](UI/ui.lua); older prototypes live under [Debug](Debug). +- **Load flow**: The mod is injected via [lovely.toml](lovely.toml): `nativefs.lua` is loaded before `main.lua`, `Core/Brainstorm.lua` is appended to `main.lua`, and `Brainstorm.init()` runs after the profile load patch in `game.lua`. +- **Globals & patching**: `Brainstorm.init()` discovers its own path, loads config, and executes `UI/ui.lua`. Core behaviors monkeypatch `Controller:key_press_update`, `Game:update`, and `create_UIBox_round_scores_row` while calling the originals; keep these reference patterns when extending patches. +- **Config storage**: Defaults sit in `Brainstorm.DEFAULT_CONFIG` and are serialized to `config.lua` via `STR_PACK/STR_UNPACK` using `nativefs`. Config load merges defaults and drops dead fields (e.g., the old `keybind_autoreroll`); when adding settings, seed defaults, keep backward compatibility, and call `Brainstorm.writeConfig()` (UI exit already does this). +- **Controls**: Holding the modifier (`lctrl` by default) + `r` triggers a manual reroll; modifier + `a` toggles auto-reroll. `keybind_autoreroll` is a plain `love.keyboard.isDown` check; respect current bindings when changing input logic. +- **Manual reroll path**: `Brainstorm.reroll()` deletes and restarts the run with the current stake/seed/challenge context, preserving seeded/challenge flags on `G.GAME`. Keep these assignments if you alter reroll behavior. +- **Auto-reroll loop**: `Game:update` drives `Brainstorm.autoReroll()` on a dynamic interval derived from `ar_prefs.spf_int` (1000 ⇒ 0.01s, 500 ⇒ 0.02s) while `Brainstorm.ar_active` is true; a status text is shown after 60 frames via `Brainstorm.attentionText()` and cleared in `removeAttentionText()`. +- **Seed search implementation**: `Brainstorm.autoReroll()` FFI-loads `Immolate.dll` and calls `brainstorm(seed, pack, tag, souls)` (DLL ignores voucher/observatory/perkeo in current builds); keep the Lua `ffi.cdef` in sync with the DLL you ship. It writes `G.GAME.used_filter` and `filter_info` for downstream display; keep these fields if you change the flow. If the DLL is missing or lacks the symbol, auto-reroll now disables itself instead of crashing. +- **Immolate source**: The C++ sources live in [immolate/](immolate); `immolate.cpp` wires globals `BRAINSTORM_PACK/TAG/SOULS` into a `Search` over seeds and exports `brainstorm`/`brainstorm_cpp` plus `free_result`. Search walks blocks of 1,000,000 seeds, is multi-threaded, and stops on the first seed meeting the filter when `exitOnFind` is true. Filters currently check first-pack type, first-tag match, and require N souls from the first Mega Arcana pack; add new filters there if the DLL needs to evolve. +- **DLL version drift**: The shipped `Immolate.dll` may be older than the sources under [immolate/](immolate). Treat the C++ tree as reference, but verify exports/signatures against the actual DLL before changing `ffi.cdef` or call sites. +- **Filters & prefs**: Search options live under `Brainstorm.config.ar_filters` (pack, tag, voucher ids/names, soul skips, instant observatory/perkeo) and `ar_prefs` (seeds-per-frame). UI callbacks in [UI/ui.lua](UI/ui.lua) update these and immediately persist. +- **UI integration**: `create_tabs` is patched to append a "Brainstorm" tab (only when `tab_h == 7.05`, matching the options screen). It uses base-game helpers (`create_option_cycle`, `create_toggle`) to render cycles/toggles. `G.FUNCS.exit_overlay_menu` is wrapped to save config on close. +- **Display tweaks**: `create_UIBox_round_scores_row` colors the seed label red when seeded and blue when a filtered seed was used; preserve this branch if altering round score rows. +- **Vendored dependencies**: [nativefs.lua](nativefs.lua) is a bundled filesystem shim (LuaJIT+FFI); avoid modifications unless fixing I/O. [Immolate.dll](Immolate.dll) is an external binary providing the seed search routine. [steamodded_compat.lua](steamodded_compat.lua) only carries mod metadata. +- **Styling & formatting**: Lua uses Stylua ([stylua.toml](stylua.toml), [\.vscode/settings.json](.vscode/settings.json)) with 2-space indents, 80-col preference, and double-quote bias. See [style_guide.md](style_guide.md) for naming (snake_case vars, camelCase functions, PascalCase classes) and general conventions. +- **Adding settings**: 1) Extend `Brainstorm.config` defaults. 2) Wire UI controls in [UI/ui.lua](UI/ui.lua) with `G.FUNCS.change_*` callbacks that update config and call `Brainstorm.writeConfig()`. 3) Ensure new values are passed into `autoReroll()` if they influence search. +- **Touching globals**: Many helpers assume global tables (`G`, `G.FUNCS`, `G.UIT`). Cache originals before wrapping and re-call them to avoid breaking base game behavior. +- **Debug folder**: [Debug/settings.lua](Debug/settings.lua) and [Debug/ui.lua](Debug/ui.lua) are experimental layouts; treat them as reference prototypes, not the active UI. +- **Testing workflow**: No automated tests. Validation is manual by running Balatro/Steamodded with this mod enabled; exercise keybinds and the "Brainstorm" options tab to confirm config persistence and reroll behavior. +- **Release/versioning**: Current version string is `Brainstorm v2.2.0-alpha` in `Brainstorm.VERSION`; update this and `lovely.toml`/headers if you ship releases. +- **Safety tips**: Avoid touching `config.lua` by hand; prefer the UI or defaults. Keep non-ASCII out of sources. Do not remove calls to `Brainstorm.writeConfig()` around UI exits. + +If any part of this feels unclear or incomplete, tell me which sections to expand or examples to add. diff --git a/.github/ui_modding.md b/.github/ui_modding.md new file mode 100644 index 0000000..7118845 --- /dev/null +++ b/.github/ui_modding.md @@ -0,0 +1,136 @@ +# Balatro UI Modding Guide + +This document explains how to build custom UI using Balatro’s built-in UI engine, with a focus on creating custom settings tabs and reusable controls. + +## Mental model + +- The UI is a tree of nodes built from plain Lua tables. You pass that tree into `UIBox{definition=..., config=...}` to instantiate it. +- Node types are enumerated in `G.UIT` (see `globals.lua`): + - `T` text, `B` box, `C` column, `R` row, `O` object (Sprite/DynaText/etc.), `ROOT`, `S` slider, `I` input. +- Every node table uses keys: + - `n` (required): the node type from `G.UIT.*`. + - `config` (optional): alignment, padding, colors, callbacks, refs, ids. + - `nodes` (optional): array of child nodes. +- Layout is resolved by `UIElement:set_alignments` (engine/ui.lua). `config.align` letters: `c` center vertically, `m` center horizontally, `b` bottom, `r` right. Top/left are default. `padding` defaults to `G.UIT.padding`. +- `ref_table` + `ref_value` binds UI to live data; text and object refs auto-refresh when values change. +- Interactivity uses `config.button = 'name'` to invoke `G.FUNCS.name`. Helpers wire `hover`, `shadow`, etc. for you. + +## Useful engine locations + +- Node enums: `globals.lua` (`self.UIT = { T=1, B=2, C=3, R=4, O=5, ROOT=7, S=8, I=9 }`). +- UIBox creation, sizing, alignment: `engine/ui.lua` (class `UIBox`, `UIElement`). +- Input/callbacks for buttons, sliders, toggles, option cycles: `functions/button_callbacks.lua`. +- Prefab builders (sliders, toggles, option cycles, tabs, buttons): `functions/UI_definitions.lua` near the helper definitions. +- Settings tabs pattern: `create_UIBox_settings` and `G.UIDEF.settings_tab` in `functions/UI_definitions.lua`. + +## Core building blocks (prefabs) + +Use these helpers instead of raw nodes where possible: + +- **Button**: `UIBox_button(args)` → clickable pill. + + - Args: `button` (G.FUNCS name), `label` array, `colour`, `minw/minh`, `choice/chosen` for toggle-style buttons, `focus_args` for controller nav. + +- **Slider**: `create_slider(args)` → drag or discrete slider bound to `ref_table/ref_value`. + + - Required: `ref_table`, `ref_value`, `min`, `max` (numbers). Optional: `label`, `w/h`, `callback`, `decimal_places`, `colour`. + - Behavior implemented by `G.FUNCS.slider` and `G.FUNCS.slider_descreet`. + +- **Toggle**: `create_toggle(args)` → checkbox-style toggle bound to `ref_table/ref_value`. + + - Required: `ref_table`, `ref_value`, `label`. + - Optional: `callback` (runs on change), `info` (array of extra text lines), `active_colour`, `inactive_colour`, `scale`. + - Behavior: `G.FUNCS.toggle_button` and `G.FUNCS.toggle`. + +- **Option Cycle**: `create_option_cycle(args)` → left/right cycle with pips. + + - Required: `options` (array), `current_option` (1-based), `opt_callback` (G.FUNCS name or nil). + - Optional: `label`, `info`, `w/h`, `scale`, `cycle_shoulders` (adds shoulder prompts), `no_pips`. + - Behavior: `G.FUNCS.option_cycle` updates `current_option` and `current_option_val`, fires `opt_callback`. + +- **Tabs**: `create_tabs(args)` → tab strip + content area. + + - Each tab entry: `{ label=..., chosen=bool, tab_definition_function=fn, tab_definition_function_args=... }`. + - `create_tabs` instantiates the chosen tab’s definition into `tab_contents`. + - Useful args: `tab_h`, `tab_w`, `tab_alignment`, `snap_to_nav`, `no_shoulders`. + +- **Overlay shell**: `create_UIBox_generic_options(args)` → modal frame with optional back button and infotip slot. + + - Args: `contents` (array or single node), `back_func`, `colour`, `bg_colour`, `outline_colour`, `no_back`, `snap_back`. + +- **Dyn container**: `UIBox_dyn_container(inner_table, horizontal, colour_override, background_override, flipped, padding)` → framed grouping block. + +- **Text input**: `create_text_input(args)` for simple keyboard input; binds to `ref_table/ref_value` with max length, prompt text. + +## Making a custom settings tab (example) + +Add a new tab alongside existing ones in `create_UIBox_settings` and define its builder. Example: + +```lua +-- 1) Define your tab builder (anywhere after G.UIDEF exists) +function G.UIDEF.settings_tab_fancy() + return {n=G.UIT.ROOT, config={align="cm", padding=0.05, colour=G.C.CLEAR}, nodes={ + create_toggle({label="Enable Fancy Mode", ref_table=G.SETTINGS, ref_value="fancy_mode", callback=function(val) + G.FUNCS.apply_fancy_mode(val) + end}), + create_slider({label="Fancy Intensity", w=4, h=0.4, ref_table=G.SETTINGS, ref_value="fancy_intensity", min=0, max=100, callback="apply_fancy_intensity"}), + create_option_cycle({label="Fancy Style", options={"Soft","Bold","Loud"}, current_option=1, opt_callback="set_fancy_style"}) + }} +end + +-- 2) Insert the tab into the settings tabs list (in create_UIBox_settings) +tabs[#tabs+1] = { + label = "Fancy", + tab_definition_function = G.UIDEF.settings_tab_fancy, + tab_definition_function_args = nil +} +``` + +Then implement the callbacks you referenced (e.g., `G.FUNCS.apply_fancy_mode`, `G.FUNCS.apply_fancy_intensity`, `G.FUNCS.set_fancy_style`). They’ll receive the cycle/slider/toggle configs or values per the existing callbacks in `button_callbacks.lua`. + +## Making a standalone modal/panel + +1. Build your content nodes using rows/cols and prefabs: + +```lua +local content = { + UIBox_button({label={"Do Thing"}, button="my_action", minw=3}), + create_toggle({label="Flag", ref_table=G.SETTINGS, ref_value="my_flag"}), + create_slider({label="Value", w=4, h=0.4, ref_table=G.SETTINGS, ref_value="my_val", min=0, max=10}) +} +``` + +1. Wrap in `create_UIBox_generic_options({contents = content, back_func = "exit_overlay_menu"})` and pass that as the `definition` to a new `UIBox` to show the overlay. + +## Binding data and IDs + +- Use `ref_table/ref_value` on `T` nodes to auto-update text when values change. +- Use `id` in `config` to fetch elements later with `UIBox:get_UIE_by_ID(id)`. +- Objects (`n=G.UIT.O`) can wrap `Sprite`, `DynaText`, or another `UIBox` via `config.object`. + +## Controller focus + +- `focus_args` on interactive nodes controls navigation; helpers set sensible defaults: sliders (`type='slider'`), cycles (`type='cycle'`), tabs (`type='tab'`), buttons (`nav='wide'`, etc.). +- `snap_to_nav=true` on `create_tabs` helps initial focus within overlays. + +## Gotchas + +- Buttons need `hover=true` (helpers do this) and a `button` string to trigger a callback. +- If you bind text to changing data, a length change triggers a layout recalc; avoid `no_recalc` unless you really need fixed width. +- `id` values must be unique within a UIBox tree. +- Colors are premultiplied alpha tables; `colour[4]` near zero hides the element. + +## Where to look in code + +- Prefab helpers: `functions/UI_definitions.lua` (slider/toggle/cycle/tabs/buttons). +- Input + callbacks: `functions/button_callbacks.lua`. +- Core UI tree + layout: `engine/ui.lua`. +- Node enums/constants: `globals.lua` (G.UIT, colors in G.C). + +## Extending further + +- You can nest `UIBox` instances via `n=G.UIT.O` with `config.object = UIBox{definition=...}` to embed sub-UIs. +- `UIBox_dyn_container` gives quick framed blocks for grouped options. +- Use `create_text_input` if you need player text entry (e.g., seeds, names). + +Keep everything data-first: assemble tables, wire callbacks in `G.FUNCS`, and let the engine handle layout and interaction. diff --git a/.gitignore b/.gitignore index 08656cb..395c103 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ style_guide.md Debug/ config.lua .vscode +immolate/ +.github/ diff --git a/Core/Brainstorm.lua b/Core/Brainstorm.lua index bba8b7c..c612a9f 100644 --- a/Core/Brainstorm.lua +++ b/Core/Brainstorm.lua @@ -1,5 +1,6 @@ local lovely = require("lovely") local nfs = require("nativefs") +local ffi = require("ffi") Brainstorm = {} @@ -7,375 +8,535 @@ Brainstorm.VERSION = "Brainstorm v2.2.0-alpha" Brainstorm.SMODS = nil -Brainstorm.config = { - enable = true, - keybind_autoreroll = "r", - keybinds = { - options = "t", - modifier = "lctrl", - f_reroll = "r", - a_reroll = "a", - }, - ar_filters = { - pack = {}, - pack_id = 1, - voucher_name = "", - voucher_id = 1, - tag_name = "tag_charm", - tag_id = 2, - soul_skip = 1, - inst_observatory = false, - inst_perkeo = false, - }, - ar_prefs = { - spf_id = 3, - spf_int = 1000, - }, +Brainstorm.DEFAULT_CONFIG = { + enable = true, + keybinds = { + options = "t", + modifier = "lctrl", + f_reroll = "r", + a_reroll = "a", + }, + ar_filters = { + pack = {}, + pack_id = 1, + tag_name = "tag_charm", + tag_id = 2, + soul_skip = 1, + }, + ar_prefs = { + spf_id = 3, + spf_int = 1000, + }, + active_filter_id = nil, + debug = false, } +Brainstorm.config = {} +Brainstorm.filters = {} +Brainstorm.filters_list = {} + Brainstorm.ar_timer = 0 Brainstorm.ar_frames = 0 Brainstorm.ar_text = nil Brainstorm.ar_active = false Brainstorm.AR_INTERVAL = 0.01 +Brainstorm.immolate = nil +Brainstorm.immolate_cdef = false +Brainstorm.immolate_status = "unloaded" -- Cache frequently used functions local math_abs = math.abs local string_format = string.format local string_lower = string.lower +local function debugLog(...) + if Brainstorm.config and Brainstorm.config.debug then + print("Brainstorm:", ...) + end +end + local function findBrainstormDirectory(directory) - for _, item in ipairs(nfs.getDirectoryItems(directory)) do - local itemPath = directory .. "/" .. item - if - nfs.getInfo(itemPath, "directory") - and string_lower(item):find("brainstorm") - then - return itemPath - end - end - return nil + for _, item in ipairs(nfs.getDirectoryItems(directory)) do + local itemPath = directory .. "/" .. item + if nfs.getInfo(itemPath, "directory") and string_lower(item):find("brainstorm") then + return itemPath + end + end + return nil +end + +local function ensureImmolateLoaded() + if Brainstorm.immolate then + Brainstorm.immolate_status = "loaded" + return true + end + + if not Brainstorm.immolate_cdef then + ffi.cdef([[const char* brainstorm(const char* seed, const char* pack, const char* tag, double souls);]]) + ffi.cdef([[const char* brainstorm_query(const char* seed, const char* query_json);]]) + ffi.cdef([[void free_result(const char* result);]]) + Brainstorm.immolate_cdef = true + end + + local ok, lib = pcall(ffi.load, Brainstorm.PATH .. "/Immolate.dll") + if not ok then + debugLog("Failed to load Immolate.dll:", tostring(lib)) + Brainstorm.immolate_status = "missing" + return false + end + + Brainstorm.immolate = lib + Brainstorm.immolate_status = "loaded" + return true end local function fileExists(filePath) - return nfs.getInfo(filePath) ~= nil + return nfs.getInfo(filePath) ~= nil +end + +local function mergeConfig(defaults, loaded) + local merged = {} + for key, value in pairs(defaults) do + local incoming = loaded and loaded[key] + if type(value) == "table" then + merged[key] = mergeConfig(value, type(incoming) == "table" and incoming or {}) + elseif type(incoming) == type(value) then + merged[key] = incoming + else + merged[key] = value + end + end + return merged end function Brainstorm.loadConfig() - local configPath = Brainstorm.PATH .. "/config.lua" - if not fileExists(configPath) then - Brainstorm.writeConfig() - else - local configFile, err = nfs.read(configPath) - if not configFile then - error("Failed to read config file: " .. (err or "unknown error")) - end - Brainstorm.config = STR_UNPACK(configFile) or Brainstorm.config - end + local configPath = Brainstorm.PATH .. "/config.lua" + if not fileExists(configPath) then + Brainstorm.config = mergeConfig(Brainstorm.DEFAULT_CONFIG) + Brainstorm.writeConfig() + return + end + + local configFile, err = nfs.read(configPath) + if not configFile then + error("Failed to read config file: " .. (err or "unknown error")) + end + local unpacked = STR_UNPACK(configFile) or {} + Brainstorm.config = mergeConfig(Brainstorm.DEFAULT_CONFIG, unpacked) + Brainstorm.writeConfig() +end + +local function isArray(t) + local count = 0 + for k in pairs(t) do + if type(k) ~= "number" then + return false + end + count = count + 1 + end + return count == #t +end + +local function escapeString(str) + return str:gsub("\\", "\\\\"):gsub('"', '\\"'):gsub("%c", function(c) + return string_format("\\u%04x", c:byte()) + end) +end + +function Brainstorm.toJSON(val) + local t = type(val) + if t == "nil" then + return "null" + elseif t == "boolean" then + return val and "true" or "false" + elseif t == "number" then + return tostring(val) + elseif t == "string" then + return '"' .. escapeString(val) .. '"' + elseif t == "table" then + if isArray(val) then + local parts = {} + for i = 1, #val do + parts[#parts + 1] = Brainstorm.toJSON(val[i]) + end + return "[" .. table.concat(parts, ",") .. "]" + end + local parts = {} + for k, v in pairs(val) do + if type(k) == "string" then + parts[#parts + 1] = '"' .. escapeString(k) .. '":' .. Brainstorm.toJSON(v) + end + end + return "{" .. table.concat(parts, ",") .. "}" + end + return "null" +end + +function Brainstorm.loadFilters() + Brainstorm.filters = {} + Brainstorm.filters_list = {} + local filters_dir = Brainstorm.PATH .. "/filters" + if not nfs.getInfo(filters_dir, "directory") then + nfs.createDirectory(filters_dir) + return + end + for _, item in ipairs(nfs.getDirectoryItems(filters_dir)) do + if item:sub(-8) == ".bff.lua" then + local filePath = filters_dir .. "/" .. item + local chunk, err = load(nfs.read(filePath)) + if chunk then + local ok, data = pcall(chunk) + if ok and type(data) == "table" then + if data.version == 1 and data.id and data.label then + local entry = { + id = data.id, + label = data.label, + description = data.description, + query_json = data.query_json, + } + if not entry.query_json and data.query then + entry.query_json = Brainstorm.toJSON(data.query) + end + if entry.query_json then + Brainstorm.filters[entry.id] = entry + Brainstorm.filters_list[#Brainstorm.filters_list + 1] = entry + end + else + debugLog("Filter missing required fields in", item) + end + else + debugLog("Failed to load filter", item, tostring(data)) + end + else + debugLog("Failed to compile filter", item, tostring(err)) + end + end + end +end + +function Brainstorm.getActiveFilter() + if not Brainstorm.config.active_filter_id then + return nil + end + return Brainstorm.filters[Brainstorm.config.active_filter_id] end function Brainstorm.writeConfig() - local configPath = Brainstorm.PATH .. "/config.lua" - local success, err = nfs.write(configPath, STR_PACK(Brainstorm.config)) - if not success then - error("Failed to write config file: " .. (err or "unknown error")) - end + local configPath = Brainstorm.PATH .. "/config.lua" + local success, err = nfs.write(configPath, STR_PACK(Brainstorm.config)) + if not success then + error("Failed to write config file: " .. (err or "unknown error")) + end end function Brainstorm.init() - Brainstorm.PATH = findBrainstormDirectory(lovely.mod_dir) - Brainstorm.loadConfig() - assert(load(nfs.read(Brainstorm.PATH .. "/UI/ui.lua")))() + Brainstorm.PATH = findBrainstormDirectory(lovely.mod_dir) + if not Brainstorm.PATH then + error("Brainstorm: could not locate mod directory") + end + + Brainstorm.loadConfig() + Brainstorm.loadFilters() + Brainstorm.AR_INTERVAL = Brainstorm.getAutoInterval() + assert(load(nfs.read(Brainstorm.PATH .. "/UI/ui.lua")))() end local key_press_update_ref = Controller.key_press_update function Controller:key_press_update(key, dt) - key_press_update_ref(self, key, dt) - local keybinds = Brainstorm.config.keybinds - if love.keyboard.isDown(keybinds.modifier) then - if key == keybinds.f_reroll then - Brainstorm.reroll() - elseif key == keybinds.a_reroll then - Brainstorm.ar_active = not Brainstorm.ar_active - end - end + key_press_update_ref(self, key, dt) + local keybinds = Brainstorm.config.keybinds + if love.keyboard.isDown(keybinds.modifier) then + if key == keybinds.f_reroll then + Brainstorm.reroll() + elseif key == keybinds.a_reroll then + Brainstorm.ar_active = not Brainstorm.ar_active + end + end end function Brainstorm.reroll() - local G = G -- Cache global G for performance - G.GAME.viewed_back = nil - G.run_setup_seed = G.GAME.seeded - G.challenge_tab = G.GAME and G.GAME.challenge and G.GAME.challenge_tab or nil - G.forced_seed = G.GAME.seeded and G.GAME.pseudorandom.seed or nil - - local seed = G.run_setup_seed and G.setup_seed or G.forced_seed - local stake = ( - G.GAME.stake - or G.PROFILES[G.SETTINGS.profile].MEMORY.stake - or 1 - ) or 1 - - G:delete_run() - G:start_run({ stake = stake, seed = seed, challenge = G.challenge_tab }) + local G_ref = G + G_ref.GAME.viewed_back = nil + G_ref.run_setup_seed = G_ref.GAME.seeded + G_ref.challenge_tab = G_ref.GAME and G_ref.GAME.challenge and G_ref.GAME.challenge_tab or nil + G_ref.forced_seed = G_ref.GAME.seeded and G_ref.GAME.pseudorandom.seed or nil + + local seed = G_ref.run_setup_seed and G_ref.setup_seed or G_ref.forced_seed + local stake = (G_ref.GAME.stake or G_ref.PROFILES[G_ref.SETTINGS.profile].MEMORY.stake or 1) or 1 + + G_ref:delete_run() + G_ref:start_run({ stake = stake, seed = seed, challenge = G_ref.challenge_tab }) end local update_ref = Game.update function Game:update(dt) - update_ref(self, dt) - - if Brainstorm.ar_active then - Brainstorm.ar_frames = Brainstorm.ar_frames + 1 - Brainstorm.ar_timer = Brainstorm.ar_timer + dt - - if Brainstorm.ar_timer >= Brainstorm.AR_INTERVAL then - Brainstorm.ar_timer = Brainstorm.ar_timer - Brainstorm.AR_INTERVAL - if Brainstorm.autoReroll() then - Brainstorm.ar_active = false - Brainstorm.ar_frames = 0 - if Brainstorm.ar_text then - Brainstorm.removeAttentionText(Brainstorm.ar_text) - Brainstorm.ar_text = nil - end - end - end - - if Brainstorm.ar_frames == 60 and not Brainstorm.ar_text then - Brainstorm.ar_text = Brainstorm.attentionText({ - scale = 1.4, - text = "Rerolling...", - align = "cm", - offset = { x = 0, y = -3.5 }, - major = G.STAGE == G.STAGES.RUN and G.play or G.title_top, - }) - end - end + update_ref(self, dt) + + if Brainstorm.ar_active then + Brainstorm.ar_frames = Brainstorm.ar_frames + 1 + Brainstorm.ar_timer = Brainstorm.ar_timer + dt + local interval = Brainstorm.getAutoInterval() + + if Brainstorm.ar_timer >= interval then + Brainstorm.ar_timer = Brainstorm.ar_timer - interval + if Brainstorm.autoReroll() then + Brainstorm.ar_active = false + Brainstorm.ar_frames = 0 + if Brainstorm.ar_text then + Brainstorm.removeAttentionText(Brainstorm.ar_text) + Brainstorm.ar_text = nil + end + end + end + + if Brainstorm.ar_frames == 60 and not Brainstorm.ar_text then + Brainstorm.ar_text = Brainstorm.attentionText({ + scale = 1.4, + text = "Rerolling...", + align = "cm", + offset = { x = 0, y = -3.5 }, + major = G.STAGE == G.STAGES.RUN and G.play or G.title_top, + }) + end + end +end + +function Brainstorm.getAutoInterval() + local spf = Brainstorm.config.ar_prefs and Brainstorm.config.ar_prefs.spf_int or 1000 + local clamped = math.max(100, math.min(1000, spf)) + return 10 / clamped +end + +function Brainstorm.getImmolateStatus() + return Brainstorm.immolate_status or "unloaded" end function Brainstorm.autoReroll() - local seed_found = 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 - ) - local ffi = require("ffi") - local lovely = require("lovely") - ffi.cdef([[ - const char* brainstorm(const char* seed, const char* voucher, const char* pack, const char* tag, double souls, bool observatory, bool perkeo); - ]]) - local immolate = ffi.load(Brainstorm.PATH .. "/Immolate.dll") - local pack - if #Brainstorm.config.ar_filters.pack > 0 then - pack = Brainstorm.config.ar_filters.pack[1]:match("^(.*)_") - else - pack = {} - end - local pack_name = localize({ type = "name_text", set = "Other", key = pack }) - local tag_name = localize({ - type = "name_text", - set = "Tag", - key = Brainstorm.config.ar_filters.tag_name, - }) - local voucher_name = localize({ - type = "name_text", - set = "Voucher", - key = Brainstorm.config.ar_filters.voucher_name, - }) - print(pack_name, tag_name, voucher_name) - seed_found = ffi.string( - immolate.brainstorm( - seed_found, - voucher_name, - pack_name, - tag_name, - Brainstorm.config.ar_filters.soul_skip, - Brainstorm.config.ar_filters.inst_observatory, - Brainstorm.config.ar_filters.inst_perkeo - ) - ) - if seed_found then - _stake = G.GAME.stake - G:delete_run() - G:start_run({ - stake = _stake, - seed = seed_found, - challenge = G.GAME and G.GAME.challenge and G.GAME.challenge_tab, - }) - G.GAME.used_filter = true - G.GAME.filter_info = { - filter_params = { - seed_found, - voucher_name, - pack_name, - tag_name, - Brainstorm.config.ar_filters.soul_skip, - Brainstorm.config.ar_filters.inst_observatory, - Brainstorm.config.ar_filters.inst_perkeo, - }, - } - G.GAME.seeded = false - end - return seed_found + local cursor = G.CONTROLLER and G.CONTROLLER.cursor_hover + local entropy + if cursor and cursor.T then + entropy = cursor.T.x * 0.33411983 + cursor.T.y * 0.874146 + 0.412311010 * cursor.time + else + entropy = love.timer.getTime() + end + local seed_found = random_string(8, entropy) + + local filters = Brainstorm.config.ar_filters + local pack_key = "" + if #filters.pack > 0 then + pack_key = filters.pack[1]:match("^(.*)_") or "" + end + local pack_name = localize({ type = "name_text", set = "Other", key = pack_key }) or "" + local tag_name = localize({ + type = "name_text", + set = "Tag", + key = filters.tag_name, + }) or "" + + if not ensureImmolateLoaded() or not Brainstorm.immolate.brainstorm then + Brainstorm.ar_active = false + debugLog("Immolate not available; disabling auto-reroll") + return nil + end + + local active_filter = Brainstorm.getActiveFilter() + local seed_ptr + if active_filter and Brainstorm.immolate.brainstorm_query then + seed_ptr = Brainstorm.immolate.brainstorm_query(seed_found, active_filter.query_json) + else + seed_ptr = Brainstorm.immolate.brainstorm( + seed_found, + pack_name, + tag_name, + filters.soul_skip + ) + end + if not seed_ptr then + return nil + end + seed_found = ffi.string(seed_ptr) + if Brainstorm.immolate.free_result then + Brainstorm.immolate.free_result(seed_ptr) + end + if not seed_found or seed_found == "" then + return nil + end + + local current_stake = G.GAME.stake + G:delete_run() + G:start_run({ + stake = current_stake, + seed = seed_found, + challenge = G.GAME and G.GAME.challenge and G.GAME.challenge_tab, + }) + G.GAME.used_filter = true + G.GAME.filter_info = { + filter_params = { + seed_found, + pack_name, + tag_name, + filters.soul_skip, + }, + } + G.GAME.seeded = false + return seed_found end local cursr = create_UIBox_round_scores_row function create_UIBox_round_scores_row(score, text_colour) - local ret = cursr(score, text_colour) - ret.nodes[2].nodes[1].config.colour = (score == "seed" and G.GAME.seeded) - and G.C.RED - or (score == "seed" and G.GAME.used_filter) and G.C.BLUE - or G.C.BLACK - return ret + local ret = cursr(score, text_colour) + ret.nodes[2].nodes[1].config.colour = (score == "seed" and G.GAME.seeded) and G.C.RED + or (score == "seed" and G.GAME.used_filter) and G.C.BLUE + or G.C.BLACK + return ret end -- TODO: Rework attention text. function Brainstorm.attentionText(args) - args = args or {} - args.text = args.text or "test" - args.scale = args.scale or 1 - args.colour = copy_table(args.colour or G.C.WHITE) - args.hold = (args.hold or 0) + 0.1 * G.SPEEDFACTOR - args.pos = args.pos or { x = 0, y = 0 } - args.align = args.align or "cm" - args.emboss = args.emboss or nil - - args.fade = 1 - - if args.cover then - args.cover_colour = copy_table(args.cover_colour or G.C.RED) - args.cover_colour_l = copy_table(lighten(args.cover_colour, 0.2)) - args.cover_colour_d = copy_table(darken(args.cover_colour, 0.2)) - else - args.cover_colour = copy_table(G.C.CLEAR) - end - - args.uibox_config = { - align = args.align or "cm", - offset = args.offset or { x = 0, y = 0 }, - major = args.cover or args.major or nil, - } - - G.E_MANAGER:add_event(Event({ - trigger = "after", - delay = 0, - blockable = false, - blocking = false, - func = function() - args.AT = UIBox({ - T = { args.pos.x, args.pos.y, 0, 0 }, - definition = { - n = G.UIT.ROOT, - config = { - align = args.cover_align or "cm", - minw = (args.cover and args.cover.T.w or 0.001) - + (args.cover_padding or 0), - minh = (args.cover and args.cover.T.h or 0.001) - + (args.cover_padding or 0), - padding = 0.03, - r = 0.1, - emboss = args.emboss, - colour = args.cover_colour, - }, - nodes = { - { - n = G.UIT.O, - config = { - draw_layer = 1, - object = DynaText({ - scale = args.scale, - string = args.text, - maxw = args.maxw, - colours = { args.colour }, - float = true, - shadow = true, - silent = not args.noisy, - args.scale, - pop_in = 0, - pop_in_rate = 6, - rotate = args.rotate or nil, - }), - }, - }, - }, - }, - config = args.uibox_config, - }) - args.AT.attention_text = true - - args.text = args.AT.UIRoot.children[1].config.object - args.text:pulse(0.5) - - if args.cover then - Particles(args.pos.x, args.pos.y, 0, 0, { - timer_type = "TOTAL", - timer = 0.01, - pulse_max = 15, - max = 0, - scale = 0.3, - vel_variation = 0.2, - padding = 0.1, - fill = true, - lifespan = 0.5, - speed = 2.5, - attach = args.AT.UIRoot, - colours = { - args.cover_colour, - args.cover_colour_l, - args.cover_colour_d, - }, - }) - end - if args.backdrop_colour then - args.backdrop_colour = copy_table(args.backdrop_colour) - Particles(args.pos.x, args.pos.y, 0, 0, { - timer_type = "TOTAL", - timer = 5, - scale = 2.4 * (args.backdrop_scale or 1), - lifespan = 5, - speed = 0, - attach = args.AT, - colours = { args.backdrop_colour }, - }) - end - return true - end, - })) - return args + args = args or {} + args.text = args.text or "test" + args.scale = args.scale or 1 + args.colour = copy_table(args.colour or G.C.WHITE) + args.hold = (args.hold or 0) + 0.1 * G.SPEEDFACTOR + args.pos = args.pos or { x = 0, y = 0 } + args.align = args.align or "cm" + args.emboss = args.emboss or nil + + args.fade = 1 + + if args.cover then + args.cover_colour = copy_table(args.cover_colour or G.C.RED) + args.cover_colour_l = copy_table(lighten(args.cover_colour, 0.2)) + args.cover_colour_d = copy_table(darken(args.cover_colour, 0.2)) + else + args.cover_colour = copy_table(G.C.CLEAR) + end + + args.uibox_config = { + align = args.align or "cm", + offset = args.offset or { x = 0, y = 0 }, + major = args.cover or args.major or nil, + } + + G.E_MANAGER:add_event(Event({ + trigger = "after", + delay = 0, + blockable = false, + blocking = false, + func = function() + args.AT = UIBox({ + T = { args.pos.x, args.pos.y, 0, 0 }, + definition = { + n = G.UIT.ROOT, + config = { + align = args.cover_align or "cm", + minw = (args.cover and args.cover.T.w or 0.001) + (args.cover_padding or 0), + minh = (args.cover and args.cover.T.h or 0.001) + (args.cover_padding or 0), + padding = 0.03, + r = 0.1, + emboss = args.emboss, + colour = args.cover_colour, + }, + nodes = { + { + n = G.UIT.O, + config = { + draw_layer = 1, + object = DynaText({ + scale = args.scale, + string = args.text, + maxw = args.maxw, + colours = { args.colour }, + float = true, + shadow = true, + silent = not args.noisy, + args.scale, + pop_in = 0, + pop_in_rate = 6, + rotate = args.rotate or nil, + }), + }, + }, + }, + }, + config = args.uibox_config, + }) + args.AT.attention_text = true + + args.text = args.AT.UIRoot.children[1].config.object + args.text:pulse(0.5) + + if args.cover then + Particles(args.pos.x, args.pos.y, 0, 0, { + timer_type = "TOTAL", + timer = 0.01, + pulse_max = 15, + max = 0, + scale = 0.3, + vel_variation = 0.2, + padding = 0.1, + fill = true, + lifespan = 0.5, + speed = 2.5, + attach = args.AT.UIRoot, + colours = { + args.cover_colour, + args.cover_colour_l, + args.cover_colour_d, + }, + }) + end + if args.backdrop_colour then + args.backdrop_colour = copy_table(args.backdrop_colour) + Particles(args.pos.x, args.pos.y, 0, 0, { + timer_type = "TOTAL", + timer = 5, + scale = 2.4 * (args.backdrop_scale or 1), + lifespan = 5, + speed = 0, + attach = args.AT, + colours = { args.backdrop_colour }, + }) + end + return true + end, + })) + return args end function Brainstorm.removeAttentionText(args) - G.E_MANAGER:add_event(Event({ - trigger = "after", - delay = 0, - blockable = false, - blocking = false, - func = function() - if not args.start_time then - args.start_time = G.TIMERS.TOTAL - if args.text.pop_out then - args.text:pop_out(2) - end - else - --args.AT:align_to_attach() - args.fade = math.max(0, 1 - 3 * (G.TIMERS.TOTAL - args.start_time)) - if args.cover_colour then - args.cover_colour[4] = math.min(args.cover_colour[4], 2 * args.fade) - end - if args.cover_colour_l then - args.cover_colour_l[4] = math.min(args.cover_colour_l[4], args.fade) - end - if args.cover_colour_d then - args.cover_colour_d[4] = math.min(args.cover_colour_d[4], args.fade) - end - if args.backdrop_colour then - args.backdrop_colour[4] = math.min(args.backdrop_colour[4], args.fade) - end - args.colour[4] = math.min(args.colour[4], args.fade) - if args.fade <= 0 then - args.AT:remove() - return true - end - end - end, - })) + G.E_MANAGER:add_event(Event({ + trigger = "after", + delay = 0, + blockable = false, + blocking = false, + func = function() + if not args.start_time then + args.start_time = G.TIMERS.TOTAL + if args.text.pop_out then + args.text:pop_out(2) + end + else + --args.AT:align_to_attach() + args.fade = math.max(0, 1 - 3 * (G.TIMERS.TOTAL - args.start_time)) + if args.cover_colour then + args.cover_colour[4] = math.min(args.cover_colour[4], 2 * args.fade) + end + if args.cover_colour_l then + args.cover_colour_l[4] = math.min(args.cover_colour_l[4], args.fade) + end + if args.cover_colour_d then + args.cover_colour_d[4] = math.min(args.cover_colour_d[4], args.fade) + end + if args.backdrop_colour then + args.backdrop_colour[4] = math.min(args.backdrop_colour[4], args.fade) + end + args.colour[4] = math.min(args.colour[4], args.fade) + if args.fade <= 0 then + args.AT:remove() + return true + end + end + end, + })) end diff --git a/Immolate.dll b/Immolate.dll index cafd09b..3f450a3 100644 Binary files a/Immolate.dll and b/Immolate.dll differ diff --git a/UI/ui.lua b/UI/ui.lua index 028baee..03dc641 100644 --- a/UI/ui.lua +++ b/UI/ui.lua @@ -2,272 +2,235 @@ local lovely = require("lovely") local nativefs = require("nativefs") local tag_list = { - ["None"] = "", - ["Uncommon Tag"] = "tag_uncommon", - ["Rare Tag"] = "tag_rare", - ["Holographic Tag"] = "tag_holo", - ["Foil Tag"] = "tag_foil", - ["Polychrome Tag"] = "tag_polychrome", - ["Investment Tag"] = "tag_investment", - ["Voucher Tag"] = "tag_voucher", - ["Boss Tag"] = "tag_boss", - ["Charm Tag"] = "tag_charm", - ["Juggle Tag"] = "tag_juggle", - ["Double Tag"] = "tag_double", - ["Coupon Tag"] = "tag_coupon", - ["Economy Tag"] = "tag_economy", - ["Skip Tag"] = "tag_skip", - ["D6 Tag"] = "tag_d_six", + ["None"] = "", + ["Uncommon Tag"] = "tag_uncommon", + ["Rare Tag"] = "tag_rare", + ["Holographic Tag"] = "tag_holo", + ["Foil Tag"] = "tag_foil", + ["Polychrome Tag"] = "tag_polychrome", + ["Investment Tag"] = "tag_investment", + ["Voucher Tag"] = "tag_voucher", + ["Boss Tag"] = "tag_boss", + ["Charm Tag"] = "tag_charm", + ["Juggle Tag"] = "tag_juggle", + ["Double Tag"] = "tag_double", + ["Coupon Tag"] = "tag_coupon", + ["Economy Tag"] = "tag_economy", + ["Skip Tag"] = "tag_skip", + ["D6 Tag"] = "tag_d_six", } -local voucher_list = { - ["None"] = "", - ["Overstock"] = "v_overstock_norm", - ["Clearance Sale"] = "v_clearance_sale", - ["Hone"] = "v_hone", - ["Reroll Surplus"] = "v_reroll_surplus", - ["Crystal Ball"] = "v_crystal_ball", - ["Telescope"] = "v_telescope", - ["Grabber"] = "v_grabber", - ["Wasteful"] = "v_wasteful", - ["Tarot Merchant"] = "v_tarot_merchant", - ["Planet Merchant"] = "v_planet_merchant", - ["Seed Money"] = "v_seed_money", - ["Blank"] = "v_blank", - ["Magic Trick"] = "v_magic_trick", - ["Hieroglyph"] = "v_hieroglyph", - ["Director's Cut"] = "v_directors_cut", - ["Paint Brush"] = "v_paint_brush", -} local pack_list = { - ["None"] = {}, - ["Normal Arcana"] = { - "p_arcana_normal_1", - "p_arcana_normal_2", - "p_arcana_normal_3", - "p_arcana_normal_4", - }, - ["Jumbo Arcana"] = { "p_arcana_jumbo_1", "p_arcana_jumbo_2" }, - ["Mega Arcana"] = { "p_arcana_mega_1", "p_arcana_mega_2" }, - ["Normal Celestial"] = { - "p_celestial_normal_1", - "p_celestial_normal_2", - "p_celestial_normal_3", - "p_celestial_normal_4", - }, - ["Jumbo Celestial"] = { "p_celestial_jumbo_1", "p_celestial_jumbo_2" }, - ["Mega Celestial"] = { "p_celestial_mega_1", "p_celestial_mega_2" }, - ["Normal Standard"] = { - "p_standard_normal_1", - "p_standard_normal_2", - "p_standard_normal_3", - "p_standard_normal_4", - }, - ["Jumbo Standard"] = { "p_standard_jumbo_1", "p_standard_jumbo_2" }, - ["Mega Standard"] = { "p_standard_mega_1", "p_standard_mega_2" }, - ["Normal Buffoon"] = { "p_buffoon_normal_1", "p_buffoon_normal_2" }, - ["Jumbo Buffoon"] = { "p_buffoon_jumbo_1" }, - ["Mega Buffoon"] = { "p_buffoon_mega_1" }, - ["Normal Spectral"] = { "p_spectral_normal_1", "p_spectral_normal_2" }, - ["Jumbo Spectral"] = { "p_spectral_jumbo_1" }, - ["Mega Spectral"] = { "p_spectral_mega_1" }, + ["None"] = {}, + ["Normal Arcana"] = { + "p_arcana_normal_1", + "p_arcana_normal_2", + "p_arcana_normal_3", + "p_arcana_normal_4", + }, + ["Jumbo Arcana"] = { "p_arcana_jumbo_1", "p_arcana_jumbo_2" }, + ["Mega Arcana"] = { "p_arcana_mega_1", "p_arcana_mega_2" }, + ["Normal Celestial"] = { + "p_celestial_normal_1", + "p_celestial_normal_2", + "p_celestial_normal_3", + "p_celestial_normal_4", + }, + ["Jumbo Celestial"] = { "p_celestial_jumbo_1", "p_celestial_jumbo_2" }, + ["Mega Celestial"] = { "p_celestial_mega_1", "p_celestial_mega_2" }, + ["Normal Standard"] = { + "p_standard_normal_1", + "p_standard_normal_2", + "p_standard_normal_3", + "p_standard_normal_4", + }, + ["Jumbo Standard"] = { "p_standard_jumbo_1", "p_standard_jumbo_2" }, + ["Mega Standard"] = { "p_standard_mega_1", "p_standard_mega_2" }, + ["Normal Buffoon"] = { "p_buffoon_normal_1", "p_buffoon_normal_2" }, + ["Jumbo Buffoon"] = { "p_buffoon_jumbo_1" }, + ["Mega Buffoon"] = { "p_buffoon_mega_1" }, + ["Normal Spectral"] = { "p_spectral_normal_1", "p_spectral_normal_2" }, + ["Jumbo Spectral"] = { "p_spectral_jumbo_1" }, + ["Mega Spectral"] = { "p_spectral_mega_1" }, } local spf_list = { - ["500"] = 500, - ["750"] = 750, - ["1000"] = 1000, + ["500"] = 500, + ["750"] = 750, + ["1000"] = 1000, } local spf_keys = { "500", "750", "1000" } -local voucher_keys = { - "None", - "Overstock", - "Clearance Sale", - "Hone", - "Reroll Surplus", - "Crystal Ball", - "Telescope", - "Grabber", - "Wasteful", - "Tarot Merchant", - "Planet Merchant", - "Seed Money", - "Blank", - "Magic Trick", - "Hieroglyph", - "Director's Cut", - "Paint Brush", -} - local tag_keys = { - "None", - "Charm Tag", - "Double Tag", - "Uncommon Tag", - "Rare Tag", - "Holographic Tag", - "Foil Tag", - "Polychrome Tag", - "Investment Tag", - "Voucher Tag", - "Boss Tag", - "Juggle Tag", - "Coupon Tag", - "Economy Tag", - "Skip Tag", - "D6 Tag", + "None", + "Charm Tag", + "Double Tag", + "Uncommon Tag", + "Rare Tag", + "Holographic Tag", + "Foil Tag", + "Polychrome Tag", + "Investment Tag", + "Voucher Tag", + "Boss Tag", + "Juggle Tag", + "Coupon Tag", + "Economy Tag", + "Skip Tag", + "D6 Tag", } local pack_keys = { - "None", - "Normal Arcana", - "Jumbo Arcana", - "Mega Arcana", - "Normal Celestial", - "Jumbo Celestial", - "Mega Celestial", - "Normal Standard", - "Jumbo Standard", - "Mega Standard", - "Normal Buffoon", - "Jumbo Buffoon", - "Mega Buffoon", - "Normal Spectral", - "Jumbo Spectral", - "Mega Spectral", + "None", + "Normal Arcana", + "Jumbo Arcana", + "Mega Arcana", + "Normal Celestial", + "Jumbo Celestial", + "Mega Celestial", + "Normal Standard", + "Jumbo Standard", + "Mega Standard", + "Normal Buffoon", + "Jumbo Buffoon", + "Mega Buffoon", + "Normal Spectral", + "Jumbo Spectral", + "Mega Spectral", } -G.FUNCS.change_target_voucher = function(x) - Brainstorm.config.ar_filters.voucher_id = x.to_key - Brainstorm.config.ar_filters.voucher_name = voucher_list[x.to_val] - Brainstorm.writeConfig() +local function filter_labels() + local labels = { "Legacy (manual)" } + for _, f in ipairs(Brainstorm.filters_list or {}) do + labels[#labels + 1] = f.label + end + return labels +end + +local function filter_current_option() + if not Brainstorm.config.active_filter_id then + return 1 + end + for idx, f in ipairs(Brainstorm.filters_list or {}) do + if f.id == Brainstorm.config.active_filter_id then + return idx + 1 + end + end + return 1 +end + +G.FUNCS.change_active_filter = function(x) + local key = x.to_key or 1 + if key == 1 then + Brainstorm.config.active_filter_id = nil + else + local entry = Brainstorm.filters_list[key - 1] + Brainstorm.config.active_filter_id = entry and entry.id or nil + end + Brainstorm.writeConfig() end G.FUNCS.change_target_pack = function(x) - Brainstorm.config.ar_filters.pack_id = x.to_key - Brainstorm.config.ar_filters.pack = pack_list[x.to_val] - Brainstorm.writeConfig() + Brainstorm.config.ar_filters.pack_id = x.to_key + Brainstorm.config.ar_filters.pack = pack_list[x.to_val] + Brainstorm.writeConfig() end G.FUNCS.change_target_tag = function(x) - Brainstorm.config.ar_filters.tag_id = x.to_key - Brainstorm.config.ar_filters.tag_name = tag_list[x.to_val] - Brainstorm.writeConfig() + Brainstorm.config.ar_filters.tag_id = x.to_key + Brainstorm.config.ar_filters.tag_name = tag_list[x.to_val] + Brainstorm.writeConfig() end G.FUNCS.change_soul_count = function(x) - Brainstorm.config.ar_filters.soul_skip = x.to_val - Brainstorm.writeConfig() + Brainstorm.config.ar_filters.soul_skip = x.to_val + Brainstorm.writeConfig() end G.FUNCS.change_spf = function(x) - Brainstorm.config.ar_prefs.spf_id = x.to_key - Brainstorm.config.ar_prefs.spf_int = spf_list[x.to_val] - Brainstorm.writeConfig() + Brainstorm.config.ar_prefs.spf_id = x.to_key + Brainstorm.config.ar_prefs.spf_int = spf_list[x.to_val] + Brainstorm.writeConfig() end Brainstorm.opt_ref = G.FUNCS.options G.FUNCS.options = function(e) - Brainstorm.opt_ref(e) + Brainstorm.opt_ref(e) end local ct = create_tabs function create_tabs(args) - if args and args.tab_h == 7.05 then - args.tabs[#args.tabs + 1] = { - label = "Brainstorm", - tab_definition_function = function() - return { - n = G.UIT.ROOT, - config = { - align = "cm", - padding = 0.05, - colour = G.C.CLEAR, - }, - nodes = { - { - n = G.UIT.C, - config = { - align = "cm", - padding = 0.05, - r = 0.1, - colour = darken(G.C.UI.TRANSPARENT_DARK, 0.25), - }, - nodes = { - create_option_cycle({ - label = "AR: TAG SEARCH", - scale = 0.8, - w = 4, - options = tag_keys, - opt_callback = "change_target_tag", - current_option = Brainstorm.config.ar_filters.tag_id or 1, - }), - create_option_cycle({ - label = "AR: VOUCHER SEARCH", - scale = 0.8, - w = 4, - options = voucher_keys, - opt_callback = "change_target_voucher", - current_option = Brainstorm.config.ar_filters.voucher_id or 1, - }), - create_option_cycle({ - label = "AR: PACK SEARCH", - scale = 0.8, - w = 4, - options = pack_keys, - opt_callback = "change_target_pack", - current_option = Brainstorm.config.ar_filters.pack_id or 1, - }), - create_option_cycle({ - label = "AR: N. SOULS", - scale = 0.8, - w = 4, - options = { 0, 1 }, - opt_callback = "change_soul_count", - current_option = Brainstorm.config.ar_filters.soul_skip + 1 - or 1, - }), - }, - }, - { - n = G.UIT.C, - config = { - align = "cm", - padding = 0.05, - r = 0.1, - colour = darken(G.C.UI.TRANSPARENT_DARK, 0.25), - }, - nodes = { - create_option_cycle({ - label = "AP: Seeds per frame", - scale = 0.8, - w = 4, - options = spf_keys, - opt_callback = "change_spf", - current_option = Brainstorm.config.ar_prefs.spf_id or 1, - }), - create_toggle({ - label = "AR: INST OBSERVATORY", - scale = 0.8, - ref_table = Brainstorm.config.ar_filters, - ref_value = "inst_observatory", - callback = function(_set_toggle) end, - }), - create_toggle({ - label = "AR: INST PERKEO", - scale = 0.8, - ref_table = Brainstorm.config.ar_filters, - ref_value = "inst_perkeo", - callback = function(_set_toggle) end, - }), - }, - }, - }, - } - end, - tab_definition_function_args = "Brainstorm", - } - end - return ct(args) + if args and args.tab_h == 7.05 then + args.tabs[#args.tabs + 1] = { + label = "Brainstorm", + tab_definition_function = function() + return { + n = G.UIT.ROOT, + config = { + align = "cm", + padding = 0.05, + colour = G.C.CLEAR, + }, + nodes = { + { + n = G.UIT.C, + config = { + align = "cm", + padding = 0.05, + r = 0.1, + colour = darken(G.C.UI.TRANSPARENT_DARK, 0.25), + }, + nodes = { + create_option_cycle({ + label = "FILTER SOURCE", + scale = 0.8, + w = 4, + options = filter_labels(), + opt_callback = "change_active_filter", + current_option = filter_current_option(), + }), + create_option_cycle({ + label = "TAG SEARCH", + scale = 0.8, + w = 4, + options = tag_keys, + opt_callback = "change_target_tag", + current_option = Brainstorm.config.ar_filters.tag_id or 1, + }), + create_option_cycle({ + label = "PACK SEARCH", + scale = 0.8, + w = 4, + options = pack_keys, + opt_callback = "change_target_pack", + current_option = Brainstorm.config.ar_filters.pack_id or 1, + }), + }, + }, + { + n = G.UIT.C, + config = { + align = "cm", + padding = 0.05, + r = 0.1, + colour = darken(G.C.UI.TRANSPARENT_DARK, 0.25), + }, + nodes = { + create_option_cycle({ + label = "# of seeds to search per frame", + scale = 0.8, + w = 4, + options = spf_keys, + opt_callback = "change_spf", + current_option = Brainstorm.config.ar_prefs.spf_id or 1, + }), + }, + }, + }, + } + end, + tab_definition_function_args = "Brainstorm", + } + end + return ct(args) end diff --git a/immolate/functions.cpp b/immolate/functions.cpp new file mode 100644 index 0000000..11ee471 --- /dev/null +++ b/immolate/functions.cpp @@ -0,0 +1,11 @@ +#include "functions.hpp" + +std::vector PACK_INFO = { + Pack(Item::Arcana_Pack, 3, 1), Pack(Item::Arcana_Pack, 5, 1), + Pack(Item::Arcana_Pack, 5, 2), Pack(Item::Celestial_Pack, 3, 1), + Pack(Item::Celestial_Pack, 5, 1), Pack(Item::Celestial_Pack, 5, 2), + Pack(Item::Standard_Pack, 3, 1), Pack(Item::Standard_Pack, 5, 1), + Pack(Item::Standard_Pack, 5, 2), Pack(Item::Buffoon_Pack, 2, 1), + Pack(Item::Buffoon_Pack, 4, 1), Pack(Item::Buffoon_Pack, 4, 2), + Pack(Item::Spectral_Pack, 2, 1), Pack(Item::Spectral_Pack, 4, 1), + Pack(Item::Spectral_Pack, 4, 2)}; diff --git a/immolate/functions.hpp b/immolate/functions.hpp new file mode 100644 index 0000000..f9599bd --- /dev/null +++ b/immolate/functions.hpp @@ -0,0 +1,639 @@ +#ifndef FUNCTIONS_HPP +#define FUNCTIONS_HPP + +#include "instance.hpp" +#include "rng.hpp" +#include + +// Note: Technically, marking everything as inline is not a proper fix. Ideally, +// we'd place these correctly into hpp and cpp files BUT, i want to have sanity + +// Helper functions +inline void Instance::lock(Item item) { locked[(int)item] = true; } +inline void Instance::unlock(Item item) { locked[(int)item] = false; } +inline bool Instance::isLocked(Item item) { return locked[(int)item]; } + +// Lock initializers +inline void Instance::initLocks(int ante, bool freshProfile, bool freshRun) { + if (ante < 2) { + lock(Item::The_Mouth); + lock(Item::The_Fish); + lock(Item::The_Wall); + lock(Item::The_House); + lock(Item::The_Mark); + lock(Item::The_Wheel); + lock(Item::The_Arm); + lock(Item::The_Water); + lock(Item::The_Needle); + lock(Item::The_Flint); + lock(Item::Negative_Tag); + lock(Item::Standard_Tag); + lock(Item::Meteor_Tag); + lock(Item::Buffoon_Tag); + lock(Item::Handy_Tag); + lock(Item::Garbage_Tag); + lock(Item::Ethereal_Tag); + lock(Item::Top_up_Tag); + lock(Item::Orbital_Tag); + } + if (ante < 3) { + lock(Item::The_Tooth); + lock(Item::The_Eye); + } + if (ante < 4) { + lock(Item::The_Plant); + } + if (ante < 5) { + lock(Item::The_Serpent); + } + if (ante < 6) { + lock(Item::The_Ox); + } + if (freshProfile) { + // Tags + lock(Item::Negative_Tag); + lock(Item::Foil_Tag); + lock(Item::Holographic_Tag); + lock(Item::Polychrome_Tag); + lock(Item::Rare_Tag); + + // Jokers + lock(Item::Golden_Ticket); + lock(Item::Mr_Bones); + lock(Item::Acrobat); + lock(Item::Sock_and_Buskin); + lock(Item::Swashbuckler); + lock(Item::Troubadour); + lock(Item::Certificate); + lock(Item::Smeared_Joker); + lock(Item::Throwback); + lock(Item::Hanging_Chad); + lock(Item::Rough_Gem); + lock(Item::Bloodstone); + lock(Item::Arrowhead); + lock(Item::Onyx_Agate); + lock(Item::Glass_Joker); + lock(Item::Showman); + lock(Item::Flower_Pot); + lock(Item::Blueprint); + lock(Item::Wee_Joker); + lock(Item::Merry_Andy); + lock(Item::Oops_All_6s); + lock(Item::The_Idol); + lock(Item::Seeing_Double); + lock(Item::Matador); + lock(Item::Hit_the_Road); + lock(Item::The_Duo); + lock(Item::The_Trio); + lock(Item::The_Family); + lock(Item::The_Order); + lock(Item::The_Tribe); + lock(Item::Stuntman); + lock(Item::Invisible_Joker); + lock(Item::Brainstorm); + lock(Item::Satellite); + lock(Item::Shoot_the_Moon); + lock(Item::Drivers_License); + lock(Item::Cartomancer); + lock(Item::Astronomer); + lock(Item::Burnt_Joker); + lock(Item::Bootstraps); + + // Vouchers + lock(Item::Overstock_Plus); + lock(Item::Liquidation); + lock(Item::Glow_Up); + lock(Item::Reroll_Glut); + lock(Item::Omen_Globe); + lock(Item::Observatory); + lock(Item::Nacho_Tong); + lock(Item::Recyclomancy); + lock(Item::Tarot_Tycoon); + lock(Item::Planet_Tycoon); + lock(Item::Money_Tree); + lock(Item::Antimatter); + lock(Item::Illusion); + lock(Item::Petroglyph); + lock(Item::Retcon); + lock(Item::Palette); + } + + // Locked in start of run + if (freshRun) { + // Require hand discoveries + lock(Item::Planet_X); + lock(Item::Ceres); + lock(Item::Eris); + lock(Item::Five_of_a_Kind); + lock(Item::Flush_House); + lock(Item::Flush_Five); + + // Requires specific card enhancement + lock(Item::Stone_Joker); // Stone + lock(Item::Steel_Joker); // Steel + lock(Item::Glass_Joker); // Glass + lock(Item::Golden_Ticket); // Gold + lock(Item::Lucky_Cat); // Lucky + + // Requires Gros Michel death + lock(Item::Cavendish); + + // Vouchers + lock(Item::Overstock_Plus); + lock(Item::Liquidation); + lock(Item::Glow_Up); + lock(Item::Reroll_Glut); + lock(Item::Omen_Globe); + lock(Item::Observatory); + lock(Item::Nacho_Tong); + lock(Item::Recyclomancy); + lock(Item::Tarot_Tycoon); + lock(Item::Planet_Tycoon); + lock(Item::Money_Tree); + lock(Item::Antimatter); + lock(Item::Illusion); + lock(Item::Petroglyph); + lock(Item::Retcon); + lock(Item::Palette); + } +} +inline void Instance::initUnlocks(int ante, bool freshProfile) { + if (ante == 2) { + unlock(Item::The_Mouth); + unlock(Item::The_Fish); + unlock(Item::The_Wall); + unlock(Item::The_House); + unlock(Item::The_Mark); + unlock(Item::The_Wheel); + unlock(Item::The_Arm); + unlock(Item::The_Water); + unlock(Item::The_Needle); + unlock(Item::The_Flint); + if (!freshProfile) + unlock(Item::Negative_Tag); + unlock(Item::Standard_Tag); + unlock(Item::Meteor_Tag); + unlock(Item::Buffoon_Tag); + unlock(Item::Handy_Tag); + unlock(Item::Garbage_Tag); + unlock(Item::Ethereal_Tag); + unlock(Item::Top_up_Tag); + unlock(Item::Orbital_Tag); + } + if (ante == 3) { + unlock(Item::The_Tooth); + unlock(Item::The_Eye); + } + if (ante == 4) { + unlock(Item::The_Plant); + } + if (ante == 5) { + unlock(Item::The_Serpent); + } + if (ante == 6) { + unlock(Item::The_Ox); + } +} + +// Card Generators +inline Item Instance::nextTarot(std::string source, int ante, bool soulable) { + std::string anteStr = anteToString(ante); + if (soulable && (params.showman || !isLocked(Item::The_Soul)) && + random(RandomType::Soul + RandomType::Tarot + anteStr) > 0.997) { + return Item::The_Soul; + } + return randchoice(RandomType::Tarot + source + anteStr, TAROTS); +} + +inline Item Instance::nextPlanet(std::string source, int ante, bool soulable) { + std::string anteStr = anteToString(ante); + if (soulable && (params.showman || !isLocked(Item::Black_Hole)) && + random(RandomType::Soul + RandomType::Planet + anteStr) > 0.997) { + return Item::Black_Hole; + } + return randchoice(RandomType::Planet + source + anteStr, PLANETS); +} + +inline Item Instance::nextSpectral(std::string source, int ante, + bool soulable) { + std::string anteStr = anteToString(ante); + if (soulable) { + Item forcedKey = Item::RETRY; + if ((params.showman || !isLocked(Item::The_Soul)) && + random(RandomType::Soul + RandomType::Spectral + anteStr) > 0.997) + forcedKey = Item::The_Soul; + if ((params.showman || !isLocked(Item::Black_Hole)) && + random(RandomType::Soul + RandomType::Spectral + anteStr) > 0.997) + forcedKey = Item::Black_Hole; + if (forcedKey != Item::RETRY) + return forcedKey; + } + return randchoice(RandomType::Spectral + source + anteStr, SPECTRALS); +} + +inline JokerData Instance::nextJoker(std::string source, int ante, + bool hasStickers) { + std::string anteStr = anteToString(ante); + + // Get rarity + Item rarity; + if (source == ItemSource::Soul) + rarity = Item::Legendary; + else if (source == ItemSource::Wraith) + rarity = Item::Rare; + else if (source == ItemSource::Rare_Tag) + rarity = Item::Rare; + else if (source == ItemSource::Uncommon_Tag) + rarity = Item::Uncommon; + else { + double rarityPoll = random(RandomType::Joker_Rarity + anteStr + source); + if (rarityPoll > 0.95) + rarity = Item::Rare; + else if (rarityPoll > 0.7) + rarity = Item::Uncommon; + else + rarity = Item::Common; + } + + // Get edition + int editionRate = 1; + if (isVoucherActive(Item::Glow_Up)) + editionRate = 4; + else if (isVoucherActive(Item::Hone)) + editionRate = 2; + Item edition; + double editionPoll = random(RandomType::Joker_Edition + source + anteStr); + if (editionPoll > 0.997) { + edition = Item::Negative; + } else if (editionPoll > 1 - 0.006 * editionRate) { + edition = Item::Polychrome; + } else if (editionPoll > 1 - 0.02 * editionRate) { + edition = Item::Holographic; + } else if (editionPoll > 1 - 0.04 * editionRate) { + edition = Item::Foil; + } else { + edition = Item::No_Edition; + } + + // Get next joker + Item joker; + if (rarity == Item::Legendary) { + if (params.version > 10099) { + joker = randchoice(RandomType::Joker_Legendary, LEGENDARY_JOKERS); + } else { + joker = randchoice(RandomType::Joker_Legendary + source + anteStr, + LEGENDARY_JOKERS); + } + } else if (rarity == Item::Rare) { + if (params.version > 10099) { + joker = + randchoice(RandomType::Joker_Rare + source + anteStr, RARE_JOKERS); + } else { + joker = randchoice(RandomType::Joker_Rare + source + anteStr, + RARE_JOKERS_100); + } + } else if (rarity == Item::Uncommon) { + if (params.version > 10099) { + joker = randchoice(RandomType::Joker_Uncommon + source + anteStr, + UNCOMMON_JOKERS); + } else { + joker = randchoice(RandomType::Joker_Uncommon + source + anteStr, + UNCOMMON_JOKERS_100); + } + } else if (rarity == Item::Common) { + if (params.version > 10099) { + joker = randchoice(RandomType::Joker_Common + source + anteStr, + COMMON_JOKERS); + } else { + joker = randchoice(RandomType::Joker_Common + source + anteStr, + COMMON_JOKERS_100); + } + } + + // Get next joker stickers + JokerStickers stickers = JokerStickers(); + if (hasStickers) { + if (params.version > 10099) { + double stickerPoll = random(((source == ItemSource::Buffoon_Pack) + ? RandomType::Eternal_Perishable_Pack + : RandomType::Eternal_Perishable) + + anteStr); + if (stickerPoll > 0.7 && params.stake >= Item::Black_Stake) { + if (joker != Item::Gros_Michel && joker != Item::Ice_Cream && + joker != Item::Cavendish && joker != Item::Luchador && + joker != Item::Turtle_Bean && joker != Item::Diet_Cola && + joker != Item::Popcorn && joker != Item::Ramen && + joker != Item::Seltzer && joker != Item::Mr_Bones && + joker != Item::Invisible_Joker) { + stickers.eternal = true; + } + } + if (stickerPoll > 0.4 && stickerPoll <= 0.7 && + params.stake >= Item::Orange_Stake && + joker != Item::Ceremonial_Dagger && joker != Item::Ride_the_Bus && + joker != Item::Runner && joker != Item::Constellation && + joker != Item::Green_Joker && joker != Item::Red_Card && + joker != Item::Madness && joker != Item::Square_Joker && + joker != Item::Vampire && joker != Item::Rocket && + joker != Item::Obelisk && joker != Item::Lucky_Cat && + joker != Item::Flash_Card && joker != Item::Spare_Trousers && + joker != Item::Castle && joker != Item::Wee_Joker) { + stickers.perishable = true; + } + if (params.stake >= Item::Gold_Stake) { + stickers.rental = random(((source == ItemSource::Buffoon_Pack) + ? RandomType::Rental_Pack + : RandomType::Rental) + + anteStr) > 0.7; + } + } else { + if (params.stake >= Item::Black_Stake) { + if (joker != Item::Gros_Michel && joker != Item::Ice_Cream && + joker != Item::Cavendish && joker != Item::Luchador && + joker != Item::Turtle_Bean && joker != Item::Diet_Cola && + joker != Item::Popcorn && joker != Item::Ramen && + joker != Item::Seltzer && joker != Item::Mr_Bones && + joker != Item::Invisible_Joker) { + stickers.eternal = random(RandomType::Eternal + anteStr) > 0.7; + } + } + } + } + + return JokerData(joker, rarity, edition, stickers); +} + +// Shop Logic +inline ShopInstance Instance::getShopInstance() { + double tarotRate = 4; + double planetRate = 4; + double playingCardRate = 0; + double spectralRate = 0; + if (params.deck == Item::Ghost_Deck) { + spectralRate = 2; + } + if (isVoucherActive(Item::Tarot_Tycoon)) { + tarotRate = 32; + } else if (isVoucherActive(Item::Tarot_Merchant)) { + tarotRate = 9.6; + } + if (isVoucherActive(Item::Planet_Tycoon)) { + planetRate = 32; + } else if (isVoucherActive(Item::Planet_Merchant)) { + planetRate = 9.6; + } + if (isVoucherActive(Item::Magic_Trick)) { + playingCardRate = 4; + } + + return ShopInstance(20, tarotRate, planetRate, playingCardRate, spectralRate); +}; + +inline Item shopItemType(ShopInstance shop, double cdtPoll) { + if (cdtPoll < shop.jokerRate) { + return Item::T_Joker; + } + cdtPoll -= shop.jokerRate; + + if (cdtPoll < shop.tarotRate) { + return Item::T_Tarot; + } + cdtPoll -= shop.tarotRate; + + if (cdtPoll < shop.planetRate) { + return Item::T_Planet; + } + cdtPoll -= shop.planetRate; + + if (cdtPoll < shop.playingCardRate) { + return Item::T_Playing_Card; + } + + return Item::T_Spectral; +} + +inline ShopItem Instance::nextShopItem(int ante) { + std::string anteStr = anteToString(ante); + + ShopInstance shop = getShopInstance(); + double cdtPoll = + random(RandomType::Card_Type + anteStr) * shop.getTotalRate(); + Item type = shopItemType(shop, cdtPoll); + + if (type == Item::T_Joker) { + JokerData jkr = nextJoker(ItemSource::Shop, ante, true); + return ShopItem(type, jkr.joker, jkr); + } else if (type == Item::T_Tarot) { + return ShopItem(type, nextTarot(ItemSource::Shop, ante, false)); + } else if (type == Item::T_Planet) { + return ShopItem(type, nextPlanet(ItemSource::Shop, ante, false)); + } else if (type == Item::T_Spectral) { + return ShopItem(type, nextSpectral(ItemSource::Shop, ante, false)); + } + // Todo: Magic Trick support + return ShopItem(); +} + +// Packs and Pack Contents +inline Item Instance::nextPack(int ante) { + if (ante <= 2 && !cache.generatedFirstPack && params.version > 10099) { + cache.generatedFirstPack = true; + return Item::Buffoon_Pack; + } + std::string anteStr = anteToString(ante); + return randweightedchoice(RandomType::Shop_Pack + anteStr, PACKS); +} + +extern std::vector PACK_INFO; + +inline Pack packInfo(Item pack) { + return PACK_INFO[(int)pack - (int)Item::Arcana_Pack]; +} + +inline Card Instance::nextStandardCard(int ante) { + std::string anteStr = anteToString(ante); + + // Enhancement + Item enhancement; + if (random(RandomType::Standard_Has_Enhancement + anteStr) <= 0.6) { + enhancement = Item::No_Enhancement; + } else { + enhancement = randchoice(RandomType::Enhancement + + ItemSource::Standard_Pack + anteStr, + ENHANCEMENTS); + } + + // Base + Item base = + randchoice(RandomType::Card + ItemSource::Standard_Pack + anteStr, CARDS); + + // Edition + Item edition; + double editionPoll = random(RandomType::Standard_Edition + anteStr); + if (editionPoll > 0.988) + edition = Item::Polychrome; + else if (editionPoll > 0.96) + edition = Item::Holographic; + else if (editionPoll > 0.92) + edition = Item::Foil; + else + edition = Item::No_Edition; + + // Seal + Item seal; + if (random(RandomType::Standard_Has_Seal + anteStr) <= 0.8) { + seal = Item::No_Seal; + } else { + double sealPoll = random(RandomType::Standard_Seal + anteStr); + if (sealPoll > 0.75) { + seal = Item::Red_Seal; + } else if (sealPoll > 0.5) { + seal = Item::Blue_Seal; + } else if (sealPoll > 0.25) { + seal = Item::Gold_Seal; + } else { + seal = Item::Purple_Seal; + } + } + + return Card(base, enhancement, edition, seal); +}; + +inline std::vector Instance::nextArcanaPack(int size, int ante) { + std::vector pack; + for (int i = 0; i < size; i++) { + if (isVoucherActive(Item::Omen_Globe) && + random(RandomType::Omen_Globe) > 0.8) { + pack.push_back(nextSpectral(ItemSource::Omen_Globe, ante, true)); + } else { + pack.push_back(nextTarot(ItemSource::Arcana_Pack, ante, true)); + } + if (!params.showman) { + lock(pack[i]); + } + } + for (int i = 0; i < size; i++) { + unlock(pack[i]); + } + return pack; +}; + +inline std::vector Instance::nextCelestialPack(int size, int ante) { + std::vector pack; + for (int i = 0; i < size; i++) { + pack.push_back(nextPlanet(ItemSource::Celestial_Pack, ante, true)); + if (!params.showman) + lock(pack[i]); + } + for (int i = 0; i < size; i++) { + unlock(pack[i]); + } + return pack; +}; + +inline std::vector Instance::nextSpectralPack(int size, int ante) { + std::vector pack; + for (int i = 0; i < size; i++) { + pack.push_back(nextSpectral(ItemSource::Spectral_Pack, ante, true)); + if (!params.showman) + lock(pack[i]); + } + for (int i = 0; i < size; i++) { + unlock(pack[i]); + } + return pack; +}; + +inline std::vector Instance::nextStandardPack(int size, int ante) { + std::vector pack; + for (int i = 0; i < size; i++) { + pack.push_back(nextStandardCard(ante)); + } + return pack; +}; + +inline std::vector Instance::nextBuffoonPack(int size, int ante) { + std::vector pack; + for (int i = 0; i < size; i++) { + pack.push_back(nextJoker(ItemSource::Buffoon_Pack, ante, true)); + if (!params.showman) + lock(pack[i].joker); + } + for (int i = 0; i < size; i++) { + unlock(pack[i].joker); + } + return pack; +}; + +// Misc +inline bool Instance::isVoucherActive(Item voucher) { + return params.vouchers[(int)voucher - (int)Item::Overstock]; +} + +inline void Instance::activateVoucher(Item voucher) { + params.vouchers[(int)voucher - (int)Item::Overstock] = true; + lock(voucher); + // Unlock next level voucher + for (unsigned long int i = 0; i < VOUCHERS.size(); i += 2) { + if (VOUCHERS[i] == voucher) { + unlock(VOUCHERS[i + 1]); + }; + }; +}; + +inline Item Instance::nextVoucher(int ante) { + return randchoice(RandomType::Voucher + anteToString(ante), VOUCHERS); +} + +inline void Instance::setDeck(Item deck) { + params.deck = deck; + if (deck == Item::Magic_Deck) { + activateVoucher(Item::Crystal_Ball); + } + if (deck == Item::Nebula_Deck) { + activateVoucher(Item::Telescope); + } + if (deck == Item::Zodiac_Deck) { + activateVoucher(Item::Tarot_Merchant); + activateVoucher(Item::Planet_Merchant); + activateVoucher(Item::Overstock); + } +} + +inline void Instance::setStake(Item stake) { params.stake = stake; } + +inline Item Instance::nextTag(int ante) { + return randchoice(RandomType::Tags + anteToString(ante), TAGS); +} + +inline Item Instance::nextBoss(int ante) { + constexpr int MAX_BOSSES = + 16; // Adjust this value based on the maximum number of bosses you expect + std::array bossPool; + int numBosses = 0; + + for (unsigned long int i = 0; i < BOSSES.size(); i++) { + if (!isLocked(BOSSES[i])) { + if ((ante % 8 == 0 && BOSSES[i] > Item::B_F_BEGIN) || + (ante % 8 != 0 && BOSSES[i] < Item::B_F_BEGIN)) { + bossPool[numBosses++] = BOSSES[i]; + } + } + } + + if (numBosses == 0) { + for (unsigned long int i = 0; i < BOSSES.size(); i++) { + if ((ante % 8 == 0 && BOSSES[i] > Item::B_F_BEGIN) || + (ante % 8 != 0 && BOSSES[i] < Item::B_F_BEGIN)) { + unlock(BOSSES[i]); + } + } + return nextBoss(ante); + } + + Item chosenBoss = randchoice("boss", bossPool); + lock(chosenBoss); + return chosenBoss; +} + +#endif \ No newline at end of file diff --git a/immolate/immolate.cpp b/immolate/immolate.cpp new file mode 100644 index 0000000..18244b4 --- /dev/null +++ b/immolate/immolate.cpp @@ -0,0 +1,514 @@ +#include "functions.hpp" +#include "minijson.hpp" +#include "search.hpp" +#include +#include + +#ifdef _WIN32 + #ifdef BUILDING_DLL + #define IMMOLATE_API __declspec(dllexport) + #else + #define IMMOLATE_API __declspec(dllimport) + #endif +#else + #define IMMOLATE_API +#endif + +Item BRAINSTORM_PACK = Item::RETRY; +Item BRAINSTORM_TAG = Item::Charm_Tag; +long BRAINSTORM_SOULS = 1; + +long filter(Instance inst) { + if (BRAINSTORM_PACK != Item::RETRY) { + inst.cache.generatedFirstPack = true; // we don't care about Pack 1 + if (inst.nextPack(1) != BRAINSTORM_PACK) { + return 0; + } + } + if (BRAINSTORM_TAG != Item::RETRY) { + if (inst.nextTag(1) != BRAINSTORM_TAG) { + return 0; + } + } + if (BRAINSTORM_SOULS > 0) { + for (int i = 1; i <= BRAINSTORM_SOULS; i++) { + auto tarots = inst.nextArcanaPack(5, 1); // Mega Arcana Pack + bool found_soul = false; + for (int t = 0; t < 5; t++) { + if (tarots[t] == Item::The_Soul) { + found_soul = true; + break; + } + } + if (!found_soul) { + return 0; + } + } + } + return 1; +}; + +IMMOLATE_API std::string brainstorm_cpp(std::string seed, std::string pack, +std::string tag, double souls) { BRAINSTORM_PACK = stringToItem(pack); + BRAINSTORM_TAG = stringToItem(tag); + BRAINSTORM_SOULS = souls; + Search search(filter, seed, 1, 100000000); + search.exitOnFind = true; + return search.search(); +} + +// --------------------------------------------------------------------------- +// Step / filter tree +// --------------------------------------------------------------------------- + +struct Step { + std::string op; + mini_json::Value args; + std::vector subSteps; // used by "all" and "any" combinator ops +}; + +// --------------------------------------------------------------------------- +// Matching helpers +// --------------------------------------------------------------------------- + +static Item parseItemSafe(const mini_json::Value &v) { + if (!v.isString()) return Item::RETRY; + return stringToItem(v.getString()); +} + +// Match a single Item against { "equals": "...", "in": [...] } +static bool matchesItem(Item actual, const mini_json::Value &cond) { + const auto &eq = cond["equals"]; + if (eq.isString() && actual != parseItemSafe(eq)) return false; + const auto &inArr = cond["in"]; + if (inArr.isArray()) { + bool ok = false; + for (const auto &el : inArr.array) + if (el.isString() && actual == parseItemSafe(el)) { ok = true; break; } + if (!ok) return false; + } + return true; +} + +// Match a JokerData against { "joker": "...", "rarity": "...", "edition": "...", "stickers": {...} } +static bool matchesJoker(const JokerData &jd, const mini_json::Value &cond) { + if (!cond.isObject()) return true; + if (cond["joker"].isString() && jd.joker != parseItemSafe(cond["joker"])) return false; + if (cond["rarity"].isString() && jd.rarity != parseItemSafe(cond["rarity"])) return false; + if (cond["edition"].isString() && jd.edition != parseItemSafe(cond["edition"])) return false; + const auto &stickers = cond["stickers"]; + if (stickers.isObject()) { + if (stickers["eternal"].isBool() && jd.stickers.eternal != stickers["eternal"].getBool()) return false; + if (stickers["perishable"].isBool() && jd.stickers.perishable != stickers["perishable"].getBool()) return false; + if (stickers["rental"].isBool() && jd.stickers.rental != stickers["rental"].getBool()) return false; + } + return true; +} + +// Match a Card against { "base": "...", "enhancement": "...", "edition": "...", "seal": "..." } +static bool matchesCard(const Card &card, const mini_json::Value &cond) { + if (!cond.isObject()) return true; + if (cond["base"].isString() && card.base != parseItemSafe(cond["base"])) return false; + if (cond["enhancement"].isString() && card.enhancement != parseItemSafe(cond["enhancement"])) return false; + if (cond["edition"].isString() && card.edition != parseItemSafe(cond["edition"])) return false; + if (cond["seal"].isString() && card.seal != parseItemSafe(cond["seal"])) return false; + return true; +} + +// Match a ShopItem against { "type": "...", "item": "...", "match": {joker cond} } +static bool matchesShopItem(const ShopItem &si, const mini_json::Value &cond) { + if (!cond.isObject()) return true; + if (cond["type"].isString() && si.type != parseItemSafe(cond["type"])) return false; + if (cond["item"].isString() && si.item != parseItemSafe(cond["item"])) return false; + if (cond["match"].isObject() && !matchesJoker(si.jokerData, cond["match"])) return false; + return true; +} + +static long long getNumber(const mini_json::Value &v, long long def) { + return v.isNumber() ? static_cast(v.number) : def; +} + +// Apply any/all/none/count sub-conditions over a vector pack. +// Sub-conditions use the same { "equals"/"in" } format as matchesItem. +static bool matchesItemPack(const std::vector &items, const mini_json::Value &args) { + const auto &anyCond = args["any"]; + if (anyCond.isObject()) { + bool found = false; + for (const auto &item : items) + if (matchesItem(item, anyCond)) { found = true; break; } + if (!found) return false; + } + const auto &allCond = args["all"]; + if (allCond.isObject()) { + for (const auto &item : items) + if (!matchesItem(item, allCond)) return false; + } + const auto &noneCond = args["none"]; + if (noneCond.isObject()) { + for (const auto &item : items) + if (matchesItem(item, noneCond)) return false; + } + const auto &countCond = args["count"]; + if (countCond.isObject()) { + const auto &matchCond = countCond["match"]; + int cnt = 0; + for (const auto &item : items) + if (!matchCond.isObject() || matchesItem(item, matchCond)) cnt++; + long long minV = getNumber(countCond["min"], -1); + long long maxV = getNumber(countCond["max"], -1); + long long eqV = getNumber(countCond["equals"], -1); + if (minV >= 0 && cnt < (int)minV) return false; + if (maxV >= 0 && cnt > (int)maxV) return false; + if (eqV >= 0 && cnt != (int)eqV) return false; + } + return true; +} + +// Apply any/all/none/count sub-conditions over a vector pack. +static bool matchesJokerPack(const std::vector &jokers, const mini_json::Value &args) { + const auto &anyCond = args["any"]; + if (anyCond.isObject()) { + bool found = false; + for (const auto &jd : jokers) + if (matchesJoker(jd, anyCond)) { found = true; break; } + if (!found) return false; + } + const auto &allCond = args["all"]; + if (allCond.isObject()) { + for (const auto &jd : jokers) + if (!matchesJoker(jd, allCond)) return false; + } + const auto &noneCond = args["none"]; + if (noneCond.isObject()) { + for (const auto &jd : jokers) + if (matchesJoker(jd, noneCond)) return false; + } + const auto &countCond = args["count"]; + if (countCond.isObject()) { + const auto &matchCond = countCond["match"]; + int cnt = 0; + for (const auto &jd : jokers) + if (!matchCond.isObject() || matchesJoker(jd, matchCond)) cnt++; + long long minV = getNumber(countCond["min"], -1); + long long maxV = getNumber(countCond["max"], -1); + long long eqV = getNumber(countCond["equals"], -1); + if (minV >= 0 && cnt < (int)minV) return false; + if (maxV >= 0 && cnt > (int)maxV) return false; + if (eqV >= 0 && cnt != (int)eqV) return false; + } + return true; +} + +// Apply any/all/none/count sub-conditions over a vector pack. +static bool matchesCardPack(const std::vector &cards, const mini_json::Value &args) { + const auto &anyCond = args["any"]; + if (anyCond.isObject()) { + bool found = false; + for (const auto &card : cards) + if (matchesCard(card, anyCond)) { found = true; break; } + if (!found) return false; + } + const auto &allCond = args["all"]; + if (allCond.isObject()) { + for (const auto &card : cards) + if (!matchesCard(card, allCond)) return false; + } + const auto &noneCond = args["none"]; + if (noneCond.isObject()) { + for (const auto &card : cards) + if (matchesCard(card, noneCond)) return false; + } + const auto &countCond = args["count"]; + if (countCond.isObject()) { + const auto &matchCond = countCond["match"]; + int cnt = 0; + for (const auto &card : cards) + if (!matchCond.isObject() || matchesCard(card, matchCond)) cnt++; + long long minV = getNumber(countCond["min"], -1); + long long maxV = getNumber(countCond["max"], -1); + long long eqV = getNumber(countCond["equals"], -1); + if (minV >= 0 && cnt < (int)minV) return false; + if (maxV >= 0 && cnt > (int)maxV) return false; + if (eqV >= 0 && cnt != (int)eqV) return false; + } + return true; +} + +// --------------------------------------------------------------------------- +// Step execution +// --------------------------------------------------------------------------- + +static bool applyStep(const Step &step, Instance &inst) { + const auto &args = step.args; + + // --- Combinators --- + + // "all": every sub-step must pass (AND) + if (step.op == "all") { + for (const auto &sub : step.subSteps) + if (!applyStep(sub, inst)) return false; + return true; + } + // "any": at least one sub-step must pass (OR) + if (step.op == "any") { + for (const auto &sub : step.subSteps) + if (applyStep(sub, inst)) return true; + return false; + } + + // --- Simple game-state ops --- + + if (step.op == "tag") { + int idx = static_cast(getNumber(args["index"], 1)); + return matchesItem(inst.nextTag(idx), args); + } + if (step.op == "pack") { + int idx = static_cast(getNumber(args["index"], 1)); + return matchesItem(inst.nextPack(idx), args); + } + if (step.op == "voucher") { + int idx = static_cast(getNumber(args["index"], 1)); + Item val = inst.nextVoucher(idx); + if (!matchesItem(val, args)) return false; + if (args["activate"].getBool(false)) inst.activateVoucher(val); + return true; + } + if (step.op == "boss") { + int idx = static_cast(getNumber(args["index"], 1)); + return matchesItem(inst.nextBoss(idx), args); + } + + // --- Individual card draws --- + + // "joker": draw (and optionally advance) joker draws. + // draw=N checks the 1st joker and advances through N draws total (original behaviour). + if (step.op == "joker") { + int draw = static_cast(getNumber(args["draw"], 1)); + int ante = static_cast(getNumber(args["ante"], 1)); + bool stickers = args["has_stickers"].getBool(true); + std::string source = args["source"].getString("Brainstorm_Joker"); + JokerData jd = inst.nextJoker(source, ante, stickers); + for (int i = 1; i < draw; i++) inst.nextJoker(source, ante, stickers); + return matchesJoker(jd, args["match"]); + } + // "joker_window": search up to limit draws for any matching joker. + if (step.op == "joker_window") { + int limit = static_cast(getNumber(args["limit"], 1)); + int ante = static_cast(getNumber(args["ante"], 1)); + bool stickers = args["has_stickers"].getBool(true); + std::string source = args["source"].getString("Brainstorm_Joker_Window"); + const auto &match = args["any"]["match"].isObject() ? args["any"]["match"] : args["match"]; + for (int i = 0; i < limit; i++) { + JokerData jd = inst.nextJoker(source, ante, stickers); + if (matchesJoker(jd, match)) return true; + } + return false; + } + // "tarot": draw the Nth tarot card (draw=N skips N-1 then checks the Nth). + if (step.op == "tarot") { + int draw = static_cast(getNumber(args["draw"], 1)); + int ante = static_cast(getNumber(args["ante"], 1)); + bool soulable = args["soulable"].getBool(true); + std::string source = args["source"].getString("Brainstorm_Tarot"); + for (int i = 1; i < draw; i++) inst.nextTarot(source, ante, soulable); + return matchesItem(inst.nextTarot(source, ante, soulable), args); + } + // "planet": draw the Nth planet card. + if (step.op == "planet") { + int draw = static_cast(getNumber(args["draw"], 1)); + int ante = static_cast(getNumber(args["ante"], 1)); + bool soulable = args["soulable"].getBool(true); + std::string source = args["source"].getString("Brainstorm_Planet"); + for (int i = 1; i < draw; i++) inst.nextPlanet(source, ante, soulable); + return matchesItem(inst.nextPlanet(source, ante, soulable), args); + } + // "spectral": draw the Nth spectral card. + if (step.op == "spectral") { + int draw = static_cast(getNumber(args["draw"], 1)); + int ante = static_cast(getNumber(args["ante"], 1)); + bool soulable = args["soulable"].getBool(true); + std::string source = args["source"].getString("Brainstorm_Spectral"); + for (int i = 1; i < draw; i++) inst.nextSpectral(source, ante, soulable); + return matchesItem(inst.nextSpectral(source, ante, soulable), args); + } + // "shop_item": draw the Nth shop item. + if (step.op == "shop_item") { + int draw = static_cast(getNumber(args["draw"], 1)); + int ante = static_cast(getNumber(args["ante"], 1)); + for (int i = 1; i < draw; i++) inst.nextShopItem(ante); + return matchesShopItem(inst.nextShopItem(ante), args); + } + // "standard_card": draw the Nth standard playing card. + if (step.op == "standard_card") { + int draw = static_cast(getNumber(args["draw"], 1)); + int ante = static_cast(getNumber(args["ante"], 1)); + for (int i = 1; i < draw; i++) inst.nextStandardCard(ante); + return matchesCard(inst.nextStandardCard(ante), args); + } + + // --- Pack contents ops --- + // Each uses any/all/none/count sub-conditions on the pack's items. + + // "arcana_pack": generate a tarot/soul pack and inspect its contents. + if (step.op == "arcana_pack") { + int size = static_cast(getNumber(args["size"], 3)); + int ante = static_cast(getNumber(args["ante"], 1)); + return matchesItemPack(inst.nextArcanaPack(size, ante), args); + } + // "celestial_pack": generate a planet pack and inspect its contents. + if (step.op == "celestial_pack") { + int size = static_cast(getNumber(args["size"], 3)); + int ante = static_cast(getNumber(args["ante"], 1)); + return matchesItemPack(inst.nextCelestialPack(size, ante), args); + } + // "spectral_pack": generate a spectral pack and inspect its contents. + if (step.op == "spectral_pack") { + int size = static_cast(getNumber(args["size"], 2)); + int ante = static_cast(getNumber(args["ante"], 1)); + return matchesItemPack(inst.nextSpectralPack(size, ante), args); + } + // "buffoon_pack": generate a joker pack and inspect its contents. + if (step.op == "buffoon_pack") { + int size = static_cast(getNumber(args["size"], 2)); + int ante = static_cast(getNumber(args["ante"], 1)); + return matchesJokerPack(inst.nextBuffoonPack(size, ante), args); + } + // "standard_pack": generate a playing-card pack and inspect its contents. + if (step.op == "standard_pack") { + int size = static_cast(getNumber(args["size"], 3)); + int ante = static_cast(getNumber(args["ante"], 1)); + return matchesCardPack(inst.nextStandardPack(size, ante), args); + } + + // --- State mutation ops (always return true) --- + + // "init_locks": initialise lock/unlock state for a given ante/profile. + if (step.op == "init_locks") { + int ante = static_cast(getNumber(args["ante"], 1)); + bool freshProfile = args["fresh_profile"].getBool(false); + bool freshRun = args["fresh_run"].getBool(false); + inst.initLocks(ante, freshProfile, freshRun); + if (args["unlock"].getBool(false)) inst.initUnlocks(ante, freshProfile); + return true; + } + // "set": change deck or stake for subsequent draws. + if (step.op == "set") { + const auto &deck = args["deck"]; + const auto &stake = args["stake"]; + if (deck.isString()) inst.setDeck(stringToItem(deck.getString())); + if (stake.isString()) inst.setStake(stringToItem(stake.getString())); + else if (stake.isNumber()) inst.setStake(static_cast(static_cast(stake.number))); + return true; + } + + // --- Seed state inspection --- + + if (step.op == "state") { + std::string field = args["field"].getString(""); + if (field == "id") { + long long id = inst.seed.getID(); + const auto &eq = args["equals"]; + if (eq.isNumber() && id != static_cast(eq.number)) return false; + const auto &range = args["range"]; + if (range.isArray() && range.array.size() >= 2) { + long long lo = static_cast(range.array[0].number); + long long hi = static_cast(range.array[1].number); + if (id < lo || id > hi) return false; + } + } + return true; + } + + // Unknown op → fail safely so malformed queries don't silently pass. + return false; +} + +// --------------------------------------------------------------------------- +// Filter tree construction from JSON +// --------------------------------------------------------------------------- + +static void collectSteps(const mini_json::Value &node, std::vector &out) { + if (!node.isObject()) return; + + // { "all": [...] } → flatten children into sequential AND (same as before) + if (node["all"].isArray()) { + for (const auto &child : node["all"].array) + collectSteps(child, out); + return; + } + // { "any": [...] } → single OR combinator step with children + if (node["any"].isArray()) { + Step anyStep; + anyStep.op = "any"; + for (const auto &child : node["any"].array) + collectSteps(child, anyStep.subSteps); + out.push_back(anyStep); + return; + } + // { "op": "...", "args": {...} } → leaf step + if (node["op"].isString()) { + Step s; + s.op = node["op"].getString(); + s.args = node["args"]; + out.push_back(s); + } +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +static const char *dupCString(const std::string &str) { + char *c_result = (char *)malloc(str.length() + 1); + if (!c_result) return nullptr; + std::strcpy(c_result, str.c_str()); + return c_result; +} + +IMMOLATE_API const char *brainstorm_query(const char *seed, + const char *query_json) { + std::string seed_str = seed ? seed : ""; + std::string query_str = query_json ? query_json : ""; + + mini_json::Value root; + if (!mini_json::parse(query_str, root) || !root.isObject()) { + return dupCString(""); + } + + std::vector steps; + collectSteps(root["filter"], steps); + if (steps.empty()) { + return dupCString(""); + } + + const auto &search = root["search"]; + int threads = static_cast(getNumber(search["threads"], 1)); + long long max_seeds = getNumber(search["max_seeds"], 100000000); + bool exit_on_find = search["exit_on_find"].getBool(true); + + Search s([steps](Instance inst) { + for (const auto &step : steps) + if (!applyStep(step, inst)) return 0; + return 1; + }, seed_str, threads, max_seeds > 0 ? max_seeds : 100000000); + s.exitOnFind = exit_on_find; + std::string result = s.search(); + return dupCString(result); +} + +extern "C" { + IMMOLATE_API const char* brainstorm(const char* seed, const char* pack, +const char* tag, double souls) { std::string cpp_seed(seed); std::string +cpp_pack(pack); std::string cpp_tag(tag); std::string result = +brainstorm_cpp(cpp_seed, cpp_pack, cpp_tag, souls); + + char* c_result = (char*)malloc(result.length() + 1); + strcpy(c_result, result.c_str()); + + return c_result; + } + + IMMOLATE_API void free_result(const char* result) { + free((void*)result); + } +} diff --git a/immolate/immolate.hpp b/immolate/immolate.hpp new file mode 100644 index 0000000..8cd5188 --- /dev/null +++ b/immolate/immolate.hpp @@ -0,0 +1,143 @@ + +#include +#ifdef _WIN32 +#ifdef BUILDING_DLL +#define IMMOLATE_API __declspec(dllexport) +#else +#define IMMOLATE_API __declspec(dllimport) +#endif +#else +#define IMMOLATE_API +#endif + +// Declare the functions with IMMOLATE_API +IMMOLATE_API std::string brainstorm_cpp(std::string seed, std::string pack, + std::string tag, double souls); +IMMOLATE_API const char *brainstorm_query(const char *seed, + const char *query_json); +extern "C" { +IMMOLATE_API const char *brainstorm(const char *seed, const char *pack, + const char *tag, double souls); +IMMOLATE_API void free_result(const char *result); +} + +#ifdef __EMSCRIPTEN__ +#include +using namespace emscripten; +EMSCRIPTEN_BINDINGS(Immolate) { + // instance.hpp + register_vector("VectorStr"); + register_vector("VectorJkr"); + register_vector("VectorCrd"); + class_("InstParams") + .constructor<>() + .constructor() + .property("deck", &InstParams::deck) + .property("stake", &InstParams::stake) + .property("showman", &InstParams::showman) + .property("vouchers", &InstParams::vouchers) + .property("version", &InstParams::version); + class_("Instance") + .constructor() + .function("get_node", &Instance::get_node) + .function("random", &Instance::random) + .function("randint", &Instance::randint) + .function("randchoice", &Instance::randchoice) + .property("params", &Instance::params) + .property("seed", &Instance::seed) + + // functions.hpp + .function("lock", &Instance::lock) + .function("unlock", &Instance::unlock) + .function("isLocked", &Instance::isLocked) + .function("initLocks", &Instance::initLocks) + .function("initUnlocks", &Instance::initUnlocks) + .function("nextTarot", &Instance::nextTarot) + .function("nextPlanet", &Instance::nextPlanet) + .function("nextSpectral", &Instance::nextSpectral) + .function("nextJoker", &Instance::nextJoker) + .function("getShopInstance", &Instance::getShopInstance) + .function("nextShopItem", &Instance::nextShopItem) + .function("nextPack", &Instance::nextPack) + .function("nextStandardCard", &Instance::nextStandardCard) + .function("nextArcanaPack", &Instance::nextArcanaPack) + .function("nextCelestialPack", &Instance::nextCelestialPack) + .function("nextSpectralPack", &Instance::nextSpectralPack) + .function("nextBuffoonPack", &Instance::nextBuffoonPack) + .function("nextStandardPack", &Instance::nextStandardPack) + .function("isVoucherActive", &Instance::isVoucherActive) + .function("activateVoucher", &Instance::activateVoucher) + .function("nextVoucher", &Instance::nextVoucher) + .function("setDeck", &Instance::setDeck) + .function("setStake", &Instance::setStake) + .function("nextTag", &Instance::nextTag) + .function("nextBoss", &Instance::nextBoss); + function("packInfo", &packInfo); + + // items.hpp + class_("ShopInstance") + .constructor<>() + .constructor() + .function("getTotalRate", &ShopInstance::getTotalRate) + .property("jokerRate", &ShopInstance::jokerRate) + .property("tarotRate", &ShopInstance::tarotRate) + .property("planetRate", &ShopInstance::planetRate) + .property("playingCardRate", &ShopInstance::playingCardRate) + .property("spectralRate", &ShopInstance::spectralRate); + class_("JokerStickers") + .constructor<>() + .constructor() + .property("eternal", &JokerStickers::eternal) + .property("perishable", &JokerStickers::perishable) + .property("rental", &JokerStickers::rental); + class_("JokerData") + .constructor<>() + .constructor() + .property("joker", &JokerData::joker) + .property("rarity", &JokerData::rarity) + .property("edition", &JokerData::edition) + .property("stickers", &JokerData::stickers); + class_("ShopItem") + .constructor<>() + .constructor() + .constructor() + .property("type", &ShopItem::type) + .property("item", &ShopItem::item) + .property("jokerData", &ShopItem::jokerData); + class_("WeightedItem") + .constructor() + .property("item", &WeightedItem::item) + .property("weight", &WeightedItem::weight); + class_("Pack") + .constructor() + .property("type", &Pack::type) + .property("size", &Pack::size) + .property("choices", &Pack::choices); + class_("Card") + .constructor() + .property("base", &Card::base) + .property("enhancement", &Card::enhancement) + .property("edition", &Card::edition) + .property("seal", &Card::seal); + constant("ENHANCEMENTS", &ENHANCEMENTS); + constant("CARDS", &CARDS); + constant("SUITS", &SUITS); + constant("RANKS", &RANKS); + constant("TAROTS", &TAROTS); + constant("PLANETS", &PLANETS); + constant("COMMON_JOKERS", &COMMON_JOKERS); + constant("UNCOMMON_JOKERS", &UNCOMMON_JOKERS); + constant("RARE_JOKERS", &RARE_JOKERS); + constant("LEGENDARY_JOKERS", &LEGENDARY_JOKERS); + constant("VOUCHERS", &VOUCHERS); + constant("SPECTRALS", &SPECTRALS); + constant("TAGS", &TAGS); + constant("BOSSES", &BOSSES); + + // util.hpp + function("pseudohash", &pseudohash) class_("LuaRandom") + .constructor<>() + .constructor() + .function("random", &LuaRandom::random); +} +#endif diff --git a/immolate/instance.hpp b/immolate/instance.hpp new file mode 100644 index 0000000..4489a81 --- /dev/null +++ b/immolate/instance.hpp @@ -0,0 +1,135 @@ +#include "items.hpp" +#include "seed.hpp" +#include "util.hpp" +#include +#include +#pragma once + +struct Cache { + std::map nodes; + bool generatedFirstPack = false; +}; + +struct InstParams { + Item deck; + Item stake; + bool showman; + int sixesFactor; + long version; + bool vouchers[32] = {false}; + InstParams() { + deck = Item::Red_Deck; + stake = Item::White_Stake; + showman = false; + sixesFactor = 1; + version = 10103; // 1.0.1c + } + InstParams(Item d, Item s, bool show, long v) { + deck = d; + stake = s; + showman = show; + sixesFactor = 1; + version = v; + } +}; + +struct Instance { + bool locked[(int)Item::ITEMS_END] = {false}; + Seed &seed; + double hashedSeed; + Cache cache; + InstParams params; + LuaRandom rng; + Instance(Seed &s) : seed(s) { + hashedSeed = s.pseudohash(0); + params = InstParams(); + rng = LuaRandom(0); + }; + void reset(Seed &s) { // This is slow, use next() unless necessary + seed = s; + hashedSeed = s.pseudohash(0); + params = InstParams(); + cache.nodes + .clear(); // Somehow `clear` is faster than swapping with empty map + cache.generatedFirstPack = false; + }; + void next() { + seed.next(); + hashedSeed = seed.pseudohash(0); + params = InstParams(); + cache.nodes.clear(); + cache.generatedFirstPack = false; + } + double get_node(std::string ID) { + if (cache.nodes.count(ID) == 0) { + cache.nodes[ID] = pseudohash_from(ID, seed.pseudohash(ID.length())); + } + cache.nodes[ID] = + round13(fract(cache.nodes[ID] * 1.72431234 + 2.134453429141)); + return (cache.nodes[ID] + hashedSeed) / 2; + } + double random(std::string ID) { + rng = LuaRandom(get_node(ID)); + return rng.random(); + } + int randint(std::string ID, int min, int max) { + rng = LuaRandom(get_node(ID)); + return rng.randint(min, max); + } + template + Item randchoice(std::string ID, const std::array &items) { + rng = LuaRandom(get_node(ID)); + Item item = items[rng.randint(0, items.size() - 1)]; + if ((params.showman == false && isLocked(item)) || item == Item::RETRY) { + int resample = 2; + while (true) { + rng = LuaRandom(get_node(ID + "_resample" + anteToString(resample))); + Item item = items[rng.randint(0, items.size() - 1)]; + resample++; + if ((item != Item::RETRY && !isLocked(item)) || resample > 1000) + return item; + } + } + return item; + } + template + Item randweightedchoice(std::string ID, + const std::array &items) { + rng = LuaRandom(get_node(ID)); + double poll = rng.random() * items[0].weight; + int idx = 1; + double weight = 0; + while (weight < poll) { + weight += items[idx].weight; + idx++; + } + return items[idx - 1].item; + } + + // Functions defined in functions.hpp + void lock(Item item); + void unlock(Item item); + bool isLocked(Item item); + void initLocks(int ante, bool freshProfile, bool freshRun); + void initUnlocks(int ante, bool freshProfile); + Item nextTarot(std::string source, int ante, bool soulable); + Item nextPlanet(std::string source, int ante, bool soulable); + Item nextSpectral(std::string source, int ante, bool soulable); + JokerData nextJoker(std::string source, int ante, bool hasStickers); + ShopInstance getShopInstance(); + ShopItem nextShopItem(int ante); + Item nextPack(int ante); + std::vector nextArcanaPack(int size, int ante); + std::vector nextCelestialPack(int size, int ante); + std::vector nextSpectralPack(int size, int ante); + std::vector nextBuffoonPack(int size, int ante); + std::vector nextStandardPack(int size, int ante); + Card nextStandardCard(int ante); + bool isVoucherActive(Item voucher); + void activateVoucher(Item voucher); + Item nextVoucher(int ante); + void setDeck(Item deck); + void setStake(Item stake); + Item nextTag(int ante); + Item nextBoss(int ante); +}; \ No newline at end of file diff --git a/immolate/items.cpp b/immolate/items.cpp new file mode 100644 index 0000000..eb9a04e --- /dev/null +++ b/immolate/items.cpp @@ -0,0 +1,356 @@ +// #include "items.hpp" + +// std::vector ENHANCEMENTS = { +// Item::Bonus_Card, Item::Mult_Card, Item::Wild_Card, Item::Glass_Card, +// Item::Steel_Card, Item::Stone_Card, Item::Gold_Card, Item::Lucky_Card}; + +// std::vector CARDS = { +// Item::C_2, Item::C_3, Item::C_4, Item::C_5, Item::C_6, Item::C_7, +// Item::C_8, Item::C_9, Item::C_A, Item::C_J, Item::C_K, Item::C_Q, +// Item::C_T, Item::D_2, Item::D_3, Item::D_4, Item::D_5, Item::D_6, +// Item::D_7, Item::D_8, Item::D_9, Item::D_A, Item::D_J, Item::D_K, +// Item::D_Q, Item::D_T, Item::H_2, Item::H_3, Item::H_4, Item::H_5, +// Item::H_6, Item::H_7, Item::H_8, Item::H_9, Item::H_A, Item::H_J, +// Item::H_K, Item::H_Q, Item::H_T, Item::S_2, Item::S_3, Item::S_4, +// Item::S_5, Item::S_6, Item::S_7, Item::S_8, Item::S_9, Item::S_A, +// Item::S_J, Item::S_K, Item::S_Q, Item::S_T}; + +// std::vector SUITS = {Item::Spades, Item::Hearts, Item::Clubs, +// Item::Diamonds}; + +// std::vector RANKS = {Item::_2, Item::_3, Item::_4, Item::_5, +// Item::_6, Item::_7, Item::_8, Item::_9, +// Item::_10, Item::Jack, Item::Queen, Item::King, +// Item::Ace}; + +// std::vector PACKS = { +// WeightedItem(Item::RETRY, 22.42), // total +// WeightedItem(Item::Arcana_Pack, 4), +// WeightedItem(Item::Jumbo_Arcana_Pack, 2), +// WeightedItem(Item::Mega_Arcana_Pack, 0.5), +// WeightedItem(Item::Celestial_Pack, 4), +// WeightedItem(Item::Jumbo_Celestial_Pack, 2), +// WeightedItem(Item::Mega_Celestial_Pack, 0.5), +// WeightedItem(Item::Standard_Pack, 4), +// WeightedItem(Item::Jumbo_Standard_Pack, 2), +// WeightedItem(Item::Mega_Standard_Pack, 0.5), +// WeightedItem(Item::Buffoon_Pack, 1.2), +// WeightedItem(Item::Jumbo_Buffoon_Pack, 0.6), +// WeightedItem(Item::Mega_Buffoon_Pack, 0.15), +// WeightedItem(Item::Spectral_Pack, 0.6), +// WeightedItem(Item::Jumbo_Spectral_Pack, 0.3), +// WeightedItem(Item::Mega_Spectral_Pack, 0.07)}; + +// std::vector TAROTS = {Item::The_Fool, +// Item::The_Magician, +// Item::The_High_Priestess, +// Item::The_Empress, +// Item::The_Emperor, +// Item::The_Hierophant, +// Item::The_Lovers, +// Item::The_Chariot, +// Item::Justice, +// Item::The_Hermit, +// Item::The_Wheel_of_Fortune, +// Item::Strength, +// Item::The_Hanged_Man, +// Item::Death, +// Item::Temperance, +// Item::The_Devil, +// Item::The_Tower, +// Item::The_Star, +// Item::The_Moon, +// Item::The_Sun, +// Item::Judgement, +// Item::The_World}; + +// std::vector PLANETS = {Item::Mercury, Item::Venus, Item::Earth, +// Item::Mars, Item::Jupiter, Item::Saturn, +// Item::Uranus, Item::Neptune, Item::Pluto, +// Item::Planet_X, Item::Ceres, Item::Eris}; + +// std::vector COMMON_JOKERS_100 = {Item::Joker, +// Item::Greedy_Joker, +// Item::Lusty_Joker, +// Item::Wrathful_Joker, +// Item::Gluttonous_Joker, +// Item::Jolly_Joker, +// Item::Zany_Joker, +// Item::Mad_Joker, +// Item::Crazy_Joker, +// Item::Droll_Joker, +// Item::Sly_Joker, +// Item::Wily_Joker, +// Item::Clever_Joker, +// Item::Devious_Joker, +// Item::Crafty_Joker, +// Item::Half_Joker, +// Item::Credit_Card, +// Item::Banner, +// Item::Mystic_Summit, +// Item::_8_Ball, +// Item::Misprint, +// Item::Raised_Fist, +// Item::Chaos_the_Clown, +// Item::Scary_Face, +// Item::Abstract_Joker, +// Item::Delayed_Gratification, +// Item::Gros_Michel, +// Item::Even_Steven, +// Item::Odd_Todd, +// Item::Scholar, +// Item::Business_Card, +// Item::Supernova, +// Item::Ride_the_Bus, +// Item::Egg, +// Item::Runner, +// Item::Ice_Cream, +// Item::Splash, +// Item::Blue_Joker, +// Item::Faceless_Joker, +// Item::Green_Joker, +// Item::Superposition, +// Item::To_Do_List, +// Item::Cavendish, +// Item::Red_Card, +// Item::Square_Joker, +// Item::Riff_raff, +// Item::Photograph, +// Item::Mail_In_Rebate, +// Item::Hallucination, +// Item::Fortune_Teller, +// Item::Juggler, +// Item::Drunkard, +// Item::Golden_Joker, +// Item::Popcorn, +// Item::Walkie_Talkie, +// Item::Smiley_Face, +// Item::Golden_Ticket, +// Item::Swashbuckler, +// Item::Hanging_Chad, +// Item::Shoot_the_Moon}; + +// std::vector COMMON_JOKERS = { +// Item::Joker, +// Item::Greedy_Joker, +// Item::Lusty_Joker, +// Item::Wrathful_Joker, +// Item::Gluttonous_Joker, +// Item::Jolly_Joker, +// Item::Zany_Joker, +// Item::Mad_Joker, +// Item::Crazy_Joker, +// Item::Droll_Joker, +// Item::Sly_Joker, +// Item::Wily_Joker, +// Item::Clever_Joker, +// Item::Devious_Joker, +// Item::Crafty_Joker, +// Item::Half_Joker, +// Item::Credit_Card, +// Item::Banner, +// Item::Mystic_Summit, +// Item::_8_Ball, +// Item::Misprint, +// Item::Raised_Fist, +// Item::Chaos_the_Clown, +// Item::Scary_Face, +// Item::Abstract_Joker, +// Item::Delayed_Gratification, +// Item::Gros_Michel, +// Item::Even_Steven, +// Item::Odd_Todd, +// Item::Scholar, +// Item::Business_Card, +// Item::Supernova, +// Item::Ride_the_Bus, +// Item::Egg, +// Item::Runner, +// Item::Ice_Cream, +// Item::Splash, +// Item::Blue_Joker, +// Item::Faceless_Joker, +// Item::Green_Joker, +// Item::Superposition, +// Item::To_Do_List, +// Item::Cavendish, +// Item::Red_Card, +// Item::Square_Joker, +// Item::Riff_raff, +// Item::Photograph, +// Item::Reserved_Parking, +// Item::Mail_In_Rebate, +// Item::Hallucination, +// Item::Fortune_Teller, +// Item::Juggler, +// Item::Drunkard, +// Item::Golden_Joker, +// Item::Popcorn, +// Item::Walkie_Talkie, +// Item::Smiley_Face, +// Item::Golden_Ticket, +// Item::Swashbuckler, +// Item::Hanging_Chad, +// Item::Shoot_the_Moon, +// }; + +// std::vector UNCOMMON_JOKERS_100 = { +// Item::Joker_Stencil, Item::Four_Fingers, +// Item::Mime, Item::Ceremonial_Dagger, +// Item::Marble_Joker, Item::Loyalty_Card, +// Item::Dusk, Item::Fibonacci, +// Item::Steel_Joker, Item::Hack, +// Item::Pareidolia, Item::Space_Joker, +// Item::Burglar, Item::Blackboard, +// Item::Constellation, Item::Hiker, +// Item::Card_Sharp, Item::Madness, +// Item::Vampire, Item::Shortcut, +// Item::Hologram, Item::Vagabond, +// Item::Cloud_9, Item::Rocket, +// Item::Midas_Mask, Item::Luchador, +// Item::Gift_Card, Item::Turtle_Bean, +// Item::Erosion, Item::Reserved_Parking, +// Item::To_the_Moon, Item::Stone_Joker, +// Item::Lucky_Cat, Item::Bull, +// Item::Diet_Cola, Item::Trading_Card, +// Item::Flash_Card, Item::Spare_Trousers, +// Item::Ramen, Item::Seltzer, +// Item::Castle, Item::Mr_Bones, +// Item::Acrobat, Item::Sock_and_Buskin, +// Item::Troubadour, Item::Certificate, +// Item::Smeared_Joker, Item::Throwback, +// Item::Rough_Gem, Item::Bloodstone, +// Item::Arrowhead, Item::Onyx_Agate, +// Item::Glass_Joker, Item::Showman, +// Item::Flower_Pot, Item::Merry_Andy, +// Item::Oops_All_6s, Item::The_Idol, +// Item::Seeing_Double, Item::Matador, +// Item::Stuntman, Item::Satellite, +// Item::Cartomancer, Item::Astronomer, +// Item::Burnt_Joker, Item::Bootstraps}; + +// std::vector UNCOMMON_JOKERS = { +// Item::Joker_Stencil, Item::Four_Fingers, +// Item::Mime, Item::Ceremonial_Dagger, +// Item::Marble_Joker, Item::Loyalty_Card, +// Item::Dusk, Item::Fibonacci, +// Item::Steel_Joker, Item::Hack, +// Item::Pareidolia, Item::Space_Joker, +// Item::Burglar, Item::Blackboard, +// Item::Sixth_Sense, Item::Constellation, +// Item::Hiker, Item::Card_Sharp, +// Item::Madness, Item::Seance, +// Item::Vampire, Item::Shortcut, +// Item::Hologram, Item::Cloud_9, +// Item::Rocket, Item::Midas_Mask, +// Item::Luchador, Item::Gift_Card, +// Item::Turtle_Bean, Item::Erosion, +// Item::To_the_Moon, Item::Stone_Joker, +// Item::Lucky_Cat, Item::Bull, +// Item::Diet_Cola, Item::Trading_Card, +// Item::Flash_Card, Item::Spare_Trousers, +// Item::Ramen, Item::Seltzer, +// Item::Castle, Item::Mr_Bones, +// Item::Acrobat, Item::Sock_and_Buskin, +// Item::Troubadour, Item::Certificate, +// Item::Smeared_Joker, Item::Throwback, +// Item::Rough_Gem, Item::Bloodstone, +// Item::Arrowhead, Item::Onyx_Agate, +// Item::Glass_Joker, Item::Showman, +// Item::Flower_Pot, Item::Merry_Andy, +// Item::Oops_All_6s, Item::The_Idol, +// Item::Seeing_Double, Item::Matador, +// Item::Satellite, Item::Cartomancer, +// Item::Astronomer, Item::Bootstraps, +// }; + +// std::vector RARE_JOKERS_100 = {Item::DNA, +// Item::Sixth_Sense, +// Item::Seance, +// Item::Baron, +// Item::Obelisk, +// Item::Baseball_Card, +// Item::Ancient_Joker, +// Item::Campfire, +// Item::Blueprint, +// Item::Wee_Joker, +// Item::Hit_the_Road, +// Item::The_Duo, +// Item::The_Trio, +// Item::The_Family, +// Item::The_Order, +// Item::The_Tribe, +// Item::Invisible_Joker, +// Item::Brainstorm, +// Item::Drivers_License}; + +// std::vector RARE_JOKERS = { +// Item::DNA, +// Item::Vagabond, +// Item::Baron, +// Item::Obelisk, +// Item::Baseball_Card, +// Item::Ancient_Joker, +// Item::Campfire, +// Item::Blueprint, +// Item::Wee_Joker, +// Item::Hit_the_Road, +// Item::The_Duo, +// Item::The_Trio, +// Item::The_Family, +// Item::The_Order, +// Item::The_Tribe, +// Item::Stuntman, +// Item::Invisible_Joker, +// Item::Brainstorm, +// Item::Drivers_License, +// Item::Burnt_Joker, +// }; + +// std::vector LEGENDARY_JOKERS = {Item::Canio, Item::Triboulet, +// Item::Yorick, Item::Chicot, +// Item::Perkeo}; + +// std::vector VOUCHERS = { +// Item::Overstock, Item::Overstock_Plus, Item::Clearance_Sale, +// Item::Liquidation, Item::Hone, Item::Glow_Up, +// Item::Reroll_Surplus, Item::Reroll_Glut, Item::Crystal_Ball, +// Item::Omen_Globe, Item::Telescope, Item::Observatory, +// Item::Grabber, Item::Nacho_Tong, Item::Wasteful, +// Item::Recyclomancy, Item::Tarot_Merchant, Item::Tarot_Tycoon, +// Item::Planet_Merchant, Item::Planet_Tycoon, Item::Seed_Money, +// Item::Money_Tree, Item::Blank, Item::Antimatter, +// Item::Magic_Trick, Item::Illusion, Item::Hieroglyph, +// Item::Petroglyph, Item::Directors_Cut, Item::Retcon, +// Item::Paint_Brush, Item::Palette}; + +// std::vector SPECTRALS = { +// Item::Familiar, Item::Grim, Item::Incantation, Item::Talisman, +// Item::Aura, Item::Wraith, Item::Sigil, Item::Ouija, +// Item::Ectoplasm, Item::Immolate, Item::Ankh, Item::Deja_Vu, +// Item::Hex, Item::Trance, Item::Medium, Item::Cryptid, +// Item::RETRY, // Soul +// Item::RETRY // Black_Hole +// }; + +// std::vector TAGS = { +// Item::Uncommon_Tag, Item::Rare_Tag, Item::Negative_Tag, +// Item::Foil_Tag, Item::Holographic_Tag, Item::Polychrome_Tag, +// Item::Investment_Tag, Item::Voucher_Tag, Item::Boss_Tag, +// Item::Standard_Tag, Item::Charm_Tag, Item::Meteor_Tag, +// Item::Buffoon_Tag, Item::Handy_Tag, Item::Garbage_Tag, +// Item::Ethereal_Tag, Item::Coupon_Tag, Item::Double_Tag, +// Item::Juggle_Tag, Item::D6_Tag, Item::Top_up_Tag, +// Item::Speed_Tag, Item::Orbital_Tag, Item::Economy_Tag}; + +// std::vector BOSSES = { +// Item::The_Arm, Item::The_Club, Item::The_Eye, +// Item::Amber_Acorn, Item::Cerulean_Bell, Item::Crimson_Heart, +// Item::Verdant_Leaf, Item::Violet_Vessel, Item::The_Fish, +// Item::The_Flint, Item::The_Goad, Item::The_Head, +// Item::The_Hook, Item::The_House, Item::The_Manacle, +// Item::The_Mark, Item::The_Mouth, Item::The_Needle, +// Item::The_Ox, Item::The_Pillar, Item::The_Plant, +// Item::The_Psychic, Item::The_Serpent, Item::The_Tooth, +// Item::The_Wall, Item::The_Water, Item::The_Wheel, +// Item::The_Window}; diff --git a/immolate/items.hpp b/immolate/items.hpp new file mode 100644 index 0000000..ded376d --- /dev/null +++ b/immolate/items.hpp @@ -0,0 +1,3578 @@ +#ifndef ITEMS_HPP +#define ITEMS_HPP + +#include +#include +#include +#include +#include + +enum class Item { + RETRY, + + // Jokers + J_BEGIN, + + J_C_BEGIN, + Joker, + Greedy_Joker, + Lusty_Joker, + Wrathful_Joker, + Gluttonous_Joker, + Jolly_Joker, + Zany_Joker, + Mad_Joker, + Crazy_Joker, + Droll_Joker, + Sly_Joker, + Wily_Joker, + Clever_Joker, + Devious_Joker, + Crafty_Joker, + Half_Joker, + Credit_Card, + Banner, + Mystic_Summit, + _8_Ball, + Misprint, + Raised_Fist, + Chaos_the_Clown, + Scary_Face, + Abstract_Joker, + Delayed_Gratification, + Gros_Michel, + Even_Steven, + Odd_Todd, + Scholar, + Business_Card, + Supernova, + Ride_the_Bus, + Egg, + Runner, + Ice_Cream, + Splash, + Blue_Joker, + Faceless_Joker, + Green_Joker, + Superposition, + To_Do_List, + Cavendish, + Red_Card, + Square_Joker, + Riff_raff, + Photograph, + Reserved_Parking, + Mail_In_Rebate, + Hallucination, + Fortune_Teller, + Juggler, + Drunkard, + Golden_Joker, + Popcorn, + Walkie_Talkie, + Smiley_Face, + Golden_Ticket, + Swashbuckler, + Hanging_Chad, + Shoot_the_Moon, + J_C_END, + + J_U_BEGIN, + Joker_Stencil, + Four_Fingers, + Mime, + Ceremonial_Dagger, + Marble_Joker, + Loyalty_Card, + Dusk, + Fibonacci, + Steel_Joker, + Hack, + Pareidolia, + Space_Joker, + Burglar, + Blackboard, + Sixth_Sense, + Constellation, + Hiker, + Card_Sharp, + Madness, + Seance, + Shortcut, + Hologram, + Cloud_9, + Rocket, + Midas_Mask, + Luchador, + Gift_Card, + Turtle_Bean, + Erosion, + To_the_Moon, + Stone_Joker, + Lucky_Cat, + Bull, + Diet_Cola, + Trading_Card, + Flash_Card, + Spare_Trousers, + Ramen, + Seltzer, + Castle, + Mr_Bones, + Acrobat, + Sock_and_Buskin, + Troubadour, + Certificate, + Smeared_Joker, + Throwback, + Rough_Gem, + Bloodstone, + Arrowhead, + Onyx_Agate, + Glass_Joker, + Showman, + Flower_Pot, + Merry_Andy, + Oops_All_6s, + The_Idol, + Seeing_Double, + Matador, + Stuntman, + Satellite, + Cartomancer, + Astronomer, + Bootstraps, + J_U_END, + + J_R_BEGIN, + DNA, + Vampire, + Vagabond, + Baron, + Obelisk, + Baseball_Card, + Ancient_Joker, + Campfire, + Blueprint, + Wee_Joker, + Hit_the_Road, + The_Duo, + The_Trio, + The_Family, + The_Order, + The_Tribe, + Invisible_Joker, + Brainstorm, + Drivers_License, + Burnt_Joker, + J_R_END, + + J_L_BEGIN, + Canio, + Triboulet, + Yorick, + Chicot, + Perkeo, + J_L_END, + + J_END, + + // Vouchers + V_BEGIN, + Overstock, + Overstock_Plus, + Clearance_Sale, + Liquidation, + Hone, + Glow_Up, + Reroll_Surplus, + Reroll_Glut, + Crystal_Ball, + Omen_Globe, + Telescope, + Observatory, + Grabber, + Nacho_Tong, + Wasteful, + Recyclomancy, + Tarot_Merchant, + Tarot_Tycoon, + Planet_Merchant, + Planet_Tycoon, + Seed_Money, + Money_Tree, + Blank, + Antimatter, + Magic_Trick, + Illusion, + Hieroglyph, + Petroglyph, + Directors_Cut, + Retcon, + Paint_Brush, + Palette, + V_END, + + // Tarots + T_BEGIN, + The_Fool, + The_Magician, + The_High_Priestess, + The_Empress, + The_Emperor, + The_Hierophant, + The_Lovers, + The_Chariot, + Justice, + The_Hermit, + The_Wheel_of_Fortune, + Strength, + The_Hanged_Man, + Death, + Temperance, + The_Devil, + The_Tower, + The_Star, + The_Moon, + The_Sun, + Judgement, + The_World, + T_END, + + // Planets + P_BEGIN, + Mercury, + Venus, + Earth, + Mars, + Jupiter, + Saturn, + Uranus, + Neptune, + Pluto, + Planet_X, + Ceres, + Eris, + P_END, + + // Hands + H_BEGIN, + Pair, + Three_of_a_Kind, + Full_House, + Four_of_a_Kind, + Flush, + Straight, + Two_Pair, + Straight_Flush, + High_Card, + Five_of_a_Kind, + Flush_House, + Flush_Five, + H_END, + + // Spectrals + S_BEGIN, + Familiar, + Grim, + Incantation, + Talisman, + Aura, + Wraith, + Sigil, + Ouija, + Ectoplasm, + Immolate, + Ankh, + Deja_Vu, + Hex, + Trance, + Medium, + Cryptid, + The_Soul, + Black_Hole, + S_END, + + // Enhancements + ENHANCEMENT_BEGIN, + No_Enhancement, + Bonus_Card, + Mult_Card, + Wild_Card, + Glass_Card, + Steel_Card, + Stone_Card, + Gold_Card, + Lucky_Card, + ENHANCEMENT_END, + + // Seals + SEAL_BEGIN, + No_Seal, + Gold_Seal, + Red_Seal, + Blue_Seal, + Purple_Seal, + SEAL_END, + + // Editions + E_BEGIN, + No_Edition, + Foil, + Holographic, + Polychrome, + Negative, + E_END, + + // Booster Packs + PACK_BEGIN, + Arcana_Pack, + Jumbo_Arcana_Pack, + Mega_Arcana_Pack, + Celestial_Pack, + Jumbo_Celestial_Pack, + Mega_Celestial_Pack, + Standard_Pack, + Jumbo_Standard_Pack, + Mega_Standard_Pack, + Buffoon_Pack, + Jumbo_Buffoon_Pack, + Mega_Buffoon_Pack, + Spectral_Pack, + Jumbo_Spectral_Pack, + Mega_Spectral_Pack, + PACK_END, + + // Tags + TAG_BEGIN, + Uncommon_Tag, + Rare_Tag, + Negative_Tag, + Foil_Tag, + Holographic_Tag, + Polychrome_Tag, + Investment_Tag, + Voucher_Tag, + Boss_Tag, + Standard_Tag, + Charm_Tag, + Meteor_Tag, + Buffoon_Tag, + Handy_Tag, + Garbage_Tag, + Ethereal_Tag, + Coupon_Tag, + Double_Tag, + Juggle_Tag, + D6_Tag, + Top_up_Tag, + Speed_Tag, + Orbital_Tag, + Economy_Tag, + TAG_END, + + // Blinds + B_BEGIN, + Small_Blind, + Big_Blind, + The_Hook, + The_Ox, + The_House, + The_Wall, + The_Wheel, + The_Arm, + The_Club, + The_Fish, + The_Psychic, + The_Goad, + The_Water, + The_Window, + The_Manacle, + The_Eye, + The_Mouth, + The_Plant, + The_Serpent, + The_Pillar, + The_Needle, + The_Head, + The_Tooth, + The_Flint, + The_Mark, + B_F_BEGIN, + Amber_Acorn, + Verdant_Leaf, + Violet_Vessel, + Crimson_Heart, + Cerulean_Bell, + B_F_END, + B_END, + + // Suits + SUIT_BEGIN, + Hearts, + Clubs, + Diamonds, + Spades, + SUIT_END, + + // Ranks + RANK_BEGIN, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + Jack, + Queen, + King, + Ace, + RANK_END, + + // Cards + C_BEGIN, + C_2, + C_3, + C_4, + C_5, + C_6, + C_7, + C_8, + C_9, + C_A, + C_J, + C_K, + C_Q, + C_T, + D_2, + D_3, + D_4, + D_5, + D_6, + D_7, + D_8, + D_9, + D_A, + D_J, + D_K, + D_Q, + D_T, + H_2, + H_3, + H_4, + H_5, + H_6, + H_7, + H_8, + H_9, + H_A, + H_J, + H_K, + H_Q, + H_T, + S_2, + S_3, + S_4, + S_5, + S_6, + S_7, + S_8, + S_9, + S_A, + S_J, + S_K, + S_Q, + S_T, + C_END, + + // Decks + D_BEGIN, + Red_Deck, + Blue_Deck, + Yellow_Deck, + Green_Deck, + Black_Deck, + Magic_Deck, + Nebula_Deck, + Ghost_Deck, + Abandoned_Deck, + Checkered_Deck, + Zodiac_Deck, + Painted_Deck, + Anaglyph_Deck, + Plasma_Deck, + Erratic_Deck, + Challenge_Deck, + D_END, + + // Challenges + CHAL_BEGIN, + The_Omelette, + _15_Minute_City, + Rich_get_Richer, + On_a_Knifes_Edge, + X_ray_Vision, + Mad_World, + Luxury_Tax, + Non_Perishable, + Medusa, + Double_or_Nothing, + Typecast, + Inflation, + Bram_Poker, + Fragile, + Monolith, + Blast_Off, + Five_Card_Draw, + Golden_Needle, + Cruelty, + Jokerless, + CHAL_END, + + // Stakes + STAKE_BEGIN, + White_Stake, + Red_Stake, + Green_Stake, + Black_Stake, + Blue_Stake, + Purple_Stake, + Orange_Stake, + Gold_Stake, + STAKE_END, + + RARITY_BEGIN, + Common, + Uncommon, + Rare, + Legendary, + RARITY_END, + + TYPE_BEGIN, + T_Joker, + T_Tarot, + T_Planet, + T_Spectral, + T_Playing_Card, + TYPE_END, + + ITEMS_END +}; +inline std::string itemToString(Item i) { + switch (i) { + case Item::RETRY: + return "RETRY"; + case Item::J_BEGIN: + return "J BEGIN"; + case Item::J_C_BEGIN: + return "J C BEGIN"; + case Item::Joker: + return "Joker"; + case Item::Greedy_Joker: + return "Greedy Joker"; + case Item::Lusty_Joker: + return "Lusty Joker"; + case Item::Wrathful_Joker: + return "Wrathful Joker"; + case Item::Gluttonous_Joker: + return "Gluttonous Joker"; + case Item::Jolly_Joker: + return "Jolly Joker"; + case Item::Zany_Joker: + return "Zany Joker"; + case Item::Mad_Joker: + return "Mad Joker"; + case Item::Crazy_Joker: + return "Crazy Joker"; + case Item::Droll_Joker: + return "Droll Joker"; + case Item::Sly_Joker: + return "Sly Joker"; + case Item::Wily_Joker: + return "Wily Joker"; + case Item::Clever_Joker: + return "Clever Joker"; + case Item::Devious_Joker: + return "Devious Joker"; + case Item::Crafty_Joker: + return "Crafty Joker"; + case Item::Half_Joker: + return "Half Joker"; + case Item::Credit_Card: + return "Credit Card"; + case Item::Banner: + return "Banner"; + case Item::Mystic_Summit: + return "Mystic Summit"; + case Item::_8_Ball: + return "8 Ball"; + case Item::Misprint: + return "Misprint"; + case Item::Raised_Fist: + return "Raised Fist"; + case Item::Chaos_the_Clown: + return "Chaos the Clown"; + case Item::Scary_Face: + return "Scary Face"; + case Item::Abstract_Joker: + return "Abstract Joker"; + case Item::Delayed_Gratification: + return "Delayed Gratification"; + case Item::Gros_Michel: + return "Gros Michel"; + case Item::Even_Steven: + return "Even Steven"; + case Item::Odd_Todd: + return "Odd Todd"; + case Item::Scholar: + return "Scholar"; + case Item::Business_Card: + return "Business Card"; + case Item::Supernova: + return "Supernova"; + case Item::Ride_the_Bus: + return "Ride the Bus"; + case Item::Egg: + return "Egg"; + case Item::Runner: + return "Runner"; + case Item::Ice_Cream: + return "Ice Cream"; + case Item::Splash: + return "Splash"; + case Item::Blue_Joker: + return "Blue Joker"; + case Item::Faceless_Joker: + return "Faceless Joker"; + case Item::Green_Joker: + return "Green Joker"; + case Item::Superposition: + return "Superposition"; + case Item::To_Do_List: + return "To Do List"; + case Item::Cavendish: + return "Cavendish"; + case Item::Red_Card: + return "Red Card"; + case Item::Square_Joker: + return "Square Joker"; + case Item::Riff_raff: + return "Riff-raff"; + case Item::Photograph: + return "Photograph"; + case Item::Reserved_Parking: + return "Reserved Parking"; + case Item::Mail_In_Rebate: + return "Mail-In Rebate"; + case Item::Hallucination: + return "Hallucination"; + case Item::Fortune_Teller: + return "Fortune Teller"; + case Item::Juggler: + return "Juggler"; + case Item::Drunkard: + return "Drunkard"; + case Item::Golden_Joker: + return "Golden Joker"; + case Item::Popcorn: + return "Popcorn"; + case Item::Walkie_Talkie: + return "Walkie Talkie"; + case Item::Smiley_Face: + return "Smiley Face"; + case Item::Golden_Ticket: + return "Golden Ticket"; + case Item::Swashbuckler: + return "Swashbuckler"; + case Item::Hanging_Chad: + return "Hanging Chad"; + case Item::Shoot_the_Moon: + return "Shoot the Moon"; + case Item::J_C_END: + return "J C END"; + case Item::J_U_BEGIN: + return "J U BEGIN"; + case Item::Joker_Stencil: + return "Joker Stencil"; + case Item::Four_Fingers: + return "Four Fingers"; + case Item::Mime: + return "Mime"; + case Item::Ceremonial_Dagger: + return "Ceremonial Dagger"; + case Item::Marble_Joker: + return "Marble Joker"; + case Item::Loyalty_Card: + return "Loyalty Card"; + case Item::Dusk: + return "Dusk"; + case Item::Fibonacci: + return "Fibonacci"; + case Item::Steel_Joker: + return "Steel Joker"; + case Item::Hack: + return "Hack"; + case Item::Pareidolia: + return "Pareidolia"; + case Item::Space_Joker: + return "Space Joker"; + case Item::Burglar: + return "Burglar"; + case Item::Blackboard: + return "Blackboard"; + case Item::Sixth_Sense: + return "Sixth Sense"; + case Item::Constellation: + return "Constellation"; + case Item::Hiker: + return "Hiker"; + case Item::Card_Sharp: + return "Card Sharp"; + case Item::Madness: + return "Madness"; + case Item::Seance: + return "SΘance"; + case Item::Shortcut: + return "Shortcut"; + case Item::Hologram: + return "Hologram"; + case Item::Cloud_9: + return "Cloud 9"; + case Item::Rocket: + return "Rocket"; + case Item::Midas_Mask: + return "Midas Mask"; + case Item::Luchador: + return "Luchador"; + case Item::Gift_Card: + return "Gift Card"; + case Item::Turtle_Bean: + return "Turtle Bean"; + case Item::Erosion: + return "Erosion"; + case Item::To_the_Moon: + return "To the Moon"; + case Item::Stone_Joker: + return "Stone Joker"; + case Item::Lucky_Cat: + return "Lucky Cat"; + case Item::Bull: + return "Bull"; + case Item::Diet_Cola: + return "Diet Cola"; + case Item::Trading_Card: + return "Trading Card"; + case Item::Flash_Card: + return "Flash Card"; + case Item::Spare_Trousers: + return "Spare Trousers"; + case Item::Ramen: + return "Ramen"; + case Item::Seltzer: + return "Seltzer"; + case Item::Castle: + return "Castle"; + case Item::Mr_Bones: + return "Mr. Bones"; + case Item::Acrobat: + return "Acrobat"; + case Item::Sock_and_Buskin: + return "Sock and Buskin"; + case Item::Troubadour: + return "Troubadour"; + case Item::Certificate: + return "Certificate"; + case Item::Smeared_Joker: + return "Smeared Joker"; + case Item::Throwback: + return "Throwback"; + case Item::Rough_Gem: + return "Rough Gem"; + case Item::Bloodstone: + return "Bloodstone"; + case Item::Arrowhead: + return "Arrowhead"; + case Item::Onyx_Agate: + return "Onyx Agate"; + case Item::Glass_Joker: + return "Glass Joker"; + case Item::Showman: + return "Showman"; + case Item::Flower_Pot: + return "Flower Pot"; + case Item::Merry_Andy: + return "Merry Andy"; + case Item::Oops_All_6s: + return "Oops! All 6s"; + case Item::The_Idol: + return "The Idol"; + case Item::Seeing_Double: + return "Seeing Double"; + case Item::Matador: + return "Matador"; + case Item::Stuntman: + return "Stuntman"; + case Item::Satellite: + return "Satellite"; + case Item::Cartomancer: + return "Cartomancer"; + case Item::Astronomer: + return "Astronomer"; + case Item::Bootstraps: + return "Bootstraps"; + case Item::J_U_END: + return "J U END"; + case Item::J_R_BEGIN: + return "J R BEGIN"; + case Item::DNA: + return "DNA"; + case Item::Vampire: + return "Vampire"; + case Item::Vagabond: + return "Vagabond"; + case Item::Baron: + return "Baron"; + case Item::Obelisk: + return "Obelisk"; + case Item::Baseball_Card: + return "Baseball Card"; + case Item::Ancient_Joker: + return "Ancient Joker"; + case Item::Campfire: + return "Campfire"; + case Item::Blueprint: + return "Blueprint"; + case Item::Wee_Joker: + return "Wee Joker"; + case Item::Hit_the_Road: + return "Hit the Road"; + case Item::The_Duo: + return "The Duo"; + case Item::The_Trio: + return "The Trio"; + case Item::The_Family: + return "The Family"; + case Item::The_Order: + return "The Order"; + case Item::The_Tribe: + return "The Tribe"; + case Item::Invisible_Joker: + return "Invisible Joker"; + case Item::Brainstorm: + return "Brainstorm"; + case Item::Drivers_License: + return "Driver's License"; + case Item::Burnt_Joker: + return "Burnt Joker"; + case Item::J_R_END: + return "J R END"; + case Item::J_L_BEGIN: + return "J L BEGIN"; + case Item::Canio: + return "Canio"; + case Item::Triboulet: + return "Triboulet"; + case Item::Yorick: + return "Yorick"; + case Item::Chicot: + return "Chicot"; + case Item::Perkeo: + return "Perkeo"; + case Item::J_L_END: + return "J L END"; + case Item::J_END: + return "J END"; + case Item::V_BEGIN: + return "V BEGIN"; + case Item::Overstock: + return "Overstock"; + case Item::Overstock_Plus: + return "Overstock Plus"; + case Item::Clearance_Sale: + return "Clearance Sale"; + case Item::Liquidation: + return "Liquidation"; + case Item::Hone: + return "Hone"; + case Item::Glow_Up: + return "Glow Up"; + case Item::Reroll_Surplus: + return "Reroll Surplus"; + case Item::Reroll_Glut: + return "Reroll Glut"; + case Item::Crystal_Ball: + return "Crystal Ball"; + case Item::Omen_Globe: + return "Omen Globe"; + case Item::Telescope: + return "Telescope"; + case Item::Observatory: + return "Observatory"; + case Item::Grabber: + return "Grabber"; + case Item::Nacho_Tong: + return "Nacho Tong"; + case Item::Wasteful: + return "Wasteful"; + case Item::Recyclomancy: + return "Recyclomancy"; + case Item::Tarot_Merchant: + return "Tarot Merchant"; + case Item::Tarot_Tycoon: + return "Tarot Tycoon"; + case Item::Planet_Merchant: + return "Planet Merchant"; + case Item::Planet_Tycoon: + return "Planet Tycoon"; + case Item::Seed_Money: + return "Seed Money"; + case Item::Money_Tree: + return "Money Tree"; + case Item::Blank: + return "Blank"; + case Item::Antimatter: + return "Antimatter"; + case Item::Magic_Trick: + return "Magic Trick"; + case Item::Illusion: + return "Illusion"; + case Item::Hieroglyph: + return "Hieroglyph"; + case Item::Petroglyph: + return "Petroglyph"; + case Item::Directors_Cut: + return "Director's Cut"; + case Item::Retcon: + return "Retcon"; + case Item::Paint_Brush: + return "Paint Brush"; + case Item::Palette: + return "Palette"; + case Item::V_END: + return "V END"; + case Item::T_BEGIN: + return "T BEGIN"; + case Item::The_Fool: + return "The Fool"; + case Item::The_Magician: + return "The Magician"; + case Item::The_High_Priestess: + return "The High Priestess"; + case Item::The_Empress: + return "The Empress"; + case Item::The_Emperor: + return "The Emperor"; + case Item::The_Hierophant: + return "The Hierophant"; + case Item::The_Lovers: + return "The Lovers"; + case Item::The_Chariot: + return "The Chariot"; + case Item::Justice: + return "Justice"; + case Item::The_Hermit: + return "The Hermit"; + case Item::The_Wheel_of_Fortune: + return "The Wheel of Fortune"; + case Item::Strength: + return "Strength"; + case Item::The_Hanged_Man: + return "The Hanged Man"; + case Item::Death: + return "Death"; + case Item::Temperance: + return "Temperance"; + case Item::The_Devil: + return "The Devil"; + case Item::The_Tower: + return "The Tower"; + case Item::The_Star: + return "The Star"; + case Item::The_Moon: + return "The Moon"; + case Item::The_Sun: + return "The Sun"; + case Item::Judgement: + return "Judgement"; + case Item::The_World: + return "The World"; + case Item::T_END: + return "T END"; + case Item::P_BEGIN: + return "P BEGIN"; + case Item::Mercury: + return "Mercury"; + case Item::Venus: + return "Venus"; + case Item::Earth: + return "Earth"; + case Item::Mars: + return "Mars"; + case Item::Jupiter: + return "Jupiter"; + case Item::Saturn: + return "Saturn"; + case Item::Uranus: + return "Uranus"; + case Item::Neptune: + return "Neptune"; + case Item::Pluto: + return "Pluto"; + case Item::Planet_X: + return "Planet X"; + case Item::Ceres: + return "Ceres"; + case Item::Eris: + return "Eris"; + case Item::P_END: + return "P END"; + case Item::H_BEGIN: + return "H BEGIN"; + case Item::Pair: + return "Pair"; + case Item::Three_of_a_Kind: + return "Three of a Kind"; + case Item::Full_House: + return "Full House"; + case Item::Four_of_a_Kind: + return "Four of a Kind"; + case Item::Flush: + return "Flush"; + case Item::Straight: + return "Straight"; + case Item::Two_Pair: + return "Two Pair"; + case Item::Straight_Flush: + return "Straight Flush"; + case Item::High_Card: + return "High Card"; + case Item::Five_of_a_Kind: + return "Five of a Kind"; + case Item::Flush_House: + return "Flush House"; + case Item::Flush_Five: + return "Flush Five"; + case Item::H_END: + return "H END"; + case Item::S_BEGIN: + return "S BEGIN"; + case Item::Familiar: + return "Familiar"; + case Item::Grim: + return "Grim"; + case Item::Incantation: + return "Incantation"; + case Item::Talisman: + return "Talisman"; + case Item::Aura: + return "Aura"; + case Item::Wraith: + return "Wraith"; + case Item::Sigil: + return "Sigil"; + case Item::Ouija: + return "Ouija"; + case Item::Ectoplasm: + return "Ectoplasm"; + case Item::Immolate: + return "Immolate"; + case Item::Ankh: + return "Ankh"; + case Item::Deja_Vu: + return "Deja Vu"; + case Item::Hex: + return "Hex"; + case Item::Trance: + return "Trance"; + case Item::Medium: + return "Medium"; + case Item::Cryptid: + return "Cryptid"; + case Item::The_Soul: + return "The Soul"; + case Item::Black_Hole: + return "Black Hole"; + case Item::S_END: + return "S END"; + case Item::ENHANCEMENT_BEGIN: + return "ENHANCEMENT BEGIN"; + case Item::No_Enhancement: + return "No Enhancement"; + case Item::Bonus_Card: + return "Bonus Card"; + case Item::Mult_Card: + return "Mult Card"; + case Item::Wild_Card: + return "Wild Card"; + case Item::Glass_Card: + return "Glass Card"; + case Item::Steel_Card: + return "Steel Card"; + case Item::Stone_Card: + return "Stone Card"; + case Item::Gold_Card: + return "Gold Card"; + case Item::Lucky_Card: + return "Lucky Card"; + case Item::ENHANCEMENT_END: + return "ENHANCEMENT END"; + case Item::SEAL_BEGIN: + return "SEAL BEGIN"; + case Item::No_Seal: + return "No Seal"; + case Item::Gold_Seal: + return "Gold Seal"; + case Item::Red_Seal: + return "Red Seal"; + case Item::Blue_Seal: + return "Blue Seal"; + case Item::Purple_Seal: + return "Purple Seal"; + case Item::SEAL_END: + return "SEAL END"; + case Item::E_BEGIN: + return "E BEGIN"; + case Item::No_Edition: + return "No Edition"; + case Item::Foil: + return "Foil"; + case Item::Holographic: + return "Holographic"; + case Item::Polychrome: + return "Polychrome"; + case Item::Negative: + return "Negative"; + case Item::E_END: + return "E END"; + case Item::PACK_BEGIN: + return "PACK BEGIN"; + case Item::Arcana_Pack: + return "Arcana Pack"; + case Item::Jumbo_Arcana_Pack: + return "Jumbo Arcana Pack"; + case Item::Mega_Arcana_Pack: + return "Mega Arcana Pack"; + case Item::Celestial_Pack: + return "Celestial Pack"; + case Item::Jumbo_Celestial_Pack: + return "Jumbo Celestial Pack"; + case Item::Mega_Celestial_Pack: + return "Mega Celestial Pack"; + case Item::Standard_Pack: + return "Standard Pack"; + case Item::Jumbo_Standard_Pack: + return "Jumbo Standard Pack"; + case Item::Mega_Standard_Pack: + return "Mega Standard Pack"; + case Item::Buffoon_Pack: + return "Buffoon Pack"; + case Item::Jumbo_Buffoon_Pack: + return "Jumbo Buffoon Pack"; + case Item::Mega_Buffoon_Pack: + return "Mega Buffoon Pack"; + case Item::Spectral_Pack: + return "Spectral Pack"; + case Item::Jumbo_Spectral_Pack: + return "Jumbo Spectral Pack"; + case Item::Mega_Spectral_Pack: + return "Mega Spectral Pack"; + case Item::PACK_END: + return "PACK END"; + case Item::TAG_BEGIN: + return "TAG BEGIN"; + case Item::Uncommon_Tag: + return "Uncommon Tag"; + case Item::Rare_Tag: + return "Rare Tag"; + case Item::Negative_Tag: + return "Negative Tag"; + case Item::Foil_Tag: + return "Foil Tag"; + case Item::Holographic_Tag: + return "Holographic Tag"; + case Item::Polychrome_Tag: + return "Polychrome Tag"; + case Item::Investment_Tag: + return "Investment Tag"; + case Item::Voucher_Tag: + return "Voucher Tag"; + case Item::Boss_Tag: + return "Boss Tag"; + case Item::Standard_Tag: + return "Standard Tag"; + case Item::Charm_Tag: + return "Charm Tag"; + case Item::Meteor_Tag: + return "Meteor Tag"; + case Item::Buffoon_Tag: + return "Buffoon Tag"; + case Item::Handy_Tag: + return "Handy Tag"; + case Item::Garbage_Tag: + return "Garbage Tag"; + case Item::Ethereal_Tag: + return "Ethereal Tag"; + case Item::Coupon_Tag: + return "Coupon Tag"; + case Item::Double_Tag: + return "Double Tag"; + case Item::Juggle_Tag: + return "Juggle Tag"; + case Item::D6_Tag: + return "D6 Tag"; + case Item::Top_up_Tag: + return "Top-up Tag"; + case Item::Speed_Tag: + return "Speed Tag"; + case Item::Orbital_Tag: + return "Orbital Tag"; + case Item::Economy_Tag: + return "Economy Tag"; + case Item::TAG_END: + return "TAG END"; + case Item::B_BEGIN: + return "B BEGIN"; + case Item::Small_Blind: + return "Small Blind"; + case Item::Big_Blind: + return "Big Blind"; + case Item::The_Hook: + return "The Hook"; + case Item::The_Ox: + return "The Ox"; + case Item::The_House: + return "The House"; + case Item::The_Wall: + return "The Wall"; + case Item::The_Wheel: + return "The Wheel"; + case Item::The_Arm: + return "The Arm"; + case Item::The_Club: + return "The Club"; + case Item::The_Fish: + return "The Fish"; + case Item::The_Psychic: + return "The Psychic"; + case Item::The_Goad: + return "The Goad"; + case Item::The_Water: + return "The Water"; + case Item::The_Window: + return "The Window"; + case Item::The_Manacle: + return "The Manacle"; + case Item::The_Eye: + return "The Eye"; + case Item::The_Mouth: + return "The Mouth"; + case Item::The_Plant: + return "The Plant"; + case Item::The_Serpent: + return "The Serpent"; + case Item::The_Pillar: + return "The Pillar"; + case Item::The_Needle: + return "The Needle"; + case Item::The_Head: + return "The Head"; + case Item::The_Tooth: + return "The Tooth"; + case Item::The_Flint: + return "The Flint"; + case Item::The_Mark: + return "The Mark"; + case Item::B_F_BEGIN: + return "B F BEGIN"; + case Item::Amber_Acorn: + return "Amber Acorn"; + case Item::Verdant_Leaf: + return "Verdant Leaf"; + case Item::Violet_Vessel: + return "Violet Vessel"; + case Item::Crimson_Heart: + return "Crimson Heart"; + case Item::Cerulean_Bell: + return "Cerulean Bell"; + case Item::B_F_END: + return "B F END"; + case Item::B_END: + return "B END"; + case Item::SUIT_BEGIN: + return "SUIT BEGIN"; + case Item::Hearts: + return "Hearts"; + case Item::Clubs: + return "Clubs"; + case Item::Diamonds: + return "Diamonds"; + case Item::Spades: + return "Spades"; + case Item::SUIT_END: + return "SUIT END"; + case Item::RANK_BEGIN: + return "RANK BEGIN"; + case Item::_2: + return "2"; + case Item::_3: + return "3"; + case Item::_4: + return "4"; + case Item::_5: + return "5"; + case Item::_6: + return "6"; + case Item::_7: + return "7"; + case Item::_8: + return "8"; + case Item::_9: + return "9"; + case Item::_10: + return "10"; + case Item::Jack: + return "Jack"; + case Item::Queen: + return "Queen"; + case Item::King: + return "King"; + case Item::Ace: + return "Ace"; + case Item::RANK_END: + return "RANK END"; + case Item::C_BEGIN: + return "C BEGIN"; + case Item::C_2: + return "C 2"; + case Item::C_3: + return "C 3"; + case Item::C_4: + return "C 4"; + case Item::C_5: + return "C 5"; + case Item::C_6: + return "C 6"; + case Item::C_7: + return "C 7"; + case Item::C_8: + return "C 8"; + case Item::C_9: + return "C 9"; + case Item::C_A: + return "C A"; + case Item::C_J: + return "C J"; + case Item::C_K: + return "C K"; + case Item::C_Q: + return "C Q"; + case Item::C_T: + return "C T"; + case Item::D_2: + return "D 2"; + case Item::D_3: + return "D 3"; + case Item::D_4: + return "D 4"; + case Item::D_5: + return "D 5"; + case Item::D_6: + return "D 6"; + case Item::D_7: + return "D 7"; + case Item::D_8: + return "D 8"; + case Item::D_9: + return "D 9"; + case Item::D_A: + return "D A"; + case Item::D_J: + return "D J"; + case Item::D_K: + return "D K"; + case Item::D_Q: + return "D Q"; + case Item::D_T: + return "D T"; + case Item::H_2: + return "H 2"; + case Item::H_3: + return "H 3"; + case Item::H_4: + return "H 4"; + case Item::H_5: + return "H 5"; + case Item::H_6: + return "H 6"; + case Item::H_7: + return "H 7"; + case Item::H_8: + return "H 8"; + case Item::H_9: + return "H 9"; + case Item::H_A: + return "H A"; + case Item::H_J: + return "H J"; + case Item::H_K: + return "H K"; + case Item::H_Q: + return "H Q"; + case Item::H_T: + return "H T"; + case Item::S_2: + return "S 2"; + case Item::S_3: + return "S 3"; + case Item::S_4: + return "S 4"; + case Item::S_5: + return "S 5"; + case Item::S_6: + return "S 6"; + case Item::S_7: + return "S 7"; + case Item::S_8: + return "S 8"; + case Item::S_9: + return "S 9"; + case Item::S_A: + return "S A"; + case Item::S_J: + return "S J"; + case Item::S_K: + return "S K"; + case Item::S_Q: + return "S Q"; + case Item::S_T: + return "S T"; + case Item::C_END: + return "C END"; + case Item::D_BEGIN: + return "D BEGIN"; + case Item::Red_Deck: + return "Red Deck"; + case Item::Blue_Deck: + return "Blue Deck"; + case Item::Yellow_Deck: + return "Yellow Deck"; + case Item::Green_Deck: + return "Green Deck"; + case Item::Black_Deck: + return "Black Deck"; + case Item::Magic_Deck: + return "Magic Deck"; + case Item::Nebula_Deck: + return "Nebula Deck"; + case Item::Ghost_Deck: + return "Ghost Deck"; + case Item::Abandoned_Deck: + return "Abandoned Deck"; + case Item::Checkered_Deck: + return "Checkered Deck"; + case Item::Zodiac_Deck: + return "Zodiac Deck"; + case Item::Painted_Deck: + return "Painted Deck"; + case Item::Anaglyph_Deck: + return "Anaglyph Deck"; + case Item::Plasma_Deck: + return "Plasma Deck"; + case Item::Erratic_Deck: + return "Erratic Deck"; + case Item::Challenge_Deck: + return "Challenge Deck"; + case Item::D_END: + return "D END"; + case Item::CHAL_BEGIN: + return "CHAL BEGIN"; + case Item::The_Omelette: + return "The Omelette"; + case Item::_15_Minute_City: + return "15 Minute City"; + case Item::Rich_get_Richer: + return "Rich get Richer"; + case Item::On_a_Knifes_Edge: + return "On a Knife's Edge"; + case Item::X_ray_Vision: + return "X-ray Vision"; + case Item::Mad_World: + return "Mad World"; + case Item::Luxury_Tax: + return "Luxury Tax"; + case Item::Non_Perishable: + return "Non-Perishable"; + case Item::Medusa: + return "Medusa"; + case Item::Double_or_Nothing: + return "Double or Nothing"; + case Item::Typecast: + return "Typecast"; + case Item::Inflation: + return "Inflation"; + case Item::Bram_Poker: + return "Bram Poker"; + case Item::Fragile: + return "Fragile"; + case Item::Monolith: + return "Monolith"; + case Item::Blast_Off: + return "Blast Off"; + case Item::Five_Card_Draw: + return "Five-Card Draw"; + case Item::Golden_Needle: + return "Golden Needle"; + case Item::Cruelty: + return "Cruelty"; + case Item::Jokerless: + return "Jokerless"; + case Item::CHAL_END: + return "CHAL END"; + case Item::STAKE_BEGIN: + return "STAKE BEGIN"; + case Item::White_Stake: + return "White Stake"; + case Item::Red_Stake: + return "Red Stake"; + case Item::Green_Stake: + return "Green Stake"; + case Item::Black_Stake: + return "Black Stake"; + case Item::Blue_Stake: + return "Blue Stake"; + case Item::Purple_Stake: + return "Purple Stake"; + case Item::Orange_Stake: + return "Orange Stake"; + case Item::Gold_Stake: + return "Gold Stake"; + case Item::STAKE_END: + return "STAKE END"; + case Item::RARITY_BEGIN: + return "RARITY BEGIN"; + case Item::Common: + return "Common"; + case Item::Uncommon: + return "Uncommon"; + case Item::Rare: + return "Rare"; + case Item::Legendary: + return "Legendary"; + case Item::RARITY_END: + return "RARITY END"; + case Item::TYPE_BEGIN: + return "TYPE BEGIN"; + case Item::T_Joker: + return "T Joker"; + case Item::T_Tarot: + return "T Tarot"; + case Item::T_Planet: + return "T Planet"; + case Item::T_Spectral: + return "T Spectral"; + case Item::T_Playing_Card: + return "T Playing Card"; + case Item::TYPE_END: + return "TYPE END"; + default: + std::cout << "ERROR; stringToItem found no items... contact dev" + << std::endl; + EXIT_FAILURE; + } +} +inline Item stringToItem(std::string i) { + if (i == "RETRY") { + return Item::RETRY; + }; + if (i == "J BEGIN") { + return Item::J_BEGIN; + }; + if (i == "J C BEGIN") { + return Item::J_C_BEGIN; + }; + if (i == "Joker") { + return Item::Joker; + }; + if (i == "Greedy Joker") { + return Item::Greedy_Joker; + }; + if (i == "Lusty Joker") { + return Item::Lusty_Joker; + }; + if (i == "Wrathful Joker") { + return Item::Wrathful_Joker; + }; + if (i == "Gluttonous Joker") { + return Item::Gluttonous_Joker; + }; + if (i == "Jolly Joker") { + return Item::Jolly_Joker; + }; + if (i == "Zany Joker") { + return Item::Zany_Joker; + }; + if (i == "Mad Joker") { + return Item::Mad_Joker; + }; + if (i == "Crazy Joker") { + return Item::Crazy_Joker; + }; + if (i == "Droll Joker") { + return Item::Droll_Joker; + }; + if (i == "Sly Joker") { + return Item::Sly_Joker; + }; + if (i == "Wily Joker") { + return Item::Wily_Joker; + }; + if (i == "Clever Joker") { + return Item::Clever_Joker; + }; + if (i == "Devious Joker") { + return Item::Devious_Joker; + }; + if (i == "Crafty Joker") { + return Item::Crafty_Joker; + }; + if (i == "Half Joker") { + return Item::Half_Joker; + }; + if (i == "Credit Card") { + return Item::Credit_Card; + }; + if (i == "Banner") { + return Item::Banner; + }; + if (i == "Mystic Summit") { + return Item::Mystic_Summit; + }; + if (i == "8 Ball") { + return Item::_8_Ball; + }; + if (i == "Misprint") { + return Item::Misprint; + }; + if (i == "Raised Fist") { + return Item::Raised_Fist; + }; + if (i == "Chaos the Clown") { + return Item::Chaos_the_Clown; + }; + if (i == "Scary Face") { + return Item::Scary_Face; + }; + if (i == "Abstract Joker") { + return Item::Abstract_Joker; + }; + if (i == "Delayed Gratification") { + return Item::Delayed_Gratification; + }; + if (i == "Gros Michel") { + return Item::Gros_Michel; + }; + if (i == "Even Steven") { + return Item::Even_Steven; + }; + if (i == "Odd Todd") { + return Item::Odd_Todd; + }; + if (i == "Scholar") { + return Item::Scholar; + }; + if (i == "Business Card") { + return Item::Business_Card; + }; + if (i == "Supernova") { + return Item::Supernova; + }; + if (i == "Ride the Bus") { + return Item::Ride_the_Bus; + }; + if (i == "Egg") { + return Item::Egg; + }; + if (i == "Runner") { + return Item::Runner; + }; + if (i == "Ice Cream") { + return Item::Ice_Cream; + }; + if (i == "Splash") { + return Item::Splash; + }; + if (i == "Blue Joker") { + return Item::Blue_Joker; + }; + if (i == "Faceless Joker") { + return Item::Faceless_Joker; + }; + if (i == "Green Joker") { + return Item::Green_Joker; + }; + if (i == "Superposition") { + return Item::Superposition; + }; + if (i == "To Do List") { + return Item::To_Do_List; + }; + if (i == "Cavendish") { + return Item::Cavendish; + }; + if (i == "Red Card") { + return Item::Red_Card; + }; + if (i == "Square Joker") { + return Item::Square_Joker; + }; + if (i == "Riff-raff") { + return Item::Riff_raff; + }; + if (i == "Photograph") { + return Item::Photograph; + }; + if (i == "Reserved Parking") { + return Item::Reserved_Parking; + }; + if (i == "Mail-In Rebate") { + return Item::Mail_In_Rebate; + }; + if (i == "Hallucination") { + return Item::Hallucination; + }; + if (i == "Fortune Teller") { + return Item::Fortune_Teller; + }; + if (i == "Juggler") { + return Item::Juggler; + }; + if (i == "Drunkard") { + return Item::Drunkard; + }; + if (i == "Golden Joker") { + return Item::Golden_Joker; + }; + if (i == "Popcorn") { + return Item::Popcorn; + }; + if (i == "Walkie Talkie") { + return Item::Walkie_Talkie; + }; + if (i == "Smiley Face") { + return Item::Smiley_Face; + }; + if (i == "Golden Ticket") { + return Item::Golden_Ticket; + }; + if (i == "Swashbuckler") { + return Item::Swashbuckler; + }; + if (i == "Hanging Chad") { + return Item::Hanging_Chad; + }; + if (i == "Shoot the Moon") { + return Item::Shoot_the_Moon; + }; + if (i == "J C END") { + return Item::J_C_END; + }; + if (i == "J U BEGIN") { + return Item::J_U_BEGIN; + }; + if (i == "Joker Stencil") { + return Item::Joker_Stencil; + }; + if (i == "Four Fingers") { + return Item::Four_Fingers; + }; + if (i == "Mime") { + return Item::Mime; + }; + if (i == "Ceremonial Dagger") { + return Item::Ceremonial_Dagger; + }; + if (i == "Marble Joker") { + return Item::Marble_Joker; + }; + if (i == "Loyalty Card") { + return Item::Loyalty_Card; + }; + if (i == "Dusk") { + return Item::Dusk; + }; + if (i == "Fibonacci") { + return Item::Fibonacci; + }; + if (i == "Steel Joker") { + return Item::Steel_Joker; + }; + if (i == "Hack") { + return Item::Hack; + }; + if (i == "Pareidolia") { + return Item::Pareidolia; + }; + if (i == "Space Joker") { + return Item::Space_Joker; + }; + if (i == "Burglar") { + return Item::Burglar; + }; + if (i == "Blackboard") { + return Item::Blackboard; + }; + if (i == "Sixth Sense") { + return Item::Sixth_Sense; + }; + if (i == "Constellation") { + return Item::Constellation; + }; + if (i == "Hiker") { + return Item::Hiker; + }; + if (i == "Card Sharp") { + return Item::Card_Sharp; + }; + if (i == "Madness") { + return Item::Madness; + }; + if (i == "SΘance") { + return Item::Seance; + }; + if (i == "Shortcut") { + return Item::Shortcut; + }; + if (i == "Hologram") { + return Item::Hologram; + }; + if (i == "Cloud 9") { + return Item::Cloud_9; + }; + if (i == "Rocket") { + return Item::Rocket; + }; + if (i == "Midas Mask") { + return Item::Midas_Mask; + }; + if (i == "Luchador") { + return Item::Luchador; + }; + if (i == "Gift Card") { + return Item::Gift_Card; + }; + if (i == "Turtle Bean") { + return Item::Turtle_Bean; + }; + if (i == "Erosion") { + return Item::Erosion; + }; + if (i == "To the Moon") { + return Item::To_the_Moon; + }; + if (i == "Stone Joker") { + return Item::Stone_Joker; + }; + if (i == "Lucky Cat") { + return Item::Lucky_Cat; + }; + if (i == "Bull") { + return Item::Bull; + }; + if (i == "Diet Cola") { + return Item::Diet_Cola; + }; + if (i == "Trading Card") { + return Item::Trading_Card; + }; + if (i == "Flash Card") { + return Item::Flash_Card; + }; + if (i == "Spare Trousers") { + return Item::Spare_Trousers; + }; + if (i == "Ramen") { + return Item::Ramen; + }; + if (i == "Seltzer") { + return Item::Seltzer; + }; + if (i == "Castle") { + return Item::Castle; + }; + if (i == "Mr. Bones") { + return Item::Mr_Bones; + }; + if (i == "Acrobat") { + return Item::Acrobat; + }; + if (i == "Sock and Buskin") { + return Item::Sock_and_Buskin; + }; + if (i == "Troubadour") { + return Item::Troubadour; + }; + if (i == "Certificate") { + return Item::Certificate; + }; + if (i == "Smeared Joker") { + return Item::Smeared_Joker; + }; + if (i == "Throwback") { + return Item::Throwback; + }; + if (i == "Rough Gem") { + return Item::Rough_Gem; + }; + if (i == "Bloodstone") { + return Item::Bloodstone; + }; + if (i == "Arrowhead") { + return Item::Arrowhead; + }; + if (i == "Onyx Agate") { + return Item::Onyx_Agate; + }; + if (i == "Glass Joker") { + return Item::Glass_Joker; + }; + if (i == "Showman") { + return Item::Showman; + }; + if (i == "Flower Pot") { + return Item::Flower_Pot; + }; + if (i == "Merry Andy") { + return Item::Merry_Andy; + }; + if (i == "Oops! All 6s") { + return Item::Oops_All_6s; + }; + if (i == "The Idol") { + return Item::The_Idol; + }; + if (i == "Seeing Double") { + return Item::Seeing_Double; + }; + if (i == "Matador") { + return Item::Matador; + }; + if (i == "Stuntman") { + return Item::Stuntman; + }; + if (i == "Satellite") { + return Item::Satellite; + }; + if (i == "Cartomancer") { + return Item::Cartomancer; + }; + if (i == "Astronomer") { + return Item::Astronomer; + }; + if (i == "Bootstraps") { + return Item::Bootstraps; + }; + if (i == "J U END") { + return Item::J_U_END; + }; + if (i == "J R BEGIN") { + return Item::J_R_BEGIN; + }; + if (i == "DNA") { + return Item::DNA; + }; + if (i == "Vampire") { + return Item::Vampire; + }; + if (i == "Vagabond") { + return Item::Vagabond; + }; + if (i == "Baron") { + return Item::Baron; + }; + if (i == "Obelisk") { + return Item::Obelisk; + }; + if (i == "Baseball Card") { + return Item::Baseball_Card; + }; + if (i == "Ancient Joker") { + return Item::Ancient_Joker; + }; + if (i == "Campfire") { + return Item::Campfire; + }; + if (i == "Blueprint") { + return Item::Blueprint; + }; + if (i == "Wee Joker") { + return Item::Wee_Joker; + }; + if (i == "Hit the Road") { + return Item::Hit_the_Road; + }; + if (i == "The Duo") { + return Item::The_Duo; + }; + if (i == "The Trio") { + return Item::The_Trio; + }; + if (i == "The Family") { + return Item::The_Family; + }; + if (i == "The Order") { + return Item::The_Order; + }; + if (i == "The Tribe") { + return Item::The_Tribe; + }; + if (i == "Invisible Joker") { + return Item::Invisible_Joker; + }; + if (i == "Brainstorm") { + return Item::Brainstorm; + }; + if (i == "Driver's License") { + return Item::Drivers_License; + }; + if (i == "Burnt Joker") { + return Item::Burnt_Joker; + }; + if (i == "J R END") { + return Item::J_R_END; + }; + if (i == "J L BEGIN") { + return Item::J_L_BEGIN; + }; + if (i == "Canio") { + return Item::Canio; + }; + if (i == "Triboulet") { + return Item::Triboulet; + }; + if (i == "Yorick") { + return Item::Yorick; + }; + if (i == "Chicot") { + return Item::Chicot; + }; + if (i == "Perkeo") { + return Item::Perkeo; + }; + if (i == "J L END") { + return Item::J_L_END; + }; + if (i == "J END") { + return Item::J_END; + }; + if (i == "V BEGIN") { + return Item::V_BEGIN; + }; + if (i == "Overstock") { + return Item::Overstock; + }; + if (i == "Overstock Plus") { + return Item::Overstock_Plus; + }; + if (i == "Clearance Sale") { + return Item::Clearance_Sale; + }; + if (i == "Liquidation") { + return Item::Liquidation; + }; + if (i == "Hone") { + return Item::Hone; + }; + if (i == "Glow Up") { + return Item::Glow_Up; + }; + if (i == "Reroll Surplus") { + return Item::Reroll_Surplus; + }; + if (i == "Reroll Glut") { + return Item::Reroll_Glut; + }; + if (i == "Crystal Ball") { + return Item::Crystal_Ball; + }; + if (i == "Omen Globe") { + return Item::Omen_Globe; + }; + if (i == "Telescope") { + return Item::Telescope; + }; + if (i == "Observatory") { + return Item::Observatory; + }; + if (i == "Grabber") { + return Item::Grabber; + }; + if (i == "Nacho Tong") { + return Item::Nacho_Tong; + }; + if (i == "Wasteful") { + return Item::Wasteful; + }; + if (i == "Recyclomancy") { + return Item::Recyclomancy; + }; + if (i == "Tarot Merchant") { + return Item::Tarot_Merchant; + }; + if (i == "Tarot Tycoon") { + return Item::Tarot_Tycoon; + }; + if (i == "Planet Merchant") { + return Item::Planet_Merchant; + }; + if (i == "Planet Tycoon") { + return Item::Planet_Tycoon; + }; + if (i == "Seed Money") { + return Item::Seed_Money; + }; + if (i == "Money Tree") { + return Item::Money_Tree; + }; + if (i == "Blank") { + return Item::Blank; + }; + if (i == "Antimatter") { + return Item::Antimatter; + }; + if (i == "Magic Trick") { + return Item::Magic_Trick; + }; + if (i == "Illusion") { + return Item::Illusion; + }; + if (i == "Hieroglyph") { + return Item::Hieroglyph; + }; + if (i == "Petroglyph") { + return Item::Petroglyph; + }; + if (i == "Director's Cut") { + return Item::Directors_Cut; + }; + if (i == "Retcon") { + return Item::Retcon; + }; + if (i == "Paint Brush") { + return Item::Paint_Brush; + }; + if (i == "Palette") { + return Item::Palette; + }; + if (i == "V END") { + return Item::V_END; + }; + if (i == "T BEGIN") { + return Item::T_BEGIN; + }; + if (i == "The Fool") { + return Item::The_Fool; + }; + if (i == "The Magician") { + return Item::The_Magician; + }; + if (i == "The High Priestess") { + return Item::The_High_Priestess; + }; + if (i == "The Empress") { + return Item::The_Empress; + }; + if (i == "The Emperor") { + return Item::The_Emperor; + }; + if (i == "The Hierophant") { + return Item::The_Hierophant; + }; + if (i == "The Lovers") { + return Item::The_Lovers; + }; + if (i == "The Chariot") { + return Item::The_Chariot; + }; + if (i == "Justice") { + return Item::Justice; + }; + if (i == "The Hermit") { + return Item::The_Hermit; + }; + if (i == "The Wheel of Fortune") { + return Item::The_Wheel_of_Fortune; + }; + if (i == "Strength") { + return Item::Strength; + }; + if (i == "The Hanged Man") { + return Item::The_Hanged_Man; + }; + if (i == "Death") { + return Item::Death; + }; + if (i == "Temperance") { + return Item::Temperance; + }; + if (i == "The Devil") { + return Item::The_Devil; + }; + if (i == "The Tower") { + return Item::The_Tower; + }; + if (i == "The Star") { + return Item::The_Star; + }; + if (i == "The Moon") { + return Item::The_Moon; + }; + if (i == "The Sun") { + return Item::The_Sun; + }; + if (i == "Judgement") { + return Item::Judgement; + }; + if (i == "The World") { + return Item::The_World; + }; + if (i == "T END") { + return Item::T_END; + }; + if (i == "P BEGIN") { + return Item::P_BEGIN; + }; + if (i == "Mercury") { + return Item::Mercury; + }; + if (i == "Venus") { + return Item::Venus; + }; + if (i == "Earth") { + return Item::Earth; + }; + if (i == "Mars") { + return Item::Mars; + }; + if (i == "Jupiter") { + return Item::Jupiter; + }; + if (i == "Saturn") { + return Item::Saturn; + }; + if (i == "Uranus") { + return Item::Uranus; + }; + if (i == "Neptune") { + return Item::Neptune; + }; + if (i == "Pluto") { + return Item::Pluto; + }; + if (i == "Planet X") { + return Item::Planet_X; + }; + if (i == "Ceres") { + return Item::Ceres; + }; + if (i == "Eris") { + return Item::Eris; + }; + if (i == "P END") { + return Item::P_END; + }; + if (i == "H BEGIN") { + return Item::H_BEGIN; + }; + if (i == "Pair") { + return Item::Pair; + }; + if (i == "Three of a Kind") { + return Item::Three_of_a_Kind; + }; + if (i == "Full House") { + return Item::Full_House; + }; + if (i == "Four of a Kind") { + return Item::Four_of_a_Kind; + }; + if (i == "Flush") { + return Item::Flush; + }; + if (i == "Straight") { + return Item::Straight; + }; + if (i == "Two Pair") { + return Item::Two_Pair; + }; + if (i == "Straight Flush") { + return Item::Straight_Flush; + }; + if (i == "High Card") { + return Item::High_Card; + }; + if (i == "Five of a Kind") { + return Item::Five_of_a_Kind; + }; + if (i == "Flush House") { + return Item::Flush_House; + }; + if (i == "Flush Five") { + return Item::Flush_Five; + }; + if (i == "H END") { + return Item::H_END; + }; + if (i == "S BEGIN") { + return Item::S_BEGIN; + }; + if (i == "Familiar") { + return Item::Familiar; + }; + if (i == "Grim") { + return Item::Grim; + }; + if (i == "Incantation") { + return Item::Incantation; + }; + if (i == "Talisman") { + return Item::Talisman; + }; + if (i == "Aura") { + return Item::Aura; + }; + if (i == "Wraith") { + return Item::Wraith; + }; + if (i == "Sigil") { + return Item::Sigil; + }; + if (i == "Ouija") { + return Item::Ouija; + }; + if (i == "Ectoplasm") { + return Item::Ectoplasm; + }; + if (i == "Immolate") { + return Item::Immolate; + }; + if (i == "Ankh") { + return Item::Ankh; + }; + if (i == "Deja Vu") { + return Item::Deja_Vu; + }; + if (i == "Hex") { + return Item::Hex; + }; + if (i == "Trance") { + return Item::Trance; + }; + if (i == "Medium") { + return Item::Medium; + }; + if (i == "Cryptid") { + return Item::Cryptid; + }; + if (i == "The Soul") { + return Item::The_Soul; + }; + if (i == "Black Hole") { + return Item::Black_Hole; + }; + if (i == "S END") { + return Item::S_END; + }; + if (i == "ENHANCEMENT BEGIN") { + return Item::ENHANCEMENT_BEGIN; + }; + if (i == "No Enhancement") { + return Item::No_Enhancement; + }; + if (i == "Bonus Card") { + return Item::Bonus_Card; + }; + if (i == "Mult Card") { + return Item::Mult_Card; + }; + if (i == "Wild Card") { + return Item::Wild_Card; + }; + if (i == "Glass Card") { + return Item::Glass_Card; + }; + if (i == "Steel Card") { + return Item::Steel_Card; + }; + if (i == "Stone Card") { + return Item::Stone_Card; + }; + if (i == "Gold Card") { + return Item::Gold_Card; + }; + if (i == "Lucky Card") { + return Item::Lucky_Card; + }; + if (i == "ENHANCEMENT END") { + return Item::ENHANCEMENT_END; + }; + if (i == "SEAL BEGIN") { + return Item::SEAL_BEGIN; + }; + if (i == "No Seal") { + return Item::No_Seal; + }; + if (i == "Gold Seal") { + return Item::Gold_Seal; + }; + if (i == "Red Seal") { + return Item::Red_Seal; + }; + if (i == "Blue Seal") { + return Item::Blue_Seal; + }; + if (i == "Purple Seal") { + return Item::Purple_Seal; + }; + if (i == "SEAL END") { + return Item::SEAL_END; + }; + if (i == "E BEGIN") { + return Item::E_BEGIN; + }; + if (i == "No Edition") { + return Item::No_Edition; + }; + if (i == "Foil") { + return Item::Foil; + }; + if (i == "Holographic") { + return Item::Holographic; + }; + if (i == "Polychrome") { + return Item::Polychrome; + }; + if (i == "Negative") { + return Item::Negative; + }; + if (i == "E END") { + return Item::E_END; + }; + if (i == "PACK BEGIN") { + return Item::PACK_BEGIN; + }; + if (i == "Arcana Pack") { + return Item::Arcana_Pack; + }; + if (i == "Jumbo Arcana Pack") { + return Item::Jumbo_Arcana_Pack; + }; + if (i == "Mega Arcana Pack") { + return Item::Mega_Arcana_Pack; + }; + if (i == "Celestial Pack") { + return Item::Celestial_Pack; + }; + if (i == "Jumbo Celestial Pack") { + return Item::Jumbo_Celestial_Pack; + }; + if (i == "Mega Celestial Pack") { + return Item::Mega_Celestial_Pack; + }; + if (i == "Standard Pack") { + return Item::Standard_Pack; + }; + if (i == "Jumbo Standard Pack") { + return Item::Jumbo_Standard_Pack; + }; + if (i == "Mega Standard Pack") { + return Item::Mega_Standard_Pack; + }; + if (i == "Buffoon Pack") { + return Item::Buffoon_Pack; + }; + if (i == "Jumbo Buffoon Pack") { + return Item::Jumbo_Buffoon_Pack; + }; + if (i == "Mega Buffoon Pack") { + return Item::Mega_Buffoon_Pack; + }; + if (i == "Spectral Pack") { + return Item::Spectral_Pack; + }; + if (i == "Jumbo Spectral Pack") { + return Item::Jumbo_Spectral_Pack; + }; + if (i == "Mega Spectral Pack") { + return Item::Mega_Spectral_Pack; + }; + if (i == "PACK END") { + return Item::PACK_END; + }; + if (i == "TAG BEGIN") { + return Item::TAG_BEGIN; + }; + if (i == "Uncommon Tag") { + return Item::Uncommon_Tag; + }; + if (i == "Rare Tag") { + return Item::Rare_Tag; + }; + if (i == "Negative Tag") { + return Item::Negative_Tag; + }; + if (i == "Foil Tag") { + return Item::Foil_Tag; + }; + if (i == "Holographic Tag") { + return Item::Holographic_Tag; + }; + if (i == "Polychrome Tag") { + return Item::Polychrome_Tag; + }; + if (i == "Investment Tag") { + return Item::Investment_Tag; + }; + if (i == "Voucher Tag") { + return Item::Voucher_Tag; + }; + if (i == "Boss Tag") { + return Item::Boss_Tag; + }; + if (i == "Standard Tag") { + return Item::Standard_Tag; + }; + if (i == "Charm Tag") { + return Item::Charm_Tag; + }; + if (i == "Meteor Tag") { + return Item::Meteor_Tag; + }; + if (i == "Buffoon Tag") { + return Item::Buffoon_Tag; + }; + if (i == "Handy Tag") { + return Item::Handy_Tag; + }; + if (i == "Garbage Tag") { + return Item::Garbage_Tag; + }; + if (i == "Ethereal Tag") { + return Item::Ethereal_Tag; + }; + if (i == "Coupon Tag") { + return Item::Coupon_Tag; + }; + if (i == "Double Tag") { + return Item::Double_Tag; + }; + if (i == "Juggle Tag") { + return Item::Juggle_Tag; + }; + if (i == "D6 Tag") { + return Item::D6_Tag; + }; + if (i == "Top-up Tag") { + return Item::Top_up_Tag; + }; + if (i == "Speed Tag") { + return Item::Speed_Tag; + }; + if (i == "Orbital Tag") { + return Item::Orbital_Tag; + }; + if (i == "Economy Tag") { + return Item::Economy_Tag; + }; + if (i == "TAG END") { + return Item::TAG_END; + }; + if (i == "B BEGIN") { + return Item::B_BEGIN; + }; + if (i == "Small Blind") { + return Item::Small_Blind; + }; + if (i == "Big Blind") { + return Item::Big_Blind; + }; + if (i == "The Hook") { + return Item::The_Hook; + }; + if (i == "The Ox") { + return Item::The_Ox; + }; + if (i == "The House") { + return Item::The_House; + }; + if (i == "The Wall") { + return Item::The_Wall; + }; + if (i == "The Wheel") { + return Item::The_Wheel; + }; + if (i == "The Arm") { + return Item::The_Arm; + }; + if (i == "The Club") { + return Item::The_Club; + }; + if (i == "The Fish") { + return Item::The_Fish; + }; + if (i == "The Psychic") { + return Item::The_Psychic; + }; + if (i == "The Goad") { + return Item::The_Goad; + }; + if (i == "The Water") { + return Item::The_Water; + }; + if (i == "The Window") { + return Item::The_Window; + }; + if (i == "The Manacle") { + return Item::The_Manacle; + }; + if (i == "The Eye") { + return Item::The_Eye; + }; + if (i == "The Mouth") { + return Item::The_Mouth; + }; + if (i == "The Plant") { + return Item::The_Plant; + }; + if (i == "The Serpent") { + return Item::The_Serpent; + }; + if (i == "The Pillar") { + return Item::The_Pillar; + }; + if (i == "The Needle") { + return Item::The_Needle; + }; + if (i == "The Head") { + return Item::The_Head; + }; + if (i == "The Tooth") { + return Item::The_Tooth; + }; + if (i == "The Flint") { + return Item::The_Flint; + }; + if (i == "The Mark") { + return Item::The_Mark; + }; + if (i == "B F BEGIN") { + return Item::B_F_BEGIN; + }; + if (i == "Amber Acorn") { + return Item::Amber_Acorn; + }; + if (i == "Verdant Leaf") { + return Item::Verdant_Leaf; + }; + if (i == "Violet Vessel") { + return Item::Violet_Vessel; + }; + if (i == "Crimson Heart") { + return Item::Crimson_Heart; + }; + if (i == "Cerulean Bell") { + return Item::Cerulean_Bell; + }; + if (i == "B F END") { + return Item::B_F_END; + }; + if (i == "B END") { + return Item::B_END; + }; + if (i == "SUIT BEGIN") { + return Item::SUIT_BEGIN; + }; + if (i == "Hearts") { + return Item::Hearts; + }; + if (i == "Clubs") { + return Item::Clubs; + }; + if (i == "Diamonds") { + return Item::Diamonds; + }; + if (i == "Spades") { + return Item::Spades; + }; + if (i == "SUIT END") { + return Item::SUIT_END; + }; + if (i == "RANK BEGIN") { + return Item::RANK_BEGIN; + }; + if (i == "2") { + return Item::_2; + }; + if (i == "3") { + return Item::_3; + }; + if (i == "4") { + return Item::_4; + }; + if (i == "5") { + return Item::_5; + }; + if (i == "6") { + return Item::_6; + }; + if (i == "7") { + return Item::_7; + }; + if (i == "8") { + return Item::_8; + }; + if (i == "9") { + return Item::_9; + }; + if (i == "10") { + return Item::_10; + }; + if (i == "Jack") { + return Item::Jack; + }; + if (i == "Queen") { + return Item::Queen; + }; + if (i == "King") { + return Item::King; + }; + if (i == "Ace") { + return Item::Ace; + }; + if (i == "RANK END") { + return Item::RANK_END; + }; + if (i == "C BEGIN") { + return Item::C_BEGIN; + }; + if (i == "C 2") { + return Item::C_2; + }; + if (i == "C 3") { + return Item::C_3; + }; + if (i == "C 4") { + return Item::C_4; + }; + if (i == "C 5") { + return Item::C_5; + }; + if (i == "C 6") { + return Item::C_6; + }; + if (i == "C 7") { + return Item::C_7; + }; + if (i == "C 8") { + return Item::C_8; + }; + if (i == "C 9") { + return Item::C_9; + }; + if (i == "C A") { + return Item::C_A; + }; + if (i == "C J") { + return Item::C_J; + }; + if (i == "C K") { + return Item::C_K; + }; + if (i == "C Q") { + return Item::C_Q; + }; + if (i == "C T") { + return Item::C_T; + }; + if (i == "D 2") { + return Item::D_2; + }; + if (i == "D 3") { + return Item::D_3; + }; + if (i == "D 4") { + return Item::D_4; + }; + if (i == "D 5") { + return Item::D_5; + }; + if (i == "D 6") { + return Item::D_6; + }; + if (i == "D 7") { + return Item::D_7; + }; + if (i == "D 8") { + return Item::D_8; + }; + if (i == "D 9") { + return Item::D_9; + }; + if (i == "D A") { + return Item::D_A; + }; + if (i == "D J") { + return Item::D_J; + }; + if (i == "D K") { + return Item::D_K; + }; + if (i == "D Q") { + return Item::D_Q; + }; + if (i == "D T") { + return Item::D_T; + }; + if (i == "H 2") { + return Item::H_2; + }; + if (i == "H 3") { + return Item::H_3; + }; + if (i == "H 4") { + return Item::H_4; + }; + if (i == "H 5") { + return Item::H_5; + }; + if (i == "H 6") { + return Item::H_6; + }; + if (i == "H 7") { + return Item::H_7; + }; + if (i == "H 8") { + return Item::H_8; + }; + if (i == "H 9") { + return Item::H_9; + }; + if (i == "H A") { + return Item::H_A; + }; + if (i == "H J") { + return Item::H_J; + }; + if (i == "H K") { + return Item::H_K; + }; + if (i == "H Q") { + return Item::H_Q; + }; + if (i == "H T") { + return Item::H_T; + }; + if (i == "S 2") { + return Item::S_2; + }; + if (i == "S 3") { + return Item::S_3; + }; + if (i == "S 4") { + return Item::S_4; + }; + if (i == "S 5") { + return Item::S_5; + }; + if (i == "S 6") { + return Item::S_6; + }; + if (i == "S 7") { + return Item::S_7; + }; + if (i == "S 8") { + return Item::S_8; + }; + if (i == "S 9") { + return Item::S_9; + }; + if (i == "S A") { + return Item::S_A; + }; + if (i == "S J") { + return Item::S_J; + }; + if (i == "S K") { + return Item::S_K; + }; + if (i == "S Q") { + return Item::S_Q; + }; + if (i == "S T") { + return Item::S_T; + }; + if (i == "C END") { + return Item::C_END; + }; + if (i == "D BEGIN") { + return Item::D_BEGIN; + }; + if (i == "Red Deck") { + return Item::Red_Deck; + }; + if (i == "Blue Deck") { + return Item::Blue_Deck; + }; + if (i == "Yellow Deck") { + return Item::Yellow_Deck; + }; + if (i == "Green Deck") { + return Item::Green_Deck; + }; + if (i == "Black Deck") { + return Item::Black_Deck; + }; + if (i == "Magic Deck") { + return Item::Magic_Deck; + }; + if (i == "Nebula Deck") { + return Item::Nebula_Deck; + }; + if (i == "Ghost Deck") { + return Item::Ghost_Deck; + }; + if (i == "Abandoned Deck") { + return Item::Abandoned_Deck; + }; + if (i == "Checkered Deck") { + return Item::Checkered_Deck; + }; + if (i == "Zodiac Deck") { + return Item::Zodiac_Deck; + }; + if (i == "Painted Deck") { + return Item::Painted_Deck; + }; + if (i == "Anaglyph Deck") { + return Item::Anaglyph_Deck; + }; + if (i == "Plasma Deck") { + return Item::Plasma_Deck; + }; + if (i == "Erratic Deck") { + return Item::Erratic_Deck; + }; + if (i == "Challenge Deck") { + return Item::Challenge_Deck; + }; + if (i == "D END") { + return Item::D_END; + }; + if (i == "CHAL BEGIN") { + return Item::CHAL_BEGIN; + }; + if (i == "The Omelette") { + return Item::The_Omelette; + }; + if (i == "15 Minute City") { + return Item::_15_Minute_City; + }; + if (i == "Rich get Richer") { + return Item::Rich_get_Richer; + }; + if (i == "On a Knife's Edge") { + return Item::On_a_Knifes_Edge; + }; + if (i == "X-ray Vision") { + return Item::X_ray_Vision; + }; + if (i == "Mad World") { + return Item::Mad_World; + }; + if (i == "Luxury Tax") { + return Item::Luxury_Tax; + }; + if (i == "Non-Perishable") { + return Item::Non_Perishable; + }; + if (i == "Medusa") { + return Item::Medusa; + }; + if (i == "Double or Nothing") { + return Item::Double_or_Nothing; + }; + if (i == "Typecast") { + return Item::Typecast; + }; + if (i == "Inflation") { + return Item::Inflation; + }; + if (i == "Bram Poker") { + return Item::Bram_Poker; + }; + if (i == "Fragile") { + return Item::Fragile; + }; + if (i == "Monolith") { + return Item::Monolith; + }; + if (i == "Blast Off") { + return Item::Blast_Off; + }; + if (i == "Five-Card Draw") { + return Item::Five_Card_Draw; + }; + if (i == "Golden Needle") { + return Item::Golden_Needle; + }; + if (i == "Cruelty") { + return Item::Cruelty; + }; + if (i == "Jokerless") { + return Item::Jokerless; + }; + if (i == "CHAL END") { + return Item::CHAL_END; + }; + if (i == "STAKE BEGIN") { + return Item::STAKE_BEGIN; + }; + if (i == "White Stake") { + return Item::White_Stake; + }; + if (i == "Red Stake") { + return Item::Red_Stake; + }; + if (i == "Green Stake") { + return Item::Green_Stake; + }; + if (i == "Black Stake") { + return Item::Black_Stake; + }; + if (i == "Blue Stake") { + return Item::Blue_Stake; + }; + if (i == "Purple Stake") { + return Item::Purple_Stake; + }; + if (i == "Orange Stake") { + return Item::Orange_Stake; + }; + if (i == "Gold Stake") { + return Item::Gold_Stake; + }; + if (i == "STAKE END") { + return Item::STAKE_END; + }; + if (i == "RARITY BEGIN") { + return Item::RARITY_BEGIN; + }; + if (i == "Common") { + return Item::Common; + }; + if (i == "Uncommon") { + return Item::Uncommon; + }; + if (i == "Rare") { + return Item::Rare; + }; + if (i == "Legendary") { + return Item::Legendary; + }; + if (i == "RARITY END") { + return Item::RARITY_END; + }; + if (i == "TYPE BEGIN") { + return Item::TYPE_BEGIN; + }; + if (i == "T Joker") { + return Item::T_Joker; + }; + if (i == "T Tarot") { + return Item::T_Tarot; + }; + if (i == "T Planet") { + return Item::T_Planet; + }; + if (i == "T Spectral") { + return Item::T_Spectral; + }; + if (i == "T Playing Card") { + return Item::T_Playing_Card; + }; + if (i == "TYPE END") { + return Item::TYPE_END; + }; + return Item::RETRY; +} + +// Structs for storing information +struct ShopInstance { + double jokerRate; + double tarotRate; + double planetRate; + double playingCardRate; + double spectralRate; + ShopInstance() { + jokerRate = 20; + tarotRate = 4; + planetRate = 4; + playingCardRate = 0; + spectralRate = 0; + }; + ShopInstance(double j, double t, double p, double c, double s) { + jokerRate = j; + tarotRate = t; + planetRate = p; + playingCardRate = c; + spectralRate = s; + } + double getTotalRate() { + return jokerRate + tarotRate + planetRate + playingCardRate + spectralRate; + } +}; + +struct JokerStickers { + bool eternal; + bool perishable; + bool rental; + JokerStickers() { + eternal = false; + perishable = false; + rental = false; + }; + JokerStickers(bool e, bool p, bool r) { + eternal = e; + perishable = p; + rental = r; + } +}; + +struct JokerData { + Item joker; + Item rarity; + Item edition; + JokerStickers stickers; + JokerData() { + joker = Item::Joker; + rarity = Item::Common; + edition = Item::No_Edition; + stickers = JokerStickers(); + }; + JokerData(Item j, Item r, Item e, JokerStickers s) { + joker = j; + rarity = r; + edition = e; + stickers = s; + }; +}; + +struct ShopItem { + Item type; + Item item; + JokerData jokerData; + ShopItem() { + type = Item::T_Tarot; + item = Item::The_Fool; + }; + ShopItem(Item t, Item i) { + type = t; + item = i; + }; + ShopItem(Item t, Item i, JokerData j) { + type = t; + item = i; + jokerData = j; + }; +}; + +struct WeightedItem { + Item item; + double weight; + WeightedItem(Item i, double w) { + item = i; + weight = w; + }; +}; + +struct Pack { + Item type; + int size; + int choices; + Pack(Item t, int s, int c) { + type = t; + size = s; + choices = c; + } +}; + +struct Card { + Item base; + Item enhancement; + Item edition; + Item seal; + Card(Item b, Item n, Item e, Item s) { + base = b; + enhancement = n; + edition = e; + seal = s; + } +}; + +constexpr inline std::array ENHANCEMENTS = { + Item::Bonus_Card, Item::Mult_Card, Item::Wild_Card, Item::Glass_Card, + Item::Steel_Card, Item::Stone_Card, Item::Gold_Card, Item::Lucky_Card}; + +constexpr inline std::array CARDS = { + Item::C_2, Item::C_3, Item::C_4, Item::C_5, Item::C_6, Item::C_7, Item::C_8, + Item::C_9, Item::C_A, Item::C_J, Item::C_K, Item::C_Q, Item::C_T, Item::D_2, + Item::D_3, Item::D_4, Item::D_5, Item::D_6, Item::D_7, Item::D_8, Item::D_9, + Item::D_A, Item::D_J, Item::D_K, Item::D_Q, Item::D_T, Item::H_2, Item::H_3, + Item::H_4, Item::H_5, Item::H_6, Item::H_7, Item::H_8, Item::H_9, Item::H_A, + Item::H_J, Item::H_K, Item::H_Q, Item::H_T, Item::S_2, Item::S_3, Item::S_4, + Item::S_5, Item::S_6, Item::S_7, Item::S_8, Item::S_9, Item::S_A, Item::S_J, + Item::S_K, Item::S_Q, Item::S_T}; + +constexpr inline std::array SUITS = {Item::Spades, Item::Hearts, + Item::Clubs, Item::Diamonds}; + +constexpr inline std::array RANKS = { + Item::_2, Item::_3, Item::_4, Item::_5, Item::_6, + Item::_7, Item::_8, Item::_9, Item::_10, Item::Jack, + Item::Queen, Item::King, Item::Ace}; + +inline std::array PACKS = { + WeightedItem(Item::RETRY, 22.42), // total + WeightedItem(Item::Arcana_Pack, 4), + WeightedItem(Item::Jumbo_Arcana_Pack, 2), + WeightedItem(Item::Mega_Arcana_Pack, 0.5), + WeightedItem(Item::Celestial_Pack, 4), + WeightedItem(Item::Jumbo_Celestial_Pack, 2), + WeightedItem(Item::Mega_Celestial_Pack, 0.5), + WeightedItem(Item::Standard_Pack, 4), + WeightedItem(Item::Jumbo_Standard_Pack, 2), + WeightedItem(Item::Mega_Standard_Pack, 0.5), + WeightedItem(Item::Buffoon_Pack, 1.2), + WeightedItem(Item::Jumbo_Buffoon_Pack, 0.6), + WeightedItem(Item::Mega_Buffoon_Pack, 0.15), + WeightedItem(Item::Spectral_Pack, 0.6), + WeightedItem(Item::Jumbo_Spectral_Pack, 0.3), + WeightedItem(Item::Mega_Spectral_Pack, 0.07)}; + +constexpr inline std::array TAROTS = {Item::The_Fool, + Item::The_Magician, + Item::The_High_Priestess, + Item::The_Empress, + Item::The_Emperor, + Item::The_Hierophant, + Item::The_Lovers, + Item::The_Chariot, + Item::Justice, + Item::The_Hermit, + Item::The_Wheel_of_Fortune, + Item::Strength, + Item::The_Hanged_Man, + Item::Death, + Item::Temperance, + Item::The_Devil, + Item::The_Tower, + Item::The_Star, + Item::The_Moon, + Item::The_Sun, + Item::Judgement, + Item::The_World}; + +constexpr inline std::array PLANETS = { + Item::Mercury, Item::Venus, Item::Earth, Item::Mars, + Item::Jupiter, Item::Saturn, Item::Uranus, Item::Neptune, + Item::Pluto, Item::Planet_X, Item::Ceres, Item::Eris}; + +constexpr inline std::array COMMON_JOKERS_100 = { + Item::Joker, + Item::Greedy_Joker, + Item::Lusty_Joker, + Item::Wrathful_Joker, + Item::Gluttonous_Joker, + Item::Jolly_Joker, + Item::Zany_Joker, + Item::Mad_Joker, + Item::Crazy_Joker, + Item::Droll_Joker, + Item::Sly_Joker, + Item::Wily_Joker, + Item::Clever_Joker, + Item::Devious_Joker, + Item::Crafty_Joker, + Item::Half_Joker, + Item::Credit_Card, + Item::Banner, + Item::Mystic_Summit, + Item::_8_Ball, + Item::Misprint, + Item::Raised_Fist, + Item::Chaos_the_Clown, + Item::Scary_Face, + Item::Abstract_Joker, + Item::Delayed_Gratification, + Item::Gros_Michel, + Item::Even_Steven, + Item::Odd_Todd, + Item::Scholar, + Item::Business_Card, + Item::Supernova, + Item::Ride_the_Bus, + Item::Egg, + Item::Runner, + Item::Ice_Cream, + Item::Splash, + Item::Blue_Joker, + Item::Faceless_Joker, + Item::Green_Joker, + Item::Superposition, + Item::To_Do_List, + Item::Cavendish, + Item::Red_Card, + Item::Square_Joker, + Item::Riff_raff, + Item::Photograph, + Item::Mail_In_Rebate, + Item::Hallucination, + Item::Fortune_Teller, + Item::Juggler, + Item::Drunkard, + Item::Golden_Joker, + Item::Popcorn, + Item::Walkie_Talkie, + Item::Smiley_Face, + Item::Golden_Ticket, + Item::Swashbuckler, + Item::Hanging_Chad, + Item::Shoot_the_Moon}; + +constexpr inline std::array COMMON_JOKERS = { + Item::Joker, + Item::Greedy_Joker, + Item::Lusty_Joker, + Item::Wrathful_Joker, + Item::Gluttonous_Joker, + Item::Jolly_Joker, + Item::Zany_Joker, + Item::Mad_Joker, + Item::Crazy_Joker, + Item::Droll_Joker, + Item::Sly_Joker, + Item::Wily_Joker, + Item::Clever_Joker, + Item::Devious_Joker, + Item::Crafty_Joker, + Item::Half_Joker, + Item::Credit_Card, + Item::Banner, + Item::Mystic_Summit, + Item::_8_Ball, + Item::Misprint, + Item::Raised_Fist, + Item::Chaos_the_Clown, + Item::Scary_Face, + Item::Abstract_Joker, + Item::Delayed_Gratification, + Item::Gros_Michel, + Item::Even_Steven, + Item::Odd_Todd, + Item::Scholar, + Item::Business_Card, + Item::Supernova, + Item::Ride_the_Bus, + Item::Egg, + Item::Runner, + Item::Ice_Cream, + Item::Splash, + Item::Blue_Joker, + Item::Faceless_Joker, + Item::Green_Joker, + Item::Superposition, + Item::To_Do_List, + Item::Cavendish, + Item::Red_Card, + Item::Square_Joker, + Item::Riff_raff, + Item::Photograph, + Item::Reserved_Parking, + Item::Mail_In_Rebate, + Item::Hallucination, + Item::Fortune_Teller, + Item::Juggler, + Item::Drunkard, + Item::Golden_Joker, + Item::Popcorn, + Item::Walkie_Talkie, + Item::Smiley_Face, + Item::Golden_Ticket, + Item::Swashbuckler, + Item::Hanging_Chad, + Item::Shoot_the_Moon, +}; + +constexpr inline std::array UNCOMMON_JOKERS_100 = { + Item::Joker_Stencil, Item::Four_Fingers, + Item::Mime, Item::Ceremonial_Dagger, + Item::Marble_Joker, Item::Loyalty_Card, + Item::Dusk, Item::Fibonacci, + Item::Steel_Joker, Item::Hack, + Item::Pareidolia, Item::Space_Joker, + Item::Burglar, Item::Blackboard, + Item::Constellation, Item::Hiker, + Item::Card_Sharp, Item::Madness, + Item::Vampire, Item::Shortcut, + Item::Hologram, Item::Vagabond, + Item::Cloud_9, Item::Rocket, + Item::Midas_Mask, Item::Luchador, + Item::Gift_Card, Item::Turtle_Bean, + Item::Erosion, Item::Reserved_Parking, + Item::To_the_Moon, Item::Stone_Joker, + Item::Lucky_Cat, Item::Bull, + Item::Diet_Cola, Item::Trading_Card, + Item::Flash_Card, Item::Spare_Trousers, + Item::Ramen, Item::Seltzer, + Item::Castle, Item::Mr_Bones, + Item::Acrobat, Item::Sock_and_Buskin, + Item::Troubadour, Item::Certificate, + Item::Smeared_Joker, Item::Throwback, + Item::Rough_Gem, Item::Bloodstone, + Item::Arrowhead, Item::Onyx_Agate, + Item::Glass_Joker, Item::Showman, + Item::Flower_Pot, Item::Merry_Andy, + Item::Oops_All_6s, Item::The_Idol, + Item::Seeing_Double, Item::Matador, + Item::Stuntman, Item::Satellite, + Item::Cartomancer, Item::Astronomer, + Item::Burnt_Joker, Item::Bootstraps}; + +constexpr inline std::array UNCOMMON_JOKERS = { + Item::Joker_Stencil, Item::Four_Fingers, + Item::Mime, Item::Ceremonial_Dagger, + Item::Marble_Joker, Item::Loyalty_Card, + Item::Dusk, Item::Fibonacci, + Item::Steel_Joker, Item::Hack, + Item::Pareidolia, Item::Space_Joker, + Item::Burglar, Item::Blackboard, + Item::Sixth_Sense, Item::Constellation, + Item::Hiker, Item::Card_Sharp, + Item::Madness, Item::Seance, + Item::Vampire, Item::Shortcut, + Item::Hologram, Item::Cloud_9, + Item::Rocket, Item::Midas_Mask, + Item::Luchador, Item::Gift_Card, + Item::Turtle_Bean, Item::Erosion, + Item::To_the_Moon, Item::Stone_Joker, + Item::Lucky_Cat, Item::Bull, + Item::Diet_Cola, Item::Trading_Card, + Item::Flash_Card, Item::Spare_Trousers, + Item::Ramen, Item::Seltzer, + Item::Castle, Item::Mr_Bones, + Item::Acrobat, Item::Sock_and_Buskin, + Item::Troubadour, Item::Certificate, + Item::Smeared_Joker, Item::Throwback, + Item::Rough_Gem, Item::Bloodstone, + Item::Arrowhead, Item::Onyx_Agate, + Item::Glass_Joker, Item::Showman, + Item::Flower_Pot, Item::Merry_Andy, + Item::Oops_All_6s, Item::The_Idol, + Item::Seeing_Double, Item::Matador, + Item::Satellite, Item::Cartomancer, + Item::Astronomer, Item::Bootstraps, +}; + +constexpr inline std::array RARE_JOKERS_100 = {Item::DNA, + Item::Sixth_Sense, + Item::Seance, + Item::Baron, + Item::Obelisk, + Item::Baseball_Card, + Item::Ancient_Joker, + Item::Campfire, + Item::Blueprint, + Item::Wee_Joker, + Item::Hit_the_Road, + Item::The_Duo, + Item::The_Trio, + Item::The_Family, + Item::The_Order, + Item::The_Tribe, + Item::Invisible_Joker, + Item::Brainstorm, + Item::Drivers_License}; + +constexpr inline std::array RARE_JOKERS = { + Item::DNA, + Item::Vagabond, + Item::Baron, + Item::Obelisk, + Item::Baseball_Card, + Item::Ancient_Joker, + Item::Campfire, + Item::Blueprint, + Item::Wee_Joker, + Item::Hit_the_Road, + Item::The_Duo, + Item::The_Trio, + Item::The_Family, + Item::The_Order, + Item::The_Tribe, + Item::Stuntman, + Item::Invisible_Joker, + Item::Brainstorm, + Item::Drivers_License, + Item::Burnt_Joker, +}; + +constexpr inline std::array LEGENDARY_JOKERS = { + Item::Canio, Item::Triboulet, Item::Yorick, Item::Chicot, Item::Perkeo}; + +constexpr inline std::array VOUCHERS = { + Item::Overstock, Item::Overstock_Plus, Item::Clearance_Sale, + Item::Liquidation, Item::Hone, Item::Glow_Up, + Item::Reroll_Surplus, Item::Reroll_Glut, Item::Crystal_Ball, + Item::Omen_Globe, Item::Telescope, Item::Observatory, + Item::Grabber, Item::Nacho_Tong, Item::Wasteful, + Item::Recyclomancy, Item::Tarot_Merchant, Item::Tarot_Tycoon, + Item::Planet_Merchant, Item::Planet_Tycoon, Item::Seed_Money, + Item::Money_Tree, Item::Blank, Item::Antimatter, + Item::Magic_Trick, Item::Illusion, Item::Hieroglyph, + Item::Petroglyph, Item::Directors_Cut, Item::Retcon, + Item::Paint_Brush, Item::Palette}; + +constexpr inline std::array SPECTRALS = { + Item::Familiar, Item::Grim, Item::Incantation, Item::Talisman, + Item::Aura, Item::Wraith, Item::Sigil, Item::Ouija, + Item::Ectoplasm, Item::Immolate, Item::Ankh, Item::Deja_Vu, + Item::Hex, Item::Trance, Item::Medium, Item::Cryptid, + Item::RETRY, // Soul + Item::RETRY // Black_Hole +}; + +constexpr inline std::array TAGS = { + Item::Uncommon_Tag, Item::Rare_Tag, Item::Negative_Tag, + Item::Foil_Tag, Item::Holographic_Tag, Item::Polychrome_Tag, + Item::Investment_Tag, Item::Voucher_Tag, Item::Boss_Tag, + Item::Standard_Tag, Item::Charm_Tag, Item::Meteor_Tag, + Item::Buffoon_Tag, Item::Handy_Tag, Item::Garbage_Tag, + Item::Ethereal_Tag, Item::Coupon_Tag, Item::Double_Tag, + Item::Juggle_Tag, Item::D6_Tag, Item::Top_up_Tag, + Item::Speed_Tag, Item::Orbital_Tag, Item::Economy_Tag}; + +constexpr inline std::array BOSSES = { + Item::The_Arm, Item::The_Club, Item::The_Eye, + Item::Amber_Acorn, Item::Cerulean_Bell, Item::Crimson_Heart, + Item::Verdant_Leaf, Item::Violet_Vessel, Item::The_Fish, + Item::The_Flint, Item::The_Goad, Item::The_Head, + Item::The_Hook, Item::The_House, Item::The_Manacle, + Item::The_Mark, Item::The_Mouth, Item::The_Needle, + Item::The_Ox, Item::The_Pillar, Item::The_Plant, + Item::The_Psychic, Item::The_Serpent, Item::The_Tooth, + Item::The_Wall, Item::The_Water, Item::The_Wheel, + Item::The_Window}; + +#endif \ No newline at end of file diff --git a/immolate/main.cpp b/immolate/main.cpp new file mode 100644 index 0000000..5467468 --- /dev/null +++ b/immolate/main.cpp @@ -0,0 +1,282 @@ +#include "functions.hpp" +#include "search.hpp" +#include +#include +#include + +long filter(Instance inst) { + long legendaries = 0; + inst.nextPack(1); + for (int p = 1; p <= 3; p++) { + Pack pack = packInfo(inst.nextPack(1)); + if (pack.type == Item::Arcana_Pack) { + auto packContents = inst.nextArcanaPack(pack.size, 1); + for (int x = 0; x < pack.size; x++) { + if (packContents[x] == Item::The_Soul) + legendaries++; + } + } + if (pack.type == Item::Spectral_Pack) { + auto packContents = inst.nextSpectralPack(pack.size, 1); + for (int x = 0; x < pack.size; x++) { + if (packContents[x] == Item::The_Soul) + legendaries++; + } + } + } + return legendaries; +}; + +long filter_perkeo_observatory(Instance inst) { + if (inst.nextVoucher(1) == Item::Telescope) { + inst.activateVoucher(Item::Telescope); + if (inst.nextVoucher(2) != Item::Observatory) + return 0; + } else + return 0; + int antes[5] = {1, 1, 2, 2, 2}; + for (int i = 0; i < 5; i++) { + Pack pack = packInfo(inst.nextPack(antes[i])); + std::vector packContents; + if (pack.type == Item::Arcana_Pack) { + packContents = inst.nextArcanaPack(pack.size, antes[i]); + } else if (pack.type == Item::Spectral_Pack) { + packContents = inst.nextSpectralPack(pack.size, antes[i]); + } else + continue; + for (int x = 0; x < pack.size; x++) { + if (packContents[x] == Item::The_Soul && + inst.nextJoker(ItemSource::Soul, antes[i], true).joker == + Item::Perkeo) + return 1; + } + } + return 0; +} + +long filter_negative_tag(Instance inst) { + // Note: If the score cutoff was passed as a variable, this code could be + // significantly optimized + int maxAnte = 20; + int score = 0; + for (int i = 2; i <= maxAnte; i++) { + if (inst.nextTag(i) == Item::Negative_Tag) + score++; + } + return score; +} + +long filter_lucky(Instance inst) { + for (int i = 0; i < 7; i++) { + if (inst.random(RandomType::Lucky_Money) >= 1.0/15) { + return 0; + } + } + return 1; +} + +long filter_suas_speedrun(Instance inst) { + // First four cards in shop must include Mr. Bones, Merry Andy, and Luchador + bool bones = false, andy = false, luchador = false; + for (int i = 0; i < 4; i++) { + ShopItem item = inst.nextShopItem(2); + if (item.item == Item::Mr_Bones) + bones = true; + if (item.item == Item::Merry_Andy) + andy = true; + if (item.item == Item::Luchador) + luchador = true; + } + if (!bones || !andy || !luchador) + return 0; + // Ante 1 must have a Coupon Tag + inst.initLocks(1, false, true); + bool coupon = false; + for (int i = 0; i < 2; i++) { + if (inst.nextTag(1) == Item::Coupon_Tag) + coupon = true; + } + if (!coupon) + return 1; + // Ante 2 Boss must be The Wall + inst.nextBoss(1); + inst.initUnlocks(2, false); + if (inst.nextBoss(2) != Item::The_Wall) + return 2; + return 3; +} + +long filter_cavendish(Instance inst) { + inst.initLocks(1, false, false); + // Check for a Charm Tag (Arcana Pack) + if (inst.nextTag(1) != Item::Charm_Tag) + return 0; + // Check for a Judgement within that pack + std::vector packContents = inst.nextArcanaPack(5, 1); + bool hasJudgement = false; + for (int i = 0; i < 5; i++) { + if (packContents[i] == Item::Judgement) + hasJudgement = true; + } + if (!hasJudgement) + return 1; + // Check for Gros Michel + if (inst.nextJoker(ItemSource::Judgement, 1, false).joker != Item::Gros_Michel) + return 2; + // Check for Gros Michel break + if (inst.random(RandomType::Gros_Michel) >= 1.0/6) + return 3; + // Check for Cavendish in first shop + if (inst.nextShopItem(1).item != Item::Cavendish || inst.nextShopItem(1).item != Item::Cavendish) + return 4; + // Check for Cavendish break + if (inst.random(RandomType::Cavendish) < 1.0/1000) + return 9999; + return 5; +} + +long filter_blank(Instance inst) { return 0; } + +// These won't be permanent filters, just ones I sub in and out while JSON +// filters aren't ready yet +long filter_test(Instance inst) { + // Four Fingers, Shortcut, and Smeared Joker in first two antes + // (https://discord.com/channels/1325151824638120007/1326284714125955183) + bool fingers = false; + bool shortcut = false; + bool smeared = false; + // 4 chances in Ante 1, 6 chances in Ante 2, so no rerolling + for (int i = 0; i < 4; i++) { + ShopItem item = inst.nextShopItem(1); + if (item.item == Item::Four_Fingers) { + fingers = true; + }; + if (item.item == Item::Shortcut) { + shortcut = true; + }; + if (item.item == Item::Smeared_Joker) { + smeared = true; + }; + } + for (int i = 0; i < 6; i++) { + ShopItem item = inst.nextShopItem(2); + if (item.item == Item::Four_Fingers) { + fingers = true; + }; + if (item.item == Item::Shortcut) { + shortcut = true; + }; + if (item.item == Item::Smeared_Joker) { + smeared = true; + }; + } + if (fingers && shortcut && smeared) { + return 1; + } + return 0; +} + +// Benchmark function +// Runs 1 billion seeds of perkeo observatory +// And prints total time and seeds per second +void benchmark() { + long total = 0; + long start = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Search search(filter_perkeo_observatory, "IMMOLATE", 12, 1000000000); + search.highScore = 10; // No output + search.printDelay = 100000000000; + search.search(); + long end = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::cout << "------LONGER TESTING------\n"; + std::cout << "Total time: " << end - start << "ms\n"; + std::cout << "Seeds per second: " << std::fixed << std::setprecision(0) + << 1000000000 / ((end - start) / 1000.0) << "\n"; +} + +void benchmark_quick() { + long total = 0; + long start = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Search search(filter_perkeo_observatory, "IMMOLATE", 12, 100000000); + search.highScore = 10; // No output + search.printDelay = 100000000000; + search.search(); + long end = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::cout << "----PERKEO OBSERVATORY----\n"; + std::cout << "Total time: " << end - start << "ms\n"; + std::cout << "Seeds per second: " << std::fixed << std::setprecision(0) + << 100000000 / ((end - start) / 1000.0) << "\n"; +} + +void benchmark_quick_lucky() { + long total = 0; + long start = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Search search(filter_lucky, "IMMOLATE", 12, 100000000); + search.highScore = 10; // No output + search.printDelay = 100000000000; + search.search(); + long end = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::cout << "-------LUCKY CARDS-------\n"; + std::cout << "Total time: " << end - start << "ms\n"; + std::cout << "Seeds per second: " << std::fixed << std::setprecision(0) + << 100000000 / ((end - start) / 1000.0) << "\n"; +} + +void benchmark_single() { + long total = 0; + long start = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Search search(filter_perkeo_observatory, "IMMOLATE", 1, 10000000); + search.highScore = 10; // No output + search.printDelay = 100000000000; + search.search(); + long end = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::cout << "----SINGLE THREADED PO----\n"; + std::cout << "Total time: " << end - start << "ms\n"; + std::cout << "Seeds per second: " << std::fixed << std::setprecision(0) + << 10000000 / ((end - start) / 1000.0) << "\n"; +} + +void benchmark_blank() { + long total = 0; + long start = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Search search(filter_blank, "IMMOLATE", 12, 100000000); + search.printDelay = 100000000000; // No output + search.search(); + long end = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::cout << "-------BLANK FILTER-------\n"; + std::cout << "Total time: " << end - start << "ms\n"; + std::cout << "Seeds per second: " << std::fixed << std::setprecision(0) + << 100000000 / ((end - start) / 1000.0) << "\n"; +} + +int main() { + /*benchmark_single(); + benchmark_quick(); + benchmark_quick_lucky(); + benchmark_blank(); + benchmark();*/ + Search search(filter_cavendish, "11111J31", 8, 2318107019761); + search.highScore = 5; + search.printDelay = 2318107019761; + search.search(); + return 1; +} \ No newline at end of file diff --git a/immolate/rng.cpp b/immolate/rng.cpp new file mode 100644 index 0000000..6facfb1 --- /dev/null +++ b/immolate/rng.cpp @@ -0,0 +1,67 @@ +#include "rng.hpp" + +const std::string ItemSource::Shop = "sho"; +const std::string ItemSource::Emperor = "emp"; +const std::string ItemSource::High_Priestess = "pri"; +const std::string ItemSource::Judgement = "jud"; +const std::string ItemSource::Wraith = "wra"; +const std::string ItemSource::Arcana_Pack = "ar1"; +const std::string ItemSource::Omen_Globe = "ar2"; +const std::string ItemSource::Celestial_Pack = "pl1"; +const std::string ItemSource::Spectral_Pack = "spe"; +const std::string ItemSource::Standard_Pack = "sta"; +const std::string ItemSource::Buffoon_Pack = "buf"; +const std::string ItemSource::Vagabond = "vag"; +const std::string ItemSource::Superposition = "sup"; +const std::string ItemSource::_8_Ball = "8ba"; +const std::string ItemSource::Seance = "sea"; +const std::string ItemSource::Sixth_Sense = "sixth"; +const std::string ItemSource::Top_Up = "top"; +const std::string ItemSource::Rare_Tag = "rta"; +const std::string ItemSource::Uncommon_Tag = "uta"; +const std::string ItemSource::Purple_Seal = "8ba"; +const std::string ItemSource::Soul = "sou"; +const std::string ItemSource::Riff_Raff = "rif"; +const std::string ItemSource::Cartomancer = "car"; + +const std::string RandomType::Joker_Common = "Joker1"; +const std::string RandomType::Joker_Uncommon = "Joker2"; +const std::string RandomType::Joker_Rare = "Joker3"; +const std::string RandomType::Joker_Legendary = "Joker4"; +const std::string RandomType::Joker_Rarity = "rarity"; +const std::string RandomType::Joker_Edition = "edi"; +const std::string RandomType::Misprint = "misprint"; +const std::string RandomType::Standard_Has_Enhancement = "stdset"; +const std::string RandomType::Enhancement = "Enhanced"; +const std::string RandomType::Card = "front"; +const std::string RandomType::Standard_Edition = "standard_edition"; +const std::string RandomType::Standard_Has_Seal = "stdseal"; +const std::string RandomType::Standard_Seal = "stdsealtype"; +const std::string RandomType::Shop_Pack = "shop_pack"; +const std::string RandomType::Tarot = "Tarot"; +const std::string RandomType::Spectral = "Spectral"; +const std::string RandomType::Tags = "Tag"; +const std::string RandomType::Shuffle_New_Round = "nr"; +const std::string RandomType::Card_Type = "cdt"; +const std::string RandomType::Planet = "Planet"; +const std::string RandomType::Lucky_Mult = "lucky_mult"; +const std::string RandomType::Lucky_Money = "lucky_money"; +const std::string RandomType::Sigil = "sigil"; +const std::string RandomType::Ouija = "ouija"; +const std::string RandomType::Wheel_of_Fortune = "wheel_of_fortune"; +const std::string RandomType::Gros_Michel = "gros_michel"; +const std::string RandomType::Cavendish = "cavendish"; +const std::string RandomType::Voucher = "Voucher"; +const std::string RandomType::Voucher_Tag = "Voucher_fromtag"; +const std::string RandomType::Orbital_Tag = "orbital"; +const std::string RandomType::Soul = "soul_"; +const std::string RandomType::Erratic = "erratic"; +const std::string RandomType::Eternal = + "stake_shop_joker_eternal"; // Eternal jokers pre 1.0.1 +const std::string RandomType::Perishable = "ssjp"; +const std::string RandomType::Rental = "ssjr"; +const std::string RandomType::Eternal_Perishable = "etperpoll"; +const std::string RandomType::Rental_Pack = "packssjr"; +const std::string RandomType::Eternal_Perishable_Pack = "packetper"; +const std::string RandomType::Boss = "boss"; +const std::string RandomType::Omen_Globe = "omen_globe"; \ No newline at end of file diff --git a/immolate/rng.hpp b/immolate/rng.hpp new file mode 100644 index 0000000..df4085b --- /dev/null +++ b/immolate/rng.hpp @@ -0,0 +1,75 @@ +#ifndef RNG_HPP +#define RNG_HPP + +#include + +struct ItemSource { + static const std::string Shop; + static const std::string Emperor; + static const std::string High_Priestess; + static const std::string Judgement; + static const std::string Wraith; + static const std::string Arcana_Pack; + static const std::string Omen_Globe; + static const std::string Celestial_Pack; + static const std::string Spectral_Pack; + static const std::string Standard_Pack; + static const std::string Buffoon_Pack; + static const std::string Vagabond; + static const std::string Superposition; + static const std::string _8_Ball; + static const std::string Seance; + static const std::string Sixth_Sense; + static const std::string Top_Up; + static const std::string Rare_Tag; + static const std::string Uncommon_Tag; + static const std::string Purple_Seal; + static const std::string Soul; + static const std::string Riff_Raff; + static const std::string Cartomancer; +}; + +struct RandomType { + static const std::string Joker_Common; + static const std::string Joker_Uncommon; + static const std::string Joker_Rare; + static const std::string Joker_Legendary; + static const std::string Joker_Rarity; + static const std::string Joker_Edition; + static const std::string Misprint; + static const std::string Standard_Has_Enhancement; + static const std::string Enhancement; + static const std::string Card; + static const std::string Standard_Edition; + static const std::string Standard_Has_Seal; + static const std::string Standard_Seal; + static const std::string Shop_Pack; + static const std::string Tarot; + static const std::string Spectral; + static const std::string Tags; + static const std::string Shuffle_New_Round; + static const std::string Card_Type; + static const std::string Planet; + static const std::string Lucky_Mult; + static const std::string Lucky_Money; + static const std::string Sigil; + static const std::string Ouija; + static const std::string Wheel_of_Fortune; + static const std::string Gros_Michel; + static const std::string Cavendish; + static const std::string Voucher; + static const std::string Voucher_Tag; + static const std::string Orbital_Tag; + static const std::string Soul; + static const std::string Erratic; + static const std::string Eternal; // Eternal jokers pre 1.0.1 + static const std::string Perishable; + static const std::string Rental; + static const std::string Eternal_Perishable; + static const std::string Rental_Pack; + static const std::string Eternal_Perishable_Pack; + static const std::string Boss; + static const std::string Omen_Globe; +}; + +#endif // RNG_HPP \ No newline at end of file diff --git a/immolate/search.cpp b/immolate/search.cpp new file mode 100644 index 0000000..e69de29 diff --git a/immolate/search.hpp b/immolate/search.hpp new file mode 100644 index 0000000..4c96329 --- /dev/null +++ b/immolate/search.hpp @@ -0,0 +1,111 @@ +#ifndef SEARCH_HPP +#define SEARCH_HPP + +#include "instance.hpp" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +const long long BLOCK_SIZE = 1000000; + +class Search { +public: + std::atomic seedsProcessed{0}; + std::atomic highScore{1}; + long long printDelay = 10000000; + std::function filter; + std::atomic found{false}; // Atomic flag to signal when a solution is found + Seed foundSeed; // Store the found seed + bool exitOnFind = false; + long long startSeed; + int numThreads; + long long numSeeds; + std::mutex mtx; + std::atomic nextBlock{0}; // Shared index for the next block to be processed + + Search(std::function f) { + filter = f; + startSeed = 0; + numThreads = 1; + numSeeds = 2318107019761; + } + + Search(std::function f, int t) { + filter = f; + startSeed = 0; + numThreads = t; + numSeeds = 2318107019761; + } + + Search(std::function f, int t, long long n) { + filter = f; + startSeed = 0; + numThreads = t; + numSeeds = n; + }; + Search(std::function f, std::string seed, int t, long long n) { + filter = f; + startSeed = Seed(seed).getID(); + numThreads = t; + numSeeds = n; + }; + + void searchBlock(long long start, long long end) { + Seed s = Seed(start); + Instance inst(s); + for (long long i = start; i < end; ++i) { + if (found) return; // Exit if a solution is found + // Perform the search on the seed + int result = filter(inst); + if (result >= highScore) { + std::lock_guard lock(mtx); + highScore = result; + foundSeed = s; + std::cout << "Found seed: " << s.tostring() << " (" << result << ")" + << std::endl; + if (exitOnFind) { + found = true; + return; + } + } + seedsProcessed++; + if (seedsProcessed % printDelay == 0) { + std::cout << "Seeds processed: " << seedsProcessed << std::endl; + } + inst.next(); + } + } + + std::string search() { + std::vector threads; + long long totalBlocks = (numSeeds + BLOCK_SIZE - 1) / BLOCK_SIZE; + for (int t = 0; t < numThreads; t++) { + threads.emplace_back([this, totalBlocks]() { + while (true) { + long long block = nextBlock.fetch_add(1); + if (block >= totalBlocks) break; + long long start = block * BLOCK_SIZE + startSeed; + long long end = std::min(start + BLOCK_SIZE, numSeeds + startSeed); + searchBlock(start, end); + } + }); + } + + for (auto& thread : threads) { + thread.join(); + } + + return foundSeed.tostring(); + } +}; + +#endif \ No newline at end of file diff --git a/immolate/seed.cpp b/immolate/seed.cpp new file mode 100644 index 0000000..0595dba --- /dev/null +++ b/immolate/seed.cpp @@ -0,0 +1,112 @@ +#include "seed.hpp" +#include "util.hpp" +#include + +Seed::Seed() { + seed.fill(-1); + length = 0; + for (int i = 0; i < 8; i++) { + cache[i].fill(-1); + } +} + +Seed::Seed(std::string strSeed) { + seed.fill(-1); + length = strSeed.size(); + for (int i = 0; i < 8; i++) { + cache[i].fill(-1); + } + // Note: Assumes this is safe + for (long unsigned int i = 0; i < strSeed.size(); i++) { + seed[strSeed.size() - 1 - i] = charSeeds[strSeed[i]]; + } +} + +Seed::Seed(long long id) { + length = 0; + for (int i = 0; i < 8; i++) { + cache[i].fill(-1); + } + for (int i = 0; i < 8; i++) { + if (id > 0) { + length++; + seed[i] = (id - 1) / idCoeff[i]; + id -= 1 + seed[i] * idCoeff[i]; + } else { + seed[i] = -1; + } + } +} + +std::string Seed::tostring() { + std::string strSeed; + for (int i = 7; i >= 0; i--) { + if (seed[i] != -1) { + strSeed.push_back(seedChars[seed[i]]); + } + } + return strSeed; +} + +void Seed::debugprint() { + for (int i = 0; i < 8; i++) { + std::cout << seed[i] << " "; + } + std::cout << std::endl; +} + +long long Seed::getID() { + long long id = 0; + for (int i = 0; i <= 7; i++) { + if (seed[i] >= 0) { + id += idCoeff[i] * seed[i] + 1; + } + } + return id; +} + +void Seed::next() { + if (length < 8) { + seed[length] = 0; + length++; + } else { + int i = 7; + while (i >= 0) { + cache[i].fill(-1); + if (seed[i] == 34) { + seed[i] = -1; + length--; + } else { + seed[i]++; + break; + } + i--; + } + } +} + +// Not optimized for performance +// I don't think this will need to be implemented in searching +void Seed::next(int x) { + long long newID = (getID() + x) % 2318107019761; + *this = Seed(newID); +} + +double Seed::pseudohash(int prefixLength) { + if (length == 0) return 1; //Empty seed edge case + + if (cache[length-1][prefixLength+length-1] == -1) { + int i = length - 2; + while (i >= 0 && cache[i][prefixLength+length-1] == -1) { + i--; + } + if (i == -1) { + cache[0][prefixLength+length-1] = pseudostep(seedChars[seed[0]], prefixLength+length, 1); + i = 0; + } + for (int j = i+1; j < length; j++) { + cache[j][prefixLength+length-1] = pseudostep(seedChars[seed[j]], prefixLength+length-j, cache[j-1][prefixLength+length-1]); + } + } + return cache[length-1][prefixLength+length-1]; +} \ No newline at end of file diff --git a/immolate/seed.hpp b/immolate/seed.hpp new file mode 100644 index 0000000..8e184be --- /dev/null +++ b/immolate/seed.hpp @@ -0,0 +1,49 @@ +#ifndef SEED_HPP +#define SEED_HPP + +#include +#include + +// Seed helper class +// Caches hashing info recursively to save speed +// Because of that, also has an interesting order for seeds: +// , 1, 11, 111, ..., 11111111, 21111111, 31111111, ..., Z1111111, +// 2111111, 12111111, ..., ZZ111111, 211111, ..., ZZZZZZZZ +const std::string seedChars = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const std::array charSeeds = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, + 8, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; +const std::array idCoeff = { + 66231629136, 1892332261, 54066636, 1544761, 44136, 1261, 36, 1}; + +struct Seed { + // -1 is blank, 0 to 34 represent valid characters + // To aid in hashing, stored right to left + std::array seed; + + int length; + + // The cache. Stored as [position in seed][length of string] + std::array, 8> cache; + + Seed(); + Seed(std::string strSeed); + Seed(long long id); + + std::string tostring(); + void debugprint(); + long long getID(); + + void next(); + void next(int x); + + double pseudohash(int prefixLength); +}; + +#endif // SEED_HPP \ No newline at end of file diff --git a/immolate/util.cpp b/immolate/util.cpp new file mode 100644 index 0000000..82df8f6 --- /dev/null +++ b/immolate/util.cpp @@ -0,0 +1,178 @@ +#include "util.hpp" +#include +#include + +LuaRandom::LuaRandom(double seed) { + double d = seed; + uint64_t r = 0x11090601; + for (int i = 0; i < 4; i++) { + uint64_t m = 1ull << (r & 255); + r >>= 8; + d = d * 3.14159265358979323846 + 2.7182818284590452354; + dbllong u; + u.dbl = d; + if (u.ulong < m) + u.ulong += m; + state[i] = u.ulong; + } + for (int i = 0; i < 10; i++) { + _randint(); + } +} + +LuaRandom::LuaRandom() { LuaRandom(0); } + +uint64_t LuaRandom::_randint() { + uint64_t z = 0; + uint64_t r = 0; + z = state[0]; + z = (((z << 31ull) ^ z) >> 45ull) ^ ((z & (MAX_UINT64 << 1ull)) << 18ull); + r ^= z; + state[0] = z; + z = state[1]; + z = (((z << 19ull) ^ z) >> 30ull) ^ ((z & (MAX_UINT64 << 6ull)) << 28ull); + r ^= z; + state[1] = z; + z = state[2]; + z = (((z << 24ull) ^ z) >> 48ull) ^ ((z & (MAX_UINT64 << 9ull)) << 7ull); + r ^= z; + state[2] = z; + z = state[3]; + z = (((z << 21ull) ^ z) >> 39ull) ^ ((z & (MAX_UINT64 << 17ull)) << 8ull); + r ^= z; + state[3] = z; + return r; +} + +uint64_t LuaRandom::randdblmem() { + return (_randint() & 4503599627370495ull) | 4607182418800017408ull; +} + +double LuaRandom::random() { + dbllong u; + u.ulong = randdblmem(); + return u.dbl - 1.0; +} + +int LuaRandom::randint(int min, int max) { + return (int)(random() * (max - min + 1)) + min; +} + +int portable_clzll(uint64_t x) { + if (x == 0) + return 64; // Undefined for 0, by convention we return 64 + +#if defined(__GNUC__) || defined(__clang__) + return __builtin_clzll(x); +#elif defined(_MSC_VER) + unsigned long index; + if (_BitScanReverse64(&index, x)) { + return 63 - index; + } + return 64; +#else + // Fallback for other compilers (manual bit manipulation) + int n = 0; + if (x <= 0x00000000FFFFFFFF) { + n += 32; + x <<= 32; + } + if (x <= 0x0000FFFFFFFFFFFF) { + n += 16; + x <<= 16; + } + if (x <= 0x00FFFFFFFFFFFFFF) { + n += 8; + x <<= 8; + } + if (x <= 0x0FFFFFFFFFFFFFFF) { + n += 4; + x <<= 4; + } + if (x <= 0x3FFFFFFFFFFFFFFF) { + n += 2; + x <<= 2; + } + if (x <= 0x7FFFFFFFFFFFFFFF) { + n += 1; + } + return n; +#endif +} + +double fract(double x) { + uint64_t x_int; + + std::memcpy(&x_int, &x, sizeof(x_int)); + + uint64_t expo = (x_int & DBL_EXPO) >> DBL_MANT_SZ; + if (expo < DBL_EXPO_BIAS) { + return x; + } + if (expo == ((1 << DBL_EXPO_SZ) - 1)) { + return std::numeric_limits::quiet_NaN(); + } + uint64_t expo_biased = expo - DBL_EXPO_BIAS; + if (expo_biased >= DBL_MANT_SZ) { + return 0; + } + uint64_t mant = x_int & DBL_MANT; + uint64_t frac_mant = mant & ((1ull << (DBL_MANT_SZ - expo_biased)) - 1); + if (frac_mant == 0) { + return 0; + } + uint64_t frac_lzcnt = portable_clzll(frac_mant) - (64 - DBL_MANT_SZ); + uint64_t res_expo = (expo - frac_lzcnt - 1) << DBL_MANT_SZ; + uint64_t res_mant = (frac_mant << (frac_lzcnt + 1)) & DBL_MANT; + uint64_t res = res_expo | res_mant; + + double result; + std::memcpy(&result, &res, sizeof(result)); + return result; +} + +double pseudohash(std::string s) { + double num = 1; + for (size_t i = s.length(); i > 0; i--) { + num = fract(1.1239285023 / num * s[i - 1] * 3.141592653589793116 + + 3.141592653589793116 * i); + } + return num; +} + +double pseudohash_from(std::string s, double num) { + for (size_t i = s.length(); i > 0; i--) { + num = fract(1.1239285023 / num * s[i - 1] * 3.141592653589793116 + + 3.141592653589793116 * i); + } + return num; +} + +double pseudostep(char s, int pos, double num) { + return fract(1.1239285023 / num * s * 3.141592653589793116 + + 3.141592653589793116 * pos); +} + +std::string anteToString(int a) { + if (a < 10) + return {(char)(0x30 + a)}; + else + return {(char)(0x30 + a / 10), (char)(0x30 + a % 10)}; +} + +const double inv_prec = std::pow(10.0, 13); +const double two_inv_prec = std::pow(2.0, 13); +const double five_inv_prec = std::pow(5.0, 13); + +double round13(double x) { + double normal_case = std::round(x * inv_prec) / inv_prec; + if (normal_case == + (std::round(std::nextafter(x, -1) * inv_prec) / inv_prec)) { + return normal_case; + } + double truncated = fract(x * two_inv_prec) * five_inv_prec; + if (fract(truncated) >= 0.5) { + return (std::floor(x * inv_prec) + 1) / inv_prec; + } + return std::floor(x * inv_prec) / inv_prec; +} \ No newline at end of file diff --git a/immolate/util.hpp b/immolate/util.hpp new file mode 100644 index 0000000..4fbbe4e --- /dev/null +++ b/immolate/util.hpp @@ -0,0 +1,46 @@ +#ifndef UTIL_HPP +#define UTIL_HPP + +#include +#include +#include + +const uint64_t MAX_UINT64 = 18446744073709551615ull; + +typedef union DoubleLong { + double dbl; + uint64_t ulong; +} dbllong; + +struct LuaRandom { + uint64_t state[4]; + LuaRandom(double seed); + LuaRandom(); + uint64_t _randint(); + uint64_t randdblmem(); + double random(); + int randint(int min, int max); +}; + +#define DBL_EXPO 0x7FF0000000000000 +#define DBL_MANT 0x000FFFFFFFFFFFFF + +#define DBL_EXPO_SZ 11 +#define DBL_MANT_SZ 52 + +#define DBL_EXPO_BIAS 1023 + +#if defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanReverse64) +#endif + +int portable_clzll(uint64_t x); +double fract(double x); +double pseudohash(std::string s); +double pseudohash_from(std::string s, double num); +double pseudostep(char s, int pos, double num); +std::string anteToString(int a); +double round13(double x); + +#endif // UTIL_HPP \ No newline at end of file