diff --git a/assets/1x/seal.png b/assets/1x/seal.png index b08578f..3b129bb 100644 Binary files a/assets/1x/seal.png and b/assets/1x/seal.png differ diff --git a/assets/1x/spectral.png b/assets/1x/spectral.png index 5f17565..74d2c48 100644 Binary files a/assets/1x/spectral.png and b/assets/1x/spectral.png differ diff --git a/assets/2x/back.png b/assets/2x/back.png index 2c10276..d43f9c3 100644 Binary files a/assets/2x/back.png and b/assets/2x/back.png differ diff --git a/assets/2x/blind.png b/assets/2x/blind.png index d2aca35..caabb5e 100644 Binary files a/assets/2x/blind.png and b/assets/2x/blind.png differ diff --git a/assets/2x/charm.png b/assets/2x/charm.png index 159bafb..75b1980 100644 Binary files a/assets/2x/charm.png and b/assets/2x/charm.png differ diff --git a/assets/2x/icon.png b/assets/2x/icon.png index 96371ff..1e41869 100644 Binary files a/assets/2x/icon.png and b/assets/2x/icon.png differ diff --git a/assets/2x/joker.png b/assets/2x/joker.png index a19e469..bc70e89 100644 Binary files a/assets/2x/joker.png and b/assets/2x/joker.png differ diff --git a/assets/2x/seal.png b/assets/2x/seal.png index 07b69b2..3eb0e7c 100644 Binary files a/assets/2x/seal.png and b/assets/2x/seal.png differ diff --git a/assets/2x/spectral.png b/assets/2x/spectral.png index 1a07570..e5be358 100644 Binary files a/assets/2x/spectral.png and b/assets/2x/spectral.png differ diff --git a/assets/shaders/frozen.fs b/assets/shaders/frozen.fs new file mode 100644 index 0000000..5a3eeea --- /dev/null +++ b/assets/shaders/frozen.fs @@ -0,0 +1,199 @@ +#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH) + #define MY_HIGHP_OR_MEDIUMP highp +#else + #define MY_HIGHP_OR_MEDIUMP mediump +#endif + +extern MY_HIGHP_OR_MEDIUMP vec2 frozen; +extern MY_HIGHP_OR_MEDIUMP number dissolve; +extern MY_HIGHP_OR_MEDIUMP number time; +extern MY_HIGHP_OR_MEDIUMP vec4 texture_details; +extern MY_HIGHP_OR_MEDIUMP vec2 image_details; +extern bool shadow; +extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1; +extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2; + +vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv) +{ + if (dissolve < 0.001) { + return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a); + } + + float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; + + 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 (tex.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) { + tex.rgba = burn_colour_1.rgba; + } else if (burn_colour_2.a > 0.01) { + tex.rgba = burn_colour_2.rgba; + } + } + + return vec4(shadow ? vec3(0.,6.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0); +} + +#define DEPTH 21 +#define PHI ((1. + sqrt(5.)) / 2.) +#define PI 3.141592653589 +#define S(M, O) (0.5 + 0.5 * sin(O + M * pos.x)) / 6. +#define C(M, O) (0.5 + 0.5 * cos(O + M * pos.y)) / 6. + +vec2 rotate(vec2 v, float a) +{ + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +float cr(vec2 a, vec2 b) +{ + return a.x * b.y - b.x * a.y; +} + +float cr(vec2 a, vec2 b, vec2 c) +{ + return cr(b - a, c - b); +} + +vec4 effect(vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords) +{ + vec4 tex = Texel(texture, texture_coords); + vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba; + + if (uv.x > uv.x * 2.) { + uv = frozen; + } + + // Slightly adapted from 'snowflake lattice' by pali6. + // https://www.shadertoy.com/view/7sG3zd + // https://www.shadertoy.com/user/pali6 + float iTime = 0.0; + vec2 pos = (((texture_coords)*(image_details)) - texture_details.xx*texture_details.ba)/texture_details.ba; + + float scale = 72. + 18. * sin(iTime / 10.) + 20. * sin(4. + iTime / 15.) + 30. * sin(iTime / 6.); + vec2 camPos = vec2(0.5, 0.5); + float SF = 1. / pow(scale, 0.3); + camPos += vec2(0.02 * sin(iTime / 15.), -0.07 * cos(iTime / 19.)) * SF; + camPos += vec2(-0.1 * sin(3. + iTime / 37.), -0.1 * cos(4. + iTime / 27.)) * SF; + camPos += vec2(0.07 * sin(9. + iTime / 47.), -0.08 * cos(20. + iTime / 31.)) * SF; + camPos += vec2(0.04 * sin(19. + iTime / 7.), 0.06 * cos(25. + iTime / 5.)) * SF; + pos -= vec2(0.5, 0.5); + pos = rotate(pos, iTime / 30. + PI * cos(iTime / 37.) + 2. * PI * sin(iTime / 87. + 10.)); + pos /= scale; + pos += camPos; + vec2 ota = vec2(-1., 0.); + vec2 otb = vec2(2., 0.); + vec2 ta = ota; + vec2 tb = otb; + vec2 tc; + bool gn = true; + float fl = -1.; + float f = 0.; + float nDark = 0.; + + for (int i = 0; i < DEPTH; i++) + { + vec2 next; + + if (gn) + tc = ta + rotate(tb - ta, fl * PI / 5.) / PHI; + else + tc = ta + rotate(tb - ta, fl * 2. * PI / 5.) * PHI; + + if(!(gn && min(length(pos - ta), length(pos - tb)) > length(ta - tb) / 3. || !gn && length(pos - tb) <= length(tc - tb) * PHI / 3.5)) + { + nDark += 1.; + } + + if (gn) + { + next = tb + (ta - tb) / PHI; + + if (abs(cr(next, tc, pos)) * float(i + 1) < 0.000001) + f = 1.; + + if (cr(next, tc, pos) * cr(next, tc, tb) > 0.) + { + gn = false; + ta = next; + tb = tc; + fl *= -1.; + i--; + } + else + { + gn = true; + tb = ta; + ta = tc; + } + } + else + { + next = tc + (tb - tc) / PHI; + + if (abs(cr(ta, next, pos)) * float(i + 1) < 0.000001) + f = 1.; + + if (cr(ta, next, pos) * cr(ta, next, tb) > 0.) + { + gn = false; + ta = tb; + tb = next; + } + else + { + gn = true; + tb = ta; + ta = tc; + } + } + } + + colour = (nDark / float(DEPTH)) * vec4( + 0.1 + S(30., 10.) + C(10., 11.) + S(50., 16.) + S(99., 15.) + S(1., 42.) + C(1.5, 73.), + 0.1 + C(20., 10.) + S(70., 12.) + C(23., 18.) + C(67., 119.) + S(1.2, 49.) + C(0.9, 79.), + 0.15 + S(70., 10.5) + C(31., 17.) + S(55., 135.) + C(123., 15.) + S(0.7, 13.) + C(0.11, 31.), + 1. + ) * 1.5; + + return dissolve_mask(tex*colour, texture_coords, uv); +} + +extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos; +extern MY_HIGHP_OR_MEDIUMP float hovering; +extern MY_HIGHP_OR_MEDIUMP 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/assets/sounds/frozen.ogg b/assets/sounds/frozen.ogg new file mode 100644 index 0000000..a66a71d Binary files /dev/null and b/assets/sounds/frozen.ogg differ diff --git a/localization/en-us.lua b/localization/en-us.lua index 900dfeb..de7117b 100644 --- a/localization/en-us.lua +++ b/localization/en-us.lua @@ -109,6 +109,12 @@ return { }, }, }, + Edition = { + e_Roland_frozen = { + name = "Frozen", + text = {"Values on", "this card do", "not change"}, + }, + }, Enhanced = { m_wild = { name = "Wild Card", @@ -337,6 +343,14 @@ return { "cards in {C:hands}hand", }, }, + c_Roland_coolheaded = { + name = "Coolheaded", + text = { + "Add {C:dark_edition}Frozen {}to {C:attention}#1#", + "selected {C:attention}playing", + "{C:attention}card {}or {C:attention}Joker", + }, + }, c_Roland_mirror = { name = "Mirror", text = { @@ -386,6 +400,7 @@ return { b_Roland_of = " of ", }, labels = { + Roland_frozen = "Frozen", roland_glass_seal = "Glass Seal", }, v_text = { diff --git a/src/edition.lua b/src/edition.lua new file mode 100644 index 0000000..bfb247d --- /dev/null +++ b/src/edition.lua @@ -0,0 +1,200 @@ +local f, q = unpack(... or require "lib.shared") + +SMODS.Shader { + key = "frozen", + path = "frozen.fs", +} + +SMODS.Sound { + key = "frozen", + path = "frozen.ogg", +} + +local needs_chip_mult_override = { + Bull = true, + Estate = true, + Erosion = true, + Misprint = true, + TierList = true, + Bootstraps = true, + ["Blue Joker"] = true, + ["Abstract Joker"] = true, + ["Fortune Teller"] = true, +} + +local current_round_overrides = { + Castle = "castle_card", + Farmer = "farmer_card", + Tuxedo = "tuxedo_card", + ["Go Fish"] = "fish_rank", + ["The Idol"] = "idol_card", + ["Mail-In Rebate"] = "mail_card", + Obelisk = "most_played_poker_hand", + ["Ancient Joker"] = "ancient_card", + Wherewolf = "Bakery_Wherewolf_card", +} + +SMODS.current_mod.frozen_chip_mult = needs_chip_mult_override +SMODS.current_mod.frozen_current_round = current_round_overrides + +local function freeze(card) + ---@generic T + ---@param x T + ---@return T + local function copy(x) + return type(x) == "table" and f(x):map(copy):where(function(v, k) + return k ~= "Roland_frozen_proxy" and (k ~= "nine_tally" or v ~= 0) + end):table() or x + end + + card.Roland_frozen_ability = card.Roland_frozen_ability or copy(card.ability) + card.ability = card.Roland_frozen_ability and copy(card.Roland_frozen_ability) or card.ability + local ret = card.Roland_frozen_ability + local ability = card.ability + + if type(ability) ~= "table" or not ability.name or not G.GAME.current_round then + return ret + end + + local key = current_round_overrides[ability.name] + + if not key then + return ret + end + + card.Roland_frozen_current_round = card.Roland_frozen_current_round or copy(G.GAME.current_round[key]) + G.GAME.current_round[key] = copy(card.Roland_frozen_current_round) + return ret +end + +SMODS.Edition { + key = "frozen", + shader = "frozen", + pools = {Joker = true}, + sound = {sound = "Roland_frozen", per = 1.6, vol = 0.8}, + weight = 8, + extra_cost = 4, + in_shop = true, + apply_to_float = false, + calculate = function(_, card) + freeze(card) + end, + on_remove = function(card) + freeze(card) + card.Roland_frozen_ability = nil + end, + get_weight = function(self) + return G.GAME.edition_rate * self.weight + end, +} + +local orig_calculate_joker = Card.calculate_joker + +---@param self Card|{Roland_frozen_ability?: table} +function Card:calculate_joker(context, ...) + local ret = {orig_calculate_joker(self, context, ...)} + + if not self.edition or not self.edition.Roland_frozen or not ret[1] then + return unpack(ret) + end + + local ability = freeze(self) + + if not needs_chip_mult_override[ability.name] then + return unpack(ret) + end + + ability.Roland_frozen_mult_mod = ability.Roland_frozen_mult_mod or ret[1].mult_mod + ability.Roland_frozen_chip_mod = ability.Roland_frozen_chip_mod or ret[1].chip_mod + ability.Roland_frozen_xmult = ability.Roland_frozen_xmult or ret[1].xmult + + return { + chip_mod = ability.Roland_frozen_chip_mod, + mult_mod = ability.Roland_frozen_mult_mod, + xmult = ability.Roland_frozen_xmult, + message = localize { + type = "variable", + key = "a_mult", + vars = { + ability.Roland_frozen_mult_mod or + ability.Roland_frozen_chip_mod or + ability.Roland.frozen_xmult, + (ability.Roland_frozen_mult_mod and ability.Roland_frozen_chip_mod) and + ability.Roland_frozen_chip_mod or nil, + }, + }, + } +end + +local orig_calculate_dollar_bonus = Card.calculate_dollar_bonus + +---@param self Card|{Roland_frozen_ability?: table} +function Card:calculate_dollar_bonus(...) + if not self.edition or not self.edition.Roland_frozen then + return orig_calculate_dollar_bonus(self, ...) + end + + local ability = freeze(self) + + if ability.name == "Satellite" then + ability.Roland_frozen_planets_used = ability.Roland_frozen_planets_used or + orig_calculate_dollar_bonus(self, ...) + + return self.ability.extra * ability.Roland_frozen_planets_used + end + + return orig_calculate_dollar_bonus(self, ...) +end + +q(function() + local proxy = G.P_CENTERS.j_Bakery_Proxy + + if not proxy then + return + end + + local function get_proxied_joker(card) + if not G.jokers or not G.jokers.cards then + return + end + + local ability = freeze(card) + + ---@param v Card + local function eq(v) + return v.unique_val == ability.Roland_frozen_proxy + end + + ability.Roland_frozen_proxy = (Bakery_API.get_proxied_joker() or card).unique_val + return f(G.jokers.cards):any(eq) ---@type Card? + end + + local orig_calculate = proxy.calculate + + ---@param card Card|{ Roland_frozen_ability: table } + function proxy:calculate(card, context, ...) + if not card or not card.edition or not card.edition.Roland_frozen then + return orig_calculate(self, card, context, ...) + end + + return SMODS.blueprint_effect(card, get_proxied_joker(card), context) + end + + local orig_loc_vars = proxy.loc_vars + + function proxy:loc_vars(info_queue, card, ...) + if not card or not card.edition or not card.edition.Roland_frozen then + return orig_loc_vars(self, info_queue, card, ...) + end + + local other = get_proxied_joker(card) + + local var = (other and other ~= card) and localize { + type = "name_text", + set = other.config.center.set, + key = other.config.center.key, + } or localize "k_none" + + return {vars = {var}} + end +end) diff --git a/src/joker.lua b/src/joker.lua index 31000c3..0d181fb 100644 --- a/src/joker.lua +++ b/src/joker.lua @@ -585,11 +585,21 @@ joker { eternal_compat = true, blueprint_compat = true, perishable_compat = true, - loc_vars = function(self, _, _) - return {vars = {self.xmult()}} + loc_vars = function(self, card, _) + return {vars = {self:xmult_frozen(card)}} end, calculate = function(self, card, context) - return (context.joker_main or context.forcetrigger) and {card = card, xmult = self.xmult()} or nil + return (context.joker_main or context.forcetrigger) and {card = card, xmult = self:xmult_frozen(card)} or nil + end, + xmult_frozen = function(self, card) + if not card.edition or not card.edition.Roland_frozen then + return self.xmult() + end + + local ability = card.Roland_frozen_ability or {} + card.Roland_frozen_ability = ability + ability.Roland_frozen_xmult = ability.Roland_frozen_xmult or self.xmult() + return ability.Roland_frozen_xmult end, xmult = function() local function mult(id) diff --git a/src/lib/funky.lua b/src/lib/funky.lua index cbb6d06..1967995 100644 --- a/src/lib/funky.lua +++ b/src/lib/funky.lua @@ -409,7 +409,7 @@ end ---@param func F|fun(v: V, k: K): boolean ---@return boolean|V ---@nodiscard ----@overload fun(self: F|{ [K]: V }, func: string): boolean +---@overload fun(self: F|{ [K]: V }, func: string): boolean|V function f:any(func) func = type(func) == "string" and f.index(func) or func @@ -427,7 +427,7 @@ end ---@param func F|fun(v: V, k: K): boolean ---@return boolean|V ---@nodiscard ----@overload fun(self: F|{ [K]: V }, func: string): boolean +---@overload fun(self: F|{ [K]: V }, func: string): boolean|V function f:all(func) func = type(func) == "string" and f.index(func) or func diff --git a/src/main.lua b/src/main.lua index 8e867c8..d407e22 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,7 +1,7 @@ local qol = assert(SMODS.load_file "src/lib/shared.lua")() or require "lib.shared" local f, q = unpack(qol) -f {"challenge", "spectral", "tweaks", "blind", "charm", "joker", "back", "seal"}:each(function(v) +f {"challenge", "spectral", "edition", "tweaks", "blind", "charm", "joker", "back", "seal"}:each(function(v) assert(SMODS.load_file("src/" .. v .. ".lua"))(qol) end) diff --git a/src/spectral.lua b/src/spectral.lua index 115e2d1..951227d 100644 --- a/src/spectral.lua +++ b/src/spectral.lua @@ -69,6 +69,29 @@ spectral { end, } +spectral { + key = "coolheaded", + config = {extra = {amount = 1}}, + loc_vars = function(_, info_queue, card) + table.insert(info_queue, G.P_CENTERS.e_Roland_frozen) + return {vars = {card.ability.extra.amount}} + end, + can_use = function(_, card) + return u() and card.ability.extra.amount == #G.jokers.highlighted + #Bakery_API.get_highlighted() + end, + use = function() + f(G.jokers.highlighted):concat(Bakery_API.get_highlighted()):each(function(v) + q { + delay = 0.1, + func = function() + v:set_edition {Roland_frozen = true} + v:juice_up(1, 1) + end, + } + end) + end, +} + spectral { key = "dual", pronouns = "they_them",