diff --git a/lovely/ui_additional_text_props.toml b/lovely/ui_additional_text_props.toml new file mode 100644 index 000000000..ce1cd1b79 --- /dev/null +++ b/lovely/ui_additional_text_props.toml @@ -0,0 +1,212 @@ +[manifest] +version = "1.2" +dump_lua = true +priority = -10 + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = """ spacing = math.max(0, 0.32*(17 - #assembled_string)),""" +position = "after" +payload = """font = SMODS.Fonts[part.control.f] and SMODS.Fonts[part.control.f] or (tonumber(part.control.f) and G.FONTS[tonumber(part.control.f)]),""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = """ spacing = _spacing,""" +position = "after" +payload = """font = SMODS.Fonts[part.control.f] and SMODS.Fonts[part.control.f] or (tonumber(part.control.f) and G.FONTS[tonumber(part.control.f)]),""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = """ text = assembled_string,""" +position = "after" +payload = """font = SMODS.Fonts[part.control.f] and SMODS.Fonts[part.control.f] or (tonumber(part.control.f) and G.FONTS[tonumber(part.control.f)]),""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = """ loc_target = G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key]""" +position = "at" +payload = """ +loc_target = loc_target or {} +if pcall(function() loc_target.name_parsed = {loc_parse_string(G.localization.descriptions[(args.set or args.node.config.center.set)][args.key or args.node.config.center.key].name)} end) then +else loc_target.name_parsed = {} end""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "functions/misc_functions.lua" +pattern = """ if ret_string then return ret_string end""" +position = "before" +payload = """if ret_string and type(ret_string) == 'string' then ret_string = string.gsub(ret_string, "{.-}", "") end""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ self.config.text_drawable = love.graphics.newText(self.config.lang.font.FONT, {G.C.WHITE,self.config.text})""" +position = "before" +payload = """if self.config.font then + self.config.text_drawable = love.graphics.newText(self.config.font.FONT, {G.C.WHITE,self.config.text}) +else +""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ self.config.text_drawable = love.graphics.newText(self.config.lang.font.FONT, {G.C.WHITE,self.config.text})""" +position = "after" +payload = """end""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ local tx = node.config.lang.font.FONT:getWidth(node.config.text)*node.config.lang.font.squish*scale*G.TILESCALE*node.config.lang.font.FONTSCALE + local ty = node.config.lang.font.FONT:getHeight()*scale*G.TILESCALE*node.config.lang.font.FONTSCALE*node.config.lang.font.TEXT_HEIGHT_SCALE""" +position = "at" +payload = """ +local tx,ty +if node.config.font then + tx = node.config.font.FONT:getWidth(node.config.text)*node.config.font.squish*scale*G.TILESCALE*node.config.font.FONTSCALE + ty = node.config.font.FONT:getHeight()*scale*G.TILESCALE*node.config.font.FONTSCALE*node.config.font.TEXT_HEIGHT_SCALE +else + tx = node.config.lang.font.FONT:getWidth(node.config.text)*node.config.lang.font.squish*scale*G.TILESCALE*node.config.lang.font.FONTSCALE + ty = node.config.lang.font.FONT:getHeight()*scale*G.TILESCALE*node.config.lang.font.FONTSCALE*node.config.lang.font.TEXT_HEIGHT_SCALE +end +""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ self.ARGS.text_parallax.sx = -self.shadow_parrallax.x*0.5/(self.config.scale*self.config.lang.font.FONTSCALE) + self.ARGS.text_parallax.sy = -self.shadow_parrallax.y*0.5/(self.config.scale*self.config.lang.font.FONTSCALE)""" +position = "before" +payload = """ +if self.config.font then + self.ARGS.text_parallax.sx = -self.shadow_parrallax.x*0.5/(self.config.scale*self.config.font.FONTSCALE) + self.ARGS.text_parallax.sy = -self.shadow_parrallax.y*0.5/(self.config.scale*self.config.font.FONTSCALE) +else +""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ self.ARGS.text_parallax.sx = -self.shadow_parrallax.x*0.5/(self.config.scale*self.config.lang.font.FONTSCALE) + self.ARGS.text_parallax.sy = -self.shadow_parrallax.y*0.5/(self.config.scale*self.config.lang.font.FONTSCALE)""" +position = "after" +payload = """ +end +""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ love.graphics.setColor(0, 0, 0, 0.3*self.config.colour[4]) + love.graphics.draw( + self.config.text_drawable, + (self.config.lang.font.TEXT_OFFSET.x + (self.config.vert and -self.ARGS.text_parallax.sy or self.ARGS.text_parallax.sx))*(self.config.scale or 1)*self.config.lang.font.FONTSCALE/G.TILESIZE, + (self.config.lang.font.TEXT_OFFSET.y + (self.config.vert and self.ARGS.text_parallax.sx or self.ARGS.text_parallax.sy))*(self.config.scale or 1)*self.config.lang.font.FONTSCALE/G.TILESIZE, + 0, + (self.config.scale)*self.config.lang.font.squish*self.config.lang.font.FONTSCALE/G.TILESIZE, + (self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE + )""" +position = "before" +payload = """ +if self.config.font then + love.graphics.setColor(0, 0, 0, 0.3*self.config.colour[4]) + love.graphics.draw( + self.config.text_drawable, + (self.config.font.TEXT_OFFSET.x + (self.config.vert and -self.ARGS.text_parallax.sy or self.ARGS.text_parallax.sx))*(self.config.scale or 1)*self.config.font.FONTSCALE/G.TILESIZE, + (-self.config.font.FONTSCALE+self.config.font.TEXT_OFFSET.y + (self.config.vert and self.ARGS.text_parallax.sx or self.ARGS.text_parallax.sy))*(self.config.scale or 1)*self.config.font.FONTSCALE/G.TILESIZE, + 0, + (self.config.scale)*self.config.font.squish*self.config.font.FONTSCALE/G.TILESIZE, + (self.config.scale)*self.config.font.FONTSCALE/G.TILESIZE + ) +else +""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ love.graphics.setColor(0, 0, 0, 0.3*self.config.colour[4]) + love.graphics.draw( + self.config.text_drawable, + (self.config.lang.font.TEXT_OFFSET.x + (self.config.vert and -self.ARGS.text_parallax.sy or self.ARGS.text_parallax.sx))*(self.config.scale or 1)*self.config.lang.font.FONTSCALE/G.TILESIZE, + (self.config.lang.font.TEXT_OFFSET.y + (self.config.vert and self.ARGS.text_parallax.sx or self.ARGS.text_parallax.sy))*(self.config.scale or 1)*self.config.lang.font.FONTSCALE/G.TILESIZE, + 0, + (self.config.scale)*self.config.lang.font.squish*self.config.lang.font.FONTSCALE/G.TILESIZE, + (self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE + )""" +position = "after" +payload = """ +end +""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ love.graphics.draw( + self.config.text_drawable, + self.config.lang.font.TEXT_OFFSET.x*(self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE, + self.config.lang.font.TEXT_OFFSET.y*(self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE, + 0, + (self.config.scale)*self.config.lang.font.squish*self.config.lang.font.FONTSCALE/G.TILESIZE, + (self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE + )""" +position = "before" +payload = """ +if self.config.font then + love.graphics.draw( + self.config.text_drawable, + self.config.font.TEXT_OFFSET.x*(self.config.scale)*self.config.font.FONTSCALE/G.TILESIZE, + self.config.font.TEXT_OFFSET.y*(self.config.scale)*self.config.font.FONTSCALE/G.TILESIZE, + 0, + (self.config.scale)*self.config.font.squish*self.config.font.FONTSCALE/G.TILESIZE, + (self.config.scale)*self.config.font.FONTSCALE/G.TILESIZE + ) +else +""" +overwrite = true +match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/ui.lua" +pattern = """ love.graphics.draw( + self.config.text_drawable, + self.config.lang.font.TEXT_OFFSET.x*(self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE, + self.config.lang.font.TEXT_OFFSET.y*(self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE, + 0, + (self.config.scale)*self.config.lang.font.squish*self.config.lang.font.FONTSCALE/G.TILESIZE, + (self.config.scale)*self.config.lang.font.FONTSCALE/G.TILESIZE + )""" +position = "after" +payload = """ +end +""" +overwrite = true +match_indent = true diff --git a/lsp_def/classes/font.lua b/lsp_def/classes/font.lua new file mode 100644 index 000000000..f9dbdb3f3 --- /dev/null +++ b/lsp_def/classes/font.lua @@ -0,0 +1,35 @@ +---@meta + +---@class SMODS.Font: SMODS.GameObject +---@field obj_table? table Table of objects registered to this class. +---@field super? SMODS.GameObject|table Parent class. +---@field path? string Name of the font file, including the extension. +---@field render_scale? number Determine the font size the text is being rendered after multiplying by `G.TILESIZE`. This is usually set to a high number for better font readability. The default font's value is `10`. +---@field TEXT_HEIGHT_SCALE? number Determines line spacing. The default font's value is `0.83`. +---@field TEXT_OFFSET? table|{x: number, y: number} Determines the offset that the font is rendered. You might need to adjust this if the font render in unexpected places. This one depends on how far the font is from expected place and is entirely up to the font itself so it is necessary to test which value works best. +---@field FONTSCALE? number Determines how big the font actually is rendered after scaling with `render_scale`. This is usually set to a lower number in inverse proportion to `render_scale` so that it renders at a proper size. The default font's value is `0.1`. +---@field squish? number Determines horizontal scaling factor of the font. Value less than 1 will result in fonts being squished together. All fonts in the base game has this value set to `1` by default. +---@field DESCSCALE? number Determines how big the description text should be in relation to normal text. Keep in mind that mobile UI makes this 1.5x bigger. The default font's value is `1`. +---@field __call? fun(self: SMODS.Font|table, o: SMODS.Font|table): nil|table|SMODS.Font +---@field extend? fun(self: SMODS.Font|table, o: SMODS.Font|table): table Primary method of creating a class. +---@field check_duplicate_register? fun(self: SMODS.Font|table): boolean? Ensures objects already registered will not register. +---@field check_duplicate_key? fun(self: SMODS.Font|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.Font|table) Registers the object. +---@field check_dependencies? fun(self: SMODS.Font|table): boolean? Returns `true` if there's no failed dependencies. +---@field process_loc_text? fun(self: SMODS.Font|table) Called during `inject_class`. Handles injecting loc_text. +---@field send_to_subclasses? fun(self: SMODS.Font|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.Font|table) Called before `inject_class`. Injects and manages class information before object injection. +---@field post_inject_class? fun(self: SMODS.Font|table) Called after `inject_class`. Injects and manages class information after object injection. +---@field inject_class? fun(self: SMODS.Font|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.Font|table, i?: number) Called during `inject_class`. Injects the object into the game. +---@field take_ownership? fun(self: SMODS.Font|table, key: string, obj: SMODS.Font|table, silent?: boolean): nil|table|SMODS.Font Takes control of vanilla objects. Child class must have get_obj for this to function +---@field get_obj? fun(self: SMODS.Font|table, key: string): SMODS.Font|table? Returns an object if one matches the `key`. +---@overload fun(self: SMODS.Font): SMODS.Font +SMODS.Font = setmetatable({}, { + __call = function(self) + return self + end +}) + +---@type table +SMODS.Fonts = {} \ No newline at end of file diff --git a/src/game_object.lua b/src/game_object.lua index 939b5e308..44e2a7e83 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -273,6 +273,51 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. return res end + ------------------------------------------------------------------------------------------------- + ----- API CODE GameObject.Font + ------------------------------------------------------------------------------------------------- + + SMODS.Fonts = {} + SMODS.Font = SMODS.GameObject:extend { + obj_table = SMODS.Fonts, + set = 'Fonts', + obj_buffer = {}, + disable_mipmap = false, + required_params = { + 'key', + 'path', + 'render_scale', + 'TEXT_HEIGHT_SCALE', + 'TEXT_OFFSET', + 'FONTSCALE', + 'squish', + 'DESCSCALE', + }, + register = function(self) + if self.registered then + sendWarnMessage(('Detected duplicate register call on object %s'):format(self.key), self.set) + return + end + self.name = self.key + SMODS.Font.super.register(self) + end, + inject = function(self) + local file_path = self.path + if file_path == 'DEFAULT' then return end + + self.full_path = (self.mod and self.mod.path or SMODS.path) .. + 'assets/fonts/' .. file_path + local file_data = assert(NFS.newFileData(self.full_path), + ('Failed to collect file data for Font %s'):format(self.key)) + local rs = (self.render_scale or 1) * G.TILESIZE + self.FONT = assert(love.graphics.newFont(file_data, rs), + ('Failed to initialize font data for Font %s'):format(self.key)) + + end, + process_loc_text = function() end, + } + + ------------------------------------------------------------------------------------------------- ----- API CODE GameObject.Language ------------------------------------------------------------------------------------------------- @@ -294,7 +339,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. local data = assert(NFS.newFileData(self.mod.path .. 'assets/fonts/' .. self.font.file), ('Failed to collect file data for font of language %s'):format(self.key)) self.font.FONT = love.graphics.newFont(data, self.font.render_scale) elseif type(self.font) ~= 'table' then - self.font = G.FONTS[type(self.font) == 'number' and self.font or 1] or G.FONTS[1] + self.font = SMODS.Fonts[self.font] or G.FONTS[type(self.font) == 'number' and self.font or 1] or G.FONTS[1] end G.LANGUAGES[self.key] = self if self.key == (G.SETTINGS.real_language or G.SETTINGS.language) then G.LANG = self end diff --git a/src/utils.lua b/src/utils.lua index 35a909ddc..ddd543909 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -2261,6 +2261,7 @@ function SMODS.localize_box(lines, args) pop_in = _pop_in, bump = _bump, spacing = _spacing, + font = SMODS.Fonts[part.control.f] and SMODS.Fonts[part.control.f] or (tonumber(part.control.f) and G.FONTS[tonumber(part.control.f)]), scale = 0.32*(part.control.s and tonumber(part.control.s) or args.scale or 1)*desc_scale}) }} elseif part.control.X or part.control.B then @@ -2268,6 +2269,7 @@ function SMODS.localize_box(lines, args) {n=G.UIT.T, config={ text = assembled_string, colour = part.control.V and args.vars.colours[tonumber(part.control.V)] or loc_colour(part.control.C or nil), + font = SMODS.Fonts[part.control.f] and SMODS.Fonts[part.control.f] or (tonumber(part.control.f) and G.FONTS[tonumber(part.control.f)]), scale = 0.32*(part.control.s and tonumber(part.control.s) or args.scale or 1)*desc_scale}}, }} else @@ -2276,6 +2278,7 @@ function SMODS.localize_box(lines, args) text = assembled_string, shadow = args.shadow, colour = part.control.V and args.vars.colours[tonumber(part.control.V)] or not part.control.C and args.text_colour or loc_colour(part.control.C or nil, args.default_col), + font = SMODS.Fonts[part.control.f] and SMODS.Fonts[part.control.f] or (tonumber(part.control.f) and G.FONTS[tonumber(part.control.f)]), scale = 0.32*(part.control.s and tonumber(part.control.s) or args.scale or 1)*desc_scale},} end end