From 70233ccb7e5a528a0479645937aed0d4cec7b07f Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 19:31:30 +1000 Subject: [PATCH 01/11] Add SMODS.Perma_Bonus Adds SMODS.Perma_Bonus and related utility functions and some changes to haw perma bonuses work --- lovely/better_calc.toml | 4 +- lovely/perma_bonus.toml | 115 +++++++++++------------- lsp_def/classes/perma_bonus.lua | 39 ++++++++ lsp_def/utils.lua | 7 +- src/game_object.lua | 9 +- src/game_objects/perma_bonuses.lua | 138 +++++++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 70 deletions(-) create mode 100644 lsp_def/classes/perma_bonus.lua create mode 100644 src/game_objects/perma_bonuses.lua diff --git a/lovely/better_calc.toml b/lovely/better_calc.toml index 1c1fd2665..eb4584df1 100644 --- a/lovely/better_calc.toml +++ b/lovely/better_calc.toml @@ -251,9 +251,9 @@ if card.ability.repetitions and card.ability.repetitions > 0 then ret.seals = ret.seals or { card = card, message = localize('k_again_ex') } ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card.ability.repetitions) or card.ability.repetitions end -if card.ability.perma_repetitions and card.ability.perma_repetitions > 0 then +if card:get_perma_bonus('repetitions') > 0 then ret.seals = ret.seals or { card = card, message = localize('k_again_ex') } - ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card.ability.perma_repetitions) or card.ability.perma_repetitions + ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card:get_perma_bonus('repetitions')) or card:get_perma_bonus('repetitions') end """ [[patches]] diff --git a/lovely/perma_bonus.toml b/lovely/perma_bonus.toml index 79a7e40da..60fd24329 100644 --- a/lovely/perma_bonus.toml +++ b/lovely/perma_bonus.toml @@ -77,8 +77,7 @@ overwrite = false target = "card.lua" pattern = "loc_vars = { playing_card = not not self.base.colour, value = self.base.value, suit = self.base.suit, colour = self.base.colour," position = "before" -payload = '''local bonus_chips = self.ability.bonus + (self.ability.perma_bonus or 0) -local total_h_dollars = self:get_h_dollars()''' +payload = '''local total_h_dollars = self:get_h_dollars()''' match_indent = true overwrite = false @@ -86,30 +85,16 @@ overwrite = false [[patches]] [patches.pattern] target = "card.lua" -pattern = "bonus_chips = (self.ability.bonus + (self.ability.perma_bonus or 0)) > 0 and (self.ability.bonus + (self.ability.perma_bonus or 0)) or nil," +pattern = '''bonus_chips = (self.ability.bonus + (self.ability.perma_bonus or 0)) > 0 and (self.ability.bonus + (self.ability.perma_bonus or 0)) or nil, +}''' position = "at" payload = ''' -bonus_x_chips = self.ability.perma_x_chips ~= 0 and (self.ability.perma_x_chips + 1) or nil, -bonus_mult = self.ability.perma_mult ~= 0 and self.ability.perma_mult or nil, -bonus_x_mult = self.ability.perma_x_mult ~= 0 and (self.ability.perma_x_mult + 1) or nil, -bonus_h_chips = self.ability.perma_h_chips ~= 0 and self.ability.perma_h_chips or nil, -bonus_h_x_chips = self.ability.perma_h_x_chips ~= 0 and (self.ability.perma_h_x_chips + 1) or nil, -bonus_h_mult = self.ability.perma_h_mult ~= 0 and self.ability.perma_h_mult or nil, -bonus_h_x_mult = self.ability.perma_h_x_mult ~= 0 and (self.ability.perma_h_x_mult + 1) or nil, -bonus_p_dollars = self.ability.perma_p_dollars ~= 0 and self.ability.perma_p_dollars or nil, -bonus_h_dollars = self.ability.perma_h_dollars ~= 0 and self.ability.perma_h_dollars or nil, -total_h_dollars = total_h_dollars ~= 0 and total_h_dollars or nil, -bonus_score = self.ability.perma_score ~= 0 and (self.ability.perma_score) or nil, -bonus_h_score = self.ability.perma_h_score ~= 0 and (self.ability.perma_h_score) or nil, -bonus_x_score = self.ability.perma_x_score ~= 0 and (self.ability.perma_x_score + 1) or nil, -bonus_h_x_score = self.ability.perma_h_x_score ~= 0 and (self.ability.perma_h_x_score + 1) or nil, -bonus_blind_size = self.ability.perma_blind_size ~= 0 and (self.ability.perma_blind_size) or nil, -bonus_h_blind_size = self.ability.perma_h_blind_size ~= 0 and (self.ability.perma_h_blind_size) or nil, -bonus_x_blind_size = self.ability.perma_x_blind_size ~= 0 and (self.ability.perma_x_blind_size + 1) or nil, -bonus_h_x_blind_size = self.ability.perma_h_x_blind_size ~= 0 and (self.ability.perma_h_x_blind_size + 1) or nil, -bonus_chips = bonus_chips ~= 0 and bonus_chips or nil, -bonus_repetitions = self.ability.perma_repetitions ~= 0 and self.ability.perma_repetitions or nil,''' -match_indent = true + total_h_dollars = total_h_dollars ~= 0 and total_h_dollars or nil + } + for k,v in pairs(SMODS.get_perma_bonus_ui_vars(self)) do + if v ~= nil then loc_vars[k] = v end + end''' +match_indent = false # set_ability: set defaults for temporary bonuses # Also add conformance with SMODS documentation. @@ -132,31 +117,15 @@ overwrite = false [[patches]] [patches.pattern] target = "card.lua" -pattern = "perma_bonus = self.ability and self.ability.perma_bonus or 0," -position = "after" +pattern = '''perma_bonus = self.ability and self.ability.perma_bonus or 0, +}''' +position = "at" payload = ''' -perma_x_chips = self.ability and self.ability.perma_x_chips or 0, -perma_mult = self.ability and self.ability.perma_mult or 0, -perma_x_mult = self.ability and self.ability.perma_x_mult or 0, -perma_h_chips = self.ability and self.ability.perma_h_chips or 0, -perma_h_x_chips = self.ability and self.ability.perma_h_x_chips or 0, -perma_h_mult = self.ability and self.ability.perma_h_mult or 0, -perma_h_x_mult = self.ability and self.ability.perma_h_x_mult or 0, -perma_p_dollars = self.ability and self.ability.perma_p_dollars or 0, -perma_h_dollars = self.ability and self.ability.perma_h_dollars or 0, -perma_repetitions = self.ability and self.ability.perma_repetitions or 0, -card_limit = self.ability and self.ability.card_limit or 0, -extra_slots_used = self.ability and self.ability.extra_slots_used or 0, -perma_score = self.ability and self.ability.perma_score or 0, -perma_h_score = self.ability and self.ability.perma_h_score or 0, -perma_x_score = self.ability and self.ability.perma_x_score or 0, -perma_h_x_score = self.ability and self.ability.perma_h_x_score or 0, -perma_blind_size = self.ability and self.ability.perma_blind_size or 0, -perma_h_blind_size = self.ability and self.ability.perma_h_blind_size or 0, -perma_x_blind_size = self.ability and self.ability.perma_x_blind_size or 0, -perma_h_x_blind_size = self.ability and self.ability.perma_h_x_blind_size or 0, -''' -match_indent = true + card_limit = self.ability and self.ability.card_limit or 0, + extra_slots_used = self.ability and self.ability.extra_slots_used or 0, + } + SMODS.set_perma_bonus(self, new_ability)''' +match_indent = false # Card:get_chip_bonus [[patches]] @@ -168,6 +137,22 @@ match_indent = true payload = ''' if self.ability.extra_enhancement then return self.ability.bonus end''' +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "return self.ability.bonus + (self.ability.perma_bonus or 0)" +position = "at" +match_indent = true +payload = "return self.ability.bonus + (self:get_perma_bonus('chips') or 0)" + +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "return self.base.nominal + self.ability.bonus + (self.ability.perma_bonus or 0)" +position = "at" +match_indent = true +payload = "return self.base.nominal + self.ability.bonus + (self:get_perma_bonus('chips') or 0)" + # Card:get_chip_mult [[patches]] [patches.pattern] @@ -184,7 +169,7 @@ else end''' position = "at" match_indent = true -payload = '''local ret = (not self.ability.extra_enhancement and self.ability.perma_mult) or 0 +payload = '''local ret = (not self.ability.extra_enhancement and self:get_perma_bonus('mult')) or 0 if self.ability.effect == "Lucky Card" then if SMODS.pseudorandom_probability(self, 'lucky_mult', 1, 5) then self.lucky_trigger = true @@ -204,7 +189,7 @@ pattern = '''if self.ability.x_mult <= 1 then return 0 end return self.ability.x_mult''' position = "at" match_indent = true -payload = '''local ret = SMODS.multiplicative_stacking(self.ability.x_mult or 1, (not self.ability.extra_enhancement and self.ability.perma_x_mult) or 0) +payload = '''local ret = SMODS.multiplicative_stacking(self.ability.x_mult or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('x_mult')) or 0) -- TARGET: get_chip_x_mult return ret ''' @@ -216,7 +201,7 @@ target = "card.lua" pattern = 'return self.ability.h_mult' position = "at" match_indent = true -payload = '''local ret = (self.ability.h_mult or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_mult) or 0) +payload = '''local ret = (self.ability.h_mult or 0) + ((not self.ability.extra_enhancement and self:get_perma_bonus('h_mult')) or 0) -- TARGET: get_chip_h_mult return ret ''' @@ -228,7 +213,7 @@ target = "card.lua" pattern = 'return self.ability.h_x_mult' position = "at" match_indent = true -payload = '''local ret = SMODS.multiplicative_stacking(self.ability.h_x_mult or 1, (not self.ability.extra_enhancement and self.ability.perma_h_x_mult) or 0) +payload = '''local ret = SMODS.multiplicative_stacking(self.ability.h_x_mult or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('h_x_mult')) or 0) -- TARGET: get_chip_h_x_mult return ret ''' @@ -246,83 +231,83 @@ match_indent = true payload = ''' function Card:get_chip_x_bonus() if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(self.ability.x_chips or 1, (not self.ability.extra_enhancement and self.ability.perma_x_chips) or 0) + local ret = SMODS.multiplicative_stacking(self.ability.x_chips or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('x_chips')) or 0) -- TARGET: get_chip_x_bonus return ret end function Card:get_chip_h_bonus() if self.debuff then return 0 end - local ret = (self.ability.h_chips or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_chips) or 0) + local ret = (self.ability.h_chips or 0) + ((not self.ability.extra_enhancement and self:get_perma_bonus('h_chips')) or 0) -- TARGET: get_chip_h_bonus return ret end function Card:get_chip_h_x_bonus() if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(self.ability.h_x_chips or 1, (not self.ability.extra_enhancement and self.ability.perma_h_x_chips) or 0) + local ret = SMODS.multiplicative_stacking(self.ability.h_x_chips or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('h_x_chips')) or 0) -- TARGET: get_chip_h_x_bonus return ret end function Card:get_h_dollars() if self.debuff then return 0 end - local ret = (self.ability.h_dollars or 0) + ((not self.ability.extra_enhancement and self.ability.perma_h_dollars) or 0) + local ret = (self.ability.h_dollars or 0) + ((not self.ability.extra_enhancement and self:get_perma_bonus('h_dollars')) or 0) -- TARGET: get_h_dollars return ret end function Card:get_bonus_score() if self.debuff then return 0 end - local ret = (self.ability.perma_score or 0) + local ret = (self:get_perma_bonus('score') or 0) -- TARGET: get_bonus_score return ret end function Card:get_bonus_x_score() if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(1, self.ability.perma_x_score or 0) + local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('x_score') or 0) -- TARGET: get_bonus_x_score return ret end function Card:get_bonus_h_score() if self.debuff then return 0 end - local ret = (self.ability.perma_h_score or 0) + local ret = (self:get_perma_bonus('h_score') or 0) -- TARGET: get_bonus_h_score return ret end function Card:get_bonus_h_x_score() if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(1, self.ability.perma_h_x_score or 0) + local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('h_x_score') or 0) -- TARGET: get_bonus_h_x_score return ret end function Card:get_bonus_blind_size() if self.debuff then return 0 end - local ret = (self.ability.perma_blind_size or 0) + local ret = (self:get_perma_bonus('blind_size') or 0) -- TARGET: get_bonus_blind_size return ret end function Card:get_bonus_x_blind_size() if self.debuff then return 1 end - local ret = SMODS.multiplicative_stacking(1, self.ability.perma_x_blind_size or 0) + local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('x_blind_size') or 0) -- TARGET: get_bonus_x_blind_size return ret end function Card:get_bonus_h_blind_size() if self.debuff then return 0 end - local ret = (self.ability.perma_h_blind_size or 0) + local ret = (self:get_perma_bonus('h_blind_size') or 0) -- TARGET: get_bonus_h_blind_size return ret end function Card:get_bonus_h_x_blind_size() if self.debuff then return 1 end - local ret = SMODS.multiplicative_stacking(1, self.ability.perma_h_x_blind_size or 0) + local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('h_x_blind_size') or 0) -- TARGET: get_bonus_h_x_blind_size return ret end @@ -340,7 +325,7 @@ match_indent = true payload = '''elseif self.ability.p_dollars < 0 then ret = ret + self.ability.p_dollars end -ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_p_dollars) or 0) +ret = ret + ((not self.ability.extra_enhancement and self:get_perma_bonus('p_dollars')) or 0) -- TARGET: get_p_dollars if ret ~= 0 then G.GAME.dollar_buffer = (G.GAME.dollar_buffer or 0) + ret''' diff --git a/lsp_def/classes/perma_bonus.lua b/lsp_def/classes/perma_bonus.lua new file mode 100644 index 000000000..3631b41f4 --- /dev/null +++ b/lsp_def/classes/perma_bonus.lua @@ -0,0 +1,39 @@ +---@meta + +---@class SMODS.Perma_Bonus: SMODS.GameObject +---@field obj_buffer? string[] Array of keys to all objects registered to this class. +---@field obj_table? table Table of objects registered to this class. +---@field super? SMODS.GameObject|table Parent class. +---@field __call? fun(self: SMODS.Perma_Bonus|table, o: SMODS.Perma_Bonus|table): nil|table|SMODS.Perma_Bonus +---@field extend? fun(self: SMODS.Perma_Bonus|table, o: SMODS.Perma_Bonus|table): table Primary method of creating a class. +---@field check_duplicate_register? fun(self: SMODS.Perma_Bonus|table): boolean? Ensures objects already registered will not register. +---@field check_duplicate_key? fun(self: SMODS.Perma_Bonus|table): boolean? Ensures objects with duplicate keys will not register. Checked on `__call` but not `take_ownership`. For take_ownership, the key must exist. +---@field register? fun(self: SMODS.Perma_Bonus|table) Registers the object. +---@field check_dependencies? fun(self: SMODS.Perma_Bonus|table): boolean? Returns `true` if there's no failed dependencies. +---@field process_loc_text? fun(self: SMODS.Perma_Bonus|table) Called during `inject_class`. Handles injecting loc_text. +---@field send_to_subclasses? fun(self: SMODS.Perma_Bonus|table, func: string, ...: any) Starting from this class, recusively searches for functions with the given key on all subordinate classes and run all found functions with the given arguments. +---@field pre_inject_class? fun(self: SMODS.Perma_Bonus|table) Called before `inject_class`. Injects and manages class information before object injection. +---@field post_inject_class? fun(self: SMODS.Perma_Bonus|table) Called after `inject_class`. Injects and manages class information after object injection. +---@field inject_class? fun(self: SMODS.Perma_Bonus|table) Injects all direct instances of class objects by calling `obj:inject` and `obj:process_loc_text`. Also injects anything necessary for the class itself. Only called if class has defined both `obj_table` and `obj_buffer`. +---@field inject? fun(self: SMODS.Perma_Bonus|table, i?: number) Called during `inject_class`. Injects the object into the game. +---@field take_ownership? fun(self: SMODS.Perma_Bonus|table, key: string, obj: SMODS.Perma_Bonus|table, silent?: boolean): nil|table|SMODS.Perma_Bonus Takes control of vanilla objects. Child class must have get_obj for this to function +---@field get_obj? fun(self: SMODS.Perma_Bonus|table, key: string): SMODS.Perma_Bonus|table? Returns an object if one matches the `key`. +---@field should_apply? fun(self: SMODS.Perma_Bonus|table, card: Card|table, calculation: string): boolean? Returns true if the Bonus applies to the given `calculation`. +---@field key string Used to reference the Perma_Bonus, also used in the card.ability table. +---@field apply_to string A string representing what calculation this bonus applies to; see the perma bonuses page for provided calculations. +---@field get_ui_value? fun(self: SMODS.Perma_Bonus|table, card: Card|table): number|nil Returns the number to be used for the display in the ui box, returns `nil` if the bonus is 0. +---@field upgrade? fun(self: SMODS.Perma_Bonus|table, card: Card|table, amount: number?, from?: Card|table?) Called through SMODS.upgrade_perma_bonus, allows for control over how the perma bonus is upgraded. +---@field loc_key? string The key of the object in G.localization.descriptions.Other, defaults to the object's key. +---@field vars_key? string How the value of the bonus is stored in the specific_vars table for playing card ui, defaults to the object's key. +---@field signed_value? boolean If this is `true`, the value in the ui box will be signed with `SMODS.signed`. +---@field signed_dollars? boolean Same as signed_value except the value is signed using `SMODS.signed_dollars`, has priority over signed_value by deafult. +---@field localize? fun(self: SMODS.Perma_Bonus|table, value: number, desc_nodes: table) Defines what to show in the playing card description, not recommended to change unless you know what you're doing. +---@overload fun(self: SMODS.Perma_Bonus): SMODS.Perma_Bonus +SMODS.Perma_Bonus = setmetatable({}, { + __call = function(self) + return self + end +}) + +---@type table +SMODS.Perma_Bonuses = {} \ No newline at end of file diff --git a/lsp_def/utils.lua b/lsp_def/utils.lua index a1f63a68e..224543c11 100644 --- a/lsp_def/utils.lua +++ b/lsp_def/utils.lua @@ -843,4 +843,9 @@ function SMODS.mod_blind_size(mod_blind_size) end ---@field mult? number Multiply blind size by this number ---@field card? Card Card responsible for blind size modification action, crucial for blind size display to work properly ---@field effect? table Table of effects that were calculated ----@field from_edition? boolean \ No newline at end of file +---@field from_edition? boolean + +---Upgrades the given perma bonuses on the given card by `amount` +---Alternatively, provide a custom `func` to override the default upgrade behaviour +---@param args table|{keys: string|table, card: Card|table, amount?: number, from?: Card|table, func?: fun(card: Card|table, amount: number, from: Card|table, perma_bonus: SMODS.Perma_Bonus)} +function SMODS.upgrade_perma_bonus(args) end \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index e73a7ac62..6113dedb2 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3298,12 +3298,12 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. localize { type = 'other', key = 'card_chips', nodes = desc_nodes, vars = { specific_vars.nominal_chips } } end SMODS.Enhancement.super.generate_ui(self, info_queue, card, desc_nodes, specific_vars, full_UI_table) - if specific_vars and specific_vars.bonus_chips then + --[[if specific_vars and specific_vars.bonus_chips then local remaining_bonus_chips = specific_vars.bonus_chips - (self.config.bonus or 0) if remaining_bonus_chips ~= 0 then localize { type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = { SMODS.signed(remaining_bonus_chips) } } end - end + end]] SMODS.localize_perma_bonuses(specific_vars, desc_nodes) end, -- other methods: @@ -3987,6 +3987,11 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. text = '^' } + ------------------------------------------------------------------------------------------------- + ----- API CODE GameObject.Perma_Bonus + ------------------------------------------------------------------------------------------------- + + assert(load(SMODS.NFS.read(SMODS.path..'src/game_objects/perma_bonuses.lua'), ('=[SMODS _ "src/game_objects/perma_bonuses.lua"]')))() ------------------------------------------------------------------------------------------------- ----- API IMPORT GameObject.DrawStep diff --git a/src/game_objects/perma_bonuses.lua b/src/game_objects/perma_bonuses.lua new file mode 100644 index 000000000..7793fc4a5 --- /dev/null +++ b/src/game_objects/perma_bonuses.lua @@ -0,0 +1,138 @@ +SMODS.Perma_Bonuses = {} +SMODS.Perma_Bonus = SMODS.GameObject:extend{ + set = "Perma_Bonus", + obj_table = SMODS.Perma_Bonuses, + obj_buffer = {}, + required_params = { + 'key', + 'apply_to', + }, + prefix_config = { key = false }, + signed_value = false, + signed_dollars = false, + should_apply = function(self, card, calculation) + if calculation == self.apply_to then return true end + end, + get_ui_value = function(self, card) + return card.ability[self.key] and card.ability[self.key] ~= 0 and (card.ability[self.key] + (self.ui_mod or 0)) or nil + end, + upgrade = function(self, card, amount) + card.ability[self.key] = card.ability[self.key] or 0 + card.ability[self.key] = card.ability[self.key] + (amount or 1) + end, + localize = function(self, value, desc_nodes) + if self.signed_dollars then value = SMODS.signed_dollars(value) + elseif self.signed_value then value = SMODS.signed(value) end + localize{type = 'other', key = self.loc_key, nodes = desc_nodes, vars = {value}} + end, + inject = function(self) + self.loc_key = self.loc_key or self.key + self.vars_key = self.vars_key or self.key + end, + process_loc_text = function(self) + SMODS.process_loc_text(G.localization.descriptions.Other, self.loc_key, self.loc_txt) + end, +} + +function SMODS.localize_perma_bonuses(specific_vars, desc_nodes) + if not specific_vars then return end + for _,_key in pairs(SMODS.Perma_Bonus.obj_buffer) do + local PB = SMODS.Perma_Bonuses[_key] + --perma_bonus text is handled by vanilla + if _key ~= 'perma_bonus' and specific_vars[PB.vars_key] then + PB:localize(specific_vars[PB.vars_key], desc_nodes) + end + end +end + +function SMODS.set_perma_bonus(card, new_ability) + for _,_key in ipairs(SMODS.Perma_Bonus.obj_buffer) do + new_ability[_key] = card.ability and card.ability[_key] or 0 + end +end + +function SMODS.get_perma_bonus_ui_vars(card) + if not card.ability then return {} end + local ret = {} + for _,v in pairs(SMODS.Perma_Bonuses) do + local bonus = v:get_ui_value(card) + ret[v.vars_key] = ((ret[v.vars_key] or 0) + (bonus or 0) ~= 0 and (ret[v.vars_key] or 0) + (bonus or 0)) or nil + end + return ret +end + +function SMODS.upgrade_perma_bonus(args) + if type(args.keys) == 'string' then args.keys = {args.keys} end + if type(args.keys) ~= 'table' then return end + args.amount = args.amount or 1 + for _,key in ipairs(args.keys) do + if SMODS.Perma_Bonuses[key] then + if args.func then + args.func(args.card, args.amount, args.from or {}, SMODS.Perma_Bonuses[key]) + else + SMODS.Perma_Bonuses[key]:upgrade(args.card, args.amount, args.from or {}) + end + end + end +end + +function Card:get_perma_bonus(calculation) + local ret = (not calculation and {}) or 0 + for k,v in pairs(SMODS.Perma_Bonuses) do + if not calculation then + ret[k] = self.ability[k] + elseif v:should_apply(self, calculation) then + ret = ret + (self.ability[k] or 0) + end + end + return ret +end + +SMODS.Perma_Bonus({ + key = 'perma_bonus', + apply_to = 'chips', + vars_key = 'bonus_chips', + loc_key = 'card_extra_chips', + signed_value = true, + get_ui_value = function(self, card) + if not (card.ability and card.ability.bonus) then return end + local bonus_chips = card.ability.bonus + (card.ability[self.key] or 0) + return bonus_chips ~= 0 and bonus_chips or nil + end +}) +for _,pb in ipairs({ + {key = 'perma_x_chips', apply_to = 'x_chips', vars_key = 'bonus_x_chips', loc_key = 'card_extra_x_chips', ui_mod = 1}, + + {key = 'perma_mult', apply_to = 'mult', vars_key = 'bonus_mult', loc_key = 'card_extra_mult', signed_value = true}, + {key = 'perma_x_mult', apply_to = 'x_mult', vars_key = 'bonus_x_mult', loc_key = 'card_extra_x_mult', ui_mod = 1}, + + {key = 'perma_h_chips', apply_to = 'h_chips', vars_key = 'bonus_h_chips', loc_key = 'card_extra_h_chips', signed_value = true}, + {key = 'perma_h_x_chips', apply_to = 'h_x_chips', vars_key = 'bonus_h_x_chips', loc_key = 'card_extra_h_x_chips', ui_mod = 1}, + {key = 'perma_h_mult', apply_to = 'h_mult', vars_key = 'bonus_h_mult', loc_key = 'card_extra_h_mult', signed_value = true}, + {key = 'perma_h_x_mult', apply_to = 'h_x_mult', vars_key = 'bonus_h_x_mult', loc_key = 'card_extra_h_x_mult', ui_mod = 1}, + + {key = 'perma_p_dollars', apply_to = 'p_dollars', vars_key = 'bonus_p_dollars', loc_key = 'card_extra_p_dollars', signed_dollars = true}, + {key = 'perma_h_dollars', apply_to = 'h_dollars', vars_key = 'bonus_h_dollars', loc_key = 'card_extra_h_dollars', signed_dollars = true}, + + {key = 'perma_score', apply_to = 'score', vars_key = 'bonus_score', loc_key = 'card_extra_score', signed_value = true}, + {key = 'perma_h_score', apply_to = 'h_score', vars_key = 'bonus_h_score', loc_key = 'card_extra_h_score', signed_value = true}, + {key = 'perma_x_score', apply_to = 'x_score', vars_key = 'bonus_x_score', loc_key = 'card_extra_x_score', ui_mod = 1}, + {key = 'perma_h_x_score', apply_to = 'h_x_score', vars_key = 'bonus_h_x_score', loc_key = 'card_extra_h_x_score', ui_mod = 1}, + + {key = 'perma_blind_size', apply_to = 'blind_size', vars_key = 'bonus_blind_size', loc_key = 'card_extra_blind_size', signed_value = true}, + {key = 'perma_h_blind_size', apply_to = 'h_blind_size', vars_key = 'bonus_h_blind_size', loc_key = 'card_extra_h_blind_size', signed_value = true}, + {key = 'perma_x_blind_size', apply_to = 'x_blind_size', vars_key = 'bonus_x_blind_size', loc_key = 'card_extra_x_blind_size', ui_mod = 1}, + {key = 'perma_h_x_blind_size', apply_to = 'h_x_blind_size', vars_key = 'bonus_h_x_blind_size', loc_key = 'card_extra_h_x_blind_size', ui_mod = 1}, +}) do + SMODS.Perma_Bonus(pb) +end + +SMODS.Perma_Bonus({ + key = 'perma_repetitions', + apply_to = 'repetitions', + vars_key = 'bonus_repetitions', + loc_key = 'card_extra_repetitions', + localize = function(self, value, desc_nodes) + localize{type = 'other', key = self.loc_key, nodes = desc_nodes, vars = {value, localize(value > 1 and 'b_retrigger_plural' or 'b_retrigger_single')}} + end +}) \ No newline at end of file From 67e3ddfc3742bc4ba6985fb1f02517cbbc5bef9e Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 20:25:08 +1000 Subject: [PATCH 02/11] Early return if no card provided in SMODS.upgrade_perma_bonus --- src/game_objects/perma_bonuses.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game_objects/perma_bonuses.lua b/src/game_objects/perma_bonuses.lua index 7793fc4a5..5c1cfc8f5 100644 --- a/src/game_objects/perma_bonuses.lua +++ b/src/game_objects/perma_bonuses.lua @@ -63,7 +63,7 @@ end function SMODS.upgrade_perma_bonus(args) if type(args.keys) == 'string' then args.keys = {args.keys} end - if type(args.keys) ~= 'table' then return end + if type(args.keys) ~= 'table' or type(args.card) ~= 'table' then return end args.amount = args.amount or 1 for _,key in ipairs(args.keys) do if SMODS.Perma_Bonuses[key] then @@ -135,4 +135,4 @@ SMODS.Perma_Bonus({ localize = function(self, value, desc_nodes) localize{type = 'other', key = self.loc_key, nodes = desc_nodes, vars = {value, localize(value > 1 and 'b_retrigger_plural' or 'b_retrigger_single')}} end -}) \ No newline at end of file +}) From e9106b43a28212a75d778ba78656ee4620103501 Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 20:26:55 +1000 Subject: [PATCH 03/11] Remove old SMODS.localize_perma_bonus function --- src/utils.lua | 57 --------------------------------------------------- 1 file changed, 57 deletions(-) diff --git a/src/utils.lua b/src/utils.lua index 617b8c59a..e41d00e69 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -3275,63 +3275,6 @@ function SMODS.next_stake(stake_key, deck_key, ignore_unlock) return (next_stake or SMODS.Stakes.stake_white).key end -function SMODS.localize_perma_bonuses(specific_vars, desc_nodes) - if specific_vars and specific_vars.bonus_x_chips then - localize{type = 'other', key = 'card_x_chips', nodes = desc_nodes, vars = {specific_vars.bonus_x_chips}} - end - if specific_vars and specific_vars.bonus_mult then - localize{type = 'other', key = 'card_extra_mult', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_mult)}} - end - if specific_vars and specific_vars.bonus_x_mult then - localize{type = 'other', key = 'card_x_mult', nodes = desc_nodes, vars = {specific_vars.bonus_x_mult}} - end - if specific_vars and specific_vars.bonus_h_chips then - localize{type = 'other', key = 'card_extra_h_chips', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_h_chips)}} - end - if specific_vars and specific_vars.bonus_h_x_chips then - localize{type = 'other', key = 'card_h_x_chips', nodes = desc_nodes, vars = {specific_vars.bonus_h_x_chips}} - end - if specific_vars and specific_vars.bonus_h_mult then - localize{type = 'other', key = 'card_extra_h_mult', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_h_mult)}} - end - if specific_vars and specific_vars.bonus_h_x_mult then - localize{type = 'other', key = 'card_h_x_mult', nodes = desc_nodes, vars = {specific_vars.bonus_h_x_mult}} - end - if specific_vars and specific_vars.bonus_p_dollars then - localize{type = 'other', key = 'card_extra_p_dollars', nodes = desc_nodes, vars = {SMODS.signed_dollars(specific_vars.bonus_p_dollars)}} - end - if specific_vars and specific_vars.bonus_h_dollars then - localize{type = 'other', key = 'card_extra_h_dollars', nodes = desc_nodes, vars = {SMODS.signed_dollars(specific_vars.bonus_h_dollars)}} - end - if specific_vars and specific_vars.bonus_score then - localize{type = 'other', key = 'card_extra_score', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_score)}} - end - if specific_vars and specific_vars.bonus_h_score then - localize{type = 'other', key = 'card_extra_h_score', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_h_score)}} - end - if specific_vars and specific_vars.bonus_x_score then - localize{type = 'other', key = 'card_extra_x_score', nodes = desc_nodes, vars = {(specific_vars.bonus_x_score)}} - end - if specific_vars and specific_vars.bonus_h_x_score then - localize{type = 'other', key = 'card_extra_h_x_score', nodes = desc_nodes, vars = {(specific_vars.bonus_h_x_score)}} - end - if specific_vars and specific_vars.bonus_blind_size then - localize{type = 'other', key = 'card_extra_blind_size', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_blind_size)}} - end - if specific_vars and specific_vars.bonus_h_blind_size then - localize{type = 'other', key = 'card_extra_h_blind_size', nodes = desc_nodes, vars = {SMODS.signed(specific_vars.bonus_h_blind_size)}} - end - if specific_vars and specific_vars.bonus_x_blind_size then - localize{type = 'other', key = 'card_extra_x_blind_size', nodes = desc_nodes, vars = {(specific_vars.bonus_x_blind_size)}} - end - if specific_vars and specific_vars.bonus_h_x_blind_size then - localize{type = 'other', key = 'card_extra_h_x_blind_size', nodes = desc_nodes, vars = {(specific_vars.bonus_h_x_blind_size)}} - end - if specific_vars and specific_vars.bonus_repetitions then - localize{type = 'other', key = 'card_extra_repetitions', nodes = desc_nodes, vars = {specific_vars.bonus_repetitions, localize(specific_vars.bonus_repetitions > 1 and 'b_retrigger_plural' or 'b_retrigger_single')}} - end -end - local ease_dollar_ref = ease_dollars function ease_dollars(mod, instant) ease_dollar_ref(mod, instant) From 085153f1bc8d838be1cae9ff94c048d5070c64e9 Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 20:29:15 +1000 Subject: [PATCH 04/11] Remove commented-out code chunk from testing --- src/game_object.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game_object.lua b/src/game_object.lua index 6113dedb2..962c84838 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3298,12 +3298,12 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. localize { type = 'other', key = 'card_chips', nodes = desc_nodes, vars = { specific_vars.nominal_chips } } end SMODS.Enhancement.super.generate_ui(self, info_queue, card, desc_nodes, specific_vars, full_UI_table) - --[[if specific_vars and specific_vars.bonus_chips then + if specific_vars and specific_vars.bonus_chips then local remaining_bonus_chips = specific_vars.bonus_chips - (self.config.bonus or 0) if remaining_bonus_chips ~= 0 then localize { type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = { SMODS.signed(remaining_bonus_chips) } } end - end]] + end SMODS.localize_perma_bonuses(specific_vars, desc_nodes) end, -- other methods: From a965d5ffa8d3beadb756fbba5419de93262b63a4 Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 21:05:19 +1000 Subject: [PATCH 05/11] Fix: Double descriptions when two Perma_Bonuses share a vars_key --- src/game_objects/perma_bonuses.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/game_objects/perma_bonuses.lua b/src/game_objects/perma_bonuses.lua index 5c1cfc8f5..d977cf190 100644 --- a/src/game_objects/perma_bonuses.lua +++ b/src/game_objects/perma_bonuses.lua @@ -36,11 +36,13 @@ SMODS.Perma_Bonus = SMODS.GameObject:extend{ function SMODS.localize_perma_bonuses(specific_vars, desc_nodes) if not specific_vars then return end + local used_keys = {} for _,_key in pairs(SMODS.Perma_Bonus.obj_buffer) do local PB = SMODS.Perma_Bonuses[_key] --perma_bonus text is handled by vanilla - if _key ~= 'perma_bonus' and specific_vars[PB.vars_key] then + if _key ~= 'perma_bonus' and specific_vars[PB.vars_key] and not used_keys[PB.vars_key] then PB:localize(specific_vars[PB.vars_key], desc_nodes) + used_keys[PB.vars_key] = true end end end From cc701e24b43b51ca86a696750d0d7785db568263 Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 21:08:02 +1000 Subject: [PATCH 06/11] Requested change --- src/game_objects/perma_bonuses.lua | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/game_objects/perma_bonuses.lua b/src/game_objects/perma_bonuses.lua index d977cf190..ae9f7e243 100644 --- a/src/game_objects/perma_bonuses.lua +++ b/src/game_objects/perma_bonuses.lua @@ -1,7 +1,7 @@ -SMODS.Perma_Bonuses = {} -SMODS.Perma_Bonus = SMODS.GameObject:extend{ - set = "Perma_Bonus", - obj_table = SMODS.Perma_Bonuses, +SMODS.PermaBonuses = {} +SMODS.PermaBonus = SMODS.GameObject:extend{ + set = "PermaBonus", + obj_table = SMODS.PermaBonuses, obj_buffer = {}, required_params = { 'key', @@ -37,8 +37,8 @@ SMODS.Perma_Bonus = SMODS.GameObject:extend{ function SMODS.localize_perma_bonuses(specific_vars, desc_nodes) if not specific_vars then return end local used_keys = {} - for _,_key in pairs(SMODS.Perma_Bonus.obj_buffer) do - local PB = SMODS.Perma_Bonuses[_key] + for _,_key in pairs(SMODS.PermaBonus.obj_buffer) do + local PB = SMODS.PermaBonuses[_key] --perma_bonus text is handled by vanilla if _key ~= 'perma_bonus' and specific_vars[PB.vars_key] and not used_keys[PB.vars_key] then PB:localize(specific_vars[PB.vars_key], desc_nodes) @@ -48,7 +48,7 @@ function SMODS.localize_perma_bonuses(specific_vars, desc_nodes) end function SMODS.set_perma_bonus(card, new_ability) - for _,_key in ipairs(SMODS.Perma_Bonus.obj_buffer) do + for _,_key in ipairs(SMODS.PermaBonus.obj_buffer) do new_ability[_key] = card.ability and card.ability[_key] or 0 end end @@ -56,7 +56,7 @@ end function SMODS.get_perma_bonus_ui_vars(card) if not card.ability then return {} end local ret = {} - for _,v in pairs(SMODS.Perma_Bonuses) do + for _,v in pairs(SMODS.PermaBonuses) do local bonus = v:get_ui_value(card) ret[v.vars_key] = ((ret[v.vars_key] or 0) + (bonus or 0) ~= 0 and (ret[v.vars_key] or 0) + (bonus or 0)) or nil end @@ -68,11 +68,11 @@ function SMODS.upgrade_perma_bonus(args) if type(args.keys) ~= 'table' or type(args.card) ~= 'table' then return end args.amount = args.amount or 1 for _,key in ipairs(args.keys) do - if SMODS.Perma_Bonuses[key] then + if SMODS.PermaBonuses[key] then if args.func then - args.func(args.card, args.amount, args.from or {}, SMODS.Perma_Bonuses[key]) + args.func(args.card, args.amount, args.from or {}, SMODS.PermaBonuses[key]) else - SMODS.Perma_Bonuses[key]:upgrade(args.card, args.amount, args.from or {}) + SMODS.PermaBonuses[key]:upgrade(args.card, args.amount, args.from or {}) end end end @@ -80,7 +80,7 @@ end function Card:get_perma_bonus(calculation) local ret = (not calculation and {}) or 0 - for k,v in pairs(SMODS.Perma_Bonuses) do + for k,v in pairs(SMODS.PermaBonuses) do if not calculation then ret[k] = self.ability[k] elseif v:should_apply(self, calculation) then @@ -90,7 +90,7 @@ function Card:get_perma_bonus(calculation) return ret end -SMODS.Perma_Bonus({ +SMODS.PermaBonus({ key = 'perma_bonus', apply_to = 'chips', vars_key = 'bonus_chips', @@ -126,10 +126,10 @@ for _,pb in ipairs({ {key = 'perma_x_blind_size', apply_to = 'x_blind_size', vars_key = 'bonus_x_blind_size', loc_key = 'card_extra_x_blind_size', ui_mod = 1}, {key = 'perma_h_x_blind_size', apply_to = 'h_x_blind_size', vars_key = 'bonus_h_x_blind_size', loc_key = 'card_extra_h_x_blind_size', ui_mod = 1}, }) do - SMODS.Perma_Bonus(pb) + SMODS.PermaBonus(pb) end -SMODS.Perma_Bonus({ +SMODS.PermaBonus({ key = 'perma_repetitions', apply_to = 'repetitions', vars_key = 'bonus_repetitions', From d2cd8c599279914c10ce9adc026cf6a25d59676a Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 21:13:36 +1000 Subject: [PATCH 07/11] Requested Style Change + missed instance of new_ability in overrides.lua --- lsp_def/classes/perma_bonus.lua | 60 +++++++++++++++--------------- lsp_def/utils.lua | 2 +- src/game_object.lua | 6 +-- src/game_objects/perma_bonuses.lua | 20 +++++++--- src/overrides.lua | 20 +--------- 5 files changed, 50 insertions(+), 58 deletions(-) diff --git a/lsp_def/classes/perma_bonus.lua b/lsp_def/classes/perma_bonus.lua index 3631b41f4..734875a04 100644 --- a/lsp_def/classes/perma_bonus.lua +++ b/lsp_def/classes/perma_bonus.lua @@ -1,39 +1,39 @@ ---@meta ----@class SMODS.Perma_Bonus: SMODS.GameObject +---@class SMODS.PermaBonus: SMODS.GameObject ---@field obj_buffer? string[] Array of keys to all objects registered to this class. ----@field obj_table? table Table of objects registered to this class. +---@field obj_table? table Table of objects registered to this class. ---@field super? SMODS.GameObject|table Parent class. ----@field __call? fun(self: SMODS.Perma_Bonus|table, o: SMODS.Perma_Bonus|table): nil|table|SMODS.Perma_Bonus ----@field extend? fun(self: SMODS.Perma_Bonus|table, o: SMODS.Perma_Bonus|table): table Primary method of creating a class. ----@field check_duplicate_register? fun(self: SMODS.Perma_Bonus|table): boolean? Ensures objects already registered will not register. ----@field check_duplicate_key? fun(self: SMODS.Perma_Bonus|table): boolean? Ensures objects with duplicate keys will not register. Checked on `__call` but not `take_ownership`. For take_ownership, the key must exist. ----@field register? fun(self: SMODS.Perma_Bonus|table) Registers the object. ----@field check_dependencies? fun(self: SMODS.Perma_Bonus|table): boolean? Returns `true` if there's no failed dependencies. ----@field process_loc_text? fun(self: SMODS.Perma_Bonus|table) Called during `inject_class`. Handles injecting loc_text. ----@field send_to_subclasses? fun(self: SMODS.Perma_Bonus|table, func: string, ...: any) Starting from this class, recusively searches for functions with the given key on all subordinate classes and run all found functions with the given arguments. ----@field pre_inject_class? fun(self: SMODS.Perma_Bonus|table) Called before `inject_class`. Injects and manages class information before object injection. ----@field post_inject_class? fun(self: SMODS.Perma_Bonus|table) Called after `inject_class`. Injects and manages class information after object injection. ----@field inject_class? fun(self: SMODS.Perma_Bonus|table) Injects all direct instances of class objects by calling `obj:inject` and `obj:process_loc_text`. Also injects anything necessary for the class itself. Only called if class has defined both `obj_table` and `obj_buffer`. ----@field inject? fun(self: SMODS.Perma_Bonus|table, i?: number) Called during `inject_class`. Injects the object into the game. ----@field take_ownership? fun(self: SMODS.Perma_Bonus|table, key: string, obj: SMODS.Perma_Bonus|table, silent?: boolean): nil|table|SMODS.Perma_Bonus Takes control of vanilla objects. Child class must have get_obj for this to function ----@field get_obj? fun(self: SMODS.Perma_Bonus|table, key: string): SMODS.Perma_Bonus|table? Returns an object if one matches the `key`. ----@field should_apply? fun(self: SMODS.Perma_Bonus|table, card: Card|table, calculation: string): boolean? Returns true if the Bonus applies to the given `calculation`. ----@field key string Used to reference the Perma_Bonus, also used in the card.ability table. ----@field apply_to string A string representing what calculation this bonus applies to; see the perma bonuses page for provided calculations. ----@field get_ui_value? fun(self: SMODS.Perma_Bonus|table, card: Card|table): number|nil Returns the number to be used for the display in the ui box, returns `nil` if the bonus is 0. ----@field upgrade? fun(self: SMODS.Perma_Bonus|table, card: Card|table, amount: number?, from?: Card|table?) Called through SMODS.upgrade_perma_bonus, allows for control over how the perma bonus is upgraded. ----@field loc_key? string The key of the object in G.localization.descriptions.Other, defaults to the object's key. ----@field vars_key? string How the value of the bonus is stored in the specific_vars table for playing card ui, defaults to the object's key. ----@field signed_value? boolean If this is `true`, the value in the ui box will be signed with `SMODS.signed`. ----@field signed_dollars? boolean Same as signed_value except the value is signed using `SMODS.signed_dollars`, has priority over signed_value by deafult. ----@field localize? fun(self: SMODS.Perma_Bonus|table, value: number, desc_nodes: table) Defines what to show in the playing card description, not recommended to change unless you know what you're doing. ----@overload fun(self: SMODS.Perma_Bonus): SMODS.Perma_Bonus -SMODS.Perma_Bonus = setmetatable({}, { +---@field __call? fun(self: SMODS.PermaBonus|table, o: SMODS.PermaBonus|table): nil|table|SMODS.PermaBonus +---@field extend? fun(self: SMODS.PermaBonus|table, o: SMODS.PermaBonus|table): table Primary method of creating a class. +---@field check_duplicate_register? fun(self: SMODS.PermaBonus|table): boolean? Ensures objects already registered will not register. +---@field check_duplicate_key? fun(self: SMODS.PermaBonus|table): boolean? Ensures objects with duplicate keys will not register. Checked on `__call` but not `take_ownership`. For take_ownership, the key must exist. +---@field register? fun(self: SMODS.PermaBonus|table) Registers the object. +---@field check_dependencies? fun(self: SMODS.PermaBonus|table): boolean? Returns `true` if there's no failed dependencies. +---@field process_loc_text? fun(self: SMODS.PermaBonus|table) Called during `inject_class`. Handles injecting loc_text. +---@field send_to_subclasses? fun(self: SMODS.PermaBonus|table, func: string, ...: any) Starting from this class, recusively searches for functions with the given key on all subordinate classes and run all found functions with the given arguments. +---@field pre_inject_class? fun(self: SMODS.PermaBonus|table) Called before `inject_class`. Injects and manages class information before object injection. +---@field post_inject_class? fun(self: SMODS.PermaBonus|table) Called after `inject_class`. Injects and manages class information after object injection. +---@field inject_class? fun(self: SMODS.PermaBonus|table) Injects all direct instances of class objects by calling `obj:inject` and `obj:process_loc_text`. Also injects anything necessary for the class itself. Only called if class has defined both `obj_table` and `obj_buffer`. +---@field inject? fun(self: SMODS.PermaBonus|table, i?: number) Called during `inject_class`. Injects the object into the game. +---@field take_ownership? fun(self: SMODS.PermaBonus|table, key: string, obj: SMODS.PermaBonus|table, silent?: boolean): nil|table|SMODS.PermaBonus Takes control of vanilla objects. Child class must have get_obj for this to function +---@field get_obj? fun(self: SMODS.PermaBonus|table, key: string): SMODS.PermaBonus|table? Returns an object if one matches the `key`. +---@field should_apply? fun(self: SMODS.PermaBonus|table, card: Card|table, calculation: string): boolean? Returns true if the Bonus applies to the given `calculation`. +---@field key string Used to reference the PermaBonus, also used in the card.ability table. +---@field apply_to string[] An array of strings representing what calculation this bonus applies to; see the perma bonuses page for provided calculations. +---@field get_ui_value? fun(self: SMODS.PermaBonus|table, card: Card|table): number|nil Returns the number to be used for the display in the ui box, returns `nil` if the bonus is 0. +---@field upgrade? fun(self: SMODS.PermaBonus|table, card: Card|table, amount: number?, from?: Card|table?) Called through SMODS.upgrade_perma_bonus, allows for control over how the perma bonus is upgraded. +---@field loc_key? string The key of the object in G.localization.descriptions.Other, defaults to the object's key. +---@field vars_keys? table How the value of the bonus is stored in the specific_vars table for playing card ui, defaults to the object's key. +---@field signed_value? boolean If this is `true`, the value in the ui box will be signed with `SMODS.signed`. +---@field signed_dollars? boolean Same as signed_value except the value is signed using `SMODS.signed_dollars`, has priority over signed_value by deafult. +---@field localize? fun(self: SMODS.PermaBonus|table, value: number, desc_nodes: table) Defines what to show in the playing card description, not recommended to change unless you know what you're doing. +---@overload fun(self: SMODS.PermaBonus): SMODS.PermaBonus +SMODS.PermaBonus = setmetatable({}, { __call = function(self) return self end }) ----@type table -SMODS.Perma_Bonuses = {} \ No newline at end of file +---@type table +SMODS.PermaBonuses = {} \ No newline at end of file diff --git a/lsp_def/utils.lua b/lsp_def/utils.lua index 224543c11..c1b63f486 100644 --- a/lsp_def/utils.lua +++ b/lsp_def/utils.lua @@ -847,5 +847,5 @@ function SMODS.mod_blind_size(mod_blind_size) end ---Upgrades the given perma bonuses on the given card by `amount` ---Alternatively, provide a custom `func` to override the default upgrade behaviour ----@param args table|{keys: string|table, card: Card|table, amount?: number, from?: Card|table, func?: fun(card: Card|table, amount: number, from: Card|table, perma_bonus: SMODS.Perma_Bonus)} +---@param args table|{keys: string|table, card: Card|table, amount?: number, from?: Card|table, func?: fun(card: Card|table, amount: number, from: Card|table, perma_bonus: SMODS.PermaBonus)} function SMODS.upgrade_perma_bonus(args) end \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index 962c84838..99ee16e4c 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -3298,12 +3298,12 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. localize { type = 'other', key = 'card_chips', nodes = desc_nodes, vars = { specific_vars.nominal_chips } } end SMODS.Enhancement.super.generate_ui(self, info_queue, card, desc_nodes, specific_vars, full_UI_table) - if specific_vars and specific_vars.bonus_chips then + --[[if specific_vars and specific_vars.bonus_chips then local remaining_bonus_chips = specific_vars.bonus_chips - (self.config.bonus or 0) if remaining_bonus_chips ~= 0 then localize { type = 'other', key = 'card_extra_chips', nodes = desc_nodes, vars = { SMODS.signed(remaining_bonus_chips) } } end - end + end]] SMODS.localize_perma_bonuses(specific_vars, desc_nodes) end, -- other methods: @@ -3988,7 +3988,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. } ------------------------------------------------------------------------------------------------- - ----- API CODE GameObject.Perma_Bonus + ----- API CODE GameObject.PermaBonus ------------------------------------------------------------------------------------------------- assert(load(SMODS.NFS.read(SMODS.path..'src/game_objects/perma_bonuses.lua'), ('=[SMODS _ "src/game_objects/perma_bonuses.lua"]')))() diff --git a/src/game_objects/perma_bonuses.lua b/src/game_objects/perma_bonuses.lua index ae9f7e243..bfa382da6 100644 --- a/src/game_objects/perma_bonuses.lua +++ b/src/game_objects/perma_bonuses.lua @@ -11,7 +11,9 @@ SMODS.PermaBonus = SMODS.GameObject:extend{ signed_value = false, signed_dollars = false, should_apply = function(self, card, calculation) - if calculation == self.apply_to then return true end + for _,v in ipairs(self.apply_to) do + if calculation == v then return true end + end end, get_ui_value = function(self, card) return card.ability[self.key] and card.ability[self.key] ~= 0 and (card.ability[self.key] + (self.ui_mod or 0)) or nil @@ -27,7 +29,9 @@ SMODS.PermaBonus = SMODS.GameObject:extend{ end, inject = function(self) self.loc_key = self.loc_key or self.key - self.vars_key = self.vars_key or self.key + self.vars_keys = self.vars_keys or self.key + if type(self.vars_keys) ~= 'table' then self.vars_keys = {default = self.vars_keys} end + if type(self.apply_to) ~= 'table' then self.apply_to = {self.apply_to} end end, process_loc_text = function(self) SMODS.process_loc_text(G.localization.descriptions.Other, self.loc_key, self.loc_txt) @@ -58,14 +62,20 @@ function SMODS.get_perma_bonus_ui_vars(card) local ret = {} for _,v in pairs(SMODS.PermaBonuses) do local bonus = v:get_ui_value(card) - ret[v.vars_key] = ((ret[v.vars_key] or 0) + (bonus or 0) ~= 0 and (ret[v.vars_key] or 0) + (bonus or 0)) or nil + if type(bonus) == 'table' then + for vars_key,bonus_val in pairs(bonus) do + ret[vars_key] = ((ret[vars_key] or 0) + (bonus_val or 0) ~= 0 and (ret[vars_key] or 0) + (bonus_val or 0)) or nil + end + else + ret[v.vars_key] = ((ret[v.vars_key] or 0) + (bonus or 0) ~= 0 and (ret[v.vars_key] or 0) + (bonus or 0)) or nil + end end return ret end function SMODS.upgrade_perma_bonus(args) if type(args.keys) == 'string' then args.keys = {args.keys} end - if type(args.keys) ~= 'table' or type(args.card) ~= 'table' then return end + if type(args.keys) ~= 'table' then return end args.amount = args.amount or 1 for _,key in ipairs(args.keys) do if SMODS.PermaBonuses[key] then @@ -137,4 +147,4 @@ SMODS.PermaBonus({ localize = function(self, value, desc_nodes) localize{type = 'other', key = self.loc_key, nodes = desc_nodes, vars = {value, localize(value > 1 and 'b_retrigger_plural' or 'b_retrigger_single')}} end -}) +}) \ No newline at end of file diff --git a/src/overrides.lua b/src/overrides.lua index 5d49ecf2a..dc1d4e9cb 100644 --- a/src/overrides.lua +++ b/src/overrides.lua @@ -2764,27 +2764,9 @@ function Card:quantum_set_ability(center) new_ability.type = center.config.type or '' new_ability.order = center.order or nil new_ability.forced_selection = self.ability and self.ability.forced_selection or nil - new_ability.perma_bonus = self.ability and self.ability.perma_bonus or 0 - new_ability.perma_x_chips = self.ability and self.ability.perma_x_chips or 0 - new_ability.perma_mult = self.ability and self.ability.perma_mult or 0 - new_ability.perma_x_mult = self.ability and self.ability.perma_x_mult or 0 - new_ability.perma_h_chips = self.ability and self.ability.perma_h_chips or 0 - new_ability.perma_h_x_chips = self.ability and self.ability.perma_h_x_chips or 0 - new_ability.perma_h_mult = self.ability and self.ability.perma_h_mult or 0 - new_ability.perma_h_x_mult = self.ability and self.ability.perma_h_x_mult or 0 - new_ability.perma_p_dollars = self.ability and self.ability.perma_p_dollars or 0 - new_ability.perma_h_dollars = self.ability and self.ability.perma_h_dollars or 0 - new_ability.perma_repetitions = self.ability and self.ability.perma_repetitions or 0 new_ability.card_limit = self.ability and self.ability.card_limit or 0 new_ability.extra_slots_used = self.ability and self.ability.extra_slots_used or 0 - new_ability.perma_score = self.ability and self.ability.perma_score or 0 - new_ability.perma_h_score = self.ability and self.ability.perma_h_score or 0 - new_ability.perma_x_score = self.ability and self.ability.perma_x_score or 0 - new_ability.perma_h_x_score = self.ability and self.ability.perma_h_x_score or 0 - new_ability.perma_blind_size = self.ability and self.ability.perma_blind_size or 0 - new_ability.perma_h_blind_size = self.ability and self.ability.perma_h_blind_size or 0 - new_ability.perma_x_blind_size = self.ability and self.ability.perma_x_blind_size or 0 - new_ability.perma_h_x_blind_size = self.ability and self.ability.perma_h_x_blind_size or 0 + SMODS.set_perma_bonus(self, new_ability) self.ability = self.ability or {} new_ability.extra_value = nil From e1a5daedee61940579293fd618fc03efdb4a7727 Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 21:19:58 +1000 Subject: [PATCH 08/11] vars_key should be a string --- lsp_def/classes/perma_bonus.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lsp_def/classes/perma_bonus.lua b/lsp_def/classes/perma_bonus.lua index 734875a04..34e0d2e6e 100644 --- a/lsp_def/classes/perma_bonus.lua +++ b/lsp_def/classes/perma_bonus.lua @@ -24,7 +24,7 @@ ---@field get_ui_value? fun(self: SMODS.PermaBonus|table, card: Card|table): number|nil Returns the number to be used for the display in the ui box, returns `nil` if the bonus is 0. ---@field upgrade? fun(self: SMODS.PermaBonus|table, card: Card|table, amount: number?, from?: Card|table?) Called through SMODS.upgrade_perma_bonus, allows for control over how the perma bonus is upgraded. ---@field loc_key? string The key of the object in G.localization.descriptions.Other, defaults to the object's key. ----@field vars_keys? table How the value of the bonus is stored in the specific_vars table for playing card ui, defaults to the object's key. +---@field vars_key? string How the value of the bonus is stored in the specific_vars table for playing card ui, defaults to the object's key. ---@field signed_value? boolean If this is `true`, the value in the ui box will be signed with `SMODS.signed`. ---@field signed_dollars? boolean Same as signed_value except the value is signed using `SMODS.signed_dollars`, has priority over signed_value by deafult. ---@field localize? fun(self: SMODS.PermaBonus|table, value: number, desc_nodes: table) Defines what to show in the playing card description, not recommended to change unless you know what you're doing. @@ -36,4 +36,4 @@ SMODS.PermaBonus = setmetatable({}, { }) ---@type table -SMODS.PermaBonuses = {} \ No newline at end of file +SMODS.PermaBonuses = {} From 8d8ad51d4ce6cb0bfbcc85a4ef2db894bc98e72a Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Sun, 12 Apr 2026 21:21:24 +1000 Subject: [PATCH 09/11] vars_key should be a string --- src/game_objects/perma_bonuses.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/game_objects/perma_bonuses.lua b/src/game_objects/perma_bonuses.lua index bfa382da6..f6598dd8d 100644 --- a/src/game_objects/perma_bonuses.lua +++ b/src/game_objects/perma_bonuses.lua @@ -29,8 +29,7 @@ SMODS.PermaBonus = SMODS.GameObject:extend{ end, inject = function(self) self.loc_key = self.loc_key or self.key - self.vars_keys = self.vars_keys or self.key - if type(self.vars_keys) ~= 'table' then self.vars_keys = {default = self.vars_keys} end + self.vars_key = self.vars_key or self.key if type(self.apply_to) ~= 'table' then self.apply_to = {self.apply_to} end end, process_loc_text = function(self) @@ -147,4 +146,4 @@ SMODS.PermaBonus({ localize = function(self, value, desc_nodes) localize{type = 'other', key = self.loc_key, nodes = desc_nodes, vars = {value, localize(value > 1 and 'b_retrigger_plural' or 'b_retrigger_single')}} end -}) \ No newline at end of file +}) From c257b3cb80bfe330fe599a7cd8a01c6c96da88f0 Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Wed, 15 Apr 2026 15:21:43 +1000 Subject: [PATCH 10/11] Change to how calculation is handled Changes how the calculation of perma bonuses is handled to allow for smoother addition of new effects --- lovely/better_calc.toml | 61 ++++---- lovely/perma_bonus.toml | 223 ++++++++++++++++++++++++----- lsp_def/classes/perma_bonus.lua | 2 +- src/game_objects/perma_bonuses.lua | 99 +++++++++++-- src/utils.lua | 1 - 5 files changed, 311 insertions(+), 75 deletions(-) diff --git a/lovely/better_calc.toml b/lovely/better_calc.toml index eb4584df1..3f7a6fce3 100644 --- a/lovely/better_calc.toml +++ b/lovely/better_calc.toml @@ -55,32 +55,32 @@ target = "functions/common_events.lua" pattern = ''' if context.cardarea == G.play then local chips = card:get_chip_bonus() - if chips > 0 then + if chips > 0 then ret.chips = chips end local mult = card:get_chip_mult() - if mult > 0 then + if mult > 0 then ret.mult = mult end local x_mult = card:get_chip_x_mult(context) - if x_mult > 0 then + if x_mult > 0 then ret.x_mult = x_mult end local p_dollars = card:get_p_dollars() - if p_dollars > 0 then + if p_dollars > 0 then ret.p_dollars = p_dollars end local jokers = card:calculate_joker(context) - if jokers then + if jokers then ret.jokers = jokers end local edition = card:get_edition(context) - if edition then + if edition then ret.edition = edition end end @@ -90,12 +90,12 @@ position = "at" payload = """ if context.cardarea == G.play and context.main_scoring then ret.playing_card = {} - local chips = card:get_chip_bonus() + local chips = card:get_chip_bonus(context) if chips ~= 0 then ret.playing_card.chips = chips end - local mult = card:get_chip_mult() + local mult = card:get_chip_mult(context) if mult ~= 0 then ret.playing_card.mult = mult end @@ -105,30 +105,30 @@ if context.cardarea == G.play and context.main_scoring then ret.playing_card.x_mult = x_mult end - local p_dollars = card:get_p_dollars() + local p_dollars = card:get_p_dollars(context) if p_dollars ~= 0 then ret.playing_card.p_dollars = p_dollars end - local x_chips = card:get_chip_x_bonus() + local x_chips = card:get_chip_x_bonus(context) if x_chips > 0 then ret.playing_card.x_chips = x_chips end - local score = card:get_bonus_score() + local score = card:get_bonus_score(context) if score ~= 0 then ret.playing_card.score = score end - local x_score = card:get_bonus_x_score() + local x_score = card:get_bonus_x_score(context) if x_score > 0 then ret.playing_card.x_score = x_score end - local blind_size = card:get_bonus_blind_size() + local blind_size = card:get_bonus_blind_size(context) if blind_size ~= 0 then ret.playing_card.blind_size = blind_size end - local x_blind_size = card:get_bonus_x_blind_size() + local x_blind_size = card:get_bonus_x_blind_size(context) if x_blind_size > 0 then ret.playing_card.x_blind_size = x_blind_size end @@ -167,39 +167,39 @@ position = "at" payload = """ if context.cardarea == G.hand and context.main_scoring then ret.playing_card = {} - local h_mult = card:get_chip_h_mult() + local h_mult = card:get_chip_h_mult(context) if h_mult ~= 0 then ret.playing_card.h_mult = h_mult end - local h_x_mult = card:get_chip_h_x_mult() + local h_x_mult = card:get_chip_h_x_mult(context) if h_x_mult > 0 then ret.playing_card.x_mult = h_x_mult end - local h_chips = card:get_chip_h_bonus() + local h_chips = card:get_chip_h_bonus(context) if h_chips ~= 0 then ret.playing_card.h_chips = h_chips end - local h_x_chips = card:get_chip_h_x_bonus() + local h_x_chips = card:get_chip_h_x_bonus(context) if h_x_chips > 0 then ret.playing_card.x_chips = h_x_chips end - local h_score = card:get_bonus_h_score() + local h_score = card:get_bonus_h_score(context) if h_score ~= 0 then ret.playing_card.h_score = h_score end - local h_x_score = card:get_bonus_h_x_score() + local h_x_score = card:get_bonus_h_x_score(context) if h_x_score > 0 then ret.playing_card.h_x_score = h_x_score end - local h_blind_size = card:get_bonus_h_blind_size() + local h_blind_size = card:get_bonus_h_blind_size(context) if h_blind_size ~= 0 then ret.playing_card.blind_size = h_blind_size end - local h_x_blind_size = card:get_bonus_h_x_blind_size() + local h_x_blind_size = card:get_bonus_h_x_blind_size(context) if h_x_blind_size > 0 then ret.playing_card.x_blind_size = h_x_blind_size end @@ -251,9 +251,10 @@ if card.ability.repetitions and card.ability.repetitions > 0 then ret.seals = ret.seals or { card = card, message = localize('k_again_ex') } ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card.ability.repetitions) or card.ability.repetitions end -if card:get_perma_bonus('repetitions') > 0 then - ret.seals = ret.seals or { card = card, message = localize('k_again_ex') } - ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + card:get_perma_bonus('repetitions')) or card:get_perma_bonus('repetitions') +local perma_repetitions = SMODS.calculate_perma_bonuses(card, context, 'repetitions') +if perma_repetitions.repetitions and perma_repetitions.repetitions > 0 then + ret.seals = ret.seals or { card = perma_repetitions.card or card, message = perma_repetitions.message or localize('k_again_ex') } + ret.seals.repetitions = (ret.seals.repetitions and ret.seals.repetitions + perma_repetitions.repetitions) or perma_repetitions.repetitions end """ [[patches]] @@ -290,6 +291,16 @@ for _,k in ipairs(SMODS.Sticker.obj_buffer) do end end +if not card.ability.extra_enhancement and next(card:get_perma_bonus()) then + local bonuses = SMODS.calculate_perma_bonuses(card, context) + if bonuses then for k,v in pairs(bonuses) do + if not SMODS.default_perma_bonus_keys[k] then + ret.playing_card = ret.playing_card or {} + local val = SMODS.stack_perma_bonus(k,v,ret.playing_card) + ret.playing_card[k] = val + end + end end +end -- TARGET: evaluate your own general effects """ [[patches]] diff --git a/lovely/perma_bonus.toml b/lovely/perma_bonus.toml index 60fd24329..0c9ccf3e4 100644 --- a/lovely/perma_bonus.toml +++ b/lovely/perma_bonus.toml @@ -132,10 +132,20 @@ match_indent = false [patches.pattern] target = "card.lua" pattern = '''function Card:get_chip_bonus(*''' -position = "after" +position = "at" match_indent = true payload = ''' - if self.ability.extra_enhancement then return self.ability.bonus end''' +function Card:get_chip_bonus(context) + if self.ability.extra_enhancement then return self.ability.bonus end + if context then + local perma = 0 + local calc = SMODS.calculate_perma_bonuses(self, context, 'chips') + for k,v in pairs(calc) do + if k == 'chips' and v[k] and v[k] > 0 then + perma = SMODS.stack_perma_bonus(k, v, {[k] = perma}) + end + end + end''' [[patches]] [patches.pattern] @@ -143,7 +153,7 @@ target = "card.lua" pattern = "return self.ability.bonus + (self.ability.perma_bonus or 0)" position = "at" match_indent = true -payload = "return self.ability.bonus + (self:get_perma_bonus('chips') or 0)" +payload = "return self.ability.bonus + (perma or 0)" [[patches]] [patches.pattern] @@ -151,9 +161,17 @@ target = "card.lua" pattern = "return self.base.nominal + self.ability.bonus + (self.ability.perma_bonus or 0)" position = "at" match_indent = true -payload = "return self.base.nominal + self.ability.bonus + (self:get_perma_bonus('chips') or 0)" +payload = "return self.base.nominal + self.ability.bonus + (perma or 0)" # Card:get_chip_mult +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = 'function Card:get_chip_mult()' +position = "at" +match_indent = true +payload = "function Card:get_chip_mult(context)" + [[patches]] [patches.pattern] target = "card.lua" @@ -169,7 +187,7 @@ else end''' position = "at" match_indent = true -payload = '''local ret = (not self.ability.extra_enhancement and self:get_perma_bonus('mult')) or 0 +payload = '''local ret = 0 if self.ability.effect == "Lucky Card" then if SMODS.pseudorandom_probability(self, 'lucky_mult', 1, 5) then self.lucky_trigger = true @@ -178,6 +196,14 @@ if self.ability.effect == "Lucky Card" then else ret = ret + self.ability.mult end +if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'mult') + for k,v in pairs(calc) do + if k == 'h_x_mult' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end +end -- TARGET: get_chip_mult return ret''' @@ -189,31 +215,74 @@ pattern = '''if self.ability.x_mult <= 1 then return 0 end return self.ability.x_mult''' position = "at" match_indent = true -payload = '''local ret = SMODS.multiplicative_stacking(self.ability.x_mult or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('x_mult')) or 0) +payload = '''local ret = 0 +if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_x_mult') + for k,v in pairs(calc) do + if k == 'h_x_mult' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end +end +ret = SMODS.multiplicative_stacking(self.ability.x_mult or 1, 0) -- TARGET: get_chip_x_mult return ret ''' # Card:get_chip_h_mult +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = 'function Card:get_chip_h_mult()' +position = "at" +match_indent = true +payload = "function Card:get_chip_h_mult(context)" + [[patches]] [patches.pattern] target = "card.lua" pattern = 'return self.ability.h_mult' position = "at" match_indent = true -payload = '''local ret = (self.ability.h_mult or 0) + ((not self.ability.extra_enhancement and self:get_perma_bonus('h_mult')) or 0) +payload = '''local ret = (self.ability.h_mult or 0) +if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_x_mult') + for k,v in pairs(calc) do + if k == 'h_x_mult' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end +end -- TARGET: get_chip_h_mult return ret ''' # Card:get_chip_h_x_mult +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = 'function Card:get_chip_h_x_mult()' +position = "at" +match_indent = true +payload = "function Card:get_chip_h_x_mult(context)" + [[patches]] [patches.pattern] target = "card.lua" pattern = 'return self.ability.h_x_mult' position = "at" match_indent = true -payload = '''local ret = SMODS.multiplicative_stacking(self.ability.h_x_mult or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('h_x_mult')) or 0) +payload = ''' +local ret = 0 +if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_x_mult') + for k,v in pairs(calc) do + if k == 'h_x_mult' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end +end +ret = SMODS.multiplicative_stacking(self.ability.h_x_mult or 1, ret or 0) -- TARGET: get_chip_h_x_mult return ret ''' @@ -229,85 +298,174 @@ pattern = 'function Card:get_edition()' position = "before" match_indent = true payload = ''' -function Card:get_chip_x_bonus() +function Card:get_chip_x_bonus(context) if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(self.ability.x_chips or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('x_chips')) or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'x_chips') + for k,v in pairs(calc) do + if k == 'x_chips' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = SMODS.multiplicative_stacking(self.ability.x_chips or 1, ret or 0) -- TARGET: get_chip_x_bonus return ret end -function Card:get_chip_h_bonus() +function Card:get_chip_h_bonus(context) if self.debuff then return 0 end - local ret = (self.ability.h_chips or 0) + ((not self.ability.extra_enhancement and self:get_perma_bonus('h_chips')) or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_chips') + for k,v in pairs(calc) do + if k == 'h_chips' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = (self.ability.h_chips or 0) + ret -- TARGET: get_chip_h_bonus return ret end -function Card:get_chip_h_x_bonus() +function Card:get_chip_h_x_bonus(context) if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(self.ability.h_x_chips or 1, (not self.ability.extra_enhancement and self:get_perma_bonus('h_x_chips')) or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_x_chips') + for k,v in pairs(calc) do + if k == 'h_x_chips' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = SMODS.multiplicative_stacking(self.ability.h_x_chips or 1, ret or 0) -- TARGET: get_chip_h_x_bonus return ret end -function Card:get_h_dollars() +function Card:get_h_dollars(context) if self.debuff then return 0 end - local ret = (self.ability.h_dollars or 0) + ((not self.ability.extra_enhancement and self:get_perma_bonus('h_dollars')) or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_dollars') + for k,v in pairs(calc) do + if k == 'h_dollars' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = (self.ability.h_dollars or 0) + ret -- TARGET: get_h_dollars return ret end -function Card:get_bonus_score() +function Card:get_bonus_score(context) if self.debuff then return 0 end - local ret = (self:get_perma_bonus('score') or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'score') + for k,v in pairs(calc) do + if k == 'score' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end -- TARGET: get_bonus_score return ret end -function Card:get_bonus_x_score() +function Card:get_bonus_x_score(context) if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('x_score') or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'x_score') + for k,v in pairs(calc) do + if k == 'x_score' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_x_score return ret end -function Card:get_bonus_h_score() +function Card:get_bonus_h_score(context) if self.debuff then return 0 end - local ret = (self:get_perma_bonus('h_score') or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_score') + for k,v in pairs(calc) do + if k == 'h_score' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end -- TARGET: get_bonus_h_score return ret end -function Card:get_bonus_h_x_score() +function Card:get_bonus_h_x_score(context) if self.debuff then return 0 end - local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('h_x_score') or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_x_score') + for k,v in pairs(calc) do + if k == 'h_x_score' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_h_x_score return ret end -function Card:get_bonus_blind_size() + +function Card:get_bonus_blind_size(context) if self.debuff then return 0 end - local ret = (self:get_perma_bonus('blind_size') or 0) + local ret = 0 -- TARGET: get_bonus_blind_size return ret end -function Card:get_bonus_x_blind_size() +function Card:get_bonus_x_blind_size(context) if self.debuff then return 1 end - local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('x_blind_size') or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'x_blind_size') + for k,v in pairs(calc) do + if k == 'x_blind_size' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_x_blind_size return ret end -function Card:get_bonus_h_blind_size() +function Card:get_bonus_h_blind_size(context) if self.debuff then return 0 end - local ret = (self:get_perma_bonus('h_blind_size') or 0) + local ret = 0 -- TARGET: get_bonus_h_blind_size return ret end -function Card:get_bonus_h_x_blind_size() +function Card:get_bonus_h_x_blind_size(context) if self.debuff then return 1 end - local ret = SMODS.multiplicative_stacking(1, self:get_perma_bonus('h_x_blind_size') or 0) + local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_x_blind_size') + for k,v in pairs(calc) do + if k == 'h_x_blind_size' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + end + ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_h_x_blind_size return ret end @@ -325,7 +483,6 @@ match_indent = true payload = '''elseif self.ability.p_dollars < 0 then ret = ret + self.ability.p_dollars end -ret = ret + ((not self.ability.extra_enhancement and self:get_perma_bonus('p_dollars')) or 0) -- TARGET: get_p_dollars if ret ~= 0 then G.GAME.dollar_buffer = (G.GAME.dollar_buffer or 0) + ret''' diff --git a/lsp_def/classes/perma_bonus.lua b/lsp_def/classes/perma_bonus.lua index 34e0d2e6e..274f674c2 100644 --- a/lsp_def/classes/perma_bonus.lua +++ b/lsp_def/classes/perma_bonus.lua @@ -26,7 +26,7 @@ ---@field loc_key? string The key of the object in G.localization.descriptions.Other, defaults to the object's key. ---@field vars_key? string How the value of the bonus is stored in the specific_vars table for playing card ui, defaults to the object's key. ---@field signed_value? boolean If this is `true`, the value in the ui box will be signed with `SMODS.signed`. ----@field signed_dollars? boolean Same as signed_value except the value is signed using `SMODS.signed_dollars`, has priority over signed_value by deafult. +---@field signed_dollars? boolean Same as signed_value except the value is signed using `SMODS.signed_dollars`, has priority over signed_value by default. ---@field localize? fun(self: SMODS.PermaBonus|table, value: number, desc_nodes: table) Defines what to show in the playing card description, not recommended to change unless you know what you're doing. ---@overload fun(self: SMODS.PermaBonus): SMODS.PermaBonus SMODS.PermaBonus = setmetatable({}, { diff --git a/src/game_objects/perma_bonuses.lua b/src/game_objects/perma_bonuses.lua index f6598dd8d..8d11b2a32 100644 --- a/src/game_objects/perma_bonuses.lua +++ b/src/game_objects/perma_bonuses.lua @@ -10,11 +10,6 @@ SMODS.PermaBonus = SMODS.GameObject:extend{ prefix_config = { key = false }, signed_value = false, signed_dollars = false, - should_apply = function(self, card, calculation) - for _,v in ipairs(self.apply_to) do - if calculation == v then return true end - end - end, get_ui_value = function(self, card) return card.ability[self.key] and card.ability[self.key] ~= 0 and (card.ability[self.key] + (self.ui_mod or 0)) or nil end, @@ -34,7 +29,7 @@ SMODS.PermaBonus = SMODS.GameObject:extend{ end, process_loc_text = function(self) SMODS.process_loc_text(G.localization.descriptions.Other, self.loc_key, self.loc_txt) - end, + end } function SMODS.localize_perma_bonuses(specific_vars, desc_nodes) @@ -87,18 +82,55 @@ function SMODS.upgrade_perma_bonus(args) end end -function Card:get_perma_bonus(calculation) - local ret = (not calculation and {}) or 0 - for k,v in pairs(SMODS.PermaBonuses) do - if not calculation then - ret[k] = self.ability[k] - elseif v:should_apply(self, calculation) then - ret = ret + (self.ability[k] or 0) +function SMODS.stack_perma_bonus(apply_to, bonus_table, scoring_table) + if bonus_table.multiplicative then + return SMODS.multiplicative_stacking(scoring_table[apply_to], bonus_table[apply_to]) + end + local ret = (scoring_table[apply_to] or 0) + (bonus_table[apply_to] or 0) +end + +function SMODS.calculate_perma_bonuses(card, context, _type) + local ret = {} + local bonuses = card:get_perma_bonus() + for k,_ in pairs(bonuses) do + local obj = SMODS.PermaBonuses[k] or {} + if obj.calculate and type(obj.calculate) == 'function' then + local bonus = obj:calculate(card, context) + if type(bonus) ~= 'table' then bonus = nil end + if bonus and not _type then --Check for a calculate return + for _,key in ipairs(obj.apply_to) do + if bonus[key] and bonus[key] ~= 0 then + if ret[key] then + ret[key][key] = SMODS.stack_perma_bonus(key, bonus, ret[key]) + SMODS.merge_defaults(ret[key], bonus) + else + ret[key] = bonus + end + end + end + elseif bonus and bonus[_type] and bonus[_type] ~= 0 then + if ret[_type] then + ret[_type][_type] = SMODS.stack_perma_bonus(_type, bonus, ret[_type]) + SMODS.merge_defaults(ret[_type], bonus) + else + ret[_type] = bonus + end + end end end return ret end +function Card:get_perma_bonus() + local ret = {} + for _,k in ipairs(SMODS.PermaBonus.obj_buffer) do + ret[k] = self.ability[k] and self.ability[k] ~= 0 and self.ability[k] or nil + end + return ret +end + +SMODS.default_perma_bonus_keys = {perma_bonus = true} + SMODS.PermaBonus({ key = 'perma_bonus', apply_to = 'chips', @@ -109,7 +141,7 @@ SMODS.PermaBonus({ if not (card.ability and card.ability.bonus) then return end local bonus_chips = card.ability.bonus + (card.ability[self.key] or 0) return bonus_chips ~= 0 and bonus_chips or nil - end + end, }) for _,pb in ipairs({ {key = 'perma_x_chips', apply_to = 'x_chips', vars_key = 'bonus_x_chips', loc_key = 'card_extra_x_chips', ui_mod = 1}, @@ -135,6 +167,34 @@ for _,pb in ipairs({ {key = 'perma_x_blind_size', apply_to = 'x_blind_size', vars_key = 'bonus_x_blind_size', loc_key = 'card_extra_x_blind_size', ui_mod = 1}, {key = 'perma_h_x_blind_size', apply_to = 'h_x_blind_size', vars_key = 'bonus_h_x_blind_size', loc_key = 'card_extra_h_x_blind_size', ui_mod = 1}, }) do + local multiplicative = not not string.find(pb.key, '_x_') + if not string.find(pb.key, '_h_') then + pb.calculate = function(self, card, context) + if context.main_scoring and context.cardarea == G.play then + return { + [self.apply_to[1]] = card.ability[self.key], + multiplicative = multiplicative + } + end + end + elseif pb.key == 'perma_h_dollars' then + pb.calculate = function(self, card, context) + if context.end_of_round and context.cardarea == G.hand and context.playing_card_end_of_round then + return { + h_dollars = card.ability[self.key] + } + end + end + else + pb.calculate = function(self, card, context) + if context.main_scoring and context.cardarea == G.hand then + return { + [self.apply_to[1]] = card.ability[self.key], + multiplicative = multiplicative + } + end + end + end SMODS.PermaBonus(pb) end @@ -145,5 +205,14 @@ SMODS.PermaBonus({ loc_key = 'card_extra_repetitions', localize = function(self, value, desc_nodes) localize{type = 'other', key = self.loc_key, nodes = desc_nodes, vars = {value, localize(value > 1 and 'b_retrigger_plural' or 'b_retrigger_single')}} + end, + calculate = function(self, card, context) + if context.repetition_only and card.ability[self.key] > 0 then + return { + card = card, + repetitions = card.ability[self.key], + message = localize('k_again_ex') + } + end end -}) +}) \ No newline at end of file diff --git a/src/utils.lua b/src/utils.lua index e41d00e69..7f933db59 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -1473,7 +1473,6 @@ SMODS.calculate_individual_effect = function(effect, scored_card, key, amount, f if key == 'debuff' then return { [key] = amount, debuff_source = scored_card } end - end -- Used to calculate a table of effects generated in evaluate_play From addb1416d56a7153e2673d7f6d79ff1199a2cf5e Mon Sep 17 00:00:00 2001 From: Gloompashcy Date: Wed, 15 Apr 2026 15:48:47 +1000 Subject: [PATCH 11/11] Misc fixes --- lovely/perma_bonus.toml | 64 ++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/lovely/perma_bonus.toml b/lovely/perma_bonus.toml index 0c9ccf3e4..831ae6383 100644 --- a/lovely/perma_bonus.toml +++ b/lovely/perma_bonus.toml @@ -137,15 +137,15 @@ match_indent = true payload = ''' function Card:get_chip_bonus(context) if self.ability.extra_enhancement then return self.ability.bonus end + local perma = 0 if context then - local perma = 0 local calc = SMODS.calculate_perma_bonuses(self, context, 'chips') for k,v in pairs(calc) do if k == 'chips' and v[k] and v[k] > 0 then perma = SMODS.stack_perma_bonus(k, v, {[k] = perma}) end end - end''' + else perma = self.ability.perma_bonus or 0 end''' [[patches]] [patches.pattern] @@ -203,7 +203,7 @@ if context then ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end -end +else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_mult) or 0) end -- TARGET: get_chip_mult return ret''' @@ -223,7 +223,7 @@ if context then ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end -end +else ret = ((not self.ability.extra_enhancement and self.ability.perma_x_mult) or 0) end ret = SMODS.multiplicative_stacking(self.ability.x_mult or 1, 0) -- TARGET: get_chip_x_mult return ret @@ -252,7 +252,7 @@ if context then ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end -end +else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_h_mult) or 0) end -- TARGET: get_chip_h_mult return ret ''' @@ -281,7 +281,7 @@ if context then ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end -end +else ret = ((not self.ability.extra_enhancement and self.ability.perma_h_x_mult) or 0) end ret = SMODS.multiplicative_stacking(self.ability.h_x_mult or 1, ret or 0) -- TARGET: get_chip_h_x_mult return ret @@ -308,7 +308,7 @@ function Card:get_chip_x_bonus(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ((not self.ability.extra_enhancement and self.ability.perma_x_chips) or 0) end ret = SMODS.multiplicative_stacking(self.ability.x_chips or 1, ret or 0) -- TARGET: get_chip_x_bonus return ret @@ -324,7 +324,7 @@ function Card:get_chip_h_bonus(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_h_chips) or 0) end ret = (self.ability.h_chips or 0) + ret -- TARGET: get_chip_h_bonus return ret @@ -340,7 +340,7 @@ function Card:get_chip_h_x_bonus(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ((not self.ability.extra_enhancement and self.ability.perma_h_x_chips) or 0) end ret = SMODS.multiplicative_stacking(self.ability.h_x_chips or 1, ret or 0) -- TARGET: get_chip_h_x_bonus return ret @@ -356,7 +356,7 @@ function Card:get_h_dollars(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_h_dollars) or 0) end ret = (self.ability.h_dollars or 0) + ret -- TARGET: get_h_dollars return ret @@ -372,7 +372,7 @@ function Card:get_bonus_score(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_score) or 0) end -- TARGET: get_bonus_score return ret end @@ -387,7 +387,7 @@ function Card:get_bonus_x_score(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ((not self.ability.extra_enhancement and self.ability.perma_x_score) or 0) end ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_x_score return ret @@ -403,7 +403,7 @@ function Card:get_bonus_h_score(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_h_score) or 0) end -- TARGET: get_bonus_h_score return ret end @@ -418,7 +418,7 @@ function Card:get_bonus_h_x_score(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ((not self.ability.extra_enhancement and self.ability.perma_h_x_score) or 0) end ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_h_x_score return ret @@ -427,6 +427,14 @@ end function Card:get_bonus_blind_size(context) if self.debuff then return 0 end local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'blind_size') + for k,v in pairs(calc) do + if k == 'blind_size' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_blind_size) or 0) end -- TARGET: get_bonus_blind_size return ret end @@ -441,7 +449,7 @@ function Card:get_bonus_x_blind_size(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ((not self.ability.extra_enhancement and self.ability.perma_x_blind_size) or 0) end ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_x_blind_size return ret @@ -450,6 +458,14 @@ end function Card:get_bonus_h_blind_size(context) if self.debuff then return 0 end local ret = 0 + if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'h_blind_size') + for k,v in pairs(calc) do + if k == 'h_blind_size' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end + else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_h_blind_size) or 0) end -- TARGET: get_bonus_h_blind_size return ret end @@ -464,7 +480,7 @@ function Card:get_bonus_h_x_blind_size(context) ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) end end - end + else ret = ((not self.ability.extra_enhancement and self.ability.perma_h_x_blind_size) or 0) end ret = SMODS.multiplicative_stacking(1, ret) -- TARGET: get_bonus_h_x_blind_size return ret @@ -472,6 +488,14 @@ end ''' # Card:get_p_dollars +[[patches]] +[patches.pattern] +target = "card.lua" +pattern = "function Card:get_p_dollars()" +position = "at" +match_indent = true +payload = "function Card:get_p_dollars(context)" + [[patches]] [patches.pattern] target = "card.lua" @@ -483,6 +507,14 @@ match_indent = true payload = '''elseif self.ability.p_dollars < 0 then ret = ret + self.ability.p_dollars end +if context then + local calc = SMODS.calculate_perma_bonuses(self, context, 'p_dollars') + for k,v in pairs(calc) do + if k == 'p_dollars' and v[k] and v[k] > 0 then + ret = SMODS.stack_perma_bonus(k, v, {[k] = ret}) + end + end +else ret = ret + ((not self.ability.extra_enhancement and self.ability.perma_p_dollars) or 0) end -- TARGET: get_p_dollars if ret ~= 0 then G.GAME.dollar_buffer = (G.GAME.dollar_buffer or 0) + ret'''