diff --git a/lovely/backgrounds.toml b/lovely/backgrounds.toml new file mode 100644 index 000000000..fabe8b854 --- /dev/null +++ b/lovely/backgrounds.toml @@ -0,0 +1,146 @@ +[manifest] +version = "1.0.0" +dump_lua = true +priority = -1 + +# Handle the vanilla background shaders +# Sprite:draw +[[patches]] +[patches.pattern] +target = "engine/sprite.lua" +pattern = ''' +elseif _shader == 'vortex' then + G.SHADERS['vortex']:send('vortex_amt', G.TIMERS.REAL - (G.vortex_time or 0)) +''' +position = "after" +payload = ''' +elseif _shader == 'background' then + G.SHADERS['background']:send('time', G.TIMERS.REAL_SHADER) + G.SHADERS['background']:send('spin_time', G.TIMERS.BACKGROUND) + G.SHADERS['background']:send('colour_1', G.C.BACKGROUND.C) + G.SHADERS['background']:send('colour_2', G.C.BACKGROUND.L) + G.SHADERS['background']:send('colour_3', G.C.BACKGROUND.D) + G.SHADERS['background']:send('contrast', G.C.BACKGROUND.contrast) + G.SHADERS['background']:send('spin_amount', G.ARGS.spin and G.ARGS.spin.amount or 0) +elseif _shader == 'splash' then + G.SHADERS['splash']:send('time', G.TIMERS.REAL) + G.SHADERS['splash']:send('vort_speed', G.STATE == G.STATES.SPLASH and 1 or 0.4) + G.SHADERS['splash']:send('colour_1', G.STATE == G.STATES.SPLASH and G.C.BLUE or G.C.RED) + G.SHADERS['splash']:send('colour_2', G.STATE == G.STATES.SPLASH and G.C.WHITE or G.C.BLUE) + G.SHADERS['splash']:send('mid_flash', SMODS.splash_mid_flash) + G.SHADERS['splash']:send('vort_offset', G.STATE == G.STATES.SPLASH and SMODS.splash_vort_offset or 0) +''' +match_indent = true + +# Make splash_args global +# Game:main_menu, Game:demo_cta +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = ''' +local splash_args = {mid_flash = change_context == 'splash' and 1.6 or 0.} + ease_value(splash_args, 'mid_flash', -(change_context == 'splash' and 1.6 or 0), nil, nil, nil, 4) +''' +position = "after" +payload = ''' +SMODS.splash_mid_flash = change_context == 'splash' and 1.6 or 0 +ease_value(SMODS, 'splash_mid_flash', -(change_context == 'splash' and 1.6 or 0), nil, nil, nil, 4) +''' +match_indent = true + +# Make vort_offset global +# Game:splash_screen +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = '''--Prep the splash screen shaders for both the background(colour swirl) and the foreground(white flash), starting at black''' +position = "after" +payload = ''' +SMODS.vort_offset = ((2*90.15315131*os.time())%100000) +''' +match_indent = true + +# Draw custom bgs +# Game:draw +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = ''' +if G.SPLASH_BACK then + if G.debug_background_toggle then + love.graphics.clear({0,1,0,1}) + else + love.graphics.push() + G.SPLASH_BACK:translate_container() + G.SPLASH_BACK:draw() + love.graphics.pop() + end + end +''' +position = "at" +payload = ''' +--[[ +if G.SPLASH_BACK then + if G.debug_background_toggle then + love.graphics.clear({0,1,0,1}) + else + love.graphics.push() + G.SPLASH_BACK:translate_container() + G.SPLASH_BACK:draw() + love.graphics.pop() + end +end +]] + +if SMODS.PRIMARY_BG_CANVAS then + if G.debug_background_toggle then + love.graphics.clear({0,1,0,1}) + else + SMODS.draw_background() + end +end + +''' +match_indent = true + +# Get current bg +# Game:update +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = '''modulate_sound(dt)''' +position = "after" +payload = ''' +SMODS.update_background() +''' +match_indent = true + +# Create SMODS bg stuff +# Game:main_menu, Game:demo_cta, Game:sandbox +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = '''if G.SPLASH_BACK then G.SPLASH_BACK:remove(); G.SPLASH_BACK = nil end''' +position = "after" +payload = ''' +SMODS.create_bg_canvasses() +''' +match_indent = true + +# Game:start_run +[[patches]] +[patches.pattern] +target = 'game.lua' +pattern = ''' +G.SPLASH_BACK:set_alignment({ + major = G.play, + type = 'cm', + bond = 'Strong', + offset = {x=0,y=0} + }) +''' +position = "after" +payload = ''' +SMODS.create_bg_canvasses() +''' +match_indent = true diff --git a/lovely/screenshader_rendering.toml b/lovely/screenshader_rendering.toml index 1aa6b050b..a9689c383 100644 --- a/lovely/screenshader_rendering.toml +++ b/lovely/screenshader_rendering.toml @@ -68,5 +68,18 @@ position = "after" payload = ''' G.SHADER_CANVAS_A = SMODS.create_canvas() G.SHADER_CANVAS_B = SMODS.create_canvas() + +-- easier to just stick this here +if not SMODS.BG_CANVAS_1 then + SMODS.BG_CANVAS_1 = SMODS.BackgroundCanvas(-30, -13, w, h, SMODS.Background:get_current_background()) +end +if not SMODS.BG_CANVAS_2 then + SMODS.BG_CANVAS_2 = SMODS.BackgroundCanvas(-30, -13, w, h, nil, 0) +end + +SMODS.PRIMARY_BG_CANVAS = SMODS.PRIMARY_BG_CANVAS or SMODS.BG_CANVAS_1 + +SMODS.BG_CANVAS_1.canvas = SMODS.create_canvas() +SMODS.BG_CANVAS_2.canvas = SMODS.create_canvas() ''' match_indent = true diff --git a/lsp_def/classes/background.lua b/lsp_def/classes/background.lua new file mode 100644 index 000000000..df6074f30 --- /dev/null +++ b/lsp_def/classes/background.lua @@ -0,0 +1,39 @@ +---@meta + +---@class SMODS.Background: SMODS.GameObject +---@field obj_table? table Table of objects registered to this class. +---@field super? SMODS.GameObject|table Parent class. +---@field key string Unique string to reference this object. +---@field shader? string Key of the shader to apply to the background, shader must already exist to use this. +---@field path? string Name of the shader file to use if `shader` is not provided. +---@field atlas? string Key to the atlas used for the background sprite. +---@field pos? table|{x: integer, y: integer} Position of the background's sprite. +---@field fade_time? number Time it takes for this background to fade in. +---@field fade_ease? string Easing curve used when this background fades in. Must be in SMODS.ease_types +---@field extend? fun(self: SMODS.Background|table, o: SMODS.Background|table): table Primary method of creating a class. +---@field check_duplicate_register? fun(self: SMODS.Background|table): boolean? Ensures objects already registered will not register. +---@field check_duplicate_key? fun(self: SMODS.Background|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.Background|table) Registers the object. +---@field check_dependencies? fun(self: SMODS.Background|table): boolean? Returns `true` if there's no failed dependencies. +---@field process_loc_text? fun(self: SMODS.Background|table) Called during `inject_class`. Handles injecting loc_text. +---@field send_to_subclasses? fun(self: SMODS.Background|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.Background|table) Called before `inject_class`. Injects and manages class information before object injection. +---@field post_inject_class? fun(self: SMODS.Background|table) Called after `inject_class`. Injects and manages class information after object injection. +---@field inject_class? fun(self: SMODS.Background|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.Background|table, i?: number) Called during `inject_class`. Injects the object into the game. +---@field take_ownership? fun(self: SMODS.Background|table, key: string, obj: SMODS.Background|table, silent?: boolean): nil|table|SMODS.Background Takes control of vanilla objects. Child class must have get_obj for this to function +---@field get_obj? fun(self: SMODS.Background|table, key: string): SMODS.Background|table? Returns an object if one matches the `key`. +---@field select_background? fun(self: SMODS.Background|table): nil|number|boolean Called each frame. Determines what background to use. Background with the highest number is used. +---@field set_sprites? fun(self: SMODS.Background|table, bg: SMODS.BackgroundCanvas|table) Used for setting and manipulating sprites of the background when created or loaded. +---@field update? fun(self: SMODS.Center|table, bg: SMODS.BackgroundCanvas|table) Allows logic for this card to be run per-frame. +---@field send_vars? fun(self: SMODS.Background|table): table? Used to send extra args to the shader via `Shader:send(key, value)`. +---@field get_current_background? fun(self: SMODS.Background|table): nil|string Polls `SMODS.Background:select_background` and returns the key to the background to use. +---@overload fun(self: SMODS.Background): SMODS.Background +SMODS.Background = setmetatable({}, { + __call = function(self) + return self + end +}) + +---@type table +SMODS.Backgrounds = {} \ No newline at end of file diff --git a/lsp_def/classes/background_draw_step.lua b/lsp_def/classes/background_draw_step.lua new file mode 100644 index 000000000..1db45f000 --- /dev/null +++ b/lsp_def/classes/background_draw_step.lua @@ -0,0 +1,26 @@ +---@meta + +---@class SMODS.BackgroundDrawStep: SMODS.GameObject +---@field key string Unique string to reference this object. +---@field order? number Sets the order. `BackgroundDrawStep` objects are evaluated in order from lowest to highest. +---@field __call? fun(self: SMODS.BackgroundDrawStep|table, o: SMODS.BackgroundDrawStep|table): nil|table|SMODS.BackgroundDrawStep +---@field extend? fun(self: SMODS.BackgroundDrawStep|table, o: SMODS.BackgroundDrawStep|table): table Primary method of creating a class. +---@field check_duplicate_register? fun(self: SMODS.BackgroundDrawStep|table): boolean? Ensures objects already registered will not register. +---@field check_duplicate_key? fun(self: SMODS.BackgroundDrawStep|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.BackgroundDrawStep|table) Registers the object. +---@field check_dependencies? fun(self: SMODS.BackgroundDrawStep|table): boolean? Returns `true` if there's no failed dependencies. +---@field process_loc_text? fun(self: SMODS.BackgroundDrawStep|table) Called during `inject_class`. Handles injecting loc_text. +---@field send_to_subclasses? fun(self: SMODS.BackgroundDrawStep|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.BackgroundDrawStep|table) Called before `inject_class`. Injects and manages class information before object injection. +---@field post_inject_class? fun(self: SMODS.BackgroundDrawStep|table) Called after `inject_class`. Injects and manages class information after object injection. +---@field inject_class? fun(self: SMODS.BackgroundDrawStep|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.BackgroundDrawStep|table, i?: number) Called during `inject_class`. Injects the object into the game. +---@field take_ownership? fun(self: SMODS.BackgroundDrawStep|table, key: string, obj: SMODS.BackgroundDrawStep|table, silent?: boolean): nil|table|SMODS.BackgroundDrawStep Takes control of vanilla objects. Child class must have get_obj for this to function +---@field get_obj? fun(self: SMODS.BackgroundDrawStep|table, key: string): SMODS.BackgroundDrawStep|table? Returns an object if one matches the `key`. +---@field func? fun(card: Card|table, layer?: string) Handles the drawing logic of the `DrawStep`. +---@overload fun(self: SMODS.BackgroundDrawStep): SMODS.BackgroundDrawStep +SMODS.BackgroundDrawStep = setmetatable({}, { + __call = function(self) + return self + end +}) \ No newline at end of file diff --git a/lsp_def/utils.lua b/lsp_def/utils.lua index 78ec29136..5419b00db 100644 --- a/lsp_def/utils.lua +++ b/lsp_def/utils.lua @@ -846,4 +846,22 @@ 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 + +---@class SMODS.BackgroundCanvas: Moveable +---@overload fun(...: any): SMODS.BackgroundCanvas|table +SMODS.BackgroundCanvas = {} +function SMODS.BackgroundCanvas:__call(...) return self end + +--- Creates SMODS.BG_CANVAS_1 and SMODS.BG_CANVAS_2, and removes previous instances of both. +function SMODS.create_bg_canvasses() end + +--- Draws the background. +function SMODS.draw_background() end + +--- Updates the current background, and handles the background fade queue. Gets run every frame. +function SMODS.update_background() end + +--- Begins fading the background to a new background. +--- @param bg string Key for an SMODS.Background object. +function SMODS.switch_background(bg) end \ No newline at end of file diff --git a/src/core.lua b/src/core.lua index 9102d8599..948d63975 100644 --- a/src/core.lua +++ b/src/core.lua @@ -6,7 +6,8 @@ for _, path in ipairs { "src/overrides.lua", "src/game_object.lua", "src/compat_0_9_8.lua", - "src/utils/weights.lua" + "src/utils/weights.lua", + "src/utils/backgrounds.lua", } do assert(load(SMODS.NFS.read(SMODS.path..path), ('=[SMODS _ "%s"]'):format(path)))() end diff --git a/src/game_object.lua b/src/game_object.lua index e70f0ce5e..ea06a292e 100644 --- a/src/game_object.lua +++ b/src/game_object.lua @@ -399,6 +399,12 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. assert(load(SMODS.NFS.read(SMODS.path..'src/game_objects/attributes.lua'), ('=[SMODS _ "src/game_objects/attributes.lua"]')))() + ------------------------------------------------------------------------------------------------- + ----- API CODE GameObject.Background + ------------------------------------------------------------------------------------------------- + + assert(load(SMODS.NFS.read(SMODS.path..'src/game_objects/backgrounds.lua'), ('=[SMODS _ "src/game_objects/backgrounds.lua"]')))() + ------------------------------------------------------------------------------------------------- ----- INTERNAL API CODE GameObject._Loc_Pre ------------------------------------------------------------------------------------------------- diff --git a/src/game_objects/backgrounds.lua b/src/game_objects/backgrounds.lua new file mode 100644 index 000000000..06f1599e7 --- /dev/null +++ b/src/game_objects/backgrounds.lua @@ -0,0 +1,195 @@ +-- bg draw steps; allows for creating multilayered backgrounds +SMODS.BackgroundDrawSteps = {} +SMODS.BackgroundDrawStep = SMODS.GameObject:extend { + obj_table = SMODS.BackgroundDrawSteps, + obj_buffer = {}, + required_params = { + 'key', + 'order', + 'func', + }, + -- func = function(bg) end, + set = "Background Draw Steps", + register = function(self) + if self.registered then + sendWarnMessage(('Detected duplicate register call on object %s'):format(self.key), self.set) + return + end + SMODS.BackgroundDrawStep.super.register(self) + end, + inject = function() end, + post_inject_class = function(self) + table.sort(self.obj_buffer, function(_self, _other) return self.obj_table[_self].order < self.obj_table[_other].order end) + end, +} + +SMODS.BackgroundDrawStep { + key = "base", + order = -math.huge, + func = function(self) + if self.children.base then + self.children.base:draw_shader(self.shader or "dissolve") + end + end, +} + +-- DO NOT CREATE NEW INSTANCES OF THIS +SMODS.BackgroundCanvas = Moveable:extend() + +function SMODS.BackgroundCanvas:init(X, Y, W, H, prototype, alpha) + Moveable.init(self, X, Y, W, H) + prototype = (type(prototype) == "string" and SMODS.Backgrounds[prototype] or prototype) or SMODS.Backgrounds.splash + + self.states.collide.can = false + self.states.hover.can = false + self.states.drag.can = false + self.states.click.can = false + + self.alpha = alpha or 1 + self.canvas = SMODS.create_canvas() + self.children = {} + + if prototype then + self:set_active_bg(prototype) + end +end + +function SMODS.BackgroundCanvas:draw() + if self.alpha <= 0 then return end + local fading = self.alpha ~= 1 + if fading then + love.graphics.setCanvas(self.canvas) + end + for _, k in ipairs(SMODS.BackgroundDrawStep.obj_buffer) do + SMODS.BackgroundDrawSteps[k].func(self) + end + if fading then + love.graphics.setCanvas(G.CANVAS) + love.graphics.setColor(1,1,1,self.alpha) + love.graphics.draw(self.canvas, 0, 0) + love.graphics.setColor(1,1,1,1) + end +end + +function SMODS.BackgroundCanvas:set_sprites() + self.children = self.children or {} + local obj = self.prototype + + local atlas = obj.atlas_key or 'ui_1' + local pos = obj.pos or {x = obj.atlas_key and 2 or 0, y = 0} + self.children.base = SMODS.create_sprite(self.T.x, self.T.y, self.T.w, self.T.h, atlas, pos) + self.children.base:set_alignment({ + major = self, + type = 'cm', + bond = 'Glued', + offset = {x=0,y=0} + }) + + if obj.set_sprites and type(obj.set_sprites) == 'function' then + obj:set_sprites(self) + end +end + +function SMODS.BackgroundCanvas:clean_up_children() + for _, v in pairs(self.children) do v:remove(); v = nil end +end + +function SMODS.BackgroundCanvas:set_active_bg(prototype) + self.prototype = prototype + self.shader = prototype.shader + self:clean_up_children() + self:set_sprites() +end + +function SMODS.BackgroundCanvas:remove() + self:clean_up_children() + Moveable.remove(self) +end + +function SMODS.BackgroundCanvas:update() + local obj = self.prototype + if obj and obj.update and type(obj.update) == 'function' then + obj:update(self) + end +end + +function SMODS.BackgroundCanvas:ease_alpha(target, delay, ease_type) + if target == self.alpha then return end + target = math.min(1, math.max(target, 0)) + delay = delay or 2 + SMODS.is_bg_fading = true + G.E_MANAGER:add_event(Event({ + trigger = "ease", + ref_table = self, + ref_value = "alpha", + ease_to = target, + delay = delay, + type = ease_type, + blocking = false, + blockable = false, + })) + G.E_MANAGER:add_event(Event({ + func = function() + SMODS.is_bg_fading = false + return true + end, + trigger = "after", + delay = delay, + blocking = false, + blockable = false, + })) +end + +SMODS.Backgrounds = {} +SMODS.Background = SMODS.GameObject:extend { + obj_table = SMODS.Backgrounds, + obj_buffer = {}, + set = "Background", + required_params = { + 'key', + }, + send_vars = nil, -- same as Shader.send_vars + select_background = nil, -- should this bg be used, works like SMODS.Sounds:select_music_track + set_sprites = nil, -- sets sprites + update = nil, -- runs on update + + fade_time = nil, -- how much time it takes for this bg to fade in + fade_ease = nil, -- which easing curve this bg uses when fading in + + inject = function(self) + -- assert(self.shader or self.path, "Background " .. self.key .. " not given shader key or path") + if self.path and (not self.shader) then + SMODS.Shader.inject(self) + self.shader = self.key + end + + self.fade_time = self.fade_time or 2 + end, + get_current_background = function(self) + local bg + local maxp = -math.huge + for _, v in ipairs(self.obj_buffer) do + local s = self.obj_table[v] + if s.select_background and type(s.select_background) == 'function' then + local res = s:select_background() + if res then + if type(res) ~= 'number' then res = 0 end + if res > maxp then bg, maxp = v, res end + end + end + end + return bg or (G.STAGE == G.STAGES.RUN and "background" or "splash") + end +} + +SMODS.splash_mid_flash = 0 +SMODS.splash_vort_offset = 0 +SMODS.Background { + key = "splash", + shader = "splash" +} + +SMODS.Background { + key = "background", + shader = "background" +} \ No newline at end of file diff --git a/src/utils/backgrounds.lua b/src/utils/backgrounds.lua new file mode 100644 index 000000000..184f5351f --- /dev/null +++ b/src/utils/backgrounds.lua @@ -0,0 +1,56 @@ +function SMODS.create_bg_canvasses() + if SMODS.BG_CANVAS_1 then SMODS.BG_CANVAS_1:remove(); SMODS.BG_CANVAS_1 = nil end + if SMODS.BG_CANVAS_2 then SMODS.BG_CANVAS_2:remove(); SMODS.BG_CANVAS_2 = nil end + SMODS.background_queue = {} + + local w, h = G.CANVAS:getDimensions() + SMODS.BG_CANVAS_1 = SMODS.BackgroundCanvas(-30, -13, w, h, SMODS.Background:get_current_background()) + SMODS.BG_CANVAS_2 = SMODS.BackgroundCanvas(-30, -13, w, h, nil, 0) + + SMODS.PRIMARY_BG_CANVAS = SMODS.BG_CANVAS_1 +end + +function SMODS.draw_background() + local primary_canvas = SMODS.PRIMARY_BG_CANVAS == SMODS.BG_CANVAS_1 and SMODS.BG_CANVAS_1 or SMODS.BG_CANVAS_2 + local secondary_canvas = SMODS.PRIMARY_BG_CANVAS == SMODS.BG_CANVAS_1 and SMODS.BG_CANVAS_2 or SMODS.BG_CANVAS_1 + + primary_canvas:draw() + secondary_canvas:draw() +end + +SMODS.background_queue = {} +SMODS.is_bg_fading = false + +function SMODS.update_background() + -- queue bg fades + local cur_bg = SMODS.Background:get_current_background() + if + SMODS.PRIMARY_BG_CANVAS and SMODS.PRIMARY_BG_CANVAS.prototype and SMODS.PRIMARY_BG_CANVAS.prototype.key ~= cur_bg + and SMODS.background_queue[#SMODS.background_queue] ~= cur_bg + then + SMODS.background_queue[#SMODS.background_queue+1] = cur_bg + end + + -- handle fade queue + if not SMODS.is_bg_fading and #SMODS.background_queue > 0 then + local bg = SMODS.Backgrounds[table.remove(SMODS.background_queue, 1)] + SMODS.switch_background(bg) + end +end + +function SMODS.switch_background(bg) + local w, h = G.CANVAS:getDimensions() + SMODS.BG_CANVAS_1 = SMODS.BG_CANVAS_1 or SMODS.BackgroundCanvas(-30, -13, w, h) + SMODS.BG_CANVAS_2 = SMODS.BG_CANVAS_2 or SMODS.BackgroundCanvas(-30, -13, w, h) + + local new_canvas = SMODS.PRIMARY_BG_CANVAS == SMODS.BG_CANVAS_1 and SMODS.BG_CANVAS_2 or SMODS.BG_CANVAS_1 + local old_canvas = SMODS.PRIMARY_BG_CANVAS == SMODS.BG_CANVAS_1 and SMODS.BG_CANVAS_1 or SMODS.BG_CANVAS_2 + + -- set new canvas + SMODS.PRIMARY_BG_CANVAS = new_canvas + new_canvas:set_active_bg(bg) + + -- fade out old canvas + old_canvas:ease_alpha(0, bg.fade_time, bg.fade_ease) + new_canvas.alpha = 1 +end