diff --git a/core/game_object.lua b/core/game_object.lua index ea76e8a9..a1ff9555 100644 --- a/core/game_object.lua +++ b/core/game_object.lua @@ -2605,6 +2605,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. 'path', }, set = 'Shader', + send_vars = nil, -- function (sprite) - get custom externs to send to shader. inject = function(self) self.full_path = (self.mod and self.mod.path or SMODS.path) .. 'assets/shaders/' .. self.path @@ -2647,6 +2648,7 @@ Set `prefix_config.key = false` on your object instead.]]):format(obj.key), obj. calculate = nil, -- function (self) on_apply = nil, -- function (card) - modify card when edition is applied on_remove = nil, -- function (card) - modify card when edition is removed + on_load = nil, -- function (card) - modify card when it is loaded from the save file register = function(self) self.config = self.config or {} SMODS.Edition.super.register(self) diff --git a/core/overrides.lua b/core/overrides.lua index 64efb448..bb2725a4 100644 --- a/core/overrides.lua +++ b/core/overrides.lua @@ -1353,6 +1353,18 @@ function Card:should_draw_shadow() return not next(self.ignore_shadow or {}) end +local smods_card_load = Card.load +-- +function Card:load(cardTable, other_card) + local ret = smods_card_load(self, cardTable, other_card) + local on_edition_loaded = self.edition and self.edition.key and G.P_CENTERS[self.edition.key].on_load + if type(on_edition_loaded) == "function" then + on_edition_loaded(self) + end + + return ret +end + -- self = pass the card -- edition = -- nil (removes edition) diff --git a/example_mods/Mods/EditionExamples/EditionExamples.lua b/example_mods/Mods/EditionExamples/EditionExamples.lua index 45bf3b9c..88861f3b 100644 --- a/example_mods/Mods/EditionExamples/EditionExamples.lua +++ b/example_mods/Mods/EditionExamples/EditionExamples.lua @@ -5,7 +5,7 @@ --- MOD_AUTHOR: [Eremel_, stupxd] --- MOD_DESCRIPTION: Adds editions that demonstrate Edition API. --- BADGE_COLOUR: 3FC7EB ---- DEPENDENCIES: [Steamodded>=1.0.0~ALPHA-0812d] +--- DEPENDENCIES: [Steamodded>=1.0.0~ALPHA-0905a] SMODS.Atlas({ key = 'edition_example', @@ -122,6 +122,29 @@ SMODS.Back({ unlocked = true }) +---- CUSTOM VARS USAGE EXAMPLE +SMODS.Shader { + key = 'gold', + path = 'gold.fs', + -- card can be nil if sprite.role.major is not Card + send_vars = function (sprite, card) + return { + lines_offset = card and card.edition.example_gold_seed or 0 + } + end, +} +SMODS.Edition { + key = "gold", + shader = "gold", + on_apply = function (card) + -- Randomize offset to -1..1 + -- Save in card.edition table so it persists after game restart. + card.edition.example_gold_seed = pseudorandom('e_example_gold') * 2 - 1 + end, +} +---- CUSTOM VARS USAGE EXAMPLE END + + SMODS.Shader({ key = 'anaglyphic', path = 'anaglyphic.fs' }) SMODS.Shader({ key = 'flipped', path = 'flipped.fs' }) SMODS.Shader({ key = 'fluorescent', path = 'fluorescent.fs' }) diff --git a/example_mods/Mods/EditionExamples/README.md b/example_mods/Mods/EditionExamples/README.md index e3d443f4..66cde69d 100644 --- a/example_mods/Mods/EditionExamples/README.md +++ b/example_mods/Mods/EditionExamples/README.md @@ -1,7 +1,9 @@ # Introduction -A Balatro example mod for adding a custom Editions in Steamodded. +A Balatro example mod for adding custom Editions and Shaders. -Feel free to copy and use this for any of projects! +Feel free to copy and use this* for any of projects! + +***`anaglyphic`, `fluorescent`, `gilded`, `ionized`, `monochrome`, `greyscale` and `overexposed` shaders are not for public use and are only provided for learning purposes! (as requested by Eremel)** ## Notes in case you can't read **If you want an Edition to have more than one of the following:** diff --git a/example_mods/Mods/EditionExamples/assets/shaders/flipped.fs b/example_mods/Mods/EditionExamples/assets/shaders/flipped.fs index e26b72f3..1860353e 100644 --- a/example_mods/Mods/EditionExamples/assets/shaders/flipped.fs +++ b/example_mods/Mods/EditionExamples/assets/shaders/flipped.fs @@ -4,6 +4,12 @@ #define PRECISION mediump #endif +// +// Shader made by: stupxd +// You are free to use and modify this shader in your projects, +// as long as you credit me for the original work. +// + // Look ionized.fs for explanation extern PRECISION vec2 flipped; diff --git a/example_mods/Mods/EditionExamples/assets/shaders/gold.fs b/example_mods/Mods/EditionExamples/assets/shaders/gold.fs new file mode 100644 index 00000000..25963fd2 --- /dev/null +++ b/example_mods/Mods/EditionExamples/assets/shaders/gold.fs @@ -0,0 +1,121 @@ +#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH) + #define PRECISION highp +#else + #define PRECISION mediump +#endif + +// +// Shader made by: stupxd +// You are free to use and modify this shader in your projects, +// as long as you credit me for the original work. +// + +extern PRECISION vec2 gold; + +extern PRECISION number dissolve; +extern PRECISION number time; +extern PRECISION vec4 texture_details; +extern PRECISION vec2 image_details; +extern bool shadow; +extern PRECISION vec4 burn_colour_1; +extern PRECISION vec4 burn_colour_2; + +// Custom extern from SMODS.Shader +extern PRECISION float lines_offset; + +#define TWO_PI 6.28318530718 + +vec4 gold_color = vec4(231., 164., 25., 0.) / 255.; + +vec4 dissolve_mask(vec4 final_pixel, vec2 texture_coords, vec2 uv); + +bool line(vec2 uv, float offset, float width) { + uv.x = uv.x * texture_details.z / texture_details.w; + + offset = offset + 0.35 * sin(gold.x + TWO_PI * lines_offset); + width = width + 0.005 * sin(gold.x); + + float min_y = -uv.x + offset; + float max_y = -uv.x + offset + width; + + return uv.y > min_y && uv.y < max_y; +} + +vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords ) +{ + vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.zw)/texture_details.zw; + vec4 pixel = Texel(texture, texture_coords); + + vec4 tex = vec4(1., 1., 1., 0.1); + + if ( + lines_offset > 0 && (line(uv, 0.0, 0.07) || line(uv, 0.4, 0.1) || line(uv, 0.55, 0.1) || line(uv, 1.3, 0.05) || line(uv, 1.8, 0.1)) || + (line(uv, -0.1, 0.13) || line(uv, 0.3, 0.05) || line(uv, 0.8, 0.1) || line(uv, 1.3, 0.11) || line(uv, 1.7, 0.07)) + ) { + tex.a = tex.a * 2.; + } else { + tex.a = 0.05; + } + + float avg = (pixel.r + pixel.g + pixel.b) / 3.; + pixel = vec4(gold_color.rgb * avg + tex.rgb * tex.a, pixel.a); + + return dissolve_mask(pixel, texture_coords, uv); +} + +vec4 dissolve_mask(vec4 final_pixel, vec2 texture_coords, vec2 uv) +{ + if (dissolve < 0.001) { + return vec4(shadow ? vec3(0.,0.,0.) : final_pixel.xyz, shadow ? final_pixel.a*0.3: final_pixel.a); + } + + float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values + + float t = time * 10.0 + 2003.; + vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a); + vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a); + + vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324)); + vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532)); + vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000)); + + float field = (1.+ ( + cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) + + cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.; + vec2 borders = vec2(0.2, 0.8); + + float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14)) + - (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve) + - (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve) + - (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve) + - (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve); + + if (final_pixel.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) { + if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) { + final_pixel.rgba = burn_colour_1.rgba; + } else if (burn_colour_2.a > 0.01) { + final_pixel.rgba = burn_colour_2.rgba; + } + } + + return vec4(shadow ? vec3(0.,0.,0.) : final_pixel.xyz, res > adjusted_dissolve ? (shadow ? final_pixel.a*0.3: final_pixel.a) : .0); +} + +extern PRECISION vec2 mouse_screen_pos; +extern PRECISION float hovering; +extern PRECISION float screen_scale; + +#ifdef VERTEX +vec4 position( mat4 transform_projection, vec4 vertex_position ) +{ + if (hovering <= 0.){ + return transform_projection * vertex_position; + } + float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy); + vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale; + float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist)) + *hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist); + + return transform_projection * vertex_position + vec4(0,0,0,scale); +} +#endif \ No newline at end of file diff --git a/lovely/edition.toml b/lovely/edition.toml index 026d26b8..6d829185 100644 --- a/lovely/edition.toml +++ b/lovely/edition.toml @@ -410,6 +410,28 @@ payload = """ elseif self.edition.type == 'negative' and (self.ability.set == 'Enhanced' or self.ability.set == 'Default') then badges[#badges + 1] = 'negative_playing_card'""" match_indent = true + +[[patches]] +[patches.pattern] +target = "engine/sprite.lua" +pattern = "love.graphics.setShader( G.SHADERS[_shader or 'dissolve'], G.SHADERS[_shader or 'dissolve'])" +position = "before" +payload = ''' +local p_shader = SMODS.Shader.obj_table[_shader or 'dissolve'] +if p_shader and type(p_shader.send_vars) == "function" then + local sh = G.SHADERS[_shader or 'dissolve'] + local parent_card = self.role.major and self.role.major:is(Card) and self.role.major + local send_vars = p_shader.send_vars(self, parent_card) + + if type(send_vars) == "table" then + for key, value in pairs(send_vars) do + sh:send(key, value) + end + end +end +''' +match_indent = true + [[patches]] [patches.pattern] target = "functions/UI_definitions.lua"