diff --git a/lovely/center.toml b/lovely/center.toml index 9625c41e8..c8ce03fdb 100644 --- a/lovely/center.toml +++ b/lovely/center.toml @@ -383,30 +383,29 @@ for _, v in ipairs(rates) do''' [[patches]] [patches.pattern] target = 'functions/common_events.lua' -pattern = "if not forced_key and soulable and (not G.GAME.banned_keys['c_soul']) then" -match_indent = true -position = 'after' -payload = ''' - local soul_total_rate = 0 - local non_soul_rate = 1 - local modded_souls = {} - for _, v in ipairs(SMODS.Consumable.legendaries) do - if (_type == v.type.key or _type == v.soul_set) and not (G.GAME.used_jokers[v.key] and not SMODS.showman(v.key) and not v.can_repeat_soul) and SMODS.add_to_pool(v) then - soul_total_rate = soul_total_rate + v.soul_rate - non_soul_rate = non_soul_rate * (1 - v.soul_rate) - non_soul_rate = math.max(non_soul_rate, 0) - table.insert(modded_souls, v) +pattern = ''' +if not forced_key and soulable and (not G.GAME.banned_keys['c_soul']) then + if (_type == 'Tarot' or _type == 'Spectral' or _type == 'Tarot_Planet') and + not (G.GAME.used_jokers['c_soul'] and not next(find_joker("Showman"))) then + if pseudorandom('soul_'.._type..G.GAME.round_resets.ante) > 0.997 then + forced_key = 'c_soul' end end - local roll = pseudorandom('soul_smods_'.._type..G.GAME.round_resets.ante) - local threshold = 1 - for _, v in ipairs(modded_souls) do - threshold = threshold - v.soul_rate/soul_total_rate * (1-non_soul_rate) - if roll > threshold then - forced_key = v.key - break + if (_type == 'Planet' or _type == 'Spectral') and + not (G.GAME.used_jokers['c_black_hole'] and not next(find_joker("Showman"))) then + if pseudorandom('soul_'.._type..G.GAME.round_resets.ante) > 0.997 then + forced_key = 'c_black_hole' end - end''' + end +end +''' +match_indent = true +position = 'at' +payload = ''' +if not forced_key and soulable then + forced_key = SMODS.poll_soul{set = _type} +end +''' [[patches]] [patches.pattern] diff --git a/lsp_def/classes/consumable.lua b/lsp_def/classes/consumable.lua index 68e746da0..b90012efe 100644 --- a/lsp_def/classes/consumable.lua +++ b/lsp_def/classes/consumable.lua @@ -3,8 +3,8 @@ ---@class SMODS.Consumable: SMODS.Center ---@field super? SMODS.Center|table Parent class. ---@field hidden? boolean Sets if this consumable is considered "legendary" (e.x. behaves like "The Soul"). ----@field soul_set? string Key to the ConsumableType set this consumable can replace. Requires `hidden` to be true. ----@field soul_rate? number Chance this card replaces a consumable. Requires `hidden` to be true. +---@field soul_set? string|table Key(s) to the ConsumableType set(s) this consumable can replace. Requires `hidden` to be true. +---@field soul_rate? number|fun(set: string):number Chance this card replaces a consumable. Requires `hidden` to be true. ---@field type? SMODS.ConsumableType|table ConsumableType this center belongs to. ---@field legendaries? (SMODS.Consumable|table)[] All injected "legendary" consumables. ---@field __call? fun(self: SMODS.Consumable|table, o: SMODS.Consumable|table): nil|table|SMODS.Consumable @@ -30,4 +30,16 @@ SMODS.Consumable = setmetatable({}, { __call = function(self) return self end -}) \ No newline at end of file +}) + +---@param args table|{set: string, key?: string, mod?: number, guaranteed?: boolean, allow_duplicates?: boolean, ignore_vanilla?: boolean} +---@return string? +--- Polls hidden consumables. +function SMODS.poll_soul(args) end + +---@param consumable SMODS.Consumable|table|string +---@param set string +---@return number? +---@return string +--- Gets soul rate of a consumable. +function SMODS.get_soul_rate(consumable, set) end \ No newline at end of file diff --git a/src/utils.lua b/src/utils.lua index 6d50c1d76..d24556ea3 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -659,6 +659,73 @@ function SMODS.poll_edition(args) return poll_edition(args.key or 'editiongeneric', args.mod, args.no_negative, args.guaranteed, args.options) end +function SMODS.poll_soul(args) + assert(args and args.set, "SMODS.poll_soul called without a set") + + local set = args.set + local forced_key + local soul_total_rate = 0 + local non_soul_rate = 1 + local modded_souls = {} + + for _, v in ipairs(SMODS.Consumable.legendaries) do + if not G.GAME.banned_keys[v.key] then + local can_repeat = SMODS.showman(v.key) or v.can_repeat_soul or args.allow_duplicates + local is_soul_set = set == v.type.key + if not is_soul_set and v.soul_set then + local consumable_soul_sets = type(v.soul_set) == "table" and v.soul_set or { v.soul_set } + for _, soul_set in ipairs(consumable_soul_sets) do + is_soul_set = set == soul_set + if is_soul_set then break end + end + end + if is_soul_set and not (G.GAME.used_jokers[v.key] and not can_repeat) and SMODS.add_to_pool(v) then + local soul_rate = SMODS.get_soul_rate(v, set) * (args.mod or 1) + v.temp_soul_rate = soul_rate -- to ensure it doesn't change and so it isn't calculated twice + soul_total_rate = soul_total_rate + soul_rate + non_soul_rate = non_soul_rate * (1 - soul_rate) + non_soul_rate = math.max(non_soul_rate, 0) + table.insert(modded_souls, v) + end + end + end + local roll = pseudorandom(args.key or ('soul_smods_' .. set .. G.GAME.round_resets.ante)) + local threshold = 1 + non_soul_rate = args.guaranteed and 0 or non_soul_rate + for _, v in ipairs(modded_souls) do + local soul_rate = v.temp_soul_rate + threshold = threshold - soul_rate / soul_total_rate * (1 - non_soul_rate) + v.temp_soul_rate = nil + if roll > threshold then + forced_key = v.key + break + end + end + if not G.GAME.banned_keys['c_soul'] and not args.ignore_vanilla then + if (set == 'Tarot' or set == 'Spectral' or set == 'Tarot_Planet') and + not (G.GAME.used_jokers['c_soul'] and not SMODS.showman('c_soul')) then + if (not forced_key and args.guaranteed) or pseudorandom(args.key or ('soul_' .. set .. G.GAME.round_resets.ante)) > 0.997 - (0.003 * ((G.GAME.soul_mod or 1) * (args.mod or 1) - 1)) then + forced_key = 'c_soul' + end + end + if (set == 'Planet' or set == 'Spectral') and + not (G.GAME.used_jokers['c_black_hole'] and not SMODS.showman('c_black_hole')) then + if ((not forced_key or forced_key == 'c_soul') and args.guaranteed) or pseudorandom(args.key or ('soul_' .. set .. G.GAME.round_resets.ante)) > 0.997 - (0.003 * ((G.GAME.soul_mod or 1) * (args.mod or 1) - 1)) then + forced_key = 'c_black_hole' + end + end + end + return forced_key +end + +function SMODS.get_soul_rate(consumable, set) + local prototype = type(consumable) == "string" and G.P_CENTERS[consumable] or consumable + if not prototype then return nil, "SMODS.get_soul_rate was called with an invalid consumable" end + local soul_rate = type(prototype.soul_rate) == "function" and prototype.soul_rate(set) or + prototype.soul_rate or 0 + return soul_rate * (G.GAME.soul_mod or 1) +end + function SMODS.poll_seal(args) args = args or {} local key = args.key or 'stdseal'