From a2d16ae12f8e50c45a77af689b43c405c9402f2f Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 14 Jul 2026 14:03:35 -0700 Subject: [PATCH] Integrate with the mods actually installed beside us New integrations.lua, loaded pcall-wrapped at the end of main.lua; every integration gates on its target's global so a missing mod is silence. Both targets load before us by priority. JokerDisplay: each known joker's display definition gains a live '(2 combos)' counter in JD's own parenthetical reminder style -- the same partner count the hover tooltip names -- by appending a dynamic element to reminder_text (created when absent) and wrapping calc_function to fill card.joker_display_values. The wrapper pcalls the original calc and blanks the text when the new jd_combos config toggle is off, so it flips mid-run in both directions. Too Many Jokers: registers the SEARCH_FIELD_FUNCS hook its api.md documents, indexing every joker under its tag labels on both sides, so the collection search finds jokers by 'money', 'Hearts', 'held-card retriggers'. Unknown centers stay nil. The smoke scenario now asserts the JokerDisplay patch against the real mod: 13/13 in-game. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 17 +++++++++- config.lua | 1 + integrations.lua | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ main.lua | 30 ++++++++++++++++++ test.lua | 53 +++++++++++++++++++++++++++++++ 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 integrations.lua diff --git a/CLAUDE.md b/CLAUDE.md index 9427136..3b7cf4d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ and pulses strongly recommended cards. Pure Lua, no build step. Syntax check, then run the test suite — **always both, after every edit**: ```sh -luac -p main.lua synergies.lua config.lua test.lua +luac -p main.lua synergies.lua config.lua integrations.lua test.lua lua test.lua ``` @@ -192,6 +192,21 @@ Three-layer split; `JCA` is the single global namespace: - `config.lua` — default settings (`pulse`, `owned_tooltip`, `threshold`). Steamodded loads and persists this table itself; read it only via `SMODS.current_mod.config` (exposed as `JCA.config`). +- `integrations.lua` — cross-mod integrations, loaded at the very end of + `main.lua` (pcall-wrapped; counts land in `JCA.integrations`). Every + integration gates on its target's global — a missing mod is silence — and + both targets load before us (JokerDisplay priority -1e9, TMJ -1000, ours 0): + - **JokerDisplay**: appends a `(2 combos)` counter element to each known + joker's `reminder_text` (created when absent) and wraps the definition's + `calc_function` to fill `card.joker_display_values.jca_combos` from + `partners_for`. The wrapper pcalls the original calc and blanks the text + when `config.jd_combos` is off, so the toggle works mid-run both ways. + Definitions are marked `jca_combo_counter` against double patching. + - **Too Many Jokers**: registers a `TMJ.SEARCH_FIELD_FUNCS` entry (its + api.md's documented hook) returning the joker's `TAG_LABEL`s on both + sides, so the collection search finds jokers by 'money', 'Hearts', + 'held-card retriggers'... Unknown centers return nil, never an error. + The smoke scenario asserts the JokerDisplay patch against the real mod. ## Conventions and gotchas diff --git a/config.lua b/config.lua index 3515e1f..84a28c5 100644 --- a/config.lua +++ b/config.lua @@ -8,6 +8,7 @@ return { threshold = 4, -- total synergy score needed to recommend a card learning_mode = false, -- hide verdicts (pulse, "Strong pick!"), keep reasons touch_mode = true, -- enlarge advice text/buttons for phone screens + jd_combos = true, -- combo counter under jokers (needs JokerDisplay) discovered = {}, -- famous pairs the player has fielded (pair keys) themes_found = {}, -- synergy themes the player has assembled (tags) } diff --git a/integrations.lua b/integrations.lua new file mode 100644 index 0000000..a70569b --- /dev/null +++ b/integrations.lua @@ -0,0 +1,81 @@ +--- Integrations with other installed mods, applied at the end of main.lua. +--- Every integration gates on its target mod's global, so a missing mod is +--- silence rather than an error, and each touches only jokers this database +--- knows. Both targets load before this mod does (JokerDisplay at priority +--- -1e9, Too Many Jokers at -1000, this mod at 0), so their tables exist by +--- the time these run. Each returns how much it patched, surfaced in +--- JCA.integrations for the tests and the debug log. + +local M = {} + +--- JokerDisplay: a live combo counter under every joker it displays. +--- Its model: JokerDisplay.Definitions[key] holds per-joker display specs; +--- dynamic text reads card.joker_display_values, which the definition's +--- calc_function fills on every display update. We append a counter element +--- in JokerDisplay's own parenthetical reminder style -- '(2 combos)', the +--- same partner count the hover tooltip names -- and wrap calc_function to +--- keep the value fresh. Definitions without a reminder_text row gain one. +--- The wrapper blanks the text when config.jd_combos is off, so the toggle +--- works mid-run in both directions; the count call is pcall-wrapped like +--- every other advisor path that runs inside the game's draw loop. +function M.jokerdisplay() + if not (JokerDisplay and JokerDisplay.Definitions) then return 0 end + local patched = 0 + for key in pairs(JCA.db) do + local def = JokerDisplay.Definitions[key] + if def and not def.jca_combo_counter then + def.jca_combo_counter = true + local node = { ref_table = 'card.joker_display_values', + ref_value = 'jca_combos', colour = G.C.GREEN } + if def.reminder_text then + def.reminder_text[#def.reminder_text + 1] = { text = ' ' } + def.reminder_text[#def.reminder_text + 1] = node + else + def.reminder_text = { node } + end + local orig = def.calc_function + def.calc_function = function(card) + -- pcall like every advisor path in the draw loop: if the + -- original calc fails, the counter must still not crash. + if orig then pcall(orig, card) end + local text = '' + if JCA.config.jd_combos ~= false then + local ok, partners = pcall(JCA.partners_for, card) + local n = ok and #partners or 0 + if n > 0 then + text = ('(%d combo%s)'):format(n, n == 1 and '' or 's') + end + end + card.joker_display_values = card.joker_display_values or {} + card.joker_display_values.jca_combos = text + end + patched = patched + 1 + end + end + return patched +end + +--- Too Many Jokers: its api.md asks for a TMJ.SEARCH_FIELD_FUNCS entry +--- mapping a center to searchable strings. We hand it every tag label on +--- both sides of a joker, so the collection search finds jokers by what +--- they give and want: 'money', 'held-card retriggers', 'Hearts'... +--- Unknown centers (other mods' jokers) return nil, the same "score 0" +--- silence the engine promises everywhere else. +function M.toomanyjokers() + if not (TMJ and TMJ.SEARCH_FIELD_FUNCS) then return 0 end + TMJ.SEARCH_FIELD_FUNCS[#TMJ.SEARCH_FIELD_FUNCS + 1] = function(center) + local entry = center and center.key and JCA.db[center.key] + if not entry then return nil end + local fields = {} + for t in pairs(entry.gives) do + if JCA.tag_label[t] then fields[#fields + 1] = JCA.tag_label[t] end + end + for t in pairs(entry.wants) do + if JCA.tag_label[t] then fields[#fields + 1] = JCA.tag_label[t] end + end + return fields + end + return 1 +end + +return M diff --git a/main.lua b/main.lua index 75f0b45..42903da 100644 --- a/main.lua +++ b/main.lua @@ -1613,6 +1613,12 @@ if SMODS.current_mod then ref_table = JCA.config, ref_value = 'touch_mode', }, }}, + {n = G.UIT.R, config = {align = 'cm', padding = 0.05}, nodes = { + create_toggle{ + label = 'Combo counter under jokers (JokerDisplay)', + ref_table = JCA.config, ref_value = 'jd_combos', + }, + }}, {n = G.UIT.R, config = {align = 'cm', padding = 0.05}, nodes = { create_option_cycle{ label = 'Recommendation threshold (lower = more picks)', @@ -1625,3 +1631,27 @@ if SMODS.current_mod then }} end end + +-- Cross-mod integrations ------------------------------------------------------- +-- integrations.lua patches JokerDisplay (combo counter under jokers) and Too +-- Many Jokers (search the collection by synergy tags) when they are present. +-- pcall on the whole load: a partner mod's surprise API change must degrade +-- to a log line, never take the advisor down with it. + +do + local loaded, integrations = pcall(function() + return assert(SMODS.load_file('integrations.lua'))() + end) + if loaded and integrations then + JCA.integrations = {} + for name, apply in pairs(integrations) do + local ok, count = pcall(apply) + JCA.integrations[name] = ok and count or 0 + if not ok then print('JCA: integration ' .. name .. ' failed: ' .. tostring(count)) end + end + dbg('integrations:', (JCA.integrations.jokerdisplay or 0) .. ' JokerDisplay defs,', + (JCA.integrations.toomanyjokers or 0) .. ' TMJ search hooks') + else + print('JCA: integrations failed to load: ' .. tostring(integrations)) + end +end diff --git a/test.lua b/test.lua index f86b730..5ca6420 100644 --- a/test.lua +++ b/test.lua @@ -111,6 +111,19 @@ UIBox = function(args) return {def = args and args.definition, remove = function create_option_cycle = function() return {n = 'R', config = {}, nodes = {}} end localize = function() error('no localization outside the game') end +-- Cross-mod integration stubs, present BEFORE main.lua loads (both real mods +-- load before this one: JokerDisplay at priority -1e9, TMJ at -1000). One +-- JokerDisplay definition of each shape: with a reminder_text row and without. +JokerDisplay = {Definitions = { + j_baron = {text = {{text = 'x'}}, + calc_function = function(card) + card.joker_display_values = card.joker_display_values or {} + card.joker_display_values.orig_ran = true + end}, + j_mime = {text = {{text = 'x'}}, reminder_text = {{text = '(hand)'}}}, +}} +TMJ = {SEARCH_FIELD_FUNCS = {}} + local data = dofile('synergies.lua') dofile('main.lua') @@ -673,6 +686,46 @@ JCA.register_pair('j_mod_test', 'j_baron', 'test blurb') eq(JCA.score('j_mod_test', 'j_baron') - before, 4, 'JCA.register_pair adds the +4 pair bonus') JCA.db.j_mod_test = nil -- keep the UI section on vanilla data only +-------------------------------------------------------------------------------- +section('Integrations: JokerDisplay counter and TMJ search') +-------------------------------------------------------------------------------- + +-- JokerDisplay: every known joker with a definition gains the counter, in +-- both definition shapes. +eq(JCA.integrations.jokerdisplay, 2, 'both stub JokerDisplay definitions were patched') +ok(JokerDisplay.Definitions.j_baron.reminder_text, + 'a definition without reminder_text gains one for the counter') +eq(#JokerDisplay.Definitions.j_mime.reminder_text, 3, + 'an existing reminder_text grows by separator + counter') + +-- The wrapped calc_function fills the live count and keeps the original. +field('j_baron', 'j_mime') +local jd_card = G.jokers.cards[1] +jd_card.joker_display_values = {} +JokerDisplay.Definitions.j_baron.calc_function(jd_card) +eq(jd_card.joker_display_values.jca_combos, '(1 combo)', + 'the counter shows the live board partner count') +ok(jd_card.joker_display_values.orig_ran, + 'and the original calc_function still runs first') + +JCA.config.jd_combos = false +JokerDisplay.Definitions.j_baron.calc_function(jd_card) +eq(jd_card.joker_display_values.jca_combos, '', + 'the config toggle blanks the counter mid-run') +JCA.config.jd_combos = true + +-- Too Many Jokers: the search hook indexes jokers by tag labels and stays +-- silent on centers the database does not know. +eq(JCA.integrations.toomanyjokers, 1, 'the TMJ search hook registered') +local tmj_fields = TMJ.SEARCH_FIELD_FUNCS[1]({key = 'j_baron'}) +local tmj_hit = false +for _, f in ipairs(tmj_fields) do + if f == 'held-card retriggers' then tmj_hit = true end +end +ok(tmj_hit, 'TMJ search finds Baron under its tag labels') +eq(TMJ.SEARCH_FIELD_FUNCS[1]({key = 'j_totally_modded'}), nil, + 'an unknown center returns nil, not an error') + -------------------------------------------------------------------------------- section('UI: catalog pages build and animate') --------------------------------------------------------------------------------