diff --git a/Neuratro.lua b/Neuratro.lua index ac05958..3c1060e 100644 --- a/Neuratro.lua +++ b/Neuratro.lua @@ -18,16 +18,72 @@ -- -fix negatives in G.playbook -- These 5 lines of code are from the mod aikoyori's playbook made by aikoyori. Check their mods out! -assert( - SMODS.current_mod.lovely, - "Lovely patches were not loaded.\nMake sure your mod folder is not nested (there should be a bunch of files in the mod folder and not just another folder)." -) +if not SMODS.current_mod.lovely then + print("[Neuratro] Warning: Lovely patches were not loaded. Some features may not work correctly.") +end AKYRS = SMODS.current_mod AKYRS.emplace_funcs = {} assert(SMODS.load_file("./modules/hooks/general.lua"))() assert(SMODS.load_file("./modules/content/cardarea.lua"))() + +-- Load utility modules (order matters - constants first, then utilities) +assert(SMODS.load_file("./modules/utils/constants.lua"))() +assert(SMODS.load_file("./modules/utils/joker_utils.lua"))() +assert(SMODS.load_file("./modules/utils/probability.lua"))() +assert(SMODS.load_file("./modules/utils/card_analysis.lua"))() +assert(SMODS.load_file("./modules/utils/random_utils.lua"))() +assert(SMODS.load_file("./modules/utils/config_builder.lua"))() +assert(SMODS.load_file("./modules/utils/joker_patterns.lua"))() +assert(SMODS.load_file("./modules/utils/safety.lua"))() + +-- Config defaults +SMODS.current_mod.config = SMODS.current_mod.config or {} +SMODS.current_mod.config.palette = SMODS.current_mod.config.palette or "default" + +assert(SMODS.load_file("./modules/utils/palette.lua"))() -- +-- Palette cycle callback +G.FUNCS.Neurocards_set_palette = function(args) + local palette_keys = { "default", "neuro", "evil" } + local key = palette_keys[args.to_key] or "default" + AKYRS.apply_palette(key) + AKYRS.config.palette = key + AKYRS.save_palette_pref(key) +end + +-- Config tab shown in Steamodded mod settings +SMODS.current_mod.config_tab = function() + local palette_options = { "Default", "Neuro-sama", "Evil Neuro" } + local palette_keys = { "default", "neuro", "evil" } + local saved = AKYRS.config.palette or "default" + local current = 1 + for i, k in ipairs(palette_keys) do + if k == saved then current = i; break end + end + return { + n = G.UIT.ROOT, + config = { align = "cm", padding = 0.2, colour = G.C.CLEAR }, + nodes = { + { + n = G.UIT.R, + config = { align = "cm", padding = 0.1 }, + nodes = { + { n = G.UIT.T, config = { text = "Palette ", scale = 0.4, colour = G.C.UI.TEXT_LIGHT } }, + create_option_cycle({ + options = palette_options, + current_option = current, + opt_callback = "Neurocards_set_palette", + w = 3.5, + scale = 0.8, + colour = AKYRS.palette.primary or G.C.RED, + }), + }, + }, + }, + } +end + function SMODS.INIT.DecColors() local dec_mod = SMODS.findModByID("Neurocards") local sprite_deck1 = SMODS.Sprite:new("cards_1", dec_mod.path, "neuroCards.png", 71, 95, "asset_atli") @@ -35,32 +91,12 @@ function SMODS.INIT.DecColors() local sprite_logo = SMODS.Sprite:new("balatro", dec_mod.path, "neuratro.png", 333, 216, "asset_atli") local sprite_enhancers = SMODS.Sprite:new("centers", dec_mod.path, "neuroEnhancers.png", 71, 95, "asset_atli") - sprite_deck1:register() +sprite_deck1:register() sprite_deck2:register() sprite_logo:register() sprite_enhancers:register() end ---This will be deleted at some point -jokers = { - j_gros_michel = { pos = 0 }, - j_joker = { pos = 1 }, -} -SMODS.Atlas({ - key = "neuroJokers", - path = "neuroJokers.png", - px = 71, - py = 95, -}) -for jkr, data in pairs(jokers) do - SMODS["Joker"]:take_ownership( - jkr, - { atlas = "neuroJokers", pos = { x = 0 + data.pos, y = 0 }, soul_pos = { x = 0 + data.pos, y = 1 } }, - true - ) -end --- - assert(SMODS.load_file("./content/hooks/hooks.lua"))() assert(SMODS.load_file("./content/hooks/nothooks.lua"))() diff --git a/assets/1x/neuroBlinds.png b/assets/1x/neuroBlinds.png deleted file mode 100644 index 9922483..0000000 Binary files a/assets/1x/neuroBlinds.png and /dev/null differ diff --git a/assets/1x/neuroCustomJokers.png b/assets/1x/neuroCustomJokers.png deleted file mode 100644 index 902e93e..0000000 Binary files a/assets/1x/neuroCustomJokers.png and /dev/null differ diff --git a/assets/1x/neuroCustomJokers2.png b/assets/1x/neuroCustomJokers2.png deleted file mode 100644 index 84e1cf4..0000000 Binary files a/assets/1x/neuroCustomJokers2.png and /dev/null differ diff --git a/assets/1x/neuroCustomTarots.png b/assets/1x/neuroCustomTarots.png deleted file mode 100644 index bd36dec..0000000 Binary files a/assets/1x/neuroCustomTarots.png and /dev/null differ diff --git a/assets/2x/neuroBlinds.png b/assets/2x/neuroBlinds.png deleted file mode 100644 index c652f63..0000000 Binary files a/assets/2x/neuroBlinds.png and /dev/null differ diff --git a/assets/2x/neuroCustomJokers.png b/assets/2x/neuroCustomJokers.png deleted file mode 100644 index ec6f283..0000000 Binary files a/assets/2x/neuroCustomJokers.png and /dev/null differ diff --git a/assets/2x/neuroCustomJokers2.png b/assets/2x/neuroCustomJokers2.png deleted file mode 100644 index 50c6d05..0000000 Binary files a/assets/2x/neuroCustomJokers2.png and /dev/null differ diff --git a/assets/2x/neuroCustomTarots.png b/assets/2x/neuroCustomTarots.png deleted file mode 100644 index 5c0dd2d..0000000 Binary files a/assets/2x/neuroCustomTarots.png and /dev/null differ diff --git a/content/hooks/hooks.lua b/content/hooks/hooks.lua index 36689bb..635ea24 100644 --- a/content/hooks/hooks.lua +++ b/content/hooks/hooks.lua @@ -1,27 +1,17 @@ local er_ref = end_round function end_round() er_ref() - local hiyori = false - for _, joker in ipairs(G.jokers.cards) do - if joker.config.center.key == "j_hiyori" then - hiyori = true - end - end - for _, joker in ipairs(G.playbook_extra.cards) do - if joker.config.center.key == "j_hiyori" then - hiyori = true - end - end - for _, v in ipairs(G.playing_cards) do + local hiyori = Neuratro.has_joker("j_hiyori") + for _, v in ipairs(G.playing_cards or {}) do SMODS.debuff_card(v, false, "filter") if v:is_suit("Hearts") and hiyori then SMODS.debuff_card(v, true, "filter") end end - for _, v in ipairs(G.jokers.cards) do + for _, v in ipairs(G.jokers and G.jokers.cards or {}) do SMODS.debuff_card(v, false, "filter") end - for _, v in ipairs(G.playbook_extra.cards) do + for _, v in ipairs(G.playbook_extra and G.playbook_extra.cards or {}) do SMODS.debuff_card(v, false, "filter") end end @@ -38,8 +28,8 @@ end local smcmb = SMODS.create_mod_badges function SMODS.create_mod_badges(obj, badges) - smcmb(obj, badges) - if obj and obj.credits then + if smcmb then smcmb(obj, badges) end + if obj and obj.credits and badges then local function calc_scale_fac(text) local size = 0.9 local font = G.LANG.font @@ -62,13 +52,13 @@ function SMODS.create_mod_badges(obj, badges) if obj.credits[v] then for i = 1, #obj.credits[v] do if pos == 1 then - strings[#strings + 1] = "Suggested by: " .. obj.credits.sug[1] + strings[#strings + 1] = "Suggested by: " .. obj.credits.sug[i] elseif pos == 2 then strings[#strings + 1] = "Idea: " .. obj.credits.idea[i] elseif pos == 3 then - strings[#strings + 1] = "Art: " .. obj.credits.art[1] + strings[#strings + 1] = "Art: " .. obj.credits.art[i] elseif pos == 4 then - strings[#strings + 1] = "Code: " .. obj.credits.code[1] + strings[#strings + 1] = "Code: " .. obj.credits.code[i] end end end @@ -150,29 +140,33 @@ function Card:get_chip_bonus() return g end -SMODS.Stickers["rental"].should_apply = function(self, card, center, area, bypass_roll) - if - G.GAME.stake ~= 8 - or pseudorandom("rentalapply") <= 0.7 - or card.ability.set ~= "Joker" - or card.config.center.rarity == "dev" - or card.config.center.rarity == 4 - then - return false + +if SMODS.Stickers and SMODS.Stickers["rental"] then + SMODS.Stickers["rental"].should_apply = function(self, card, center, area, bypass_roll) + if + G.GAME.stake ~= 8 + or pseudorandom("rentalapply") <= 0.7 + or card.ability.set ~= "Joker" + or card.config.center.rarity == "dev" + or card.config.center.rarity == 4 + then + return false + end + return true end - return true end local Game_update = Game.update local prev_money = 0 -MONEY_EARNED = 0 +Neuratro = Neuratro or {} +Neuratro.MONEY_EARNED = 0 function Game:update(dt) Game_update(self, dt) if self.GAME and self.GAME.dollars then local money = self.GAME.dollars if money > prev_money then - MONEY_EARNED = MONEY_EARNED + money - prev_money + Neuratro.MONEY_EARNED = Neuratro.MONEY_EARNED + money - prev_money end prev_money = money end @@ -180,7 +174,7 @@ end local start_run = G.FUNCS.start_run function G.FUNCS.start_run(e, args) - MONEY_EARNED = 0 + Neuratro.MONEY_EARNED = 0 prev_money = 0 start_run(e, args) end @@ -195,12 +189,17 @@ function Game:draw(args) local x = { drawg(self, args) } if G.SHADERS["CRT"] and not originalshader then originalshader = G.SHADERS["CRT"] - else - if false then - G.SHADERS["CRT"] = G.SHADERS["arg"] - else - G.SHADERS["CRT"] = originalshader - end + elseif originalshader and not G.SHADERS["CRT"] then + G.SHADERS["CRT"] = originalshader + end + return unpack(x) +end + +local op_ref = open_booster +function open_booster(self, booster, edition, skin, skip_anims) + local chimps = Neuratro.has_joker("j_chimps") + if chimps and booster and booster.ability and booster.ability.name:find("Arcana") and booster.config and booster.config.extra then + booster.config.extra = booster.config.extra + 1 end - return x + return op_ref(self, booster, edition, skin, skip_anims) end diff --git a/content/hooks/nothooks.lua b/content/hooks/nothooks.lua index 70a0cc4..6f7ee07 100644 --- a/content/hooks/nothooks.lua +++ b/content/hooks/nothooks.lua @@ -1,6 +1,11 @@ -- Sorry for misleading file name, i didn't know where to put this. -sea = function(func, delay, trigger, queue) +Neuratro = Neuratro or {} + +Neuratro.sea = function(func, delay, trigger, queue) + if not (G and G.E_MANAGER) then + return false + end G.E_MANAGER:add_event( Event({ trigger = trigger or "after", @@ -9,6 +14,7 @@ sea = function(func, delay, trigger, queue) }), queue ) + return true end SMODS.current_mod.optional_features = function() diff --git a/content/load/atlas.lua b/content/load/atlas.lua index edff789..0e569d5 100644 --- a/content/load/atlas.lua +++ b/content/load/atlas.lua @@ -48,6 +48,30 @@ SMODS.Atlas({ px = 34, py = 34, }) +SMODS.Atlas({ + key = "animeschizo", + path = "animation/AnimeSchizo.png", + atlas_table = "ANIMATION_ATLAS", + frames = 12, + px = 71, + py = 95, +}) +SMODS.Atlas({ + key = "animebao", + path = "animation/AnimeBao.png", + atlas_table = "ANIMATION_ATLAS", + frames = 6, + px = 71, + py = 95, +}) +SMODS.Atlas({ + key = "animeplasma", + path = "animation/AnimePlasma.png", + atlas_table = "ANIMATION_ATLAS", + frames = 6, + px = 71, + py = 95, +}) SMODS.Atlas({ key = "ARG_cards", px = 71, diff --git a/content/objects/blind.lua b/content/objects/blind.lua index 84c9beb..3c269d8 100644 --- a/content/objects/blind.lua +++ b/content/objects/blind.lua @@ -16,19 +16,28 @@ SMODS.Blind({ vars = {}, boss_colour = HEX("F5DD8A"), press_play = function(self) - if G.GAME.current_round.hands_played == 0 and #G.jokers.cards > 0 then + if + G + and G.GAME + and G.GAME.current_round + and G.jokers + and G.jokers.cards + and G.GAME.current_round.hands_played == 0 + and #G.jokers.cards > 0 + then + local rightmost_joker = G.jokers.cards[#G.jokers.cards] + local edition_key = rightmost_joker and rightmost_joker.edition and rightmost_joker.edition.key or nil local sticks = {} for _, sticker in ipairs(SMODS.Sticker.obj_buffer) do - if G.jokers.cards[#G.jokers.cards].ability[sticker] then + if rightmost_joker and rightmost_joker.ability[sticker] then sticks[#sticks + 1] = sticker end end - SMODS.destroy_cards(G.jokers.cards[#G.jokers.cards], true) + SMODS.destroy_cards(rightmost_joker, true) local joker = SMODS.add_card({ - set = "joker", + set = "Joker", key = "j_xdx|", - edition = G.jokers.cards[#G.jokers.cards].edition and G.jokers.cards[#G.jokers.cards].edition.key - or nil, + edition = edition_key, }) for _, sticker in ipairs(SMODS.Sticker.obj_buffer) do if joker.ability[sticker] then @@ -51,7 +60,7 @@ SMODS.Blind({ }, }, atlas = "neuroblinds", - pos = { y = 0 }, + pos = { y = 1 }, discovered = true, mult = 2, boss = { min = 2 }, @@ -64,6 +73,17 @@ SMODS.Blind({ return false end, }) + +local function clear_chatspam_debuffs() + local hiyori = Neuratro.has_joker("j_hiyori") + for _, v in ipairs(G.playing_cards or {}) do + SMODS.debuff_card(v, false, "chatspam") + if v:is_suit("Hearts") and hiyori then + SMODS.debuff_card(v, true, "filter") + end + end +end + SMODS.Blind({ key = "chatspam", loc_txt = { @@ -74,57 +94,25 @@ SMODS.Blind({ }, }, atlas = "neuroblinds", - pos = { y = 0 }, + pos = { y = 2 }, discovered = true, mult = 2, boss = { min = 3 }, loc_vars = function(self) - return { vars = { G.GAME.probabilities.normal } } + return { vars = { Neuratro.get_probability_scale() } } end, boss_colour = HEX("F5DD8A"), set_blind = function(self) - for _, pcard in ipairs(G.playing_cards) do - if pseudorandom("chatspam") <= G.GAME.probabilities.normal / 6 then + for _, pcard in ipairs(G.playing_cards or {}) do + if Neuratro.roll_simple_odds(6, "chatspam") then SMODS.debuff_card(pcard, true, "chatspam") end end end, disable = function(self) - local hiyori = false - for _, joker in ipairs(G.jokers.cards) do - if joker.config.center.key == "j_hiyori" then - hiyori = true - end - end - for _, joker in ipairs(G.playbook_extra.cards) do - if joker.config.center.key == "j_hiyori" then - hiyori = true - end - end - for _, v in ipairs(G.playing_cards) do - SMODS.debuff_card(v, false, "chatspam") - if v:is_suit("Hearts") and hiyori then - SMODS.debuff_card(v, true, "filter") - end - end + clear_chatspam_debuffs() end, defeat = function(self) - local hiyori = false - for _, joker in ipairs(G.jokers.cards) do - if joker.config.center.key == "j_hiyori" then - hiyori = true - end - end - for _, joker in ipairs(G.playbook_extra.cards) do - if joker.config.center.key == "j_hiyori" then - hiyori = true - end - end - for _, v in ipairs(G.playing_cards) do - SMODS.debuff_card(v, false, "chatspam") - if v:is_suit("Hearts") and hiyori then - SMODS.debuff_card(v, true, "filter") - end - end + clear_chatspam_debuffs() end, }) diff --git a/content/objects/consumables.lua b/content/objects/consumables.lua index 296aee1..8c60e3d 100644 --- a/content/objects/consumables.lua +++ b/content/objects/consumables.lua @@ -62,7 +62,7 @@ SMODS.Consumable({ set = "Tarot", discovered = false, atlas = "neuroCons", - pos = { x = 1, y = 0 }, + pos = { x = 2, y = 0 }, config = { max_highlighted = 1, mod_conv = "m_blood" }, loc_vars = function(self, info_queue, card) info_queue[#info_queue + 1] = G.P_CENTERS[card.ability.mod_conv] @@ -194,7 +194,7 @@ SMODS.Consumable({ SMODS.Consumable.process_loc_text(self) G.localization.descriptions[self.set][self.key].text = target_text end, - generate_ui = 0, + loc_txt = { name = "Io", }, @@ -218,7 +218,7 @@ SMODS.Consumable({ SMODS.Consumable.process_loc_text(self) G.localization.descriptions[self.set][self.key].text = target_text end, - generate_ui = 0, + loc_txt = { name = "Europa", }, @@ -242,7 +242,7 @@ SMODS.Consumable({ SMODS.Consumable.process_loc_text(self) G.localization.descriptions[self.set][self.key].text = target_text end, - generate_ui = 0, + loc_txt = { name = "Ganymede", }, @@ -266,7 +266,7 @@ SMODS.Consumable({ SMODS.Consumable.process_loc_text(self) G.localization.descriptions[self.set][self.key].text = target_text end, - generate_ui = 0, + loc_txt = { name = "Callisto", }, diff --git a/content/objects/decks.lua b/content/objects/decks.lua index 34a7d1f..b5a66e8 100644 --- a/content/objects/decks.lua +++ b/content/objects/decks.lua @@ -74,6 +74,19 @@ SMODS.Back({ }, G.deck, nil, nil, { G.C.SECONDARY_SET.Enhanced }) _card:set_ability(G.P_CENTERS["m_glorp"], nil, true) SMODS.change_base(_card, "Glorpsuit") + -- Check if Cerber joker exists and apply Negative to 2s + local has_cerber = false + if G and G.jokers and G.jokers.cards then + for _, joker in ipairs(G.jokers.cards) do + if joker.config and joker.config.center and joker.config.center.key == "j_cerber" then + has_cerber = true + break + end + end + end + if has_cerber and _rank == "2" then + _card:set_edition("e_negative", true) + end G.E_MANAGER:add_event(Event({ func = function() return true diff --git a/content/objects/editions.lua b/content/objects/editions.lua index 089985f..ca14c43 100644 --- a/content/objects/editions.lua +++ b/content/objects/editions.lua @@ -1,3 +1,9 @@ +local function handle_filtersister_trigger() + for _, joker in ipairs(Neuratro.find_jokers("j_filtersister")) do + joker.ability.extra.xmult = joker.ability.extra.xmult + joker.ability.extra.upg + end +end + SMODS.Edition({ shader = "filtered", key = "filtered", @@ -28,36 +34,51 @@ SMODS.Edition({ return true end, calculate = function(self, card, context) - if context.repetition and context.cardarea == (G.play or G.hand or G.deck) and context.other_card == card then - if pseudorandom("filter", 1, 2) == 1 then - for _, area in ipairs({ G.jokers.cards, G.playbook_extra.cards }) do - for pos, joker in ipairs(area) do - if joker.config.center.key == "j_filtersister" then - area[pos].ability.extra.xmult = area[pos].ability.extra.xmult + area[pos].ability.extra.upg - end - end - end - SMODS.debuff_card(card, true, "filter") - return { repetitions = 0, message = "[Filtered]" } - end - return { repetitions = 1 } - end - if context.retrigger_joker_check and not context.retrigger_joker and context.other_card == card then - if pseudorandom("filter", 1, 2) == 1 then - for _, area in ipairs({ G.jokers.cards, G.playbook_extra.cards }) do - for pos, joker in ipairs(area) do - if joker.config.center.key == "j_filtersister" then - area[pos].ability.extra.xmult = area[pos].ability.extra.xmult + area[pos].ability.extra.upg - end - end - end + local is_retrigger = (context.repetition and (context.cardarea == G.play or context.cardarea == G.hand or context.cardarea == G.deck)) + or (context.retrigger_joker_check and not context.retrigger_joker) + + if is_retrigger and context.other_card == card then + if Neuratro.coin_flip("filter") then + handle_filtersister_trigger() SMODS.debuff_card(card, true, "filter") return { repetitions = 0, message = "[Filtered]" } end - return { repetitions = 1, message = "Again!" } + local msg = context.retrigger_joker_check and "Again!" or nil + return { repetitions = 1, message = msg } end end, - on_apply = function(card) --adds randomness to the filtered effect so they aren't uniform + on_apply = function(card) card.edition.filtered_seed = (pseudorandom("filtered_seed") * 2 - 1) * 1000 end, }) + +SMODS.Edition({ + key = "angelic", + loc_txt = { + name = "Angelic", + label = "Angelic", + text = { + "{C:mult}+3{} Mult", + "{C:chips}+30{} Chips", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + shader = "foil", + in_shop = true, + weight = 15, + extra_cost = 2, + discovered = false, + badge_colour = HEX("FFD700"), + in_pool = function(self, args) + return true + end, + calculate = function(self, card, context) + if context.main_scoring and context.cardarea == G.play then + return { mult = 3, chips = 30 } + end + end, +}) diff --git a/content/objects/enhancements.lua b/content/objects/enhancements.lua index bf457f8..4788dbc 100644 --- a/content/objects/enhancements.lua +++ b/content/objects/enhancements.lua @@ -1,4 +1,11 @@ --Enhancement +local sea = Neuratro.sea + +-- DEPRECATED: Use Neuratro.get_probability_scale() instead +local function enhancement_probability_scale() + return Neuratro.get_probability_scale() +end + SMODS.Enhancement({ key = "m_twin", loc_txt = { @@ -29,29 +36,9 @@ SMODS.Enhancement({ config = { extra = { money = 2 } }, calculate = function(self, card, context) if context.main_scoring and context.cardarea == G.play then - local msg = false - local msg_pos = 1 - local area = nil - for pos, joker in ipairs(G.jokers.cards) do - if joker.config.center.key == "j_highlighted" and not joker.debuff then - msg = true - msg_pos = pos - area = joker.area - break - end - end - if not msg then - for pos, joker in ipairs(G.playbook_extra.cards) do - if joker.config.center.key == "j_highlighted" and not joker.debuff then - msg = true - msg_pos = pos - area = joker.area - break - end - end - end - if msg then - return { xmult = area.cards[msg_pos].ability.extra.xmult } + local highlighted = Neuratro.find_joker_undebuffed("j_highlighted") + if highlighted then + return { xmult = highlighted.ability.extra.xmult } else return { dollars = card.ability.extra.money } end @@ -71,8 +58,7 @@ SMODS.Enhancement({ pos = { x = 2, y = 0 }, weight = 0, in_pool = function(self, args) - args = false - return + return false end, calculate = function(self, card, context) if context.before then @@ -98,30 +84,31 @@ SMODS.Enhancement({ pos = { x = 6, y = 1 }, config = { extra = { base = 1, odds = 3 } }, loc_vars = function(self, info_queue, card) - return { vars = { card.ability.extra.base * (G.GAME.probabilities.normal or 1), card.ability.extra.odds } } + return { vars = { card.ability.extra.base * enhancement_probability_scale(), card.ability.extra.odds } } end, calculate = function(self, card, context) if context.after then + local full_hand = context.full_hand or {} local trg = false - for pos, pcard in ipairs(context.full_hand) do + for pos, pcard in ipairs(full_hand) do + local safe_odds = tonumber(card.ability.extra.odds) or 0 + local chance = safe_odds > 0 and (card.ability.extra.base * enhancement_probability_scale()) / safe_odds or 0 if pcard == card and pseudorandom("blood") - < (card.ability.extra.base * (G.GAME.probabilities.normal or 1)) / card.ability.extra.odds + < math.min(chance, 1) then - if context.full_hand[pos - 1] then + local left_card = full_hand[pos - 1] + if left_card then sea(function() - if context.full_hand[pos - 1] then - context.full_hand[pos - 1]:set_ability(G.P_CENTERS["m_blood"], nil, true) - end + left_card:set_ability(G.P_CENTERS["m_blood"], nil, true) return true end) end - if context.full_hand[pos + 1] then + local right_card = full_hand[pos + 1] + if right_card then sea(function() - if context.full_hand[pos + 1] then - context.full_hand[pos + 1]:set_ability(G.P_CENTERS["m_blood"], nil, true) - end + right_card:set_ability(G.P_CENTERS["m_blood"], nil, true) return true end) end @@ -139,7 +126,7 @@ SMODS.Seal({ key = "shoomiminion_seal", loc_txt = { name = "Shoominion seal", - label = "Shominion seal", + label = "Shoomiminion seal", text = { "When this card is {C:attention}destroyed{},", "create {C:attention}two{} copies of it", @@ -154,8 +141,6 @@ SMODS.Seal({ calculate = function(self, card, context) if context.remove_playing_cards then for i = 1, #context.removed do - local _suit = context.removed[i].base.suit - local _rank = tostring(context.removed[i]:get_id()) if context.removed[i].seal == "shoomiminion_seal" and context.removed[i] == card then for l = 1, 2 do local copy_card = copy_card(context.removed[i], nil, nil, G.playing_card) @@ -199,7 +184,7 @@ SMODS.Seal({ end, calculate = function(self, card, context) if context.before then - for _, pcard in ipairs(context.full_hand) do + for _, pcard in ipairs(context.full_hand or {}) do if pcard == card then card.ability.seal.mult = card.ability.seal.mult + card.ability.seal.upg return { message = "Upgrade!" } @@ -245,27 +230,9 @@ SMODS.Enhancement:take_ownership("m_steel", { end, calculate = function(self, card, context) if context.main_scoring and context.cardarea == G.hand then - local pipes = false - local pipes_pos = 1 - local area = nil - for pos, joker in ipairs(G.jokers.cards) do - if joker.config.center.key == "j_pipes" and not joker.debuff then - pipes = true - pipes_pos = pos - area = joker.area - break - end - end - for pos, joker in ipairs(G.playbook_extra.cards) do - if joker.config.center.key == "j_pipes" and not joker.debuff then - pipes = true - pipes_pos = pos - area = joker.area - break - end - end + local pipes = Neuratro.find_joker_undebuffed("j_pipes") if pipes then - return { xmult = area.cards[pipes_pos].ability.extra.xmult, message = "Pipe!", sound = "pipe_sfx" } + return { xmult = pipes.ability.extra.xmult, message = "Pipe!", sound = "pipe_sfx" } else return { xmult = card.ability.extra.xmult } end @@ -280,12 +247,9 @@ SMODS.Enhancement:take_ownership("m_stone", { replace_base_card = true, calculate = function(self, card, context) if context.main_scoring and context.cardarea == G.play then - for _, area in ipairs({ G.jokers.cards, G.playbook_extra.cards }) do - for _, joker in ipairs(area) do - if joker.config.center.key == "j_breadge" then - return { mult = joker.ability.extra.mult or 21 } - end - end + local breadge = Neuratro.find_joker("j_breadge") + if breadge then + return { mult = breadge.ability.extra.mult or 21 } end return { chips = card.ability.extra.chips } end diff --git a/content/objects/handtype.lua b/content/objects/handtype.lua index 5adbc94..b22e6af 100644 --- a/content/objects/handtype.lua +++ b/content/objects/handtype.lua @@ -4,6 +4,10 @@ SMODS.PokerHandPart({ func = function(hand) -- calculate threshold local threshold = 5 + local suit_keys = {} + for suit, _ in pairs(SMODS.Suits) do + suit_keys[#suit_keys + 1] = suit + end if next(SMODS.find_card("j_fourtoes")) then sendDebugMessage("Four Toes detected, threshold = 4", "part._mix") threshold = 4 @@ -12,14 +16,13 @@ SMODS.PokerHandPart({ if #hand < threshold then return {} end - --sendDebugMessage("----------------", "part._mix") -- find all suits that can apply to each card local applicable_suits = {} for i = 1, #hand do if not SMODS.has_enhancement(hand[i], "m_wild") then -- optimization #1: place non-wild cards before wild cards to minimize backtracking later on local index = #applicable_suits + 1 applicable_suits[index] = {} - for suit, _ in pairs(SMODS.Suits) do + for _, suit in ipairs(suit_keys) do if hand[i]:is_suit(suit, true) then applicable_suits[index][#applicable_suits[index] + 1] = suit end @@ -30,14 +33,13 @@ SMODS.PokerHandPart({ if SMODS.has_enhancement(hand[i], "m_wild") then local index = #applicable_suits + 1 applicable_suits[index] = {} - for suit, _ in pairs(SMODS.Suits) do + for _, suit in ipairs(suit_keys) do if hand[i]:is_suit(suit, true) then applicable_suits[index][#applicable_suits[index] + 1] = suit end end end end - --sendDebugMessage("applicable_suits: " .. inspectDepth(applicable_suits), "part._mix") -- backtrack through all possible suit interpretations and get max num of unique suits among all of them local function get_max_unique(applicable_suits, suits, i, count) -- due to early pruning the number is a little low sometimes but it always gets it right if hand contains a mix if i > #hand then @@ -48,32 +50,26 @@ SMODS.PokerHandPart({ local suit = applicable_suits[i][j] local is_new_suit = false if not suit then - --sendDebugMessage("[i,j] = [" .. tostring(i) .. "," .. tostring(j) .. "]: no suit", "part._mix") elseif suits[suit] then - --sendDebugMessage("[i,j] = [" .. tostring(i) .. "," .. tostring(j) .. "]: suit \"" .. tostring(suit) .. "\" already taken", "part._mix") end if suit and not suits[suit] then is_new_suit = true suits[suit] = true - --sendDebugMessage("[i,j] = [" .. tostring(i) .. "," .. tostring(j) .. "]: set suit[" .. tostring(suit) .. "] to " .. tostring(suits[suit]), "part._mix") count = count + 1 end local repeats = i - count if #hand - repeats < threshold then -- optimization #2: prunes this branch of the function once it detects the num of repeat suits is too high - --sendDebugMessage("[i,j] = [" .. tostring(i) .. "," .. tostring(j) .. "]: pruning branch (repeats = " .. tostring(repeats) .. ")", "part._mix") goto continue end local local_max = get_max_unique(applicable_suits, suits, i + 1, count) if local_max > max_count then max_count = local_max if max_count >= threshold then -- optimization #3: shortcuts execution once it detects max_count is high - --sendDebugMessage("[i,j] = [" .. tostring(i) .. "," .. tostring(j) .. "]: shortcut execution (max_count) = " .. tostring(max_count) .. ")", "part._mix") return max_count end end if is_new_suit then suits[suit] = false - --sendDebugMessage("[i,j] = [" .. tostring(i) .. "," .. tostring(j) .. "]: set suit[" .. tostring(suit) .. "] to " .. tostring(suits[suit]), "part._mix") count = count - 1 end ::continue:: @@ -83,7 +79,6 @@ SMODS.PokerHandPart({ -- check local suits_var = {} local unique_suits = get_max_unique(applicable_suits, suits_var, 1, 0) - --sendDebugMessage("there are " .. unique_suits .. " unique suits", "part._mix") if unique_suits >= threshold then return { hand } end diff --git a/content/objects/jokers.lua b/content/objects/jokers.lua index be8a342..18b4aa8 100644 --- a/content/objects/jokers.lua +++ b/content/objects/jokers.lua @@ -58,10 +58,57 @@ local neuro_jokers = { "j_shoomimi", "j_plush", "j_vedalsdrink", - "j_plush", "j_filtersister", "j_envy", + "j_koko", + "j_cerber", + "j_chimps", + "j_bwaa", + "j_coldfish", + "j_coldfish_unleashed", + "j_paulamarina", + "j_toma", + "j_tomaniacs", + "j_angel_neuro", + "j_neuro_issues", } + +local PLAYBOOK_MAX_CARD_LIMIT = 64 + +local function probability_scale() + return (G and G.GAME and G.GAME.probabilities and G.GAME.probabilities.normal) or 1 +end + +local function roll_with_odds(seed, base, odds) + local safe_odds = tonumber(odds) or 0 + if safe_odds <= 0 then + return false + end + local chance = (math.max(0, base or 0) * probability_scale()) / safe_odds + if chance <= 0 then + return false + end + chance = math.min(chance, 1) + return pseudorandom(seed) <= chance +end + +local function set_queenpb_pool_flags(song) + if not (G and G.GAME and G.GAME.pool_flags) then + return + end + G.GAME.pool_flags.BOOM = song == "BOOM" + G.GAME.pool_flags.LIFE = song == "LIFE" + G.GAME.pool_flags.NEVER = song == "NEVER" +end + +local function joker_cards() + return G and G.jokers and G.jokers.cards or {} +end + +local function playbook_cards() + return G and G.playbook_extra and G.playbook_extra.cards or {} +end + --Neuro related SMODS.Joker({ key = "3heart", @@ -106,8 +153,9 @@ SMODS.Joker({ and not context.blueprint and not context.retrigger_joker then + local scoring_hand = context.scoring_hand or {} local upgs = true - for _, playing_card in ipairs(context.scoring_hand) do + for _, playing_card in ipairs(scoring_hand) do if not playing_card:is_suit("Hearts") then upgs = false break @@ -122,7 +170,7 @@ SMODS.Joker({ return { xmult = card.ability.extra.Xmult } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -160,7 +208,7 @@ SMODS.Joker({ G.hand:change_size(1) end, remove_from_deck = function(self, card, from_debuff) - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_evilsand" and card.area ~= G.playbook_extra then return end @@ -172,6 +220,7 @@ SMODS.Joker({ context.individual and context.cardarea == G.hand and not context.end_of_round + and context.other_card and context.other_card:get_id() == 14 then if context.other_card:is_suit("Hearts") then @@ -265,7 +314,7 @@ SMODS.Joker({ } end, calculate = function(self, card, context) - if context.buying_card or context.open_booster and not context.retrigger_joker then + if (context.buying_card or context.open_booster) and not context.retrigger_joker then card.ability.extra.current = card.ability.extra.current + context.card.cost end if context.end_of_round and G.GAME.current_round.free_rerolls == 0 and not context.retrigger_joker then @@ -325,8 +374,6 @@ SMODS.Joker({ if playing_card.seal then inpool = true break - else - inpool = false end end return inpool @@ -335,34 +382,31 @@ SMODS.Joker({ loc_vars = function(self, info_queue, center) return { vars = { - center.ability.extra.base * (G.GAME and G.GAME.probabilities.normal or 1), + center.ability.extra.base * (G.GAME and G.GAME.probabilities and G.GAME.probabilities.normal or 1), center.ability.extra.odds, }, } end, calculate = function(self, card, context) if context.before then - for pos, v in pairs(context.full_hand) do - if - context.full_hand[#context.full_hand + 1 - pos].seal - and context.full_hand[#context.full_hand - pos + 2] ~= nil - and pseudorandom("anteater") - < ((G.GAME and G.GAME.probabilities.normal or 1) * card.ability.extra.base) / self.config.extra.odds - then + local full_hand = context.full_hand or {} + for pos, _ in ipairs(full_hand) do + local source_idx = #full_hand + 1 - pos + local source_card = full_hand[source_idx] + local next_card = full_hand[source_idx + 1] + if source_card and source_card.seal and next_card and roll_with_odds("anteater", card.ability.extra.base, self.config.extra.odds) then sea(function() - context.full_hand[#context.full_hand - pos + 2]:flip() - context.full_hand[#context.full_hand - pos + 2]:juice_up(0.3, 0.3) + next_card:flip() + next_card:juice_up(0.3, 0.3) return true end, 0.35, "before") + sea(function() + next_card:set_seal(source_card.seal) + return true + end, 0.55, "before") sea(function() - context.full_hand[#context.full_hand - pos + 2]:set_seal( - context.full_hand[#context.full_hand + 1 - pos].seal - ) - return true - end, 0.55, "before") - sea(function() - context.full_hand[#context.full_hand - pos + 2]:flip() - context.full_hand[#context.full_hand - pos + 2]:juice_up(0.3, 0.3) + next_card:flip() + next_card:juice_up(0.3, 0.3) return true end, 0.35, "before") end @@ -525,18 +569,15 @@ SMODS.Joker({ loc_vars = function(self, info_queue, center) return { vars = { - G.GAME.probabilities.normal * center.ability.extra.base, + (G.GAME and G.GAME.probabilities and G.GAME.probabilities.normal or 1) * center.ability.extra.base, center.ability.extra.odds, center.ability.extra.xmult, }, } end, calculate = function(self, card, context) - if context.before and context.cardarea == G.jokers and G.GAME.current_round.discards_left > 0 then - if - pseudorandom("seed") - <= (G.GAME.probabilities.normal * card.ability.extra.base) / card.ability.extra.odds - then + if context.before and context.cardarea == G.jokers and not context.blueprint and not context.retrigger_joker and G.GAME and G.GAME.current_round and G.GAME.current_round.discards_left > 0 then + if roll_with_odds("seed", card.ability.extra.base, card.ability.extra.odds) then G.GAME.current_round.discards_left = 0 return { message = "Harpooned!" } end @@ -545,7 +586,7 @@ SMODS.Joker({ return { Xmult = card.ability.extra.xmult } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -591,13 +632,13 @@ SMODS.Joker({ return { message = "Upgrade!" } end end - if context.individual and context.cardarea == G.play then + if context.individual and context.cardarea == G.play and context.other_card then if context.other_card:is_suit("Spades") and context.other_card:get_id() == 13 then return { xmult = card.ability.extra.Xmult_bonus } end end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -650,7 +691,9 @@ SMODS.Joker({ card.ability.extra.face = card.ability.extra.face + 1 if card.ability.extra.face >= card.ability.extra.goal then SMODS.destroy_cards(card) - G.GAME.pool_flags.trauma = true + if G and G.GAME and G.GAME.pool_flags then + G.GAME.pool_flags.trauma = true + end end upgraded = true end @@ -661,7 +704,7 @@ SMODS.Joker({ end end, in_pool = function(self, args) - return not G.GAME.pool_flags.trauma + return G and G.GAME and G.GAME.pool_flags and not G.GAME.pool_flags.trauma end, }) SMODS.Joker({ @@ -711,7 +754,7 @@ SMODS.Joker({ end end, in_pool = function(self, args) - return G.GAME.pool_flags.trauma + return G and G.GAME and G.GAME.pool_flags and G.GAME.pool_flags.trauma end, }) SMODS.Joker({ @@ -720,7 +763,7 @@ SMODS.Joker({ name = "PIPES", text = { "{C:attention}Steel cards{} give {X:mult,C:white}x#1#{} mult instead.", - "Leftmost card {C:atention}held{} in hand", + "Leftmost card {C:attention}held{} in hand", "becomes a {C:attention}steel card{}", "{C:red,E:1,s:0.9}Steel cards play a metal pipe sound.{}", }, @@ -748,19 +791,20 @@ SMODS.Joker({ return { vars = { center.ability.extra.xmult } } end, calculate = function(self, card, context) - if context.before and #G.hand.cards > 0 then + if context.before and G.hand and G.hand.cards and G.hand.cards[1] then + local first_hand_card = G.hand.cards[1] sea(function() - G.hand.cards[1]:flip() - G.hand.cards[1]:juice_up(0.3, 0.3) + first_hand_card:flip() + first_hand_card:juice_up(0.3, 0.3) return true end, 0.35, "before") sea(function() - G.hand.cards[1]:set_ability(G.P_CENTERS["m_steel"]) + first_hand_card:set_ability(G.P_CENTERS["m_steel"]) return true end, 0.55, "before") sea(function() - G.hand.cards[1]:flip() - G.hand.cards[1]:juice_up(0.3, 0.3) + first_hand_card:flip() + first_hand_card:juice_up(0.3, 0.3) return true end, 0.35, "before") end @@ -796,7 +840,7 @@ SMODS.Joker({ loc_vars = function(self, info_queue, center) return { vars = { - center.ability.extra.base * (G.GAME and G.GAME.probabilities.normal or 1), + center.ability.extra.base * probability_scale(), center.ability.extra.odds, center.ability.extra.upg, center.ability.extra.xmult, @@ -810,8 +854,8 @@ SMODS.Joker({ if context.individual and context.cardarea == G.play - and pseudorandom("seed") - <= ((G.GAME and G.GAME.probabilities.normal or 1) * card.ability.extra.base) / self.config.extra.odds + and context.other_card + and roll_with_odds("seed", card.ability.extra.base, self.config.extra.odds) then SMODS.destroy_cards(context.other_card) card.ability.extra.xmult = card.ability.extra.xmult + card.ability.extra.upg @@ -867,21 +911,20 @@ SMODS.Joker({ calculate = function(self, card, context) if context.joker_main then local activate = false + local full_hand = context.full_hand or {} if card.ability.extra.cycle == 1 then - activate = #context.full_hand == 2 - and context.full_hand[1]:get_id() == 9 - and context.full_hand[2]:get_id() == 9 + activate = #full_hand == 2 and full_hand[1] and full_hand[2] and full_hand[1]:get_id() == 9 and full_hand[2]:get_id() == 9 elseif card.ability.extra.cycle == 2 then - activate = #context.full_hand == 1 and context.full_hand[1]:get_id() == 9 + activate = #full_hand == 1 and full_hand[1] and full_hand[1]:get_id() == 9 elseif card.ability.extra.cycle == 3 then - activate = #context.full_hand == 1 and context.full_hand[1]:get_id() == 6 + activate = #full_hand == 1 and full_hand[1] and full_hand[1]:get_id() == 6 elseif card.ability.extra.cycle == 4 then - activate = #context.full_hand == 1 and context.full_hand[1]:get_id() == 7 + activate = #full_hand == 1 and full_hand[1] and full_hand[1]:get_id() == 7 elseif card.ability.extra.cycle == 0 then - if #context.full_hand == 4 then + if #full_hand == 4 then local fours = 0 local fives = 0 - for _key, val in ipairs(context.full_hand) do + for _key, val in ipairs(full_hand) do if val:get_id() == 4 then fours = fours + 1 end @@ -943,13 +986,13 @@ SMODS.Joker({ art = { "Tony7268" }, code = { "PaulaMarina" }, }, - atlas = "neuroCustomJokers", + atlas = "animeplasma", pools = { ["neurJoker"] = true }, blueprint_compat = true, perishable_compat = false, rarity = 2, cost = 6, - pos = { x = 9, y = 6 }, + pos = { x = 0, y = 0 }, config = { extra = { Xmult = 1, Xmult_mod = 0.75 } }, loc_vars = function(self, info_queue, card) return { vars = { card.ability.extra.Xmult_mod, card.ability.extra.Xmult } } @@ -998,7 +1041,7 @@ SMODS.Joker({ blueprint_compat = true, eternal_compat = true, perishable_compat = false, - pos = { x = 1, y = 0 }, + pos = { x = 7, y = 8 }, config = { extra = { chip_bonus = 9 } }, loc_vars = function(self, info_queue, center) return { @@ -1017,7 +1060,7 @@ SMODS.Joker({ } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -1051,20 +1094,18 @@ SMODS.Joker({ end, calculate = function(self, card, context) - if context.repetition and context.cardarea == G.play then + if context.repetition and context.cardarea == G.play and context.other_card then if SMODS.has_enhancement(context.other_card, "m_twin") then return { repetitions = 1 } end end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) local inpool = false for _, cards in ipairs(G.playing_cards) do if SMODS.has_enhancement(cards, "m_twin") then inpool = true break - else - inpool = false end end return inpool @@ -1112,7 +1153,7 @@ SMODS.Joker({ return { mult = card.ability.extra.mult } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -1157,7 +1198,7 @@ SMODS.Joker({ return { message = "Reset" } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -1218,9 +1259,10 @@ SMODS.Joker({ return { main_start = main_start } end, calculate = function(self, card, context) - if context.joker_main then + if context.joker_main and not context.retrigger_joker then + local scoring_hand = context.scoring_hand or {} local breaking = false - for _, playing_card in ipairs(context.scoring_hand) do + for _, playing_card in ipairs(scoring_hand) do if playing_card:get_id() == 13 and playing_card:is_suit("Clubs") then breaking = true end @@ -1228,7 +1270,7 @@ SMODS.Joker({ if breaking then return { chips = pseudorandom("rum", card.ability.extra.chips_min, card.ability.extra.chips_max) * 3, - func = SMODS.destroy_cards(card), + func = function() SMODS.destroy_cards(card) return true end, message = "Drunk!", } else @@ -1265,7 +1307,7 @@ SMODS.Joker({ config = { extra = { chips = 35 } }, loc_vars = function(self, info_queue, center) local n_ace = 0 - if G.playing_card then + if G.playing_cards then for _, playing_card in ipairs(G.playing_cards) do if playing_card:get_id() == 14 and playing_card:is_suit("Clubs") then n_ace = n_ace + 1 @@ -1368,7 +1410,7 @@ SMODS.Joker({ return { xmult = card.ability.extra.xmult } end end - if context.end_of_round and context.cardarea == G.jokers then + if context.end_of_round and context.cardarea == G.jokers and not context.retrigger_joker then card.ability.extra.rounds = card.ability.extra.rounds + 1 if card.ability.extra.rounds >= card.ability.extra.goal then SMODS.destroy_cards(card) @@ -1385,7 +1427,7 @@ SMODS.Joker({ name = "Abandoned Archive 2", text = { "When a joker is {C:attention}sold{}, gain mult", - "{C:attention}equal{} to it's {C:attention}sell value{}.", + "{C:attention}to its {C:attention}sell value{}.", "{C:inactive}(Currently {C:mult}+#1# {C:inactive}mult)", }, }, @@ -1503,8 +1545,9 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.after and not context.blueprint then + local scoring_hand = context.scoring_hand or {} card.ability.extra.no_bit_hands = card.ability.extra.no_bit_hands + 1 - for _k, val in ipairs(context.scoring_hand) do + for _k, val in ipairs(scoring_hand) do if SMODS.has_enhancement(val, "m_dono") then card.ability.extra.gain = card.ability.extra.gain + card.ability.extra.upg card.ability.extra.no_bit_hands = 0 @@ -1517,7 +1560,7 @@ SMODS.Joker({ end end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -1594,11 +1637,13 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.joker_main then + local full_hand = context.full_hand or {} + local scoring_hand = context.scoring_hand or {} card.ability.extra.mult_bonus = 0 - for i = 1, #context.full_hand do + for i = 1, #full_hand do card.ability.extra.mult_bonus = card.ability.extra.mult_bonus + card.ability.extra.percard end - for _, playing_card in ipairs(context.scoring_hand) do + for _, playing_card in ipairs(scoring_hand) do card.ability.extra.mult_bonus = card.ability.extra.mult_bonus - card.ability.extra.percard end return { mult = card.ability.extra.mult_bonus } @@ -1641,9 +1686,9 @@ SMODS.Joker({ if context.end_of_round and context.game_over == false and context.main_eval and not context.blueprint then local value = pseudorandom("seed") if value <= 0.55 then - card.ability.extra.price = pseudorandom("seed", 1, card.ability.extra.down) * -1 + card.ability.extra.price = pseudorandom("stocks_down", 1, card.ability.extra.down) * -1 else - card.ability.extra.price = pseudorandom("seed", 1, card.ability.extra.up) + card.ability.extra.price = pseudorandom("stocks_up", 1, card.ability.extra.up) end card.ability.extra_value = card.ability.extra_value + card.ability.extra.price card:set_cost() @@ -1689,6 +1734,7 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.joker_main then + local scoring_hand = context.scoring_hand or {} local vars = { ["Hearts"] = 0, ["Diamonds"] = 0, @@ -1696,66 +1742,66 @@ SMODS.Joker({ ["Clubs"] = 0, ["face"] = 0, } - for i = 1, #context.scoring_hand do - if not SMODS.has_any_suit(context.scoring_hand[i]) then + for i = 1, #scoring_hand do + if not SMODS.has_any_suit(scoring_hand[i]) then if - context.scoring_hand[i]:is_suit("Hearts", true) + scoring_hand[i]:is_suit("Hearts", true) and vars["Hearts"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Hearts"] = vars["Hearts"] + 1 elseif - context.scoring_hand[i]:is_suit("Diamonds", true) + scoring_hand[i]:is_suit("Diamonds", true) and vars["Diamonds"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Diamonds"] = vars["Diamonds"] + 1 elseif - context.scoring_hand[i]:is_suit("Spades", true) + scoring_hand[i]:is_suit("Spades", true) and vars["Spades"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Spades"] = vars["Spades"] + 1 elseif - context.scoring_hand[i]:is_suit("Clubs", true) + scoring_hand[i]:is_suit("Clubs", true) and vars["Clubs"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Clubs"] = vars["Clubs"] + 1 end - if context.scoring_hand[i]:is_face() and vars["face"] < 3 then + if scoring_hand[i]:is_face() and vars["face"] < 3 then vars["face"] = vars["face"] + 1 end end end - for i = 1, #context.scoring_hand do - if SMODS.has_any_suit(context.scoring_hand[i]) then + for i = 1, #scoring_hand do + if SMODS.has_any_suit(scoring_hand[i]) then if - context.scoring_hand[i]:is_suit("Hearts", true) + scoring_hand[i]:is_suit("Hearts", true) and vars["Hearts"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Hearts"] = vars["Hearts"] + 1 elseif - context.scoring_hand[i]:is_suit("Diamonds", true) + scoring_hand[i]:is_suit("Diamonds", true) and vars["Diamonds"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Diamonds"] = vars["Diamonds"] + 1 elseif - context.scoring_hand[i]:is_suit("Spades", true) + scoring_hand[i]:is_suit("Spades", true) and vars["Spades"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Spades"] = vars["Spades"] + 1 elseif - context.scoring_hand[i]:is_suit("Clubs", true) + scoring_hand[i]:is_suit("Clubs", true) and vars["Clubs"] == 0 - and context.scoring_hand[i]:is_face() + and scoring_hand[i]:is_face() then vars["Clubs"] = vars["Clubs"] + 1 end - if context.scoring_hand[i]:is_face() and vars["face"] < 3 then + if scoring_hand[i]:is_face() and vars["face"] < 3 then vars["face"] = vars["face"] + 1 end end @@ -1801,32 +1847,33 @@ SMODS.Joker({ end, pos = { x = 1, y = 0 }, calculate = function(self, card, context) - if context.repetition and context.cardarea == G.play then + if context.repetition and context.cardarea == G.play and context.other_card then if SMODS.has_enhancement(context.other_card, "m_stone") then return { repetitions = 1 } end end if context.joker_main then + local full_hand = context.full_hand or {} local nostones = true - local playing_card = context.full_hand[1] - for _, p_card in ipairs(context.full_hand) do + for _, p_card in ipairs(full_hand) do if SMODS.has_enhancement(p_card, "m_stone") then nostones = false end end - if nostones then + local first_play_card = G.play and G.play.cards and G.play.cards[1] + if nostones and first_play_card then sea(function() - G.play.cards[1]:flip() - G.play.cards[1]:juice_up(0.3, 0.3) + first_play_card:flip() + first_play_card:juice_up(0.3, 0.3) return true end, 0.35, "before") sea(function() - G.play.cards[1]:set_ability(G.P_CENTERS["m_stone"]) + first_play_card:set_ability(G.P_CENTERS["m_stone"]) return true end, 0.55, "before") sea(function() - G.play.cards[1]:flip() - G.play.cards[1]:juice_up(0.3, 0.3) + first_play_card:flip() + first_play_card:juice_up(0.3, 0.3) return true end, 0.35, "before") end @@ -1868,17 +1915,14 @@ SMODS.Joker({ return { vars = { center.ability.extra.chips, - center.ability.extra.base * G.GAME.probabilities.normal, + center.ability.extra.base * (G.GAME and G.GAME.probabilities and G.GAME.probabilities.normal or 1), center.ability.extra.odds, }, } end, calculate = function(self, card, context) if context.joker_main then - if - pseudorandom("seed") - <= (card.ability.extra.base * G.GAME.probabilities.normal) / card.ability.extra.odds - then + if roll_with_odds("seed", card.ability.extra.base, card.ability.extra.odds) then G.E_MANAGER:add_event(Event({ func = function() card:set_debuff(true) @@ -1994,7 +2038,7 @@ SMODS.Joker({ name = "Erm", text = { "If played hand is a {C:attention}High Card{},", - "{C:green}Randomize{} the rank, suit, and enhancement of all {C:attenion}scored cards{}.", + "{C:green}Randomize{} the rank, suit, and enhancement of all {C:attention}scored cards{}.", }, }, credits = { @@ -2017,7 +2061,8 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.before and context.scoring_name == "High Card" then - for _, pcard in ipairs(context.scoring_hand) do + local scoring_hand = context.scoring_hand or {} + for _, pcard in ipairs(scoring_hand) do local suit = pseudorandom_element({ "Hearts", "Diamonds", "Spades", "Clubs" }, pseudoseed("Erm")) local rank = pseudorandom_element( { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A" }, @@ -2062,7 +2107,12 @@ SMODS.Joker({ (context.setting_blind or context.pre_discard or context.after or context.open_booster) and not context.retrigger_joker then - local cards_added = 0 + (context.pre_discard and (#G.hand.cards - #context.full_hand) or #G.hand.cards or 0) + local hand_cards = G.hand and G.hand.cards or {} + local full_hand = context.full_hand or {} + if not (G.deck and G.deck.cards and G.hand and G.hand.config) then + return + end + local cards_added = context.pre_discard and math.max(#hand_cards - #full_hand, 0) or #hand_cards for _, pcard in ipairs(G.deck.cards) do if pcard:get_id() == 13 and pcard:is_suit("Diamonds") and cards_added < G.hand.config.card_limit then draw_card(G.deck, G.hand, 90, "up", nil, pcard) @@ -2101,14 +2151,14 @@ SMODS.Joker({ return { vars = { center.ability.extra.xmult } } end, calculate = function(self, card, context) - if context.repetition and context.cardarea == G.play and context.other_card:get_id() == 6 then + if context.repetition and context.cardarea == G.play and context.other_card and context.other_card:get_id() == 6 then return { repetitions = 1, card = card } end - if context.individual and context.cardarea == G.play and context.other_card:get_id() == 6 then + if context.individual and context.cardarea == G.play and context.other_card and context.other_card:get_id() == 6 then return { xmult = card.ability.extra.xmult } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -2136,23 +2186,19 @@ SMODS.Joker({ pos = { x = 8, y = 2 }, soul_pos = { x = 9, y = 2 }, calculate = function(self, card, context) - if context.discard and not context.blueprint then - if - #context.full_hand == 4 - and context.full_hand[1]:get_id() == 6 - and context.full_hand[2]:get_id() == 6 - and context.full_hand[3]:get_id() == 6 - and context.full_hand[4]:get_id() == 6 - then - end - end + local full_hand = context.full_hand or {} + if context.pre_discard - and #context.full_hand == 4 - and context.full_hand[1]:get_id() == 6 - and context.full_hand[2]:get_id() == 6 - and context.full_hand[3]:get_id() == 6 - and context.full_hand[4]:get_id() == 6 + and #full_hand == 4 + and full_hand[1] + and full_hand[2] + and full_hand[3] + and full_hand[4] + and full_hand[1]:get_id() == 6 + and full_hand[2]:get_id() == 6 + and full_hand[3]:get_id() == 6 + and full_hand[4]:get_id() == 6 and not context.blueprint then G.E_MANAGER:add_event(Event({ @@ -2219,7 +2265,7 @@ SMODS.Joker({ })) end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -2250,22 +2296,27 @@ SMODS.Joker({ perishable_compat = false, pos = { x = 4, y = 4 }, calculate = function(self, card, context) - if context.individual and context.cardarea == G.play and context.other_card:get_id() == 3 then - for key, playing_card in pairs(context.full_hand) do + local full_hand = context.full_hand or {} + if context.individual and context.cardarea == G.play and context.other_card and context.other_card:get_id() == 3 then + for key, playing_card in pairs(full_hand) do if playing_card == context.other_card then if key - 1 > 0 then + local left_card = full_hand[key - 1] + if not left_card then + break + end sea(function() - context.full_hand[key - 1]:flip() - context.full_hand[key - 1]:juice_up(0.3, 0.3) + left_card:flip() + left_card:juice_up(0.3, 0.3) return true end, 0.35, "before") sea(function() - SMODS.change_base(context.full_hand[key - 1], nil, "3") + SMODS.change_base(left_card, nil, "3") return true end, 0.55, "before") sea(function() - context.full_hand[key - 1]:flip() - context.full_hand[key - 1]:juice_up(0.3, 0.3) + left_card:flip() + left_card:juice_up(0.3, 0.3) return true end, 0.35, "before") end @@ -2367,7 +2418,7 @@ SMODS.Joker({ context.individual and context.cardarea == G.play and not context.blueprint - and not context.retrigger_joker + and context.retrigger_joker then if context.other_card then card.ability.extra.mult = card.ability.extra.mult + card.ability.extra.upg @@ -2396,14 +2447,15 @@ SMODS.Joker({ end end if context.joker_main then + local scoring_hand = context.scoring_hand or {} local debuffs = 0 - for _, playing_card in ipairs(context.scoring_hand) do + for _, playing_card in ipairs(scoring_hand) do if playing_card.debuff then debuffs = debuffs + 1 end end card.ability.extra.mult = card.ability.extra.mult - - (#context.scoring_hand * card.ability.extra.upg - debuffs * card.ability.extra.upg) + - (#scoring_hand * card.ability.extra.upg - debuffs * card.ability.extra.upg) return { mult = card.ability.extra.mult } end if context.end_of_round and context.cardarea == G.jokers then @@ -2448,19 +2500,20 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.repetition and context.cardarea == G.play then + local full_hand = context.full_hand or {} local cerbr = 0 local wans = 0 - for _, cards in ipairs(context.full_hand) do + for _, cards in ipairs(full_hand) do if cards:get_id() == 11 and cards:is_suit("Diamonds", true) then cerbr = cerbr + 1 end end - for _, cards in ipairs(G.hand.cards) do + for _, cards in ipairs(G.hand and G.hand.cards or {}) do if cards:get_id() == 2 then wans = wans + 1 end end - if context.other_card:get_id() == 11 and context.other_card:is_suit("Diamonds") and cerbr < wans then + if context.other_card and context.other_card:get_id() == 11 and context.other_card:is_suit("Diamonds") and cerbr < wans then return { repetitions = pseudorandom( "Milc", @@ -2505,8 +2558,8 @@ SMODS.Joker({ end, config = { extra = { reduce = 5 / 100 } }, calculate = function(self, card, context) - if context.before then - G.GAME.blind.chips = math.floor((G.GAME.blind.chips * (1 - card.ability.extra.reduce)) + 0.5) + if context.before and G.GAME and G.GAME.blind then + G.GAME.blind.chips = math.max(math.floor((G.GAME.blind.chips * (1 - card.ability.extra.reduce)) + 0.5), 1) G.GAME.blind.chip_text = tostring(G.GAME.blind.chips) return { message = "Backflip!" } end @@ -2553,14 +2606,14 @@ SMODS.Joker({ return { vars = { card.ability.extra.decrease * 100, percent * 100 } } end, calculate = function(self, card, context) - if context.setting_blind then + if G.GAME and G.GAME.blind and context.setting_blind then local percent = 0 for _, pcard in ipairs(G.playing_cards) do if pcard:get_id() == 8 then percent = percent + card.ability.extra.decrease end end - G.GAME.blind.chips = math.floor((G.GAME.blind.chips * (1 - percent)) + 0.5) + G.GAME.blind.chips = math.max(math.floor((G.GAME.blind.chips * (1 - percent)) + 0.5), 1) G.GAME.blind.chip_text = tostring(G.GAME.blind.chips) end end, @@ -2640,13 +2693,13 @@ SMODS.Joker({ loc_vars = function(self, info_queue, center) info_queue[#info_queue + 1] = G.P_CENTERS.j_neurodog local result = 1 - if G.jokers or false then - for _, joker in ipairs(G.jokers.cards) do + if G.jokers then + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_neurodog" then result = result + center.ability.extra.upg end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_neurodog" then result = result + center.ability.extra.upg end @@ -2654,7 +2707,7 @@ SMODS.Joker({ end return { vars = { - center.ability.extra.base * (G.GAME and G.GAME.probabilities.normal or 1), + center.ability.extra.base * probability_scale(), center.ability.extra.odds, center.ability.extra.upg, result, @@ -2664,12 +2717,12 @@ SMODS.Joker({ calculate = function(self, card, context) if context.joker_main then local result = 1 - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_neurodog" then result = result + card.ability.extra.upg end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_neurodog" then result = result + card.ability.extra.upg end @@ -2678,7 +2731,10 @@ SMODS.Joker({ end if context.setting_blind - and pseudorandom("seed") <= (G.GAME.probabilities.normal * card.ability.extra.base) / card.ability.extra.odds + and roll_with_odds("seed", card.ability.extra.base, card.ability.extra.odds) + and G.jokers + and G.jokers.cards + and G.jokers.config and #G.jokers.cards < G.jokers.config.card_limit then SMODS.add_card({ set = "Joker", area = G.jokers, key = "j_neurodog" }) @@ -2761,12 +2817,11 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.before then - for _, play_card in ipairs(context.scoring_hand) do + local scoring_hand = context.scoring_hand or {} + for _, play_card in ipairs(scoring_hand) do if play_card:get_id() == 9 then card.ability.extra.works = true return { message = "Active" } - else - card.ability.extra.works = false end end end @@ -2804,7 +2859,7 @@ SMODS.Joker({ blueprint_compat = true, eternal_compat = true, perishable_compat = true, - pos = { x = 1, y = 0 }, + pos = { x = 9, y = 9 }, in_pool = function(self, args) for _, pcard in ipairs(G.playing_cards) do if SMODS.has_enhancement(pcard, "m_blood") then @@ -2876,13 +2931,13 @@ SMODS.Joker({ end, in_pool = function(self, args) local inpool = false - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.edition and joker.edition.key == "e_negative" then inpool = true break end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.edition and joker.edition.key == "e_negative" then inpool = true break @@ -2966,7 +3021,7 @@ SMODS.Joker({ G.shared_seals["shoomiminion_seal"] = Sprite(0, 0, 71, 95, G.ASSET_ATLAS["neuroEnh"], { x = 8, y = 0 }) end, remove_from_deck = function(self, card, from_debuff) - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_shoomimi" or (joker.config.center.key == "j_evilsand" and card.area ~= G.playbook_extra) @@ -2974,7 +3029,7 @@ SMODS.Joker({ return end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_shoomimi" then return end @@ -2998,123 +3053,13 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.individual and context.cardarea == G.play then - if context.other_card.seal == "shoomiminion_seal" then + if context.other_card and context.other_card.seal == "shoomiminion_seal" then return { dollars = card.ability.extra.money } end end end, }) -SMODS.Joker({ - key = "layna", - loc_txt = { - name = "Layna", - text = { - "If scored hand has a {C:attention}9{},", - "all scored cards give {X:mult,C:white}x#1#{} mult", - "and {C:red,E:1}destroy{} themselves.", - }, - }, - credits = { - idea = { "1srscx4" }, - art = { "Etzyio+" }, - code = { "1srscx4" }, - }, - atlas = "neuroCustomJokers", - pools = { ["neurJoker"] = true }, - rarity = 3, - cost = 9, - unlocked = true, - discovered = false, - blueprint_compat = true, - eternal_compat = true, - perishable_compat = true, - pos = { x = 9, y = 3 }, - in_pool = function(self, args) - return true - end, - config = { extra = { xmult = 3, works = false } }, - loc_vars = function(self, info_queue, center) - return { vars = { center.ability.extra.xmult } } - end, - calculate = function(self, card, context) - if context.before then - for _, play_card in ipairs(context.scoring_hand) do - if play_card:get_id() == 9 then - card.ability.extra.works = true - return { message = "Active" } - else - card.ability.extra.works = false - end - end - end - if context.individual and context.cardarea == G.play and context.other_card and card.ability.extra.works then - return { - xmult = card.ability.extra.xmult, - func = function() - SMODS.destroy_cards(context.other_card) - end, - } - end - end, -}) -SMODS.Joker({ - key = "cakelayna", - loc_txt = { - name = "Abomnination Cake", - text = { - "Gives {C:mult}+#1#{} Mult for each", - "{C:attention}Bloody card{} in {C:attention}full deck{}.", - "{C:inactive}(Currently: {C:mult}+#2#{C:inactive} Mult)", - }, - }, - credits = { - idea = { "1srscx4" }, - art = { "None" }, - code = { "1srscx4" }, - }, - atlas = "neuroCustomJokers", - pools = { ["neurJoker"] = true }, - rarity = 1, - cost = 5, - unlocked = true, - discovered = false, - blueprint_compat = true, - eternal_compat = true, - perishable_compat = true, - pos = { x = 1, y = 0 }, - in_pool = function(self, args) - for _, pcard in ipairs(G.playing_cards) do - if SMODS.has_enhancement(pcard, "m_blood") then - return true - end - end - return false - end, - config = { extra = { mult = 3 } }, - loc_vars = function(self, info_queue, center) - info_queue[#info_queue + 1] = G.P_CENTERS.m_blood - local blood = 0 - if G.playing_cards then - for _, pcard in ipairs(G.playing_cards) do - if SMODS.has_enhancement(pcard, "m_blood") then - blood = blood + 1 - end - end - end - return { vars = { center.ability.extra.mult, center.ability.extra.mult * blood } } - end, - calculate = function(self, card, context) - if context.joker_main then - local blood = 0 - for _, pcard in ipairs(G.playing_cards) do - if SMODS.has_enhancement(pcard, "m_blood") then - blood = blood + 1 - end - end - return { mult = card.ability.extra.mult * blood } - end - end, -}) + SMODS.Joker({ key = "queenpb", loc_txt = { @@ -3149,73 +3094,30 @@ SMODS.Joker({ end, add_to_deck = function(self, card, from_debuff) card.ability.extra.song = pseudorandom_element({ "LIFE", "BOOM", "NEVER" }, pseudoseed("pb")) - for pos, joker in ipairs(G.jokers.cards) do + for pos, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_queenpb" and joker ~= card then - card.ability.extra.song = G.jokers.cards[pos].ability.extra.song + card.ability.extra.song = joker.ability.extra.song end end - for pos, joker in ipairs(G.playbook_extra.cards) do + for pos, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_queenpb" and joker ~= card then - card.ability.extra.song = G.playbook_extra.cards[pos].ability.extra.song + card.ability.extra.song = joker.ability.extra.song end end - if card.ability.extra.song == "BOOM" then - G.GAME.pool_flags.BOOM = true - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.NEVER = false - end - if card.ability.extra.song == "LIFE" then - G.GAME.pool_flags.LIFE = true - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.NEVER = false - end - if card.ability.extra.song == "NEVER" then - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.NEVER = true - end + set_queenpb_pool_flags(card.ability.extra.song) end, remove_from_deck = function(self, card, from_debuff) - for pos, joker in ipairs(G.jokers.cards) do + for pos, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_queenpb" and joker ~= card then return end end - if card.ability.extra.song == "BOOM" then - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.NEVER = false - end - if card.ability.extra.song == "LIFE" then - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.NEVER = false - end - if card.ability.extra.song == "NEVER" then - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.NEVER = false - end - for pos, joker in ipairs(G.playbook_extra.cards) do + for pos, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_queenpb" and joker ~= card then return end end - if card.ability.extra.song == "BOOM" then - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.NEVER = false - end - if card.ability.extra.song == "LIFE" then - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.NEVER = false - end - if card.ability.extra.song == "NEVER" then - G.GAME.pool_flags.LIFE = false - G.GAME.pool_flags.BOOM = false - G.GAME.pool_flags.NEVER = false - end + set_queenpb_pool_flags(nil) end, calculate = function(self, card, context) if @@ -3316,6 +3218,7 @@ SMODS.Joker({ if context.individual and context.cardarea == G.play + and context.other_card and context.other_card:is_face() and not context.blueprint and not context.retrigger_joker @@ -3357,17 +3260,14 @@ SMODS.Joker({ return { vars = { center.ability.extra.Xmult, - (G.GAME.probabilities.normal or 1) * center.ability.extra.base, + probability_scale() * center.ability.extra.base, center.ability.extra.odds, }, } end, calculate = function(self, card, context) if context.joker_main then - if - pseudorandom("kyoto") - < (G.GAME.probabilities.normal * card.ability.extra.base) / card.ability.extra.odds - then + if roll_with_odds("kyoto", card.ability.extra.base, card.ability.extra.odds) then return { message = localize({ type = "variable", key = "a_xmult", vars = { card.ability.extra.Xmult } }), Xmult_mod = card.ability.extra.Xmult, @@ -3429,8 +3329,9 @@ SMODS.Joker({ config = { extra = { xmult = 1, upg = 0.5 } }, calculate = function(self, card, context) if context.before and not context.blueprint and not context.retrigger_joker then + local scoring_hand = context.scoring_hand or {} local upgrade = false - for _, pcard in ipairs(context.scoring_hand) do + for _, pcard in ipairs(scoring_hand) do if pcard.seal == "osu_seal" then upgrade = true break @@ -3444,7 +3345,7 @@ SMODS.Joker({ return { message = "X", colour = G.C.RED } end end - if context.individual and context.cardarea == G.play and context.other_card.seal == "osu_seal" then + if context.individual and context.cardarea == G.play and context.other_card and context.other_card.seal == "osu_seal" then return { xmult = card.ability.extra.xmult } end end, @@ -3532,13 +3433,13 @@ SMODS.Joker({ pos = { x = 0, y = 4 }, in_pool = function(self, args) local is_in_pool = false - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_Glorp" then is_in_pool = true break end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_Glorp" then is_in_pool = true break @@ -3563,7 +3464,7 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.individual and context.cardarea == G.play then - if context.other_card:is_suit("Glorpsuit") then + if context.other_card and context.other_card:is_suit("Glorpsuit") then return { mult = card.ability.extra.mult } end end @@ -3597,7 +3498,7 @@ SMODS.Joker({ pos = { x = 6, y = 1 }, config = { extra = { odds = 3, base = 1 } }, loc_vars = function(self, info_queue, center) - return { vars = { G.GAME.probabilities.normal * center.ability.extra.base, center.ability.extra.odds } } + return { vars = { (G.GAME and G.GAME.probabilities and G.GAME.probabilities.normal or 1) * center.ability.extra.base, center.ability.extra.odds } } end, calculate = function(self, card, context) if @@ -3605,10 +3506,7 @@ SMODS.Joker({ and context.card.ability.set == "Booster" and context.card.ability.name:find("Standard") then - if - pseudorandom("seed") - <= (G.GAME.probabilities.normal * card.ability.extra.base) / card.ability.extra.odds - then + if roll_with_odds("seed", card.ability.extra.base, card.ability.extra.odds) then G.E_MANAGER:add_event(Event({ func = function() local _rank = pseudorandom_element( @@ -3624,41 +3522,41 @@ SMODS.Joker({ local enh_type = pseudorandom("seed") local edit_type = pseudorandom("seed") - if seal_type > 0.4 and seal_type <= 0.5 then + if seal_type > 0.4 then _card:set_seal("Red", true) - elseif seal_type > 0.3 and seal_type <= 0.5 then + elseif seal_type > 0.3 then _card:set_seal("Blue", true) - elseif seal_type > 0.2 and seal_type <= 0.5 then + elseif seal_type > 0.2 then _card:set_seal("Gold", true) - elseif seal_type > 0.1 and seal_type <= 0.5 then + elseif seal_type > 0.1 then _card:set_seal("Purple", true) end - if enh_type > 0.8 and enh_type <= 0.9 then + if enh_type > 0.8 then _card:set_ability(G.P_CENTERS["m_twin"], nil, true) - elseif enh_type > 0.7 and enh_type <= 0.9 then + elseif enh_type > 0.7 then _card:set_ability(G.P_CENTERS["m_glass"], nil, true) - elseif enh_type > 0.6 and enh_type <= 0.9 then + elseif enh_type > 0.6 then _card:set_ability(G.P_CENTERS["m_lucky"], nil, true) - elseif enh_type > 0.5 and enh_type <= 0.9 then + elseif enh_type > 0.5 then _card:set_ability(G.P_CENTERS["m_wild"], nil, true) - elseif enh_type > 0.4 and enh_type <= 0.9 then + elseif enh_type > 0.4 then _card:set_ability(G.P_CENTERS["m_bonus"], nil, true) - elseif enh_type > 0.3 and enh_type <= 0.9 then + elseif enh_type > 0.3 then _card:set_ability(G.P_CENTERS["m_mult"], nil, true) - elseif enh_type > 0.2 and enh_type <= 0.9 then + elseif enh_type > 0.2 then _card:set_ability(G.P_CENTERS["m_steel"], nil, true) - elseif enh_type > 0.1 and enh_type <= 0.9 then + elseif enh_type > 0.1 then _card:set_ability(G.P_CENTERS["m_stone"], nil, true) - elseif enh_type > 0 and enh_type <= 0.9 then + elseif enh_type > 0 then _card:set_ability(G.P_CENTERS["m_gold"], nil, true) end - if edit_type > 0.2 and edit_type <= 0.3 then + if edit_type > 0.2 then _card:set_edition("e_foil", true) - elseif edit_type > 0.10 and edit_type <= 0.3 then + elseif edit_type > 0.10 then _card:set_edition("e_holo", true) - elseif edit_type > 0.02 and edit_type <= 0.3 then + elseif edit_type > 0.02 then _card:set_edition("e_polychrome", true) - elseif edit_type > 0 and edit_type <= 0.3 then + elseif edit_type > 0 then _card:set_edition("e_negative", true) end G.E_MANAGER:add_event(Event({ @@ -3673,7 +3571,7 @@ SMODS.Joker({ end end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -3711,7 +3609,7 @@ SMODS.Joker({ return { xmult = pseudorandom("seed", card.ability.extra.min, card.ability.extra.max) / 10 } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) return true end, }) @@ -3748,7 +3646,7 @@ SMODS.Joker({ return { vars = { center.ability.extra.gain, center.ability.extra.goal, center.ability.extra.current } } end, calculate = function(self, card, context) - if context.buying_card or context.open_booster and not context.retrigger_joker then + if (context.buying_card or context.open_booster) and not context.retrigger_joker then card.ability.extra.current = card.ability.extra.current + context.card.cost end if context.end_of_round and G.GAME.current_round.free_rerolls == 0 and not context.retrigger_joker then @@ -3804,13 +3702,15 @@ SMODS.Joker({ pos = { x = 9, y = 7 }, config = { extra = { odds = 3, base = 1, oa6added = "0" } }, add_to_deck = function(self, card, from_debuff) - G.GAME.probabilities.normal = G.GAME.probabilities.normal + 1 + if G.GAME and G.GAME.probabilities then + G.GAME.probabilities.normal = math.max((G.GAME.probabilities.normal or 1) + 1, 1) + end end, remove_from_deck = function(self, card, from_debuff) local multiplier = 1 - local to_do = tonumber(card.ability.extra.oa6added) + local to_do = tonumber(card.ability.extra.oa6added) or 0 local done = 0 - for pos, joker in ipairs(G.jokers.cards) do + for pos, joker in ipairs(joker_cards()) do if done < to_do then if joker.config.center.key == "j_oops" then multiplier = multiplier * 2 @@ -3819,7 +3719,7 @@ SMODS.Joker({ end end if done < to_do then - for pos, joker in ipairs(G.playbook_extra.cards) do + for pos, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_oops" then if done < to_do then multiplier = multiplier * 2 @@ -3828,18 +3728,20 @@ SMODS.Joker({ end end end - G.GAME.probabilities.normal = G.GAME.probabilities.normal - (1 * multiplier) + if G.GAME and G.GAME.probabilities then + G.GAME.probabilities.normal = math.max((G.GAME.probabilities.normal or 1) - (1 * multiplier), 1) + end end, loc_vars = function(self, info_queue, card) info_queue[#info_queue + 1] = G.P_CENTERS.c_wheel_of_fortune return { - vars = { (G.GAME and G.GAME.probabilities.normal or 1) * card.ability.extra.base, card.ability.extra.odds }, + vars = { probability_scale() * card.ability.extra.base, card.ability.extra.odds }, } end, calculate = function(self, card, context) if context.setting_blind - and pseudorandom("schedule") <= ((G.GAME and G.GAME.probabilities.normal or 1) * card.ability.extra.base) / card.ability.extra.odds + and roll_with_odds("schedule", card.ability.extra.base, card.ability.extra.odds) and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then G.GAME.consumeable_buffer = G.GAME.consumeable_buffer + 1 @@ -3859,8 +3761,8 @@ SMODS.Joker({ end, })) end - if context.card_added and context.card.config.center.key == "j_oops" then - card.ability.extra.oa6added = tostring(tonumber(card.ability.extra.oa6added) + 1) + if context.card_added and context.card and context.card.config and context.card.config.center and context.card.config.center.key == "j_oops" then + card.ability.extra.oa6added = tostring((tonumber(card.ability.extra.oa6added) or 0) + 1) end end, }) @@ -3892,12 +3794,12 @@ SMODS.Joker({ calculate = function(self, card, context) if context.end_of_round and context.game_over and context.main_eval and not context.blueprint then local valid_index = {} - for pos, joker in ipairs(G.jokers.cards) do + for pos, joker in ipairs(joker_cards()) do if not joker.ability.eternal and joker ~= card then valid_index[#valid_index + 1] = pos end end - print(valid_index) + G.E_MANAGER:add_event(Event({ func = function() G.hand_text_area.blind_chips:juice_up() @@ -3905,9 +3807,11 @@ SMODS.Joker({ play_sound("tarot1") card:start_dissolve() local victim = nil - if #valid_index > 0 then + if G and G.jokers and G.jokers.cards and #valid_index > 0 then victim = G.jokers.cards[pseudorandom_element(valid_index, pseudoseed("purge"))] - victim:start_dissolve() + if victim then + victim:start_dissolve() + end end return true end, @@ -3945,34 +3849,34 @@ SMODS.Joker({ perishable_compat = true, pos = { x = 8, y = 4 }, add_to_deck = function(self, card, from_debuff) - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_Ermfish" then joker.children.center:set_sprite_pos({ x = 1, y = 7 }) end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_Ermfish" then joker.children.center:set_sprite_pos({ x = 1, y = 7 }) end end end, remove_from_deck = function(self, card, from_debuff) - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_nwooper" then return end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_nwooper" then return end end - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_Ermfish" then joker.children.center:set_sprite_pos({ x = 0, y = 7 }) end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_Ermfish" then joker.children.center:set_sprite_pos({ x = 0, y = 7 }) end @@ -4011,12 +3915,12 @@ SMODS.Joker({ return true end, add_to_deck = function(self, card, from_debuff) - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_nwooper" then card.children.center:set_sprite_pos({ x = 1, y = 7 }) end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_nwooper" then card.children.center:set_sprite_pos({ x = 1, y = 7 }) end @@ -4029,22 +3933,22 @@ SMODS.Joker({ end, calculate = function(self, card, context) if context.joker_main then - for _, pcard in ipairs(G.jokers.cards) do + for _, pcard in ipairs(joker_cards()) do if pcard.config.center.key == "j_nwooper" then return { remove_default_message = true, - mult = (mult ^ card.ability.extra.power) - mult, + xmult = card.ability.extra.power, message = { "^" .. tostring(card.ability.extra.power) }, colour = G.C.DARK_EDITION, sound = "emultsfx", } end end - for _, pcard in ipairs(G.playbook_extra.cards) do + for _, pcard in ipairs(playbook_cards()) do if pcard.config.center.key == "j_nwooper" then return { remove_default_message = true, - mult = (mult ^ card.ability.extra.power) - mult, + xmult = card.ability.extra.power, message = { "^" .. tostring(card.ability.extra.power) }, colour = G.C.DARK_EDITION, sound = "emultsfx", @@ -4069,7 +3973,7 @@ SMODS.Joker({ art = { "Tony7268" }, code = { "1srscx4" }, }, - atlas = "neuroCustomJokers", + atlas = "animeschizo", pools = { ["neurJoker"] = true }, rarity = 1, cost = 6, @@ -4094,6 +3998,13 @@ SMODS.Joker({ joker.sell_cost = 0 G.GAME.schizo = false end + if context.end_of_round and context.main_eval and not context.blueprint then + for _, joker in ipairs(joker_cards()) do + if joker.ability.schizo_sticker then + SMODS.destroy_cards(joker) + end + end + end end, }) @@ -4135,18 +4046,24 @@ SMODS.Joker({ end, calculate = function(self, card, context) + local scoring_hand = context.scoring_hand or {} if context.before and not context.retrigger_joker then - card.ability.extra.sel_card = pseudorandom("seed", 1, #context.scoring_hand) + if #scoring_hand > 0 then + card.ability.extra.sel_card = pseudorandom("seed", 1, #scoring_hand) + else + card.ability.extra.sel_card = 1 + end end if not context.end_of_round and context.repetition - and context.other_card == context.scoring_hand[card.ability.extra.sel_card] + and scoring_hand[card.ability.extra.sel_card] + and context.other_card == scoring_hand[card.ability.extra.sel_card] then return { repetitions = pseudorandom("seed", card.ability.extra.min, card.ability.extra.max) } end end, - in_pool = function(self, wawa, wawa2) + in_pool = function(self, args) --whether or not this card is in the pool, return true if it is, return false if its not return true end, @@ -4251,13 +4168,13 @@ SMODS.Joker({ pos = { x = 7, y = 6 }, in_pool = function(self, args) local inpool = false - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.edition and joker.edition.key == "e_filtered" then inpool = true break end end - for _, joker in ipairs(G.playbook_extra.cards) do + for _, joker in ipairs(playbook_cards()) do if joker.edition and joker.edition.key == "e_filtered" then inpool = true break @@ -4305,18 +4222,15 @@ SMODS.Joker({ config = { extra = { odds = 2, base = 1 } }, loc_vars = function(self, info_queue, card) return { - vars = { (G.GAME and G.GAME.probabilities.normal or 1) * self.config.extra.base, self.config.extra.odds }, + vars = { probability_scale() * self.config.extra.base, self.config.extra.odds }, } end, calculate = function(self, card, context) if context.destroy_card and not context.repetition and not context.blueprint then if context.destroy_card:get_id() == 2 or context.destroy_card:get_id() == 3 then - for _key, val in ipairs(context.scoring_hand) do - if - context.destroy_card == val - and pseudorandom("anteater") - < ((G.GAME and G.GAME.probabilities.normal or 1) * card.ability.extra.base) / self.config.extra.odds - then + local scoring_hand = context.scoring_hand or {} + for _key, val in ipairs(scoring_hand) do + if context.destroy_card == val and roll_with_odds("anteater", card.ability.extra.base, self.config.extra.odds) then return { remove = true } end end @@ -4360,13 +4274,13 @@ SMODS.Joker({ local text = "Incompatible" local self_pos = 0 local area = center.area - if G.GAME and area and #area.cards > 0 then + if G.GAME and area and area.cards and #area.cards > 0 then for key, playing_card in ipairs(area.cards) do if playing_card == center then self_pos = key end end - if area.cards[self_pos + 1] ~= nil then + if self_pos > 0 and self_pos < #area.cards and area.cards[self_pos + 1] ~= nil then for _, valid in ipairs(neuro_jokers) do if area.cards[self_pos + 1].config.center.key == valid then text = "Compatible" @@ -4382,12 +4296,15 @@ SMODS.Joker({ local works = false local self_pos = 0 local area = card.area + if not (area and area.cards and #area.cards > 0) then + return { message = "Cannot upgrade" } + end for key, playing_card in ipairs(area.cards) do if playing_card == card then self_pos = key end end - if area.cards[self_pos + 1] ~= nil then + if self_pos > 0 and self_pos < #area.cards and area.cards[self_pos + 1] ~= nil then for _, valid in ipairs(neuro_jokers) do if area.cards[self_pos + 1].config.center.key == valid then works = true @@ -4456,24 +4373,29 @@ SMODS.Joker({ return { vars = { center.ability.extra.mult } } end, calculate = function(self, card, context) + local full_hand = context.full_hand or {} if context.pre_discard and not context.blueprint and G.GAME.current_round.discards_used <= 0 and not context.retrigger_joker then + local first_discard_card = full_hand[1] + if not first_discard_card then + return + end sea(function() - context.full_hand[1]:flip() - context.full_hand[1]:juice_up(0.3, 0.3) + first_discard_card:flip() + first_discard_card:juice_up(0.3, 0.3) return true end, 0.35, "after") sea(function() - context.full_hand[1]:set_edition("e_negative", true) + first_discard_card:set_edition("e_negative", true) return true end, 0.55, "after") sea(function() - context.full_hand[1]:flip() - context.full_hand[1]:juice_up(0.3, 0.3) + first_discard_card:flip() + first_discard_card:juice_up(0.3, 0.3) return true end, 0.35, "after") end @@ -4482,6 +4404,7 @@ SMODS.Joker({ and context.cardarea == G.play and not context.blueprint and not context.retrigger_joker + and context.other_card then if context.other_card.edition and context.other_card.edition.key == "e_negative" then if @@ -4512,18 +4435,21 @@ SMODS.Joker({ end if context.discard - and #context.full_hand == 3 - and context.full_hand[1].edition - and context.full_hand[1].edition.key == "e_negative" - and context.full_hand[2].edition - and context.full_hand[2].edition.key == "e_negative" - and context.full_hand[3].edition - and context.full_hand[3].edition.key == "e_negative" + and #full_hand == 3 + and full_hand[1] + and full_hand[1].edition + and full_hand[1].edition.key == "e_negative" + and full_hand[2] + and full_hand[2].edition + and full_hand[2].edition.key == "e_negative" + and full_hand[3] + and full_hand[3].edition + and full_hand[3].edition.key == "e_negative" and not context.blueprint and not context.retrigger_joker then - for _, playing_card in ipairs(context.full_hand) do - SMODS.destroy_cards(context.other_card) + for _, playing_card in ipairs(full_hand) do + SMODS.destroy_cards(playing_card) end card.ability.extra.joker = card.ability.extra.joker + 1 if card.ability.extra.joker >= 3 then @@ -4541,9 +4467,12 @@ SMODS.Joker({ end end if context.hand_drawn and not context.blueprint and not context.retrigger_joker then - for i = 1, #G.hand.cards do + for i = 1, #(G.hand and G.hand.cards or {}) do if - G.hand.cards[i].edition + G.hand + and G.hand.cards + and G.hand.cards[i] + and G.hand.cards[i].edition and G.hand.cards[i].edition.key == "e_negative" and G.hand.cards[i].debuff then @@ -4551,7 +4480,10 @@ SMODS.Joker({ G.hand:change_size(1) end if - G.hand.cards[i].edition + G.hand + and G.hand.cards + and G.hand.cards[i] + and G.hand.cards[i].edition and G.hand.cards[i].edition.key == "e_negative" and G.hand.cards[i].facing == "back" then @@ -4629,9 +4561,15 @@ SMODS.Joker({ end, config = { extra = { card1 = nil, card2 = nil, turn = "1", sold = 0, goal = 50 }, extras = {} }, add_to_deck = function(self, card, from_debuff) - for pos, joker in ipairs(G.jokers.cards) do + if not G.playbook_extra then + return + end + for pos, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_evilsand" and joker ~= card then - G.playbook_extra.config.card_limit = ((G.playbook_extra.config.card_limit - 2) * 2) + 2 + G.playbook_extra.config.card_limit = math.min( + ((G.playbook_extra.config.card_limit - 2) * 2) + 2, + PLAYBOOK_MAX_CARD_LIMIT + ) SMODS.destroy_cards(card) return true end @@ -4645,14 +4583,17 @@ SMODS.Joker({ end, 0) end, remove_from_deck = function(self, card, from_debuff) + if not G.playbook_extra then + return + end local rst = true - for pos, joker in ipairs(G.jokers.cards) do + for pos, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_evilsand" and joker ~= card then rst = false end end if rst then - for _, v in ipairs(G.playbook_extra.cards) do + for _, v in ipairs(playbook_cards()) do if v.ability and v.ability.set == "Default" then v:start_dissolve() end @@ -4663,7 +4604,7 @@ SMODS.Joker({ if G.GAME.playbook_hph < 1 then G.playbook_extra.states.collide.can = false G.playbook_extra.states.visible = false - for _, cr in ipairs(G.playbook_extra.cards) do + for _, cr in ipairs(playbook_cards()) do if cr.ability.consumeable then draw_card(G.playbook_extra, G.consumeables, nil, nil, false, cr) elseif cr.ability.set == "Joker" then @@ -4689,7 +4630,7 @@ SMODS.Joker({ loc_vars = function(self, info_queue, card) local yes = false if G.jokers and #G.jokers.cards > 0 then - for _, joker in ipairs(G.jokers.cards) do + for _, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_evilsand" then yes = true break @@ -4705,6 +4646,9 @@ SMODS.Joker({ } end, calculate = function(self, card, context) + if not G.playbook_extra then + return + end if context.selling_card and context.card ~= card @@ -4715,18 +4659,22 @@ SMODS.Joker({ card.ability.extra.sold = card.ability.extra.sold + context.card.sell_cost if card.ability.extra.sold >= card.ability.extra.goal then card.ability.extra.sold = card.ability.extra.sold - card.ability.extra.goal - G.playbook_extra.config.card_limit = G.playbook_extra.config.card_limit + 1 + G.playbook_extra.config.card_limit = math.min( + (G.playbook_extra.config.card_limit or 0) + 1, + PLAYBOOK_MAX_CARD_LIMIT + ) end local thing = copy_card(context.card) G.playbook_extra:emplace(thing) local def_cards = 0 - for _, v in ipairs(G.playbook_extra.cards) do + for _, v in ipairs(playbook_cards()) do if v.ability and v.ability.set == "Default" then def_cards = def_cards + 1 end end - if #G.playbook_extra.cards - def_cards > G.playbook_extra.config.card_limit - 2 then - for _, v in ipairs(G.playbook_extra.cards) do + local extra_cards = G.playbook_extra and G.playbook_extra.cards or {} + if #extra_cards - def_cards > G.playbook_extra.config.card_limit - 2 then + for _, v in ipairs(playbook_cards()) do if v.ability and v.ability.set == "Joker" then SMODS.destroy_cards(v, true) break @@ -4736,13 +4684,14 @@ SMODS.Joker({ end if context.remove_playing_cards and not context.blueprint and not context.retrigger_joker then for _, cards in ipairs(context.removed) do - if tonumber(card.ability.extra.turn) == 1 then + local turn = tonumber(card.ability.extra.turn) or 1 + if turn == 1 then card.ability.extra.turn = "2" local pcard = copy_card(cards) SMODS.debuff_card(pcard, true, "sand") G.playbook_extra:emplace(pcard) card.ability.extra.card1 = true - elseif tonumber(card.ability.extra.turn) == 2 then + elseif turn == 2 then card.ability.extra.turn = "1" local pcard = copy_card(cards) SMODS.debuff_card(pcard, true, "sand") @@ -4751,13 +4700,13 @@ SMODS.Joker({ end end local def_cards = 0 - for _, v in ipairs(G.playbook_extra.cards) do + for _, v in ipairs(playbook_cards()) do if v.ability and v.ability.set == "Default" then def_cards = def_cards + 1 end end if def_cards > 2 then - for _, v in ipairs(G.playbook_extra.cards) do + for _, v in ipairs(playbook_cards()) do if v.ability and v.ability.set == "Default" then v:start_dissolve() def_cards = def_cards - 1 @@ -4769,30 +4718,46 @@ SMODS.Joker({ end end if context.before and not context.blueprint and not context.retrigger_joker then - for _, pcard in ipairs(G.playbook_extra.cards) do - print(pcard.ability.set) + local extra_scoring_cards = {} + for _, pcard in ipairs(playbook_cards()) do + if pcard.ability.set == "Default" then local added = copy_card(pcard) SMODS.debuff_card(added, false, "sand") table.insert(G.playing_cards, added) G.play:emplace(added) - table.insert(context.scoring_hand, added) + extra_scoring_cards[#extra_scoring_cards + 1] = added end end + if context.scoring_hand then + local existing_scoring_hand = context.scoring_hand + local scoring_hand = {} + for i = 1, #existing_scoring_hand do + scoring_hand[i] = existing_scoring_hand[i] + end + for _, added in ipairs(extra_scoring_cards) do + scoring_hand[#scoring_hand + 1] = added + end + context.scoring_hand = scoring_hand + end end if context.after and not context.blueprint and not context.retrigger_joker then + local full_hand = context.full_hand or {} + local hand_count = #full_hand if card.ability.extra.card1 then - SMODS.destroy_cards( - context.full_hand[card.ability.extra.card2 ~= nil and #context.full_hand - 1 or #context.full_hand] - ) + local target_index = card.ability.extra.card2 ~= nil and hand_count - 1 or hand_count + if target_index > 0 and full_hand[target_index] then + SMODS.destroy_cards(full_hand[target_index]) + end end if card.ability.extra.card2 then - SMODS.destroy_cards(context.full_hand[#context.full_hand]) + if hand_count > 0 and full_hand[hand_count] then + SMODS.destroy_cards(full_hand[hand_count]) + end end end end, }) --- SMODS.Joker:take_ownership("smiley", { config = { extra = { mult = 5 } }, loc_vars = function(self, info_queue, card) @@ -4803,7 +4768,7 @@ SMODS.Joker:take_ownership("smiley", { local deved = false local emoji_pos = 0 local area = nil - for pos, joker in ipairs(G.jokers.cards) do + for pos, joker in ipairs(joker_cards()) do if joker.config.center.key == "j_emojiman" then emoji_pos = pos deved = true @@ -4811,7 +4776,7 @@ SMODS.Joker:take_ownership("smiley", { end end if not deved then - for pos, joker in ipairs(G.playbook_extra.cards) do + for pos, joker in ipairs(playbook_cards()) do if joker.config.center.key == "j_emojiman" then emoji_pos = pos deved = true @@ -4819,7 +4784,7 @@ SMODS.Joker:take_ownership("smiley", { end end end - if context.other_card:is_face() then + if context.other_card and context.other_card:is_face() then if deved then return { xmult = area[emoji_pos].ability.extra.xmult } else @@ -4829,6 +4794,578 @@ SMODS.Joker:take_ownership("smiley", { end end, }, true) + +SMODS.Joker({ + key = "koko", + loc_txt = { + name = "Koko", + text = { + "{C:attention}Double{} the effect", + "of {C:tarot}Tarot{} cards", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 0, + cost = 3, + unlocked = true, + discovered = false, + blueprint_compat = false, + eternal_compat = true, + perishable_compat = true, + pos = { x = 1, y = 0 }, + config = { extra = { retriggering = false } }, + calculate = function(self, card, context) + if context.using_consumeable and context.consumeable.ability.set == "Tarot" and not context.retrigger_joker and not card.ability.extra.retriggering then + card.ability.extra.retriggering = true + local consumed_card = context.consumeable + G.E_MANAGER:add_event(Event({ + trigger = "after", + delay = 0.1, + func = function() + if consumed_card and not consumed_card.removed and consumed_card.area then + consumed_card:use(consumed_card.config.center, consumed_card) + end + card.ability.extra.retriggering = false + return true + end, + })) + return { message = "Again!" } + end + end, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "cerber", + loc_txt = { + name = "Cerber", + text = { + "All {C:attention}2s{} become", + "{C:dark_edition}Negative{} when obtained", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 3, + cost = 8, + unlocked = true, + discovered = false, + blueprint_compat = false, + eternal_compat = true, + perishable_compat = true, + pos = { x = 0, y = 2 }, + add_to_deck = function(self, card, from_debuff) + if G.playing_cards and not card.debuff then + for _, pcard in ipairs(G.playing_cards) do + if pcard:get_id() == 2 and not (pcard.edition and pcard.edition.key == "e_negative") then + pcard:set_edition("e_negative", true) + end + end + end + end, + calculate = function(self, card, context) + if context.playing_card_added and not context.blueprint and not context.retrigger_joker then + for _, pcard in ipairs(context.cards) do + if pcard:get_id() == 2 and not (pcard.edition and pcard.edition.key == "e_negative") then + pcard:set_edition("e_negative", true) + end + end + end + end, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "chimps", + loc_txt = { + name = "Chimps", + text = { + "{C:tarot}Tarot{} packs", + "contain {C:attention}+1{} card", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 1, + cost = 4, + unlocked = true, + discovered = false, + blueprint_compat = false, + eternal_compat = true, + perishable_compat = true, + pos = { x = 1, y = 0 }, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "bwaa", + loc_txt = { + name = "Bwaa", + text = { + "Spawn an {C:spectral}Aura{} card", + "at the {C:attention}start{} of each round", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 3, + cost = 8, + unlocked = true, + discovered = false, + blueprint_compat = false, + eternal_compat = true, + perishable_compat = true, + pos = { x = 1, y = 0 }, + calculate = function(self, card, context) + if context.setting_blind and not context.blueprint then + G.GAME.consumeable_buffer = G.GAME.consumeable_buffer + 1 + G.E_MANAGER:add_event(Event({ + func = function() + if G.GAME.consumeable_buffer > 0 then + play_sound("timpani") + SMODS.add_card({ + set = "Spectral", + key = "c_aura", + }) + end + return true + end, + })) + return { message = "Bwaa!" } + end + end, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "coldfish", + loc_txt = { + name = "Coldfish", + text = { + "{C:attention}Glass{} cards don't break", + "On the {C:attention}6th{} prevention,", + "{C:green,E:1}1 in 6{} chance to {C:red}break{}", + "the bag and become {C:attention}Unleashed", + "{C:inactive}[Preventions: #1#]", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 2, + cost = 6, + unlocked = true, + discovered = false, + blueprint_compat = false, + eternal_compat = true, + perishable_compat = true, + pos = { x = 8, y = 6 }, + config = { extra = { prevented = 0, odds = 6 } }, + loc_vars = function(self, info_queue, center) + return { vars = { center.ability.extra.prevented } } + end, + calculate = function(self, card, context) + if context.preventing_glass_break and not context.blueprint then + card.ability.extra.prevented = card.ability.extra.prevented + 1 + if card.ability.extra.prevented >= 6 then + if roll_with_odds("coldfish", 1, card.ability.extra.odds) then + G.E_MANAGER:add_event(Event({ + func = function() + card:start_dissolve() + SMODS.add_card({ set = "Joker", area = G.jokers, key = "j_coldfish_unleashed" }) + return true + end, + })) + return { message = "Unleashed!" } + end + end + return { message = "Saved!" } + end + end, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "coldfish_unleashed", + loc_txt = { + name = "Coldfish Unleashed", + text = { + "Counts as a {C:attention}Vedal{} card", + "{C:attention}Gold{} and {C:attention}Donation{} cards", + "give {C:mult}+#1#{} mult when scored", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 2, + cost = 6, + unlocked = true, + discovered = false, + blueprint_compat = true, + eternal_compat = true, + perishable_compat = true, + pos = { x = 7, y = 7 }, + config = { extra = { mult = 2 } }, + loc_vars = function(self, info_queue, center) + info_queue[#info_queue + 1] = G.P_CENTERS.m_gold + info_queue[#info_queue + 1] = G.P_CENTERS.m_dono + return { vars = { center.ability.extra.mult } } + end, + calculate = function(self, card, context) + if context.individual and context.cardarea == G.play and context.other_card then + if SMODS.has_enhancement(context.other_card, "m_gold") or SMODS.has_enhancement(context.other_card, "m_dono") then + return { mult = card.ability.extra.mult } + end + end + end, + in_pool = function(self, args) + return false + end, +}) + +SMODS.Joker({ + key = "paulamarina", + loc_txt = { + name = "PaulaMarina", + text = { + "Retrigger {C:planet}Planet{} cards", + "{C:attention}#1#{} additional time", + "Increase by {C:attention}1{} every {C:attention}16", + "upgraded levels across all hands", + "{C:inactive}[Total: #2# levels]", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 3, + cost = 8, + unlocked = true, + discovered = false, + blueprint_compat = true, + eternal_compat = true, + perishable_compat = true, + pos = { x = 1, y = 0 }, + config = { extra = { retriggers = 1, threshold = 16 } }, + loc_vars = function(self, info_queue, center) + local total_levels = 0 + if G and G.GAME and G.GAME.hands then + for hand, data in pairs(G.GAME.hands) do + total_levels = total_levels + (data.level or 1) - 1 + end + end + local retriggers = math.floor(total_levels / center.ability.extra.threshold) + 1 + return { vars = { retriggers, total_levels } } + end, + calculate = function(self, card, context) + if context.using_consumeable and context.consumeable.ability.set == "Planet" and not context.retrigger_joker then + local consumed_card = context.consumeable + local total_levels = 0 + for hand, data in pairs(G.GAME.hands) do + total_levels = total_levels + (data.level or 1) - 1 + end + local retriggers = math.floor(total_levels / card.ability.extra.threshold) + for i = 1, retriggers do + G.E_MANAGER:add_event(Event({ + trigger = "after", + delay = 0.1, + func = function() + if consumed_card and not consumed_card.removed and consumed_card.area then + consumed_card:use(consumed_card.config.center, consumed_card) + end + return true + end, + })) + end + end + end, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "toma", + loc_txt = { + name = "Toma", + text = { + "{C:attention}Bloody{} cards get {C:attention}Punched{}", + "and are {C:attention}reshuffled{} into deck", + "at end of scoring", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 2, + cost = 6, + unlocked = true, + discovered = false, + blueprint_compat = false, + eternal_compat = true, + perishable_compat = true, + pos = { x = 5, y = 7 }, + calculate = function(self, card, context) + if context.after and not context.blueprint then + local scoring_hand = context.scoring_hand or {} + for _, pcard in ipairs(scoring_hand) do + if SMODS.has_enhancement(pcard, "m_blood") then + pcard.ability.punched = true + end + end + end + if context.end_of_round and context.cardarea == G.jokers then + for _, pcard in ipairs(G.playing_cards) do + if pcard.ability.punched then + pcard.ability.punched = nil + if pcard.area and pcard.area ~= G.deck then + G.E_MANAGER:add_event(Event({ + func = function() + draw_card(pcard.area, G.deck, 90, "up", nil, pcard) + return true + end, + })) + end + end + end + end + end, + in_pool = function(self, args) + for _, pcard in ipairs(G.playing_cards) do + if SMODS.has_enhancement(pcard, "m_blood") then + return true + end + end + return false + end, +}) + +SMODS.Joker({ + key = "tomaniacs", + loc_txt = { + name = "Tomaniacs", + text = { + "{C:attention}Combo punch{} all {C:attention}Bloody{} cards", + "in played hand until no", + "{C:attention}Bloody{} cards are scored", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 3, + cost = 8, + unlocked = true, + discovered = false, + blueprint_compat = false, + eternal_compat = true, + perishable_compat = true, + pos = { x = 6, y = 7 }, + calculate = function(self, card, context) + if context.after and not context.blueprint then + local scoring_hand = context.scoring_hand or {} + local has_bloody = true + local iterations = 0 + local max_iterations = 20 + while has_bloody and iterations < max_iterations do + has_bloody = false + for _, pcard in ipairs(scoring_hand) do + if SMODS.has_enhancement(pcard, "m_blood") then + pcard.ability.punched = true + has_bloody = true + end + end + iterations = iterations + 1 + if has_bloody then + for _, pcard in ipairs(G.playing_cards) do + if pcard.ability.punched then + pcard.ability.punched = nil + if pcard.area and pcard.area ~= G.deck then + G.E_MANAGER:add_event(Event({ + func = function() + draw_card(pcard.area, G.deck, 90, "up", nil, pcard) + return true + end, + })) + end + end + end + break + end + end + end + end, + in_pool = function(self, args) + for _, joker in ipairs(joker_cards()) do + if joker.config.center.key == "j_toma" then + return true + end + end + return false + end, +}) + +SMODS.Joker({ + key = "angel_neuro", + loc_txt = { + name = "Angel Neuro", + text = { + "Apply {C:dark_edition}Angelic{} edition", + "to all scored cards", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 3, + cost = 8, + unlocked = true, + discovered = false, + blueprint_compat = true, + eternal_compat = true, + perishable_compat = true, + pos = { x = 1, y = 0 }, + calculate = function(self, card, context) + if context.before and not context.blueprint then + local scoring_hand = context.scoring_hand or {} + for _, pcard in ipairs(scoring_hand) do + pcard:set_edition("e_angelic", true) + end + end + end, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "neuro_issues", + loc_txt = { + name = "Neuro Issues", + text = { + "{C:green,E:1}1 in 10{} chance to", + "{C:attention}instantly win{} blind", + "when hand is played", + "{s:0.8,C:red}Something's wrong with my AI...", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "None" }, + code = { "1srscx4" }, + }, + atlas = "neuroCustomJokers", + pools = { ["neurJoker"] = true }, + rarity = 2, + cost = 6, + unlocked = true, + discovered = false, + blueprint_compat = true, + eternal_compat = true, + perishable_compat = true, + pos = { x = 1, y = 0 }, + config = { extra = { odds = 10 } }, + calculate = function(self, card, context) + if context.joker_main and G and G.GAME and G.GAME.blind and G.GAME.chips < G.GAME.blind.chips then + if roll_with_odds("neuro_issues", 1, card.ability.extra.odds) then + G.GAME.chips = math.max(G.GAME.chips, G.GAME.blind.chips) + return { message = "AI Error!" } + end + end + end, + in_pool = function(self, args) + return true + end, +}) + +SMODS.Joker({ + key = "bao", + loc_txt = { + name = "Bao", + text = { + "WIP", + }, + }, + credits = { + idea = { "1srscx4" }, + art = { "Tony7268" }, + code = { "1srscx4" }, + }, + atlas = "animebao", + pools = { ["neurJoker"] = true }, + rarity = 1, + cost = 4, + unlocked = true, + discovered = false, + blueprint_compat = true, + eternal_compat = true, + perishable_compat = true, + pos = { x = 0, y = 0 }, + hidden = true, + in_pool = function(self, args) + return false + end, + calculate = function(self, card, context) + -- TODO: implement Bao effect + end, +}) + --Legendaries local vedals_items = { ["j_vedalsdrink"] = true, @@ -4836,6 +5373,7 @@ local vedals_items = { ["j_tutelsoup"] = true, ["j_vedds"] = true, ["j_abandonedarchive"] = true, + ["j_coldfish_unleashed"] = true, } SMODS.Joker({ key = "vedal", @@ -4867,7 +5405,7 @@ SMODS.Joker({ info_queue[#info_queue + 1] = { set = "Other", key = "Vedal_items_desc" } local ved_cards = 0 if G and G.jokers then - for _, area in ipairs({ G.jokers.cards, G.playbook_extra.cards }) do + for _, area in ipairs({ joker_cards(), playbook_cards() }) do for _, v in ipairs(area) do if vedals_items[v.config.center.key] then ved_cards = ved_cards + 1 @@ -4875,7 +5413,8 @@ SMODS.Joker({ end end end - local mult = math.floor(MONEY_EARNED / card.ability.extra.dollars) + local money_earned = (Neuratro and Neuratro.MONEY_EARNED) or 0 + local mult = math.floor(money_earned / card.ability.extra.dollars) * (ved_cards * card.ability.extra.vedal_bonus + card.ability.extra.upg) return { vars = { @@ -4890,14 +5429,15 @@ SMODS.Joker({ calculate = function(self, card, context) local ved_cards = 0 if context.joker_main then - for _, area in ipairs({ G.jokers.cards, G.playbook_extra.cards }) do + for _, area in ipairs({ joker_cards(), playbook_cards() }) do for _, v in ipairs(area) do if vedals_items[v.config.center.key] then ved_cards = ved_cards + 1 end end end - local mult = math.floor(MONEY_EARNED / card.ability.extra.dollars) + local money_earned = (Neuratro and Neuratro.MONEY_EARNED) or 0 + local mult = math.floor(money_earned / card.ability.extra.dollars) * (ved_cards * card.ability.extra.vedal_bonus + card.ability.extra.upg) return { xmult = mult + 1 } end diff --git a/content/objects/suit.lua b/content/objects/suit.lua index a15adc2..3a911f3 100644 --- a/content/objects/suit.lua +++ b/content/objects/suit.lua @@ -2,9 +2,8 @@ SMODS.Suit({ key = "Glorpsuit", card_key = "G", loc_txt = "Gleebs", - pos = { y = 1 }, - ui_pos = { x = 0, y = 0 }, pos = { x = 0, y = 0 }, + ui_pos = { x = 0, y = 0 }, loc_vars = function(self, info_queue, card) info_queue[#info_queue + 1] = { set = "Other", key = "Gleeb_desc" } end, @@ -13,6 +12,6 @@ SMODS.Suit({ hc_ui_atlas = "neurosuitui", lc_ui_atlas = "neurosuitui", in_pool = function(self, args) - return G.GAME.pool_flags.glorpdeck + return G and G.GAME and G.GAME.pool_flags and G.GAME.pool_flags.glorpdeck end, }) diff --git a/content/objects/tags.lua b/content/objects/tags.lua index 347c020..e6203ef 100644 --- a/content/objects/tags.lua +++ b/content/objects/tags.lua @@ -20,20 +20,22 @@ SMODS.Tag({ end, apply = function(self, tag, context) if context.type == "store_joker_modify" then - local _applied = nil if not context.card.edition and not context.card.temp_edition and context.card.ability.set == "Joker" then - local lock = tag.ID - G.CONTROLLER.locks[lock] = true + local lock = tag.ID + if G.CONTROLLER and G.CONTROLLER.locks then + G.CONTROLLER.locks[lock] = true + end context.card.temp_edition = true tag:yep("+", G.C.DARK_EDITION, function() context.card:set_edition({ filtered = true }, true) context.card.ability.couponed = true context.card:set_cost() context.card.temp_edition = nil - G.CONTROLLER.locks[lock] = nil + if G.CONTROLLER and G.CONTROLLER.locks then + G.CONTROLLER.locks[lock] = nil + end return true end) - _applied = true tag.triggered = true end end @@ -60,7 +62,9 @@ SMODS.Tag({ apply = function(self, tag, context) if context.type == "new_blind_choice" then local lock = tag.ID - G.CONTROLLER.locks[lock] = true + if G.CONTROLLER and G.CONTROLLER.locks then + G.CONTROLLER.locks[lock] = true + end tag:yep("+", G.C.SECONDARY_SET.Spectral, function() local key = "p_neurpack4" local card = Card( @@ -76,7 +80,9 @@ SMODS.Tag({ card.from_tag = true G.FUNCS.use_card({ config = { ref_table = card } }) card:start_materialize() - G.CONTROLLER.locks[lock] = nil + if G.CONTROLLER and G.CONTROLLER.locks then + G.CONTROLLER.locks[lock] = nil + end return true end) tag.triggered = true diff --git a/modules/hooks/general.lua b/modules/hooks/general.lua index 08384ba..a5a86b4 100644 --- a/modules/hooks/general.lua +++ b/modules/hooks/general.lua @@ -29,6 +29,16 @@ AKYRS.card_conf_any_drag = function(area, card) return cfc(area, card) end +local _set_sprites = Card.set_sprites +function Card:set_sprites(center, ...) + if center and center.atlas and G.ANIMATION_ATLAS and G.ANIMATION_ATLAS[center.atlas] then + if not (G.ASSET_ATLAS and G.ASSET_ATLAS[center.atlas]) then + G.ASSET_ATLAS[center.atlas] = G.ANIMATION_ATLAS[center.atlas] + end + end + return _set_sprites(self, center, ...) +end + local cardUpdateHook = Card.update function Card:update(dt) self.playbook_click_delay = math.max((self.playbook_click_delay or 0) - dt, 0) @@ -56,7 +66,7 @@ function Card:click() }) end end - self.playbook_click_delay = self.playbook_click_delay + 10 + self.playbook_click_delay = (self.playbook_click_delay or 0) + 10 local x = { cardClickHook(self) } @@ -81,3 +91,22 @@ function eval_card(c, ctx) local x = { evalc_h(c, ctx) } return unpack(x) end + +local dpc_h = draw_card +function draw_card(from, to, percent, dir, sort, card,Delay, mute, resolved, final_lag) + if card and card.ability and card.ability.set == "Default" and G and G.jokers and G.jokers.cards then + local coldfish = nil + for _, joker in ipairs(G.jokers.cards) do + if joker.config.center.key == "j_coldfish" and not joker.debuff then + coldfish = joker + break + end + end + if coldfish and SMODS.has_enhancement(card, "m_glass") and card.ability.shattered then + card.ability.shattered = nil + card.ability.prevented_break = true + SMODS.calculate_context({ preventing_glass_break = true, card = card }) + end + end + return dpc_h(from, to, percent, dir, sort, card,Delay, mute, resolved, final_lag) +end diff --git a/modules/utils/card_analysis.lua b/modules/utils/card_analysis.lua new file mode 100644 index 0000000..3333406 --- /dev/null +++ b/modules/utils/card_analysis.lua @@ -0,0 +1,161 @@ + +Neuratro = Neuratro or {} + +-- Check if a card is a specific suit only (not wild) +function Neuratro.is_suit_only(card, suit) + return not SMODS.has_any_suit(card) and card:is_suit(suit, true) +end + +-- Count cards of a specific suit in a hand +function Neuratro.count_suit(cards, suit, count_wild) + local count = 0 + for _, card in ipairs(cards) do + if card:is_suit(suit, count_wild or false) then + count = count + 1 + end + end + return count +end + +-- Count unique suits in a set of cards +function Neuratro.get_unique_suits(cards) + local suits = {} + for _, card in ipairs(cards) do + for suit, _ in pairs(SMODS.Suits) do + if card:is_suit(suit, true) then + suits[suit] = true + break + end + end + end + return suits +end + +-- Count number of unique suits in cards +function Neuratro.count_unique_suits(cards) + local suits = Neuratro.get_unique_suits(cards) + local count = 0 + for _ in pairs(suits) do + count = count + 1 + end + return count +end + +-- Count face cards in a hand +function Neuratro.count_face_cards(cards, max) + local count = 0 + for _, card in ipairs(cards) do + if card:is_face() then + count = count + 1 + if max and count >= max then + break + end + end + end + return count +end + +-- Check if hand has at least N face cards from different suits +function Neuratro.has_face_cards_from_suits(cards, min_face, min_suits) + local suits_found = {} + local face_count = 0 + + for _, card in ipairs(cards) do + if card:is_face() then + face_count = face_count + 1 + + -- Track unique suits + for suit, _ in pairs(SMODS.Suits) do + if card:is_suit(suit, true) and not suits_found[suit] then + suits_found[suit] = true + break + end + end + + if face_count >= min_face then + local suit_count = 0 + for _ in pairs(suits_found) do + suit_count = suit_count + 1 + end + if suit_count >= min_suits then + return true + end + end + end + end + + return false +end + +-- Check if all cards in hand are same suit (flush-like check) +function Neuratro.is_flush(cards) + if #cards < 2 then return true end + + local first_suit = nil + for _, card in ipairs(cards) do + if not first_suit then + -- Find first card's suit + for suit, _ in pairs(SMODS.Suits) do + if card:is_suit(suit, true) then + first_suit = suit + break + end + end + else + -- Check if card matches first suit + if not card:is_suit(first_suit, true) then + return false + end + end + end + return true +end + +-- Check if all cards in hand are different suits (mix-like check) +function Neuratro.is_mix(cards, min_suits) + min_suits = min_suits or 5 + return Neuratro.count_unique_suits(cards) >= min_suits +end + +-- Check if card has specific enhancement +function Neuratro.has_enhancement(card, enhancement) + return SMODS.has_enhancement(card, enhancement) +end + +-- Check if card has specific edition +function Neuratro.has_edition(card, edition) + return card.edition and card.edition.key == edition +end + +-- Check if card has specific seal +function Neuratro.has_seal(card, seal) + return card.seal == seal +end + +-- Get card's effective rank value +function Neuratro.get_card_rank(card) + return card:get_id() +end + +-- Check if card is an Ace +function Neuratro.is_ace(card) + return card:get_id() == 14 +end + +-- Check if scoring hand meets criteria for Three of a Kind with specific suit +function Neuratro.is_three_of_a_kind_with_suit(cards, suit) + if #cards ~= 3 then return false end + + local rank = nil + for _, card in ipairs(cards) do + if not card:is_suit(suit, true) then + return false + end + if not rank then + rank = card:get_id() + elseif card:get_id() ~= rank then + return false + end + end + return true +end diff --git a/modules/utils/config_builder.lua b/modules/utils/config_builder.lua new file mode 100644 index 0000000..d87c67e --- /dev/null +++ b/modules/utils/config_builder.lua @@ -0,0 +1,117 @@ + +Neuratro = Neuratro or {} +if not Neuratro.build_config then Neuratro.build_config = {} end + +-- Build config for xmult cycler joker (cycles between multiple xmult values) +function Neuratro.build_config.xmult_cycler(base, values) + return { + xmult = base, + cycle = "1", + c1 = values[1], + c2 = values[2], + c3 = values[3], + } +end + +-- Build config for random xmult joker (randomly picks between high and low) +function Neuratro.build_config.random_xmult(high, low) + return { + xhigh = high, + xlow = low, + } +end + +-- Build config for probability-based xmult joker +function Neuratro.build_config.odds_xmult(odds, xmult_val, base) + return { + odds = odds, + xmult = xmult_val, + base = base or 1, + } +end + +-- Build config for scaling mult joker (mult decreases/increases) +function Neuratro.build_config.scaling_mult(mult, decrement) + return { + mult = mult, + decr = decrement, + } +end + +-- Build config for probability-based upgrade joker +function Neuratro.build_config.probability_upgrade(odds, upgrade_amount, current, goal) + return { + odds = odds, + xmult = current, + base = 1, + upg = upgrade_amount, + goal = goal or 0, + } +end + +-- Build config for accumulating bonus joker +function Neuratro.build_config.accumulating_bonus(bonus_name, base_value, upgrade) + local config = { + upg = upgrade, + } + config[bonus_name] = base_value + return config +end + +-- Build config for round-based joker (tracks rounds until effect) +function Neuratro.build_config.round_based(rounds, goal) + return { + rounds = rounds or 0, + goal = goal or 4, + chosen = "", + } +end + +-- Build config for price/economy joker +function Neuratro.build_config.economy(base_price, down_range, up_range) + return { + price = base_price or 0, + down = down_range or 3, + up = up_range or 6, + } +end + +-- Build config for chips bonus joker +function Neuratro.build_config.chips_bonus(base_chips) + return { + chip_bonus = base_chips, + } +end + +-- Build config for random range joker +function Neuratro.build_config.random_range(min_val, max_val) + return { + min = min_val, + max = max_val, + } +end + +-- Build config for tracking joker (tracks some state) +function Neuratro.build_config.tracker(track_name, initial, max) + local config = {} + config[track_name] = initial or 0 + config.goal = max + return config +end + +-- Build config for xmult that increases on condition +function Neuratro.build_config.scaling_xmult(base_xmult, increase) + return { + xmult = base_xmult, + upg = increase, + } +end + +-- Build config for multi-effect joker +function Neuratro.build_config.multi_effect(effects) + local config = {} + for key, value in pairs(effects) do + config[key] = value + end + return config +end diff --git a/modules/utils/constants.lua b/modules/utils/constants.lua new file mode 100644 index 0000000..9612277 --- /dev/null +++ b/modules/utils/constants.lua @@ -0,0 +1,103 @@ + +Neuratro = Neuratro or {} + +Neuratro.CONSTANTS = { + -- Song names for queenpb joker + SONGS = { + BOOM = "BOOM", + LIFE = "LIFE", + NEVER = "NEVER", + }, + + -- Card suits + SUITS = { + HEARTS = "Hearts", + DIAMONDS = "Diamonds", + SPADES = "Spades", + CLUBS = "Clubs", + }, + + -- Suit abbreviations for card generation + SUIT_ABBREVS = { + HEARTS = "H", + DIAMONDS = "D", + SPADES = "S", + CLUBS = "C", + }, + + -- Common multipliers + MULTIPLIERS = { + BASE = 1.5, + LOW = 0.25, + MID = 1.0, + HIGH = 2.0, + VERY_HIGH = 3.0, + MAX = 4.0, + }, + + -- Probability defaults + PROBABILITY = { + BASE = 1, + ODDS_COMMON = 3, + ODDS_UNCOMMON = 6, + ODDS_RARE = 10, + ODDS_VERY_RARE = 20, + }, + + -- Joker and game limits + LIMITS = { + PLAYBOOK_MAX_CARDS = 64, + DEFAULT_PLAYBOOK_SIZE = 2, + STORED_JOKERS = 2, + MAX_FACE_COUNT = 3, + MIN_SUIT_COUNT = 3, + }, + + -- Chip/Mult defaults + BONUSES = { + CHIPS_BASE = 15, + CHIPS_MID = 50, + CHIPS_HIGH = 100, + CHIPS_VERY_HIGH = 150, + MULT_BASE = 3, + MULT_MID = 5, + MULT_HIGH = 10, + }, + + -- Game states + STATES = { + ACTIVE = true, + INACTIVE = false, + }, + + -- Card ranks + RANKS = { + FACE = {"J", "Q", "K"}, + ALL = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}, + ACE = 14, + }, +} + +-- Helper to get all suit names as array +function Neuratro.CONSTANTS.get_all_suits() + return { + Neuratro.CONSTANTS.SUITS.HEARTS, + Neuratro.CONSTANTS.SUITS.DIAMONDS, + Neuratro.CONSTANTS.SUITS.SPADES, + Neuratro.CONSTANTS.SUITS.CLUBS, + } +end + +-- Helper to get all suit abbreviations as array +function Neuratro.CONSTANTS.get_all_suit_abbrev() + return {"S", "H", "D", "C"} +end + +-- Helper to get all songs as array +function Neuratro.CONSTANTS.get_all_songs() + return { + Neuratro.CONSTANTS.SONGS.BOOM, + Neuratro.CONSTANTS.SONGS.LIFE, + Neuratro.CONSTANTS.SONGS.NEVER, + } +end diff --git a/modules/utils/joker_patterns.lua b/modules/utils/joker_patterns.lua new file mode 100644 index 0000000..6abb2c6 --- /dev/null +++ b/modules/utils/joker_patterns.lua @@ -0,0 +1,146 @@ + +Neuratro = Neuratro or {} +if not Neuratro.patterns then Neuratro.patterns = {} end + +function Neuratro.patterns.upgrade_on_hand(context, hand_name, upgrade_fn, message) + if context.before and context.scoring_name == hand_name and not context.blueprint then + local upgraded = upgrade_fn() + if upgraded then + return { message = message or "Upgrade!" } + end + end + return nil +end + +function Neuratro.patterns.simple_xmult(context, xmult_value) + if context.joker_main then + return { xmult = xmult_value } + end + return nil +end + +function Neuratro.patterns.simple_mult(context, mult_value) + if context.joker_main then + return { mult = mult_value } + end + return nil +end + +function Neuratro.patterns.simple_chips(context, chips_value) + if context.joker_main then + return { chips = chips_value } + end + return nil +end + +function Neuratro.patterns.probability_trigger(context, trigger_context, base, odds, seed, effect_fn, message) + if context[trigger_context] and Neuratro.roll_with_odds(base, odds, seed) then + local result = effect_fn() + if result then + if message and not result.message then + result.message = message + end + return result + end + end + return nil +end + +function Neuratro.patterns.count_based_bonus(context, count_fn, base_value, bonus_type) + if context.joker_main then + local count = count_fn() + if count > 0 then + return { [bonus_type] = base_value * count } + end + end + return nil +end + +function Neuratro.patterns.round_upgrade(context, upgrade_fn, message) + if context.end_of_round and not context.blueprint then + local upgraded = upgrade_fn() + if upgraded then + return { message = message or "Upgrade!" } + end + end + return nil +end + +function Neuratro.patterns.self_destruct(context, condition_fn, message) + if condition_fn() then + G.E_MANAGER:add_event(Event({ + func = function() + SMODS.destroy_cards(card) + return true + end, + })) + return { message = message or "Destroyed!" } + end + return nil +end + +function Neuratro.patterns.retrigger(context, condition_fn, repetitions) + if context.repetition and condition_fn(context) then + return { repetitions = repetitions or 1 } + end + return nil +end + +function Neuratro.patterns.check_cards(scoring_hand, check_fn) + local count = 0 + local found = false + + for _, card in ipairs(scoring_hand) do + if check_fn(card) then + count = count + 1 + found = true + end + end + + return found, count +end + +function Neuratro.patterns.suit_bonus(context, scoring_hand, suit, min_cards, bonus_table) + if context.joker_main then + local count = Neuratro.count_suit(scoring_hand, suit, true) + if count >= min_cards then + return bonus_table + end + end + return nil +end + +function Neuratro.patterns.face_card_bonus(context, scoring_hand, min_faces, bonus_table) + if context.joker_main then + local count = Neuratro.count_face_cards(scoring_hand) + if count >= min_faces then + return bonus_table + end + end + return nil +end + +function Neuratro.patterns.deck_based_bonus(context, condition_fn, base_value, bonus_type) + if context.joker_main then + local count = Neuratro.count_cards(condition_fn) + if count > 0 then + return { [bonus_type] = base_value * count } + end + end + return nil +end + +function Neuratro.patterns.random_effect(seed, effects) + local selected = Neuratro.random_from_list(effects, seed) + return { [selected.type] = selected.value } +end + +function Neuratro.patterns.threshold_trigger(context, track_fn, check_fn, effect_fn) + track_fn() + + if check_fn() then + return effect_fn() + end + + return nil +end diff --git a/modules/utils/joker_utils.lua b/modules/utils/joker_utils.lua new file mode 100644 index 0000000..9530504 --- /dev/null +++ b/modules/utils/joker_utils.lua @@ -0,0 +1,78 @@ + +Neuratro = Neuratro or {} + +-- Find first joker by key across all joker areas +function Neuratro.find_joker(joker_key) + local areas = { + G.jokers and G.jokers.cards or {}, + G.playbook_extra and G.playbook_extra.cards or {} + } + + for _, area in ipairs(areas) do + for _, joker in ipairs(area) do + if joker.config and joker.config.center and joker.config.center.key == joker_key then + return joker + end + end + end + return nil +end + +-- Find all jokers by key across all areas +function Neuratro.find_jokers(joker_key) + local results = {} + local areas = { + G.jokers and G.jokers.cards or {}, + G.playbook_extra and G.playbook_extra.cards or {} + } + + for _, area in ipairs(areas) do + for _, joker in ipairs(area) do + if joker.config and joker.config.center and joker.config.center.key == joker_key then + table.insert(results, joker) + end + end + end + return results +end + +-- Check if joker exists in any area +function Neuratro.has_joker(joker_key) + return Neuratro.find_joker(joker_key) ~= nil +end + +-- Find joker by key with optional debuff check +function Neuratro.find_joker_undebuffed(joker_key) + local joker = Neuratro.find_joker(joker_key) + return joker and not joker.debuff and joker +end + +-- Count cards in deck matching condition +function Neuratro.count_cards(condition_fn) + if not G.playing_cards then return 0 end + + local count = 0 + for _, card in ipairs(G.playing_cards) do + if condition_fn(card) then + count = count + 1 + end + end + return count +end + +-- Get the area containing a specific joker +function Neuratro.get_joker_area(joker_key) + local areas = { + { cards = G.jokers and G.jokers.cards or {}, area = G.jokers }, + { cards = G.playbook_extra and G.playbook_extra.cards or {}, area = G.playbook_extra } + } + + for _, area_data in ipairs(areas) do + for pos, joker in ipairs(area_data.cards) do + if joker.config and joker.config.center and joker.config.center.key == joker_key then + return area_data.area, pos + end + end + end + return nil, nil +end diff --git a/modules/utils/probability.lua b/modules/utils/probability.lua new file mode 100644 index 0000000..7fc9a5a --- /dev/null +++ b/modules/utils/probability.lua @@ -0,0 +1,54 @@ + +Neuratro = Neuratro or {} + +-- Get the current probability scale factor from game state +-- Returns 1 if probabilities are not available +function Neuratro.get_probability_scale() + return (G and G.GAME and G.GAME.probabilities and G.GAME.probabilities.normal) or 1 +end + +-- Check if a random roll succeeds based on odds +function Neuratro.roll_with_odds(base, odds, seed) + local safe_odds = tonumber(odds) or 0 + if safe_odds <= 0 then + return false + end + + local chance = (math.max(0, base or 0) * Neuratro.get_probability_scale()) / safe_odds + if chance <= 0 then + return false + end + + return pseudorandom(seed) <= math.min(chance, 1) +end + +-- Roll with percentage chance (0-1) +function Neuratro.roll_percentage(chance, seed) + local scaled_chance = chance * Neuratro.get_probability_scale() + return pseudorandom(seed) <= math.min(scaled_chance, 1) +end + +-- Roll with simple 1/N odds +function Neuratro.roll_simple_odds(odds, seed) + return Neuratro.roll_with_odds(1, odds, seed) +end + +-- Get formatted probability string for display +function Neuratro.format_probability(base, odds) + local actual_base = (base or 1) * Neuratro.get_probability_scale() + return string.format("%d", math.floor(actual_base)) .. " in " .. tostring(odds or 1) +end + +-- Get probability variables for loc_vars +-- Returns {actual_base, odds} for use in text like "{C:green}#1# in #2#{}" +function Neuratro.get_probability_vars(base, odds) + return { + (base or 1) * Neuratro.get_probability_scale(), + odds or 1, + } +end + +-- Roll a 50/50 chance +function Neuratro.coin_flip(seed) + return pseudorandom(seed, 1, 2) == 1 +end diff --git a/modules/utils/random_utils.lua b/modules/utils/random_utils.lua new file mode 100644 index 0000000..01635b4 --- /dev/null +++ b/modules/utils/random_utils.lua @@ -0,0 +1,98 @@ + +Neuratro = Neuratro or {} + +-- Select a random element from a list +function Neuratro.random_from_list(list, seed) + if not list or #list == 0 then + return nil + end + return pseudorandom_element(list, pseudoseed(seed)) +end + +-- Get a random integer in a range +function Neuratro.random_range(seed, min_val, max_val) + return pseudorandom(seed, min_val, max_val) +end + +-- Roll a random chance +function Neuratro.random_chance(seed, chance) + return pseudorandom(seed) <= chance +end + +-- Get a random suit +function Neuratro.random_suit(seed) + local suits = Neuratro.CONSTANTS.get_all_suits() + return Neuratro.random_from_list(suits, seed) +end + +-- Get a random suit abbreviation +function Neuratro.random_suit_abbrev(seed) + local abbrevs = Neuratro.CONSTANTS.get_all_suit_abbrev() + return Neuratro.random_from_list(abbrevs, seed) +end + +-- Get a random rank +function Neuratro.random_rank(seed, face_only) + local ranks + if face_only then + ranks = Neuratro.CONSTANTS.RANKS.FACE + else + ranks = Neuratro.CONSTANTS.RANKS.ALL + end + return Neuratro.random_from_list(ranks, seed) +end + +-- Get a random song name for queenpb +function Neuratro.random_song(seed) + local songs = Neuratro.CONSTANTS.get_all_songs() + return Neuratro.random_from_list(songs, seed) +end + +-- Roll a random xmult value from options +-- Commonly used for jokers that randomly select between xmult values +function Neuratro.random_xmult(options, seed) + return Neuratro.random_from_list(options, seed) +end + +-- Roll between high and low xmult values (50/50 chance) +function Neuratro.random_xmult_high_low(high, low, seed) + local options = {high, low} + return Neuratro.random_from_list(options, seed) +end + +-- Get random chip value in range +function Neuratro.random_chips(seed, min_val, max_val) + return pseudorandom(seed, min_val, max_val) +end + +-- Generate random card properties +-- Returns suit, rank, seal type, enhancement type, and edition type +function Neuratro.random_card_properties(seed) + return { + suit = Neuratro.random_suit_abbrev(seed), + rank = Neuratro.random_rank(seed), + seal = pseudorandom(seed), + enhancement = pseudorandom(seed), + edition = pseudorandom(seed), + } +end + +-- Pick a random effect from weighted options +function Neuratro.random_weighted(options, seed) + local total_weight = 0 + for _, opt in ipairs(options) do + total_weight = total_weight + (opt.weight or 1) + end + + local roll = pseudorandom(seed) * total_weight + local current = 0 + + for _, opt in ipairs(options) do + current = current + (opt.weight or 1) + if roll <= current then + return opt.effect + end + end + + return options[#options].effect +end diff --git a/modules/utils/safety.lua b/modules/utils/safety.lua new file mode 100644 index 0000000..bdd21dc --- /dev/null +++ b/modules/utils/safety.lua @@ -0,0 +1,175 @@ + +Neuratro = Neuratro or {} + +-- Safely access nested table values +function Neuratro.safe_access(...) + local args = {...} + local default = args[#args] + local path_length = #args - 1 + + if path_length == 0 then + return default + end + + local current = args[1] + if current == nil then + return default + end + + for i = 2, path_length do + local key = args[i] + if type(current) ~= "table" or current[key] == nil then + return default + end + current = current[key] + end + + return current +end + +-- Safely call a function with error handling +function Neuratro.safe_call(fn, ...) + local success, result = pcall(fn, ...) + return success, result +end + +-- Safely get game state value with fallback +function Neuratro.safe_game_state(path, default) + if not G then + return default + end + + local current = G + for _, key in ipairs(path) do + if type(current) ~= "table" or current[key] == nil then + return default + end + current = current[key] + end + + return current +end + +-- Check if game is in a valid state for operations +function Neuratro.is_game_ready() + return G ~= nil and G.GAME ~= nil +end + +-- Safely check if a card area exists and has cards +function Neuratro.has_cards_in_area(area) + return area ~= nil and area.cards ~= nil and #area.cards > 0 +end + +-- Safely get number of cards in an area +function Neuratro.get_card_count(area) + if not Neuratro.has_cards_in_area(area) then + return 0 + end + return #area.cards +end + +-- Safely iterate over cards in an area +function Neuratro.safe_card_iterate(area, fn) + if not Neuratro.has_cards_in_area(area) then + return false + end + + for i, card in ipairs(area.cards) do + local success, err = pcall(fn, card, i) + if not success then + print("[Neuratro] Error iterating card: " .. tostring(err)) + end + end + + return true +end + +-- Safely destroy a card +function Neuratro.safe_destroy(card, force) + if not card then + return false + end + + local success, err = pcall(function() + SMODS.destroy_cards(card, force) + end) + + if not success then + print("[Neuratro] Error destroying card: " .. tostring(err)) + return false + end + + return true +end + +-- Safely debuff a card +function Neuratro.safe_debuff(card, debuff, source) + if not card then + return false + end + + local success, err = pcall(function() + SMODS.debuff_card(card, debuff, source) + end) + + if not success then + print("[Neuratro] Error debuffing card: " .. tostring(err)) + return false + end + + return true +end + +-- Safely add event to event manager +function Neuratro.safe_add_event(event) + if not G or not G.E_MANAGER then + return false + end + + local success, err = pcall(function() + G.E_MANAGER:add_event(event) + end) + + if not success then + print("[Neuratro] Error adding event: " .. tostring(err)) + return false + end + + return true +end + +-- Validate joker config structure +function Neuratro.validate_joker_config(config, required_fields) + if type(config) ~= "table" then + return false, "Config must be a table" + end + + if not config.extra then + return false, "Config must have 'extra' field" + end + + if required_fields then + for _, field in ipairs(required_fields) do + if config.extra[field] == nil then + return false, "Missing required field: " .. field + end + end + end + + return true +end + +-- Safely get pool flag +function Neuratro.get_pool_flag(flag_name) + return Neuratro.safe_game_state({"GAME", "pool_flags", flag_name}, false) +end + +-- Safely set pool flag +function Neuratro.set_pool_flag(flag_name, value) + if not Neuratro.is_game_ready() or not G.GAME.pool_flags then + return false + end + + G.GAME.pool_flags[flag_name] = value + return true +end