diff --git a/atlas/meson.build b/atlas/meson.build index 87e165a1..416ec523 100644 --- a/atlas/meson.build +++ b/atlas/meson.build @@ -8,6 +8,7 @@ atlases = [ 'common_ui', 'huge', 'portraits', + 'reimu', ] atlas_common_args = [ diff --git a/atlas/overrides/part/fantasyseal_impact.spr b/atlas/overrides/part/fantasyseal_impact.spr new file mode 100644 index 00000000..5dae7c56 --- /dev/null +++ b/atlas/overrides/part/fantasyseal_impact.spr @@ -0,0 +1,3 @@ + +w = 128 +h = 128 diff --git a/atlas/overrides/part/ofuda_glow.spr b/atlas/overrides/part/ofuda_glow.spr new file mode 100644 index 00000000..6cf09873 --- /dev/null +++ b/atlas/overrides/part/ofuda_glow.spr @@ -0,0 +1,4 @@ + +w = 20 +h = 30 + diff --git a/atlas/overrides/player/marisa.framegroup.spr b/atlas/overrides/player/marisa.framegroup.spr index 742b07b1..7cfecc5b 100644 --- a/atlas/overrides/player/marisa.framegroup.spr +++ b/atlas/overrides/player/marisa.framegroup.spr @@ -1,6 +1,3 @@ -#w = 76 -#h = 95 - w = 48 h = 60 diff --git a/atlas/overrides/player/reimu.framegroup.spr b/atlas/overrides/player/reimu.framegroup.spr new file mode 100644 index 00000000..8d5326f3 --- /dev/null +++ b/atlas/overrides/player/reimu.framegroup.spr @@ -0,0 +1,3 @@ + +w = 54 +h = 66 diff --git a/atlas/overrides/proj/hakurei_seal.spr b/atlas/overrides/proj/hakurei_seal.spr new file mode 100644 index 00000000..44d84620 --- /dev/null +++ b/atlas/overrides/proj/hakurei_seal.spr @@ -0,0 +1,4 @@ + +w = 16 +h = 39 + diff --git a/atlas/overrides/proj/needle.spr b/atlas/overrides/proj/needle.spr new file mode 100644 index 00000000..54e4ee6b --- /dev/null +++ b/atlas/overrides/proj/needle.spr @@ -0,0 +1,4 @@ + +w = 14 +h = 72 + diff --git a/atlas/overrides/proj/needle2.spr b/atlas/overrides/proj/needle2.spr new file mode 100644 index 00000000..270ebb54 --- /dev/null +++ b/atlas/overrides/proj/needle2.spr @@ -0,0 +1,3 @@ + +w = 7 +h = 65 diff --git a/atlas/overrides/proj/ofuda.spr b/atlas/overrides/proj/ofuda.spr new file mode 100644 index 00000000..26e39930 --- /dev/null +++ b/atlas/overrides/proj/ofuda.spr @@ -0,0 +1,4 @@ + +w = 16 +h = 26 + diff --git a/atlas/overrides/yinyang.spr b/atlas/overrides/yinyang.spr new file mode 100644 index 00000000..b70a2dc1 --- /dev/null +++ b/atlas/overrides/yinyang.spr @@ -0,0 +1,3 @@ + +w = 24 +h = 24 diff --git a/atlas/reimu/part/fantasyseal_impact.png b/atlas/reimu/part/fantasyseal_impact.png new file mode 100644 index 00000000..fe8eda2d Binary files /dev/null and b/atlas/reimu/part/fantasyseal_impact.png differ diff --git a/atlas/reimu/part/ofuda_glow.png b/atlas/reimu/part/ofuda_glow.png new file mode 100644 index 00000000..9a344eeb Binary files /dev/null and b/atlas/reimu/part/ofuda_glow.png differ diff --git a/atlas/reimu/player/reimu.frame0000.png b/atlas/reimu/player/reimu.frame0000.png new file mode 100644 index 00000000..8d997cae Binary files /dev/null and b/atlas/reimu/player/reimu.frame0000.png differ diff --git a/atlas/reimu/player/reimu.frame0001.png b/atlas/reimu/player/reimu.frame0001.png new file mode 100644 index 00000000..68e01263 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0001.png differ diff --git a/atlas/reimu/player/reimu.frame0002.png b/atlas/reimu/player/reimu.frame0002.png new file mode 100644 index 00000000..0c2b45c0 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0002.png differ diff --git a/atlas/reimu/player/reimu.frame0003.png b/atlas/reimu/player/reimu.frame0003.png new file mode 100644 index 00000000..535b34c5 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0003.png differ diff --git a/atlas/reimu/player/reimu.frame0004.png b/atlas/reimu/player/reimu.frame0004.png new file mode 100644 index 00000000..b7751684 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0004.png differ diff --git a/atlas/reimu/player/reimu.frame0005.png b/atlas/reimu/player/reimu.frame0005.png new file mode 100644 index 00000000..8f6a63c9 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0005.png differ diff --git a/atlas/reimu/player/reimu.frame0006.png b/atlas/reimu/player/reimu.frame0006.png new file mode 100644 index 00000000..43ec030c Binary files /dev/null and b/atlas/reimu/player/reimu.frame0006.png differ diff --git a/atlas/reimu/player/reimu.frame0007.png b/atlas/reimu/player/reimu.frame0007.png new file mode 100644 index 00000000..7803f34b Binary files /dev/null and b/atlas/reimu/player/reimu.frame0007.png differ diff --git a/atlas/reimu/player/reimu.frame0008.png b/atlas/reimu/player/reimu.frame0008.png new file mode 100644 index 00000000..6d875f06 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0008.png differ diff --git a/atlas/reimu/player/reimu.frame0009.png b/atlas/reimu/player/reimu.frame0009.png new file mode 100644 index 00000000..dc8cfad2 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0009.png differ diff --git a/atlas/reimu/player/reimu.frame0010.png b/atlas/reimu/player/reimu.frame0010.png new file mode 100644 index 00000000..18b35f5f Binary files /dev/null and b/atlas/reimu/player/reimu.frame0010.png differ diff --git a/atlas/reimu/player/reimu.frame0011.png b/atlas/reimu/player/reimu.frame0011.png new file mode 100644 index 00000000..2ded9fb0 Binary files /dev/null and b/atlas/reimu/player/reimu.frame0011.png differ diff --git a/atlas/reimu/proj/glowball.png b/atlas/reimu/proj/glowball.png new file mode 100644 index 00000000..940329a4 Binary files /dev/null and b/atlas/reimu/proj/glowball.png differ diff --git a/atlas/reimu/proj/hakurei_seal.png b/atlas/reimu/proj/hakurei_seal.png new file mode 100644 index 00000000..5a61e4e1 Binary files /dev/null and b/atlas/reimu/proj/hakurei_seal.png differ diff --git a/atlas/reimu/proj/needle.png b/atlas/reimu/proj/needle.png new file mode 100644 index 00000000..cb8336d8 Binary files /dev/null and b/atlas/reimu/proj/needle.png differ diff --git a/atlas/reimu/proj/needle2.png b/atlas/reimu/proj/needle2.png new file mode 100644 index 00000000..c4e5c346 Binary files /dev/null and b/atlas/reimu/proj/needle2.png differ diff --git a/atlas/reimu/proj/ofuda.png b/atlas/reimu/proj/ofuda.png new file mode 100644 index 00000000..82e5e0e3 Binary files /dev/null and b/atlas/reimu/proj/ofuda.png differ diff --git a/atlas/reimu/yinyang.png b/atlas/reimu/yinyang.png new file mode 100644 index 00000000..26d52c18 Binary files /dev/null and b/atlas/reimu/yinyang.png differ diff --git a/doc/STORY.txt b/doc/STORY.txt index a518c7cf..ea246899 100644 --- a/doc/STORY.txt +++ b/doc/STORY.txt @@ -58,6 +58,67 @@ ================================================================================ + ○ Hakurei Reimu + + For Reimu, today should have been a peaceful day. It was spring, so early + mornings were still chilly and flowers were just beginning to burst into + bright blooms around the shrine. But unfortunately, Reimu did not get to + enjoy her luxury of sleeping in as she was rudely awoken by insistent + prodding into her cheek. + + “Mmmh?” + + “Wake up, Reimu. There is a serious incident unraveling itself as we speak!” + + Although annoyed at being woken up in her own shrine, she nevertheless + cracked an unamused eye open to stare at her beauty sleep’s disturbance. The + voice was familiar, and so was the tall, unsettling woman peering back at + her from her own personal crack of distorted reality. + + “What now, Yukari? Can’t you see I am doing the important duty of self-care + as a shrine maiden?” + + “You should think about switching duties then,” Yukari quipped back, + reaching over to outright pinch Reimu’s cheek. “The barrier is being + distorted by someone with a great deal of unexpected power, and it is of a + sort that I cannot manipulate.” + + “So, you can’t fix it, then?” + + “Of course not. It’s too… logical. Even though I created this barrier with + mathematics in mind, my parameters were fictional. It’s a power grounded in + reality, and it seems to have outright torn open a hole in the barrier up on + Yōkai Mountain, creating a tunnel to somewhere else entirely.” + + Now fully awake, Reimu sat up and batted Yukari’s intrusive hands away. But + even though she sincerely wanted to ignore the yōkai sage that plagued her + life as a constant irritation, she too could feel something off in the air. + The Hakurei Shrine sat at the very edge of Gensōkyō, and it held the barrier + steady as a medium between both sides of the boundary. Items from the + Outside World commonly ended up strewn around the grounds, and so did the + occasional high school girl as well. Despite the unusual atmosphere of the + shrine, Reimu immediately knew just by glancing around that it was not + normal for her shrine’s walls to look patchy and moldy in places, or for the + floor to have random cracks and stains not present from everyday use. + + “You can see it, can you not? The Hakurei Shrine from the Outside World is + merging with ours as our fantasy disappears. Soon it will replace yours + entirely, and you will live in the broken down version of your beloved + home.” + + That was all it took to get Reimu in action as she slipped into her uniform + and grabbed her Yin-Yang Orbs and purification rod. Although the weakening + of the Hakurei Barrier was worrying enough on its own, there was no way she + could allow her shrine to turn into the run-down mess it was on the other + side of the border. Whoever was behind this needed to stop, for her sake and + everyone else’s. And if nothing else, the shrine maiden was loath to become + homeless. That simply would not do. + + “Yukari, you’ll take me there, won’t you?” + + Unfortunately for the shrine maiden, she was met with silence. It seemed as + though she would have to take the long route to Yōkai Mountain. + ○ Kirisame Marisa Early one spring day, just after dawn had passed, Marisa was finding a diff --git a/resources/gfx/atlas_reimu_0.png b/resources/gfx/atlas_reimu_0.png new file mode 100644 index 00000000..567c08c5 Binary files /dev/null and b/resources/gfx/atlas_reimu_0.png differ diff --git a/resources/gfx/dialog/reimu.png b/resources/gfx/dialog/reimu.png new file mode 100644 index 00000000..14de20d3 Binary files /dev/null and b/resources/gfx/dialog/reimu.png differ diff --git a/resources/gfx/gaplight.png b/resources/gfx/gaplight.png new file mode 100644 index 00000000..616c8d51 Binary files /dev/null and b/resources/gfx/gaplight.png differ diff --git a/resources/gfx/gaplight.tex b/resources/gfx/gaplight.tex new file mode 100644 index 00000000..aa5ceb47 --- /dev/null +++ b/resources/gfx/gaplight.tex @@ -0,0 +1,2 @@ +wrap_t = mirror +wrap_s = clamp diff --git a/resources/gfx/part/fantasyseal_impact.spr b/resources/gfx/part/fantasyseal_impact.spr new file mode 100644 index 00000000..c48c2ae6 --- /dev/null +++ b/resources/gfx/part/fantasyseal_impact.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1 +region_y = 1 +region_w = 256 +region_h = 256 + +# -- Pasted from the override file -- + +w = 128 +h = 128 diff --git a/resources/gfx/part/ofuda_glow.spr b/resources/gfx/part/ofuda_glow.spr new file mode 100644 index 00000000..24c9f8a3 --- /dev/null +++ b/resources/gfx/part/ofuda_glow.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1731 +region_y = 1 +region_w = 80 +region_h = 120 + +# -- Pasted from the override file -- + +w = 20 +h = 30 diff --git a/resources/gfx/player/reimu.ani b/resources/gfx/player/reimu.ani new file mode 100644 index 00000000..a4042018 --- /dev/null +++ b/resources/gfx/player/reimu.ani @@ -0,0 +1,12 @@ +@sprite_count = 11 + +main = d10 4 5 6 7 +left = m d10 0 1 2 3 +right = d10 0 1 2 3 + +main2left = m d2 8 9 10 +main2right = d2 8 9 10 +left2main = m d2 10 9 +left2right = m d2 10 9 m 8 9 10 +right2main = d2 10 9 +right2left = d2 10 9 m 8 9 10 diff --git a/resources/gfx/player/reimu.frame0000.spr b/resources/gfx/player/reimu.frame0000.spr new file mode 100644 index 00000000..2dadbb4c --- /dev/null +++ b/resources/gfx/player/reimu.frame0000.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 411 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0001.spr b/resources/gfx/player/reimu.frame0001.spr new file mode 100644 index 00000000..ba7c8f66 --- /dev/null +++ b/resources/gfx/player/reimu.frame0001.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 521 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0002.spr b/resources/gfx/player/reimu.frame0002.spr new file mode 100644 index 00000000..1fed8982 --- /dev/null +++ b/resources/gfx/player/reimu.frame0002.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 631 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0003.spr b/resources/gfx/player/reimu.frame0003.spr new file mode 100644 index 00000000..b29a3f9f --- /dev/null +++ b/resources/gfx/player/reimu.frame0003.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 741 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0004.spr b/resources/gfx/player/reimu.frame0004.spr new file mode 100644 index 00000000..8529e8ec --- /dev/null +++ b/resources/gfx/player/reimu.frame0004.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 851 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0005.spr b/resources/gfx/player/reimu.frame0005.spr new file mode 100644 index 00000000..f215eb64 --- /dev/null +++ b/resources/gfx/player/reimu.frame0005.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 961 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0006.spr b/resources/gfx/player/reimu.frame0006.spr new file mode 100644 index 00000000..448a27bd --- /dev/null +++ b/resources/gfx/player/reimu.frame0006.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1071 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0007.spr b/resources/gfx/player/reimu.frame0007.spr new file mode 100644 index 00000000..43ab82fc --- /dev/null +++ b/resources/gfx/player/reimu.frame0007.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1181 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0008.spr b/resources/gfx/player/reimu.frame0008.spr new file mode 100644 index 00000000..fcce7e6d --- /dev/null +++ b/resources/gfx/player/reimu.frame0008.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1291 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0009.spr b/resources/gfx/player/reimu.frame0009.spr new file mode 100644 index 00000000..ca90cd35 --- /dev/null +++ b/resources/gfx/player/reimu.frame0009.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1401 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0010.spr b/resources/gfx/player/reimu.frame0010.spr new file mode 100644 index 00000000..5891fe52 --- /dev/null +++ b/resources/gfx/player/reimu.frame0010.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1511 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/player/reimu.frame0011.spr b/resources/gfx/player/reimu.frame0011.spr new file mode 100644 index 00000000..f80ccee3 --- /dev/null +++ b/resources/gfx/player/reimu.frame0011.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1621 +region_y = 1 +region_w = 108 +region_h = 132 + +# -- Pasted from the override file -- + +w = 54 +h = 66 diff --git a/resources/gfx/proj/glowball.spr b/resources/gfx/proj/glowball.spr new file mode 100644 index 00000000..1947e531 --- /dev/null +++ b/resources/gfx/proj/glowball.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 259 +region_y = 1 +region_w = 150 +region_h = 150 diff --git a/resources/gfx/proj/hakurei_seal.spr b/resources/gfx/proj/hakurei_seal.spr new file mode 100644 index 00000000..ab7fd0dc --- /dev/null +++ b/resources/gfx/proj/hakurei_seal.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1813 +region_y = 1 +region_w = 64 +region_h = 156 + +# -- Pasted from the override file -- + +w = 16 +h = 39 diff --git a/resources/gfx/proj/needle.spr b/resources/gfx/proj/needle.spr new file mode 100644 index 00000000..883745b5 --- /dev/null +++ b/resources/gfx/proj/needle.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 2011 +region_y = 1 +region_w = 28 +region_h = 144 + +# -- Pasted from the override file -- + +w = 14 +h = 72 diff --git a/resources/gfx/proj/needle2.spr b/resources/gfx/proj/needle2.spr new file mode 100644 index 00000000..8b7f6f0c --- /dev/null +++ b/resources/gfx/proj/needle2.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 2041 +region_y = 1 +region_w = 14 +region_h = 130 + +# -- Pasted from the override file -- + +w = 7 +h = 65 diff --git a/resources/gfx/proj/ofuda.spr b/resources/gfx/proj/ofuda.spr new file mode 100644 index 00000000..861c0240 --- /dev/null +++ b/resources/gfx/proj/ofuda.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1879 +region_y = 1 +region_w = 64 +region_h = 104 + +# -- Pasted from the override file -- + +w = 16 +h = 26 diff --git a/resources/gfx/runes.png b/resources/gfx/runes.png new file mode 100644 index 00000000..d602a606 Binary files /dev/null and b/resources/gfx/runes.png differ diff --git a/resources/gfx/yinyang.spr b/resources/gfx/yinyang.spr new file mode 100644 index 00000000..312e029c --- /dev/null +++ b/resources/gfx/yinyang.spr @@ -0,0 +1,12 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_reimu_0 +region_x = 1945 +region_y = 1 +region_w = 64 +region_h = 64 + +# -- Pasted from the override file -- + +w = 24 +h = 24 diff --git a/resources/sfx/bomb_reimu_a.ogg b/resources/sfx/bomb_reimu_a.ogg new file mode 100644 index 00000000..d4ef9787 Binary files /dev/null and b/resources/sfx/bomb_reimu_a.ogg differ diff --git a/resources/sfx/volumes.conf b/resources/sfx/volumes.conf index a6b6a028..c4450788 100644 --- a/resources/sfx/volumes.conf +++ b/resources/sfx/volumes.conf @@ -33,3 +33,4 @@ warp = 80 damage_feedback = 120 hit0 = 120 hit1 = 120 +bomb_reimu_a = 128 diff --git a/resources/shader/lib/util.glslh b/resources/shader/lib/util.glslh index 1ddfec9e..c0ba3983 100644 --- a/resources/shader/lib/util.glslh +++ b/resources/shader/lib/util.glslh @@ -48,4 +48,33 @@ vec4 color_mul_alpha(vec4 c) { return vec4(c.rgb * c.a, c.a); } +mat2 rot(float a) { + return mat2(cos(a), -sin(a), sin(a), cos(a)); +} + +float line_segment(vec2 p, vec2 a, vec2 b, float thickness) { + vec2 pa = p - a; + vec2 ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return smoothstep(thickness, 0.0, length(pa - ba*h)); +} + +// Taken from http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + #endif diff --git a/resources/shader/meson.build b/resources/shader/meson.build index 5ba31c20..683b3cac 100644 --- a/resources/shader/meson.build +++ b/resources/shader/meson.build @@ -16,11 +16,15 @@ glsl_files = files( 'ingame_menu.frag.glsl', 'laser_generic.vert.glsl', 'marisa_hakkero.frag.glsl', + 'marisa_hakkero.vert.glsl', 'marisa_laser.frag.glsl', 'maristar_bombbg.frag.glsl', 'masterspark.frag.glsl', 'max_to_alpha.frag.glsl', 'player_death.frag.glsl', + 'reimu_bomb_bg.frag.glsl', + 'reimu_gap.frag.glsl', + 'reimu_gap_light.frag.glsl', 'spellcard_intro.frag.glsl', 'spellcard_outro.frag.glsl', 'spellcard_walloftext.frag.glsl', @@ -36,6 +40,7 @@ glsl_files = files( 'sprite_negative.frag.glsl', 'sprite_silhouette.frag.glsl', 'sprite_silhouette.vert.glsl', + 'sprite_yinyang.frag.glsl', 'sprite_youmu_charged_shot.frag.glsl', 'sprite_youmu_charged_shot.vert.glsl', 'sprite_youmu_myon_shot.frag.glsl', diff --git a/resources/shader/reimu_bomb_bg.frag.glsl b/resources/shader/reimu_bomb_bg.frag.glsl new file mode 100644 index 00000000..1e02e5a0 --- /dev/null +++ b/resources/shader/reimu_bomb_bg.frag.glsl @@ -0,0 +1,122 @@ +#version 330 core + +#include "interface/standard.glslh" +#include "lib/util.glslh" +#include "lib/render_context.glslh" + +UNIFORM(1) vec2 aspect; +UNIFORM(2) float zoom; +UNIFORM(3) float time; +UNIFORM(4) sampler2D runes; + +float _split(float x) { + return sqrt(0.25 - pow(x - 0.5, 2)); +} + +vec2 runes_transform_uv(vec2 uv) { + const float segments = 32; + uv.y = mod(uv.y, 1.0); + uv.y += floor(uv.x); + uv.y /= segments; + return uv; +} + +float split(float x) { +/* + if(x <= 0.25) { + return 0.25; + } + + if(x >= 0.75) { + return 0.75; + } +*/ + + if(x < 0.5) { + x = 1.0 - _split(2 * x) - 0.5; + } else { + x = _split(2 * x - 1) + 0.5; + } + + return 0.5 * x + 0.25; +} + +float yinyang(vec2 uv, out float light_dist, out float dark_dist) { + float s = split(uv.x); + float v; + + v = smoothstep(0.0, 0.01, uv.y - s); + light_dist = length((uv - vec2(0.75, 0.5)) * 16.0); + v = mix(v, 1.0, smoothstep(1.0, 0.95, light_dist)); + dark_dist = length((uv - vec2(0.25, 0.5)) * 16.0); + v = mix(v, 0.0, smoothstep(1.0, 0.95, dark_dist)); + + return v; +} + +float yinyang_spin(vec2 uv, vec2 aspect, float a, out float light_dist, out float dark_dist) { + mat2 r = rot(a); + uv -= 0.5; + uv *= aspect; + uv *= r; + uv += 0.5; + return yinyang(uv, light_dist, dark_dist); +} + +void main(void) { + vec2 uv = texCoord; + float ld, dd; + + float yy = yinyang_spin(uv, aspect * zoom, -time, ld, dd); + + vec2 uv_r = uv; + vec2 uv_g = uv; + vec2 uv_b = uv; + + vec3 c = vec3(0); + float rfactor = sqrt(dd*dd + ld*ld) * 0.01 * yy; + float kek = (1 - 2 * yy); + + ld = sqrt(ld); + dd = sqrt(dd); + + mat2 m_r = rot(dd * 0.00005 * kek) / (1 + ld * -0.0006 * kek); + mat2 m_g = rot(dd * 0.00009 * kek) / (1 + ld * -0.0004 * kek); + mat2 m_b = rot(dd * 0.00012 * kek) / (1 + ld * -0.0002 * kek); + + const int samples = 24; + const float isamples = 1.0 / samples; + + for(int i = 0; i < samples; ++i) { + uv_r -= 0.5; + uv_r *= m_r; + uv_r += 0.5; + c.r += texture(tex, uv_r).r; + + uv_g -= 0.5; + uv_g *= m_g; + uv_g += 0.5; + c.g += texture(tex, uv_g).g; + + uv_b -= 0.5; + uv_b *= m_b; + uv_b += 0.5; + c.b += texture(tex, uv_b).b; + } + + fragColor = vec4(c * isamples, 1) * 0.9 * mix(r_color, vec4(r_color.a), 1); + + const float rune_lines = 24; + + vec2 runes_uv = (uv - 0.5) * rot(pi * 0.5) + 0.5; + float line = floor(runes_uv.y * rune_lines); + runes_uv.x += 0.931223 * line + time * 0.1 * cos(pi * line); + runes_uv.y *= -rune_lines; + runes_uv = runes_transform_uv(runes_uv); + + float rune_factor = texture(runes, runes_uv).r; + fragColor.rgb = pow(fragColor.rgb, vec3(1 + (1 - 2 * rune_factor) * 0.1)); + // fragColor = mix(fragColor, 0.9 * fragColor + vec4(0.1), rune_factor * r_color); + fragColor += r_color * rune_factor * 0.1; + fragColor.a *= (1 - rune_factor * 0.1); +} diff --git a/resources/shader/reimu_bomb_bg.prog b/resources/shader/reimu_bomb_bg.prog new file mode 100644 index 00000000..3ba97c0c --- /dev/null +++ b/resources/shader/reimu_bomb_bg.prog @@ -0,0 +1,2 @@ + +glsl_objects = standard.vert reimu_bomb_bg.frag diff --git a/resources/shader/reimu_gap.frag.glsl b/resources/shader/reimu_gap.frag.glsl new file mode 100644 index 00000000..f875da53 --- /dev/null +++ b/resources/shader/reimu_gap.frag.glsl @@ -0,0 +1,77 @@ +#version 330 core + +#include "lib/render_context.glslh" +#include "interface/standard.glslh" +#include "lib/util.glslh" + +#define NUM_GAPS 4 +// #define DRAW_LINKS + +UNIFORM(1) vec2 viewport; +UNIFORM(2) float time; +UNIFORM(3) vec2 gap_size; +UNIFORM(4) vec2 gaps[NUM_GAPS]; +UNIFORM(8) float gap_angles[NUM_GAPS]; +UNIFORM(12) int gap_links[NUM_GAPS]; + +void main(void) { + vec4 bg = texture(tex, texCoord); + fragColor = bg; + + vec2 frag_loc = texCoord * viewport; + frag_loc.y = viewport.y - frag_loc.y; + + float t = time * 6; + const float mag = 0.5; + const float pmag = 0.123; + frag_loc.x += mag * sin(t); + frag_loc.y += mag * sin(t * 1.22 - frag_loc.x * pmag); + frag_loc.x += mag * sin(t * -1.36 - frag_loc.y * pmag); + frag_loc.y += mag * sin(t * -1.29 - frag_loc.x * pmag); + frag_loc.x += mag * sin(t * 1.35 - frag_loc.y * pmag); + + const float h0 = 1; + const float h1 = h0 * 0.8; + const vec4 gap_color = vec4(0.75, 0, 0.4, 1); + + for(int i = 0; i < NUM_GAPS; ++i) { + vec2 gap = gaps[i]; + float gap_angle = gap_angles[i]; + int link = gap_links[i]; + + vec2 next_gap = gaps[link]; + float next_gap_angle = gap_angles[link]; + + mat2 gap_rot = rot(gap_angle); + float edge = length(gap_rot * (frag_loc - gap) / gap_size); + float gap_mask = smoothstep(h0, h1, edge); + + vec2 tc_inv = 1 - texCoord; + float _ = time * 0.2;//0.5 + 0.5 * sin(time); + + vec2 tc = vec2(texCoord.x, 1 - texCoord.y) * viewport; + tc -= gap.xy; + tc *= rot((next_gap_angle - gap_angle)); + tc += next_gap.xy; + tc /= viewport; + tc.y = 1 - tc.y; + + fragColor = mix(fragColor, mix(gap_color, texture(tex, tc), 1 - pow(edge, 3)), gap_mask); + } + + #ifdef DRAW_LINKS + for(int i = 0; i < NUM_GAPS; ++i) { + vec2 gap = gaps[i]; + float gap_angle = gap_angles[i]; + int link = gap_links[i]; + + vec2 next_gap = gaps[link]; + float next_gap_angle = gap_angles[link]; + + vec2 l = texCoord * viewport; + l.y = viewport.y - l.y; + + fragColor = mix(fragColor, vec4(float(i&1), i/float(NUM_GAPS-1), 1-i/float(NUM_GAPS-1), 1), line_segment(l, gap, next_gap, 1)); + } + #endif +} diff --git a/resources/shader/reimu_gap.prog b/resources/shader/reimu_gap.prog new file mode 100644 index 00000000..dbdf8e78 --- /dev/null +++ b/resources/shader/reimu_gap.prog @@ -0,0 +1,2 @@ + +glsl_objects = standard.vert reimu_gap.frag diff --git a/resources/shader/reimu_gap_light.frag.glsl b/resources/shader/reimu_gap_light.frag.glsl new file mode 100644 index 00000000..df7a0233 --- /dev/null +++ b/resources/shader/reimu_gap_light.frag.glsl @@ -0,0 +1,50 @@ +#version 330 core + +#include "lib/render_context.glslh" +#include "interface/standard.glslh" +#include "lib/util.glslh" + +UNIFORM(1) float time; +UNIFORM(2) float strength; + +float g(float x) { + return sin(1.1 * x) * cos(1.98 * x); +} + +float wave(float x) { + return g(x - 3 * g(0.6 * x) + 0.23 * g(1.2123 * x) + 0.7634 * g(1.12365 * x) + 1.541 * g(0.26 * x)); +} + +float light_mask(vec2 tc) { + float p = 20.0; + + tc.x = mix( + clamp(0.5 - 0.5 * pow(1 - tc.x * 2, p), 0.0, 0.5), + clamp(0.5 + 0.5 * pow((tc.x - 0.5) * 2, p), 0.5, 1.0), + step(0.5, tc.x) + ); + + return smoothstep(1.0, 0.0, length((tc - 0.5) * 2.0)); +} + +void main(void) { + float s = 0.6; + float l = 1.0; + float o = 0.123 * texCoord.x; + + vec4 layer0 = texture(tex, texCoord + vec2(0, 0.23134 * time)) * vec4(hsv2rgb(vec3(time + (texCoord.y + 1*o), s, l)), 1); + layer0 *= pow(0.5 + 0.5 * wave(3215*pi + 0.9314*texCoord.y - time * 0.46), 1.0); + + vec4 layer1 = texture(tex, texCoord + vec2(0, 0.32155 * time)) * vec4(hsv2rgb(vec3(time - (texCoord.y + 2*o), s, l)), 1); + layer1 *= pow(0.5 + 0.5 * wave(7234*pi + 0.9612*texCoord.y + time * 0.64), 1.0); + + vec4 layer2 = texture(tex, texCoord - vec2(0, 0.30133 * time)) * vec4(hsv2rgb(vec3(time + (texCoord.y + 3*o), s, l)), 1); + layer2 *= pow(0.5 + 0.5 * wave(4312*pi + 0.9195*texCoord.y + time * 0.42), 1.0); + + vec4 layer3 = texture(tex, texCoord - vec2(0, 0.26424 * time)) * vec4(hsv2rgb(vec3(time - (texCoord.y + 4*o), s, l)), 1); + layer3 *= pow(0.5 + 0.5 * wave(2642*pi + 0.9195*texCoord.y + time * 0.60), 1.0); + + float mask = light_mask(texCoord); + fragColor = (layer0 + layer1 + layer2 + layer3) * mask * strength * (1.5 + 0.25 * sin(time)); + fragColor.a = 0; +} diff --git a/resources/shader/reimu_gap_light.prog b/resources/shader/reimu_gap_light.prog new file mode 100644 index 00000000..06102ee3 --- /dev/null +++ b/resources/shader/reimu_gap_light.prog @@ -0,0 +1,2 @@ + +glsl_objects = standard.vert reimu_gap_light.frag diff --git a/resources/shader/sprite_yinyang.frag.glsl b/resources/shader/sprite_yinyang.frag.glsl new file mode 100644 index 00000000..9be5ced4 --- /dev/null +++ b/resources/shader/sprite_yinyang.frag.glsl @@ -0,0 +1,9 @@ +#version 330 core + +#include "interface/sprite.glslh" + +void main(void) { + vec4 texel = texture(tex, texCoord); + fragColor.rgb = color.rgb * texel.g - vec3(0.5 * texel.r) + vec3(texel.b); + fragColor.a = texel.a * color.a; +} diff --git a/resources/shader/sprite_yinyang.prog b/resources/shader/sprite_yinyang.prog new file mode 100644 index 00000000..2a96b588 --- /dev/null +++ b/resources/shader/sprite_yinyang.prog @@ -0,0 +1 @@ +glsl_objects = sprite_default.vert sprite_yinyang.frag diff --git a/src/boss.c b/src/boss.c index 18afd0b0..eeb8c406 100644 --- a/src/boss.c +++ b/src/boss.c @@ -489,7 +489,7 @@ static DamageResult ent_damage_boss(EntityInterface *ent, const DamageInfo *dmg) play_loop("hit0"); } - return true; + return DMG_RESULT_OK; } static void boss_give_spell_bonus(Boss *boss, Attack *a, Player *plr) { @@ -769,7 +769,7 @@ void process_boss(Boss **pboss) { play_sound_ex("bossdeath", BOSS_DEATH_DELAY * 2, false); } else { if(cabs(boss->pos - global.plr.pos) < 16) { - player_death(&global.plr); + ent_damage(&global.plr.ent, &(DamageInfo) { .type = DMG_ENEMY_COLLISION }); } } diff --git a/src/dialog/meson.build b/src/dialog/meson.build index a57fe540..f2dfe457 100644 --- a/src/dialog/meson.build +++ b/src/dialog/meson.build @@ -1,4 +1,5 @@ dialog_src = files( 'marisa.c', + 'reimu.c', 'youmu.c', ) diff --git a/src/ending.c b/src/ending.c index 599bacda..73885698 100644 --- a/src/ending.c +++ b/src/ending.c @@ -86,6 +86,22 @@ void bad_ending_youmu(Ending *e) { track_ending(ENDING_BAD_2); } +void bad_ending_reimu(Ending *e) { + add_ending_entry(e, -1, "“Now, let your mind be expanded! Do you have the potential to understand the code behind reality?”", NULL); + add_ending_entry(e, -1, "Reimu may have been the mouthpiece of the gods, but this truth was far beyond the words they would push into her brain. Numbers overlapped endlessly and seamlessly over each other, and she quickly found herself in an inescapable mental haze. Without realizing it, she had stopped floating, and the Yin-Yang Orbs dropped uselessly alongside her, having lost all of their holy powers.", NULL); + add_ending_entry(e, -1, "Tired of what she perceived as nonsense, she willed the numbers to end, and they did, taking any directives of the Hakurei god along with them to create an unbearable silence that drifted darkly into unconsciousness.", NULL); + add_ending_entry(e, -1, "“Auughh!” \nBolting upwards in her futon, Reimu yelled out both in inexplicable frustration and pain as a sudden migraine woke her up on an otherwise peaceful morning. Grumbling, she glanced all around her room instinctually, as if she had seen something wrong with it before.", NULL); + add_ending_entry(e, -1, "But it looked just the same as it normally did, even through the angry throbbing in her skull. Knowing that further sleep was impossible, however, she picked herself up and started to get ready for the rest of the day. Of course, she didn’t get too far before being interrupted by an overly familiar yōkai sage.", NULL); + add_ending_entry(e, -1, "“What do you want?” Reimu asked grouchily, rubbing her forehead as she stared at a strangely concerned Yukari.", NULL); + add_ending_entry(e, -1, "“Did you really defeat Wriggle as the main culprit of the incident yesterday?”", NULL); + add_ending_entry(e, -1, "“Huh? I don’t even remember who I fought. Does it even matter?”", NULL); + add_ending_entry(e, -1, "“Yes, it does! Because Wriggle shouldn’t have that sort of power. That means… she must be more dangerous than she has been letting on! Never mind, fufu!” ", NULL); + add_ending_entry(e, -1, "Watching Yukari change her tone mid-sentence, Reimu felt disconcerted. The gap hag must have been onto something, but just as soon as she had realized it, that thought had been taken away by a mysterious force.", NULL); + add_ending_entry(e, -1, "Clearly this peace she had achieved by defeating Wriggle was just a farce, but she had no idea who was really behind it. What she needed now was another chance in another time-- to find the real logical perpetrator of the incident, she would have to go back and do it all over again.", NULL); + add_ending_entry(e, -1, "-BAD END-\nTry a clear without continues next time!", NULL); + track_ending(ENDING_BAD_3); +} + void good_ending_marisa(Ending *e) { add_ending_entry(e, -1, "“Now, let your mind be expanded! Do you have the potential to understand the code behind reality?”", NULL); add_ending_entry(e, -1, "“Ha! Who do ya take me for? Of course!”", NULL); @@ -116,6 +132,24 @@ void good_ending_youmu(Ending *e) { track_ending(ENDING_GOOD_2); } +void good_ending_reimu(Ending *e) { + add_ending_entry(e, -1, "“Now, let your mind be expanded! Do you have the potential to understand the code behind reality?”", NULL); + add_ending_entry(e, -1, "“What a pointless question! Be defeated already!”", NULL); + add_ending_entry(e, -1, "Reimu, with her straightforward clarity, was able to hold fast and ignore the barrage focused upon her mind by sealing herself off via her fantasy nature. Raising her purification rod as a command, her Yin-Ying Orbs soared into the false sky and coalesced into one, a glowing orb that quickly grew to humongous proportions.", NULL); + add_ending_entry(e, -1, "She flicked her rod downwards, and Elly found herself facing this massive spirit orb unprepared. It plowed into her, and then also into her tower, smashing all in its path with a godly roar. When the dust and rubble settled, Reimu landed on the plains below, in front of the weakened Elly.", NULL); + add_ending_entry(e, -1, "“Are you satisfied? You destroyed my research, my life, and my home. And for what? An illogical world fit for neither yōkai nor humans!” Elly shouted, trying to but failing to crawl towards her fallen scythe.", NULL); + add_ending_entry(e, -1, "“That’s not true. You’re upset because you blame Gensōkyō for rejecting you when the reality is that you simply got lost, didn’t you? Gensōkyō welcomes everyone, and it will still welcome you,” Reimu replied, kicking aside Elly’s scythe before reaching out to pull her up. Elly hesitantly took it, her knees still shaking from Reimu’s spiritual blow.", NULL); + add_ending_entry(e, -1, "“I may be able to return to Gensōkyō as a yōkai, but what will happen to my work? I was so close to the truth and you blew it all away. There's no way I could be satisfied without knowing!”", NULL); + add_ending_entry(e, -1, "Reimu mulled over this for a moment before swinging her rod over her back, looking suddenly determined.", NULL); + add_ending_entry(e, -1, "“This truth of yours almost pulled Gensōkyō apart. For that I’ll just have to figure out the rest in your stead and then deal with it in any way that I’ll need to. Seems like I can’t go home yet…” Reimu declared, breaking it off with a sigh at the end.", NULL); + add_ending_entry(e, -1, "“Oh well, I’ll just make this as quick as I can. Feel free to invite yourself over to my shrine if you want. Everyone does that even if I say no, so I can’t really stop you.”", NULL); + add_ending_entry(e, -1, "Elly blinked for a few seconds in shock, and then started to laugh. Just like in the old days, Reimu still treated everyone with perfect neutrality. If anyone was best suited to discovering something without biases, it was Reimu, the Shrine Maiden of Paradise.", NULL); + add_ending_entry(e, -1, "Reimu herself had already started floating away towards the artificial horizon, and Elly watched, now completely at ease. After all, if Reimu was on the case, it would always work out right in the end. She was the unavoidable constant in a sea of variables, and using her shrine maiden intuition as her compass, there was no way that she could fail to trailblaze the way to the truth.", NULL); + add_ending_entry(e, -1, "The only question now is what secret Reimu would, in fact, see at the end of the world.", NULL); + add_ending_entry(e, -1, "-GOOD END-\nExtra Stage has opened up for you! Do you have what it takes to discover the hidden truth behind everything?", NULL); + track_ending(ENDING_GOOD_3); +} + static void create_ending(Ending *e) { memset(e, 0, sizeof(Ending)); diff --git a/src/ending.h b/src/ending.h index 407f5bf8..3872ec34 100644 --- a/src/ending.h +++ b/src/ending.h @@ -17,12 +17,14 @@ enum { }; enum { - // do not reorder these or change the values + // WARNING: Reordering this will break current progress files. + ENDING_BAD_1, ENDING_BAD_2, ENDING_GOOD_1, ENDING_GOOD_2, - + ENDING_BAD_3, + ENDING_GOOD_3, NUM_ENDINGS, }; @@ -37,5 +39,7 @@ void ending_preload(void); */ void bad_ending_marisa(Ending *e); void bad_ending_youmu(Ending *e); +void bad_ending_reimu(Ending *e); void good_ending_marisa(Ending *e); void good_ending_youmu(Ending *e); +void good_ending_reimu(Ending *e); diff --git a/src/enemy.c b/src/enemy.c index d5d566c3..29412c43 100644 --- a/src/enemy.c +++ b/src/enemy.c @@ -66,6 +66,7 @@ Enemy *create_enemy_p(EnemyList *enemies, complex pos, float hp, EnemyVisualRule // XXX: some code relies on the insertion logic Enemy *e = (Enemy*)alist_insert(enemies, enemies->first, objpool_acquire(stage_object_pools.enemies)); + // Enemy *e = (Enemy*)alist_append(enemies, objpool_acquire(stage_object_pools.enemies)); e->moving = false; e->dir = 0; diff --git a/src/entity.c b/src/entity.c index 1672d725..f0392ed2 100644 --- a/src/entity.c +++ b/src/entity.c @@ -118,7 +118,13 @@ DamageResult ent_damage(EntityInterface *ent, const DamageInfo *damage) { return DMG_RESULT_INAPPLICABLE; } - return ent->damage_func(ent, damage); + DamageResult res = ent->damage_func(ent, damage); + + if(res == DMG_RESULT_OK) { + player_register_damage(&global.plr, ent, damage); + } + + return res; } void ent_area_damage(complex origin, float radius, const DamageInfo *damage) { diff --git a/src/player.c b/src/player.c index 728ae6ac..971c7ea8 100644 --- a/src/player.c +++ b/src/player.c @@ -1062,3 +1062,30 @@ void player_preload(void) { "extra_bomb", NULL); } + +// FIXME: where should this be? + +complex plrutil_homing_target(complex org, complex fallback) { + double mindst = INFINITY; + complex target = fallback; + + if(global.boss && boss_is_vulnerable(global.boss)) { + target = global.boss->pos; + mindst = cabs(target - org); + } + + for(Enemy *e = global.enemies.first; e; e = e->next) { + if(e->hp == ENEMY_IMMUNE) { + continue; + } + + double dst = cabs(e->pos - org); + + if(dst < mindst) { + mindst = dst; + target = e->pos; + } + } + + return target; +} diff --git a/src/player.h b/src/player.h index f93b6681..5949ce09 100644 --- a/src/player.h +++ b/src/player.h @@ -168,3 +168,6 @@ bool player_is_alive(Player *plr); double player_get_bomb_progress(Player *plr, double *out_speed); void player_preload(void); + +// FIXME: where should this be? +complex plrutil_homing_target(complex org, complex fallback); diff --git a/src/plrmodes.c b/src/plrmodes.c index 5652e2b4..5c0c7d7e 100644 --- a/src/plrmodes.c +++ b/src/plrmodes.c @@ -15,13 +15,17 @@ #include "plrmodes.h" #include "plrmodes/marisa.h" #include "plrmodes/youmu.h" +#include "plrmodes/reimu.h" static PlayerCharacter *player_characters[] = { + &character_reimu, &character_marisa, &character_youmu, }; static PlayerMode *player_modes[] = { + &plrmode_reimu_a, + &plrmode_reimu_b, &plrmode_marisa_a, &plrmode_marisa_b, &plrmode_youmu_a, diff --git a/src/plrmodes.h b/src/plrmodes.h index 740f8f0d..873283ba 100644 --- a/src/plrmodes.h +++ b/src/plrmodes.h @@ -17,17 +17,17 @@ #include "dialog.h" typedef enum { - /* do not reorder - screws replays */ + // WARNING: Reordering this will break current replays, and possibly even progress files. - PLR_CHAR_MARISA = 0, - PLR_CHAR_YOUMU = 1, - // PLR_CHAR_REIMU = 2, + PLR_CHAR_REIMU = 0, + PLR_CHAR_MARISA = 1, + PLR_CHAR_YOUMU = 2, NUM_CHARACTERS, } CharacterID; typedef enum { - /* do not reorder - screws replays */ + // WARNING: Reordering this will break current replays, and possibly even progress files. PLR_SHOT_A, PLR_SHOT_B, @@ -38,6 +38,9 @@ typedef enum { PLR_SHOT_YOUMU_MIRROR = PLR_SHOT_A, PLR_SHOT_YOUMU_HAUNTING = PLR_SHOT_B, + + PLR_SHOT_REIMU_SPIRIT = PLR_SHOT_A, + PLR_SHOT_REIMU_DREAM = PLR_SHOT_B, } ShotModeID; typedef enum { diff --git a/src/plrmodes/marisa.c b/src/plrmodes/marisa.c index 1bcdef12..9aaf2c78 100644 --- a/src/plrmodes/marisa.c +++ b/src/plrmodes/marisa.c @@ -11,7 +11,6 @@ #include "global.h" #include "plrmodes.h" #include "marisa.h" -#include "renderer/api.h" PlayerCharacter character_marisa = { .id = PLR_CHAR_MARISA, diff --git a/src/plrmodes/marisa_a.c b/src/plrmodes/marisa_a.c index 7dde059e..dd180ff5 100644 --- a/src/plrmodes/marisa_a.c +++ b/src/plrmodes/marisa_a.c @@ -526,6 +526,10 @@ static int masterspark(Enemy *e, int t2) { } static void marisa_laser_bombbg(Player *plr) { + if(!player_is_bomb_active(plr)) { + return; + } + float t = player_get_bomb_progress(&global.plr, NULL); float fade = 1; diff --git a/src/plrmodes/marisa_b.c b/src/plrmodes/marisa_b.c index f1e6b00b..227ba274 100644 --- a/src/plrmodes/marisa_b.c +++ b/src/plrmodes/marisa_b.c @@ -234,6 +234,10 @@ static void marisa_star_bomb(Player *plr) { } static void marisa_star_bombbg(Player *plr) { + if(!player_is_bomb_active(plr)) { + return; + } + float t = player_get_bomb_progress(&global.plr, NULL); ShaderProgram *s = r_shader_get("maristar_bombbg"); diff --git a/src/plrmodes/meson.build b/src/plrmodes/meson.build index 04cf4f42..3d4afd73 100644 --- a/src/plrmodes/meson.build +++ b/src/plrmodes/meson.build @@ -3,6 +3,9 @@ plrmodes_src = files( 'marisa_a.c', 'marisa_b.c', 'marisa.c', + 'reimu_a.c', + 'reimu_b.c', + 'reimu.c', 'youmu_a.c', 'youmu_b.c', 'youmu.c', diff --git a/src/plrmodes/reimu.c b/src/plrmodes/reimu.c new file mode 100644 index 00000000..4d0305ab --- /dev/null +++ b/src/plrmodes/reimu.c @@ -0,0 +1,145 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2018, Lukas Weber . + * Copyright (c) 2012-2018, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "global.h" +#include "plrmodes.h" +#include "reimu.h" +#include "stagedraw.h" + +static Framebuffer *bomb_buffer; + +PlayerCharacter character_reimu = { + .id = PLR_CHAR_REIMU, + .lower_name = "reimu", + .proper_name = "Reimu", + .full_name = "Hakurei Reimu", + .title = "Shrine Maiden", + .dialog_sprite_name = "dialog/reimu", + .player_sprite_name = "player/reimu", + .ending = { + .good = good_ending_reimu, + .bad = bad_ending_reimu, + }, +}; + +double reimu_common_property(Player *plr, PlrProperty prop) { + switch(prop) { + case PLR_PROP_BOMB_TIME: + return 300; + + case PLR_PROP_COLLECT_RADIUS: + return (plr->inputflags & INFLAG_FOCUS) ? 60 : 30; + + case PLR_PROP_SPEED: + // NOTE: For equivalents in Touhou units, divide these by 1.25. + return (plr->inputflags & INFLAG_FOCUS) ? 2.5 : 5.625; + + case PLR_PROP_POC: + return VIEWPORT_H / 3.5; + + case PLR_PROP_DEATHBOMB_WINDOW: + return 12; + } + + UNREACHABLE; +} + +static int reimu_ofuda_trail(Projectile *p, int t) { + int r = linear(p, t); + + if(t < 0) { + return r; + } + + p->color.g *= 0.95; + + return r; +} + +int reimu_common_ofuda(Projectile *p, int t) { + if(t == EVENT_DEATH) { + return ACTION_ACK; + } + + p->angle = carg(p->args[0]); + + if(t == EVENT_BIRTH) { + return ACTION_ACK; + } + + p->pos += p->args[0]; + + PARTICLE( + // .sprite_ptr = p->sprite, + .sprite = "hghost", + .color = &p->color, + .timeout = 12, + .pos = p->pos + p->args[0] * 0.3, + .args = { p->args[0] * 0.5, 0, 1+2*I }, + .rule = reimu_ofuda_trail, + .draw_rule = ScaleFade, + .layer = LAYER_PARTICLE_LOW, + .flags = PFLAG_NOREFLECT, + ); + + return ACTION_NONE; +} + +void reimu_common_draw_yinyang(Enemy *e, int t, const Color *c) { + r_draw_sprite(&(SpriteParams) { + .sprite = "yinyang", + .shader = "sprite_yinyang", + .pos = { creal(e->pos), cimag(e->pos) }, + .rotation.angle = global.frames * -6 * DEG2RAD, + // .color = rgb(0.95, 0.75, 1.0), + .color = c, + }); +} + +static void capture_frame(Framebuffer *dest, Framebuffer *src) { + r_state_push(); + r_framebuffer(dest); + r_shader_standard(); + r_color4(1, 1, 1, 1); + r_blend(BLEND_NONE); + draw_framebuffer_tex(src, VIEWPORT_W, VIEWPORT_H); + r_state_pop(); +} + +void reimu_common_bomb_bg(Player *p, float alpha) { + if(alpha <= 0) + return; + + r_state_push(); + r_color(HSLA_MUL_ALPHA(global.frames / 30.0, 0.2, 0.9, alpha)); + + r_shader("reimu_bomb_bg"); + r_texture(1, "runes"); + r_uniform_int("runes", 1); + r_uniform_float("zoom", VIEWPORT_H / sqrt(VIEWPORT_W*VIEWPORT_W + VIEWPORT_H*VIEWPORT_H)); + r_uniform_vec2("aspect", VIEWPORT_W / (float)VIEWPORT_H, 1); + r_uniform_float("time", 9000 + 3 * global.frames / 60.0); + draw_framebuffer_tex(bomb_buffer, VIEWPORT_W, VIEWPORT_H); + + r_state_pop(); + capture_frame(bomb_buffer, r_framebuffer_current()); +} + +void reimu_common_bomb_buffer_init(void) { + FBAttachmentConfig cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.attachment = FRAMEBUFFER_ATTACH_COLOR0; + cfg.tex_params.type = TEX_TYPE_RGB; + cfg.tex_params.filter.min = TEX_FILTER_LINEAR; + cfg.tex_params.filter.mag = TEX_FILTER_LINEAR; + cfg.tex_params.wrap.s = TEX_WRAP_MIRROR; + cfg.tex_params.wrap.t = TEX_WRAP_MIRROR; + bomb_buffer = stage_add_foreground_framebuffer(1, 1, &cfg); +} diff --git a/src/plrmodes/reimu.h b/src/plrmodes/reimu.h new file mode 100644 index 00000000..ef3218c5 --- /dev/null +++ b/src/plrmodes/reimu.h @@ -0,0 +1,23 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2018, Lukas Weber . + * Copyright (c) 2012-2018, Andrei Alexeyev . + */ + +#pragma once +#include "taisei.h" + +#include "plrmodes.h" +#include "dialog/reimu.h" + +extern PlayerCharacter character_reimu; +extern PlayerMode plrmode_reimu_a; +extern PlayerMode plrmode_reimu_b; + +double reimu_common_property(Player *plr, PlrProperty prop); +int reimu_common_ofuda(Projectile *p, int t); +void reimu_common_draw_yinyang(Enemy *e, int t, const Color *c); +void reimu_common_bomb_bg(Player *p, float alpha); +void reimu_common_bomb_buffer_init(void); diff --git a/src/plrmodes/reimu_a.c b/src/plrmodes/reimu_a.c new file mode 100644 index 00000000..112c2f70 --- /dev/null +++ b/src/plrmodes/reimu_a.c @@ -0,0 +1,653 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2018, Lukas Weber . + * Copyright (c) 2012-2018, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "global.h" +#include "plrmodes.h" +#include "reimu.h" + +// FIXME: We probably need a better way to store shot-specific state. +// See also MarisaA. +static struct { + uint prev_inputflags; + bool respawn_slaves; +} reimu_spirit_state; + +static void reimu_spirit_preload(void) { + const int flags = RESF_DEFAULT; + + preload_resources(RES_SPRITE, flags, + "yinyang", + "proj/ofuda", + "proj/needle", + "part/ofuda_glow", + NULL); + + preload_resources(RES_SHADER_PROGRAM, flags, + "sprite_yinyang", + NULL); + + //preload_resources(RES_SFX, flags | RESF_OPTIONAL, + //NULL); +} + +static int reimu_spirit_needle(Projectile *p, int t) { + int r = linear(p, t); + + if(t < 0) { + return r; + } + + Color c = p->color; + color_mul(&c, RGBA_MUL_ALPHA(0.75, 0.5, 1, 0.5)); + c.a = 0; + + PARTICLE( + .sprite_ptr = p->sprite, + .color = &c, + .timeout = 12, + .pos = p->pos, + .args = { p->args[0] * 0.8, 0, 0+3*I }, + .rule = linear, + .draw_rule = ScaleFade, + .layer = LAYER_PARTICLE_LOW, + .flags = PFLAG_NOREFLECT, + ); + + return r; +} + +#define REIMU_SPIRIT_HOMING_SCALE 0.75 + +static void reimu_spirit_homing_draw(Projectile *p, int t) { + r_mat_push(); + r_mat_translate(creal(p->pos), cimag(p->pos), 0); + r_mat_rotate(p->angle + M_PI/2, 0, 0, 1); + r_mat_scale(REIMU_SPIRIT_HOMING_SCALE, REIMU_SPIRIT_HOMING_SCALE, 1); + ProjDrawCore(p, &p->color); + r_mat_pop(); +} + +static Projectile* reimu_spirit_spawn_ofuda_particle(Projectile *p, int t, double vfactor) { + Color *c = HSLA_MUL_ALPHA(t * 0.1, 0.6, 0.7, 0.45); + c->a = 0; + + return PARTICLE( + .sprite = "ofuda_glow", + // .color = rgba(0.5 + 0.5 + psin(global.frames * 0.75), psin(t*0.5), 1, 0.5), + .color = c, + .timeout = 12, + .pos = p->pos, + .args = { p->args[0] * (0.6 + 0.4 * frand()) * vfactor, 0, (1+2*I) * REIMU_SPIRIT_HOMING_SCALE }, + .angle = p->angle, + .rule = linear, + .draw_rule = ScaleFade, + .layer = LAYER_PARTICLE_LOW, + .flags = PFLAG_NOREFLECT, + ); +} + +static int reimu_spirit_homing_impact(Projectile *p, int t) { + if(t < 0) { + return ACTION_ACK; + } + + Projectile *trail = reimu_spirit_spawn_ofuda_particle(p, global.frames, 1); + trail->rule = NULL; + trail->timeout = 6; + trail->angle = p->angle; + trail->ent.draw_layer = LAYER_PLAYER_FOCUS - 1; // TODO: add a layer for "super high" particles? + trail->args[2] *= 1.5 * (1 - t/p->timeout); + p->angle += 0.2; + + return ACTION_NONE; +} + +static Projectile* reimu_spirit_spawn_homing_impact(Projectile *p, int t) { + return PARTICLE( + .proto = p->proto, + .color = &p->color, + .timeout = 32, + .pos = p->pos, + .args = { 0, 0, (1+2*I) * REIMU_SPIRIT_HOMING_SCALE }, + .angle = p->angle, + .rule = reimu_spirit_homing_impact, + .draw_rule = ScaleFade, + .layer = LAYER_PARTICLE_HIGH, + .flags = PFLAG_NOREFLECT, + ); +} + +static inline double reimu_spirit_homing_aimfactor(double t, double maxt) { + t = clamp(t, 0, maxt); + double q = pow(1 - t / maxt, 3); + return 4 * q * (1 - q); +} + +static int reimu_spirit_homing(Projectile *p, int t) { + if(t < 0) { + if(t == EVENT_DEATH && !global.game_over && projectile_in_viewport(p)) { + reimu_spirit_spawn_homing_impact(p, t); + } + + return ACTION_ACK; + } + + p->args[3] = plrutil_homing_target(p->pos, p->args[3]); + double v = cabs(p->args[0]); + + complex aimdir = cexp(I*carg(p->args[3] - p->pos)); + double aim = reimu_spirit_homing_aimfactor(t, p->args[1]); + + p->args[0] += v * 0.25 * aim * aimdir; + p->args[0] = v * cexp(I*carg(p->args[0])); + p->angle = carg(p->args[0]); + + double s = 1;// max(pow(2*t/creal(p->args[1]), 2), 0.1); //(0.25 + 0.75 * (1 - aim)); + p->pos += p->args[0] * s; + reimu_spirit_spawn_ofuda_particle(p, t, 0.5); + + return ACTION_NONE; +} + +static Color *reimu_spirit_orb_color(Color *c, int i) { + *c = *RGBA((0.2 + (i==0))/1.2, (0.2 + (i==1))/1.2, (0.2 + 1.5*(i==2))/1.2, 0.0); + return c; +} + +static void reimu_spirit_bomb_orb_visual(Projectile *p, int t) { + complex pos = p->pos; + + for(int i = 0; i < 3; i++) { + complex offset = (10 + pow(t, 0.5)) * cexp(I * (2 * M_PI / 3*i + sqrt(1 + t * t / 300.0))); + + Color c; + r_draw_sprite(&(SpriteParams) { + .sprite = "proj/glowball", + .shader = "sprite_bullet", + .pos = { creal(pos+offset), cimag(pos+offset) }, + .color = reimu_spirit_orb_color(&c, i), + +// .shader_params = &(ShaderCustomParams) {.vector = {0.3,0,0,0}}, + }); + } +} + +static int reimu_spirit_bomb_orb_trail(Projectile *p, int t) { + if(t < 0) { + return ACTION_ACK; + } + + p->pos += p->args[0]; + p->angle -= 0.05; + + // p->color = *HSLA(2.0*t/p->timeout, 0.5, 0.5, 0.0); + + return ACTION_NONE; +} + +static void reimu_spirit_bomb_orb_draw_impact(Projectile *p, int t) { + float attack = min(1, (7 + 5 * p->args[0]) * t / p->timeout); + float decay = t / p->timeout; + + Color c = p->color; + color_lerp(&c, RGBA(0.2, 0.1, 0, 1.0), decay); + color_mul_scalar(&c, pow(1 - decay, 2) * 0.75); + + r_draw_sprite(&(SpriteParams) { + .sprite_ptr = p->sprite, + .pos = { creal(p->pos), cimag(p->pos) }, + .color = &c, + .shader_ptr = p->shader, + .shader_params = &p->shader_params, + .scale.both = (0.75 + 0.25 / (pow(decay, 3.0) + 1.0)) + sqrt(5 * (1 - attack)), + }); +} + +static int reimu_spirit_bomb_orb(Projectile *p, int t) { + int index = creal(p->args[1]) + 0.5; + + if(t == EVENT_BIRTH) { + if(index == 0) + global.shake_view = 4; + p->args[3] = global.plr.pos; + return ACTION_ACK; + } + + if(t == EVENT_DEATH) { + if(global.game_over) { + return ACTION_ACK; + } + + global.shake_view = 20; + global.shake_view_fade = 0.6; + + double damage = 2000; + double range = 300; + + ent_area_damage(p->pos, range, &(DamageInfo){damage, DMG_PLAYER_BOMB}); + stage_clear_hazards_at(p->pos, range, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW); + + int count = 21; + double offset = frand(); + + for(int i = 0; i < count; i++) { + PARTICLE( + .sprite_ptr = get_sprite("proj/glowball"), + .shader = "sprite_bullet", + .color = HSLA(3 * (float)i / count + offset, 1, 0.5, 0), // reimu_spirit_orb_color(&(Color){0}, i%3),w + .timeout = 60, + .pos = p->pos, + .args = { cexp(I * 2 * M_PI / count * (i + offset)) * 15 }, + .angle = 2*M_PI*frand(), + .rule = linear, + .draw_rule = Fade, + .layer = LAYER_BOSS, + .flags = PFLAG_NOREFLECT, + ); + } + + for(int i = 0; i < 3; ++i) { + PARTICLE( + .sprite = "blast", + .size = 64 * (I+1), + .color = color_mul_scalar(reimu_spirit_orb_color(&(Color){0}, i), 2), + .pos = p->pos + 30 * cexp(I*2*M_PI/3*(i+t*0.1)), + .timeout = 40, + .draw_rule = ScaleFade, + .layer = LAYER_BOSS + 2, + .args = { 0, 0, 7.5*I }, + ); + + PARTICLE( + .sprite = "fantasyseal_impact", + .color = reimu_spirit_orb_color(&(Color){0}, i), + .pos = p->pos + 2 * cexp(I*2*M_PI/3*(i+t*0.1)), + .timeout = 120, + .draw_rule = reimu_spirit_bomb_orb_draw_impact, + .layer = LAYER_BOSS + 1, + .args = { frand() }, + ); + } + + play_sound("boom"); + play_sound("spellend"); + + return ACTION_ACK; + } + + if(!player_is_bomb_active(&global.plr) > 0) { + return ACTION_DESTROY; + } + + double circletime = 100+20*index; + + if(t == circletime) { + p->args[3] = global.plr.pos - 128*I; + play_sound("redirect"); + } + + complex target_circle = global.plr.pos + 10 * sqrt(t) * p->args[0]*(1 + 0.1 * sin(0.2*t)); + p->args[0] *= cexp(I*0.12); + + double circlestrength = 1.0 / (1 + exp(t-circletime)); + + p->args[3] = plrutil_homing_target(p->pos, p->args[3]); + complex target_homing = p->args[3]; + complex homing = target_homing - p->pos; + complex v = 0.3 * (circlestrength * (target_circle - p->pos) + 0.2 * (1-circlestrength) * (homing + 2*homing/(cabs(homing)+0.01))); + p->args[2] += (v - p->args[2]) * 0.2; + p->pos += p->args[2]; + + for(int i = 0; i < 3 /*&& circlestrength < 1*/; i++) { + complex pos = p->pos + 10 * cexp(I*2*M_PI/3*(i+t*0.1)); + complex v = global.plr.pos - pos; + v *= 3 * circlestrength / cabs(v); + + PARTICLE( + .sprite_ptr = get_sprite("part/stain"), + // .color = reimu_spirit_orb_color(&(Color){0}, i), + .color = HSLA(t/p->timeout, 0.5, 0.5, 0.0), + .pos = pos, + .angle = 2*M_PI*frand(), + .timeout = 30, + .draw_rule = ScaleFade, + .rule = reimu_spirit_bomb_orb_trail, + .args = { v, 0, 0.8 }, + ); + } + + return ACTION_NONE; +} + +static void reimu_spirit_bomb(Player *p) { + int count = 6; + + for(int i = 0; i < count; i++) { + PROJECTILE( + .pos = p->pos, + .draw_rule = reimu_spirit_bomb_orb_visual, + .rule = reimu_spirit_bomb_orb, + .args = { cexp(I*2*M_PI/count*i), i, 0, 0}, + .timeout = 160 + 20 * i, + .type = PlrProj, + .damage = 0, + .size = 10 + 10*I, + .layer = LAYER_PLAYER_FOCUS - 1, + ); + } + + play_sound("bomb_reimu_a"); + play_sound("bomb_marisa_b"); +} + +static void reimu_spirit_bomb_bg(Player *p) { + if(!player_is_bomb_active(p)) { + return; + } + + double speed; + double t = player_get_bomb_progress(p, &speed); + float alpha = 0; + if(t > 0) + alpha = min(1,10*t); + if(t > 0.7) + alpha *= 1-pow((t-0.7)/0.3,4); + + reimu_common_bomb_bg(p, alpha); + colorfill(0, 0.05 * alpha, 0.1 * alpha, alpha * 0.5); +} + +static void reimu_spirit_shot(Player *p) { + play_loop("generic_shot"); + + if(!(global.frames % 3)) { + int i = 1 - 2 * (bool)(global.frames % 6); + PROJECTILE( + .proto = pp_ofuda, + .pos = p->pos + 10 * i - 15.0*I, + .color = RGBA_MUL_ALPHA(1, 1, 1, 0.5), + .rule = reimu_common_ofuda, + .args = { -20.0*I }, + .type = PlrProj, + .damage = 100 - 8 * (p->power / 100), + .shader = "sprite_default", + ); + } + + for(int pwr = 0; pwr <= p->power/100; ++pwr) { + int t = (global.frames - 5 * pwr); + + if(!(t % 16)) { + for(int i = -1; i < 2; i += 2) { + float spread = i * M_PI/32 * (1 + 0.35 * pwr); + + PROJECTILE( + .proto = pp_hakurei_seal, + .pos = p->pos - I + 5 * i, + .color = color_mul_scalar(RGBA(1, 1, 1, 0.5), 0.7), + .rule = linear, + .args = { -18.0*I*cexp(I*spread) }, + .type = PlrProj, + .damage = 60 - 5 * (p->power / 100), + .shader = "sprite_default", + ); + } + } + } +} + +static void reimu_spirit_slave_shot(Enemy *e, int t) { + int st = t; + + if(st % 3) { + return; + } + + if(global.plr.inputflags & INFLAG_FOCUS) { + PROJECTILE( + .proto = pp_needle, + .pos = e->pos - 25.0*I, + .color = RGBA_MUL_ALPHA(1, 1, 1, 0.5), + .rule = reimu_spirit_needle, + .args = { -20.0*I }, + .type = PlrProj, + .damage_type = DMG_PLAYER_SHOT, + .damage = cimag(e->args[2]), + .shader = "sprite_default", + ); + } else if(!(st % 12)) { + complex v = -10 * I * cexp(I*cimag(e->args[0])); + + PROJECTILE( + .proto = pp_ofuda, + .pos = e->pos, + .color = RGBA_MUL_ALPHA(1, 0.9, 0.95, 0.7), + .rule = reimu_spirit_homing, + .draw_rule = reimu_spirit_homing_draw, + .args = { v , 60, 0, e->pos + v * VIEWPORT_H * VIEWPORT_W /*creal(e->pos)*/ }, + .type = PlrProj, + .damage_type = DMG_PLAYER_SHOT, + .damage = creal(e->args[2]), + // .timeout = 60, + .shader = "sprite_default", + .flags = PFLAG_NOCOLLISIONEFFECT, + ); + } +} + +static int reimu_spirit_slave(Enemy *e, int t) { + TIMER(&t); + + AT(EVENT_BIRTH) { + e->pos = global.plr.pos; + return ACTION_NONE; + } + + if(t < 0) { + return ACTION_NONE; + } + + if(player_should_shoot(&global.plr, true)) { + reimu_spirit_slave_shot(e, t); + } + + if(creal(e->args[3]) != 0) { + int death_begin_time = creal(e->args[3]); + int death_duration = cimag(e->args[3]); + double death_progress = (t - death_begin_time) / (double)death_duration; + + e->pos = global.plr.pos * death_progress + e->pos0 * (1 - death_progress); + + if(death_progress >= 1) { + return ACTION_DESTROY; + } + + return ACTION_NONE; + } + + double speed = 0.005 * min(1, t / 12.0); + + if(global.plr.inputflags & INFLAG_FOCUS) { + GO_TO(e, global.plr.pos + cimag(e->args[1]) * cexp(I*(creal(e->args[0]) + t * creal(e->args[1]))), speed * cabs(e->args[1])); + } else { + GO_TO(e, global.plr.pos + e->pos0, speed * cabs(e->pos0)); + } + + return ACTION_NONE; +} + +static int reimu_spirit_yinyang_flare(Projectile *p, int t) { + double a = p->angle; + int r = linear(p, t); + p->angle = a; + return r; +} + +static void reimu_spirit_yinyang_focused_visual(Enemy *e, int t, bool render) { + if(!render && player_should_shoot(&global.plr, true)) { + PARTICLE( + .sprite = "stain", + .color = RGBA(1, 0.0 + 0.5 * frand(), 0, 0), + .timeout = 12 + 2 * nfrand(), + .pos = e->pos, + .args = { -5*I * (1 + frand()), 0, 0.5 + 0*I }, + .angle = 2*M_PI*frand(), + .rule = reimu_spirit_yinyang_flare, + .draw_rule = ScaleFade, + .layer = LAYER_PARTICLE_HIGH, + .flags = PFLAG_NOREFLECT, + ); + } + + if(render) { + reimu_common_draw_yinyang(e, t, RGB(1.0, 0.8, 0.8)); + } +} + +static void reimu_spirit_yinyang_unfocused_visual(Enemy *e, int t, bool render) { + if(!render && player_should_shoot(&global.plr, true)) { + PARTICLE( + .sprite = "stain", + .color = RGBA(1, 0.25, 0.0 + 0.5 * frand(), 0), + .timeout = 12 + 2 * nfrand(), + .pos = e->pos, + .args = { -5*I * (1 + frand()), 0, 0.5 + 0*I }, + .angle = 2*M_PI*frand(), + .rule = reimu_spirit_yinyang_flare, + .draw_rule = ScaleFade, + .layer = LAYER_PARTICLE_HIGH, + .flags = PFLAG_NOREFLECT, + ); + } + + if(render) { + reimu_common_draw_yinyang(e, t, RGB(0.95, 0.75, 1.0)); + } +} + +static inline Enemy* reimu_spirit_spawn_slave(Player *plr, complex pos, complex a0, complex a1, complex a2, complex a3, EnemyVisualRule visual) { + Enemy *e = create_enemy_p(&plr->slaves, pos, ENEMY_IMMUNE, visual, reimu_spirit_slave, a0, a1, a2, a3); + e->ent.draw_layer = LAYER_PLAYER_SLAVE; + return e; +} + +static void reimu_spirit_kill_slaves(EnemyList *slaves) { + for(Enemy *e = slaves->first, *next; e; e = next) { + next = e->next; + + if(e->hp == ENEMY_IMMUNE && creal(e->args[3]) == 0) { + // delete_enemy(slaves, e); + // e->args[3] = 1; + e->args[3] = global.frames - e->birthtime + 3 * I; + e->pos0 = e->pos; + } + } +} + +static void reimu_spirit_respawn_slaves(Player *plr, short npow, complex param) { + double dmg_homing = 120 - 12 * plr->power / 100; // every 12 frames + double dmg_needle = 90 - 10 * plr->power / 100; // every 3 frames + complex dmg = dmg_homing + I * dmg_needle; + EnemyVisualRule visual; + + if(plr->inputflags & INFLAG_FOCUS) { + visual = reimu_spirit_yinyang_focused_visual; + } else { + visual = reimu_spirit_yinyang_unfocused_visual; + } + + reimu_spirit_kill_slaves(&plr->slaves); + + switch(npow / 100) { + case 0: + break; + case 1: + reimu_spirit_spawn_slave(plr, 50.0*I, 0 , +0.10 + 60*I, dmg, 0, visual); + break; + case 2: + reimu_spirit_spawn_slave(plr, +40, 0 +M_PI/24*I, +0.10 + 60*I, dmg, 0, visual); + reimu_spirit_spawn_slave(plr, -40, M_PI -M_PI/24*I, +0.10 + 60*I, dmg, 0, visual); + break; + case 3: + reimu_spirit_spawn_slave(plr, 50.0*I, 0*2*M_PI/3 , +0.10 + 60*I, dmg, 0, visual); + reimu_spirit_spawn_slave(plr, +40, 1*2*M_PI/3 +M_PI/24*I, +0.10 + 60*I, dmg, 0, visual); + reimu_spirit_spawn_slave(plr, -40, 2*2*M_PI/3 -M_PI/24*I, +0.10 + 60*I, dmg, 0, visual); + break; + case 4: + reimu_spirit_spawn_slave(plr, +40, 0 +M_PI/32*I, +0.10 + 60*I, dmg, 0, visual); + reimu_spirit_spawn_slave(plr, -40, M_PI -M_PI/32*I, +0.10 + 60*I, dmg, 0, visual); + reimu_spirit_spawn_slave(plr, +80, 0 +M_PI/16*I, -0.05 + 80*I, dmg, 0, visual); + reimu_spirit_spawn_slave(plr, -80, M_PI -M_PI/16*I, -0.05 + 80*I, dmg, 0, visual); + break; + default: + UNREACHABLE; + } +} + +static void reimu_spirit_init(Player *plr) { + memset(&reimu_spirit_state, 0, sizeof(reimu_spirit_state)); + reimu_spirit_state.prev_inputflags = plr->inputflags; + reimu_spirit_respawn_slaves(plr, plr->power, 0); + reimu_common_bomb_buffer_init(); +} + +static void reimu_spirit_think(Player *plr) { + if((bool)(reimu_spirit_state.prev_inputflags & INFLAG_FOCUS) ^ (bool)(plr->inputflags & INFLAG_FOCUS)) { + reimu_spirit_state.respawn_slaves = true; + } + + if(reimu_spirit_state.respawn_slaves) { + if(plr->slaves.first) { + reimu_spirit_kill_slaves(&plr->slaves); + } else { + reimu_spirit_respawn_slaves(plr, plr->power, 0); + reimu_spirit_state.respawn_slaves = false; + } + } + + reimu_spirit_state.prev_inputflags = plr->inputflags; +} + +static void reimu_spirit_power(Player *plr, short npow) { + if(plr->power / 100 != npow / 100) { + reimu_spirit_respawn_slaves(plr, npow, 0); + } +} + +double reimu_spirit_property(Player *plr, PlrProperty prop) { + double base_value = reimu_common_property(plr, prop); + + switch(prop) { + case PLR_PROP_SPEED: { + return base_value * (pow(player_get_bomb_progress(plr, NULL), 0.5)); + } + + default: { + return base_value; + } + } +} + +PlayerMode plrmode_reimu_a = { + .name = "Spirit Sign", + .character = &character_reimu, + .dialog = &dialog_reimu, + .shot_mode = PLR_SHOT_REIMU_SPIRIT, + .procs = { + .property = reimu_spirit_property, + .init = reimu_spirit_init, + .think = reimu_spirit_think, + .bomb = reimu_spirit_bomb, + .bombbg = reimu_spirit_bomb_bg, + .shot = reimu_spirit_shot, + .power = reimu_spirit_power, + .preload = reimu_spirit_preload, + }, +}; diff --git a/src/plrmodes/reimu_b.c b/src/plrmodes/reimu_b.c new file mode 100644 index 00000000..76b2c5cb --- /dev/null +++ b/src/plrmodes/reimu_b.c @@ -0,0 +1,558 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2018, Lukas Weber . + * Copyright (c) 2012-2018, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "global.h" +#include "plrmodes.h" +#include "reimu.h" +#include "stagedraw.h" + +#define GAP_LENGTH 128 +#define GAP_WIDTH 16 +#define GAP_OFFSET 8 +#define GAP_LIMIT 10 + +// #define GAP_LENGTH 160 +// #define GAP_WIDTH 160 +// #define GAP_OFFSET 82 + +#define NUM_GAPS 4 +#define FOR_EACH_GAP(gap) for(Enemy *gap = global.plr.slaves.first; gap; gap = gap->next) if(gap->logic_rule == reimu_dream_gap) + +static Enemy *gap_renderer; + +static complex reimu_dream_gap_target_pos(Enemy *e) { + double x, y; + + if(creal(e->pos0)) { + x = creal(e->pos0) * VIEWPORT_W; + } else { + x = creal(global.plr.pos); + } + + if(cimag(e->pos0)) { + y = cimag(e->pos0) * VIEWPORT_H; + } else { + y = cimag(global.plr.pos); + } + + bool focus = global.plr.inputflags & INFLAG_FOCUS; + + // complex ofs = GAP_OFFSET * (1 + I) + (GAP_LENGTH * 0.5 + GAP_LIMIT) * e->args[0]; + double ofs_x = GAP_OFFSET + (GAP_LENGTH * 0.5 + GAP_LIMIT) * creal(e->args[0]); + double ofs_y = GAP_OFFSET + (!focus) * (GAP_LENGTH * 0.5 + GAP_LIMIT) * cimag(e->args[0]); + + x = clamp(x, ofs_x, VIEWPORT_W - ofs_x); + y = clamp(y, ofs_y, VIEWPORT_H - ofs_y); + + if(focus) { + /* + if(cimag(e->pos0)) { + x = VIEWPORT_W - x; + } + */ + } else { + if(creal(e->pos0)) { + y = VIEWPORT_H - y; + } + } + + return x + I * y; +} + +static int reimu_dream_gap_bomb_projectile(Projectile *p, int t) { + if(t == EVENT_BIRTH) { + return ACTION_ACK; + } + + if(t == EVENT_DEATH) { + return ACTION_ACK; + } + + p->pos += p->args[0]; + + if(!(t % 3)) { + double range = GAP_LENGTH * 0.35; + double damage = 50; + // Yes, I know, this is inefficient as hell, but I'm too lazy to write a + // stage_clear_hazards_inside_rectangle function. + stage_clear_hazards_at(p->pos, range, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW); + ent_area_damage(p->pos, range, &(DamageInfo) { damage, DMG_PLAYER_BOMB }); + } + + return ACTION_NONE; +} + +static void reimu_dream_gap_bomb_projectile_draw(Projectile *p, int t) { + r_draw_sprite(&(SpriteParams) { + .sprite_ptr = p->sprite, + .shader_ptr = p->shader, + .color = &p->color, + .shader_params = &p->shader_params, + .pos = { creal(p->pos), cimag(p->pos) }, + .scale.both = 0.75 * clamp(t / 5.0, 0.1, 1.0), + }); +} + +static void reimu_dream_gap_bomb(Enemy *e, int t) { + if(!(t % 4)) { + PROJECTILE( + .sprite = "glowball", + .size = 32 * (1 + I), + .color = HSLA(t/30.0, 0.5, 0.5, 0.5), + .pos = e->pos + e->args[0] * (frand() - 0.5) * GAP_LENGTH * 0.5, + .rule = reimu_dream_gap_bomb_projectile, + .draw_rule = reimu_dream_gap_bomb_projectile_draw, + .type = PlrProj, + .damage_type = DMG_PLAYER_BOMB, + .damage = 75, + .args = { -20 * e->pos0 }, + ); + + global.shake_view += 5; + + if(!(t % 16)) { + play_sound("boon"); + } + } +} + +static int reimu_dream_gap(Enemy *e, int t) { + if(t == EVENT_DEATH) { + free_ref(creal(e->args[3])); + return ACTION_ACK; + } + + if(t == EVENT_BIRTH) { + return ACTION_ACK; + } + + if(player_is_bomb_active(&global.plr)) { + reimu_dream_gap_bomb(e, t + cimag(e->args[3])); + } else { + complex new_pos = reimu_dream_gap_target_pos(e); + + if(t == 0) { + e->pos = new_pos; + } else { + e->pos += (new_pos - e->pos) * 0.1 * (1 - sqrt(gap_renderer->args[0])); + } + } + + return ACTION_NONE; +} + +static void reimu_dream_gap_link(Enemy *g0, Enemy *g1) { + int ref0 = add_ref(g0); + int ref1 = add_ref(g1); + g0->args[3] = ref1; + g1->args[3] = ref0; +} + +static Enemy* reimu_dream_gap_get_linked(Enemy *gap) { + return REF(creal(gap->args[3])); +} + +static void reimu_dream_gap_draw_lights(int time, double strength) { + if(strength <= 0) { + return; + } + + r_texture(0, "gaplight"); + r_shader("reimu_gap_light"); + r_uniform_float("time", time / 60.0); + r_uniform_float("strength", strength); + + FOR_EACH_GAP(gap) { + const float len = GAP_LENGTH * 3 * sqrt(log(strength + 1) / 0.693); + complex center = gap->pos - gap->pos0 * (len * 0.5 - GAP_WIDTH * 0.6); + + r_mat_push(); + r_mat_translate(creal(center), cimag(center), 0); + r_mat_rotate(carg(gap->pos0)+M_PI, 0, 0, 1); + r_mat_scale(len, GAP_LENGTH, 1); + r_draw_quad(); + r_mat_pop(); + } +} + +static void reimu_dream_gap_renderer_visual(Enemy *e, int t, bool render) { + if(!render) { + return; + } + + float gaps[NUM_GAPS][2]; + float angles[NUM_GAPS]; + int links[NUM_GAPS]; + + int i = 0; + FOR_EACH_GAP(gap) { + gaps[i][0] = creal(gap->pos); + gaps[i][1] = cimag(gap->pos); + angles[i] = carg(gap->pos0); + links[i] = cimag(reimu_dream_gap_get_linked(gap)->args[3]); + ++i; + } + + FBPair *framebuffers = stage_get_fbpair(FBPAIR_FG); + + fbpair_swap(framebuffers); + Framebuffer *target_fb = framebuffers->back; + + // This change must propagate + r_state_pop(); + r_framebuffer(target_fb); + r_state_push(); + + r_shader("reimu_gap"); + r_uniform_vec2("viewport", VIEWPORT_W, VIEWPORT_H); + r_uniform_float("time", t / (float)FPS); + r_uniform_vec2("gap_size", GAP_WIDTH/2.0, GAP_LENGTH/2.0); + r_uniform("gaps[0]", NUM_GAPS, gaps); + r_uniform("gap_angles[0]", NUM_GAPS, angles); + r_uniform("gap_links[0]", NUM_GAPS, links); + draw_framebuffer_tex(framebuffers->front, VIEWPORT_W, VIEWPORT_H); + + FOR_EACH_GAP(gap) { + r_mat_push(); + r_mat_translate(creal(gap->pos), cimag(gap->pos), 0); + complex stretch_vector = gap->args[0]; + + for(float ofs = -0.5; ofs <= 0.5; ofs += 1) { + r_draw_sprite(&(SpriteParams) { + .sprite = "yinyang", + .shader = "sprite_yinyang", + .pos = { + creal(stretch_vector) * GAP_LENGTH * ofs, + cimag(stretch_vector) * GAP_LENGTH * ofs + }, + .rotation.angle = global.frames * -6 * DEG2RAD, + .color = RGB(0.95, 0.75, 1.0), + .scale.both = 0.5, + }); + } + + r_mat_pop(); + } + + reimu_dream_gap_draw_lights(t, pow(e->args[0], 2)); +} + +static int reimu_dream_gap_renderer(Enemy *e, int t) { + if(t < 0) { + return ACTION_ACK; + } + + if(player_is_bomb_active(&global.plr)) { + e->args[0] = approach(e->args[0], 1.0, 0.1); + } else { + e->args[0] = approach(e->args[0], 0.0, 0.025); + } + + return ACTION_NONE; +} + +static void reimu_dream_preload(void) { + const int flags = RESF_DEFAULT; + + preload_resources(RES_SHADER_PROGRAM, flags, + "reimu_gap", + NULL); +} + +static void reimu_dream_bomb(Player *p) { + play_sound("bomb_marisa_a"); +} + +static void reimu_dream_bomb_bg(Player *p) { + float a = gap_renderer->args[0]; + reimu_common_bomb_bg(p, a); +} + + +static void reimu_dream_spawn_warp_effect(complex pos, bool exit) { + PARTICLE( + .sprite = "myon", + .pos = pos, + .color = RGBA(0.5, 0.5, 0.5, 0.5), + .timeout = 20, + .angle = frand() * M_PI * 2, + .draw_rule = ScaleFade, + .args = { 0, 0, 1 + 2 * I }, + .layer = LAYER_PLAYER_FOCUS, + ); + + PARTICLE( + .sprite = exit ? "stain" : "stardust", + .pos = pos, + .color = color_mul_scalar(RGBA(0.75, 0.4 * frand(), 0.4, 0), 0.8), + .timeout = 20, + .angle = frand() * M_PI * 2, + .draw_rule = ScaleFade, + .args = { 0, 0, 0.25 + 1.5 * I }, + .layer = LAYER_PLAYER_FOCUS, + ); +} + +static void reimu_dream_bullet_warp(Projectile *p, int t) { + if(creal(p->args[3]) > 0 /*global.plr.power / 100*/) { + return; + } + + double p_long_side = max(creal(p->size), cimag(p->size)); + complex half = 0.5 * (1 + I); + Rect p_bbox = { p->pos - p_long_side * half, p->pos + p_long_side * half }; + + FOR_EACH_GAP(gap) { + double a = (carg(-gap->pos0) - carg(p->args[0])); + + if(fabs(a) < 2*M_PI/3) { + continue; + } + + Rect gap_bbox, overlap; + complex gap_size = (GAP_LENGTH + I * GAP_WIDTH) * cexp(I*carg(gap->args[0])); + complex p0 = gap->pos - gap_size * 0.5; + complex p1 = gap->pos + gap_size * 0.5; + gap_bbox.top_left = min(creal(p0), creal(p1)) + I * min(cimag(p0), cimag(p1)); + gap_bbox.bottom_right = max(creal(p0), creal(p1)) + I * max(cimag(p0), cimag(p1)); + + if(rect_rect_intersection(p_bbox, gap_bbox, true, &overlap)) { + complex o = (overlap.top_left + overlap.bottom_right) / 2; + double fract; + + if(creal(gap_size) > cimag(gap_size)) { + fract = creal(o - gap_bbox.top_left) / creal(gap_size); + } else { + fract = cimag(o - gap_bbox.top_left) / cimag(gap_size); + } + + Enemy *ngap = reimu_dream_gap_get_linked(gap); + o = ngap->pos + ngap->args[0] * GAP_LENGTH * (1 - fract - 0.5); + + reimu_dream_spawn_warp_effect(gap->pos + gap->args[0] * GAP_LENGTH * (fract - 0.5), false); + reimu_dream_spawn_warp_effect(o, true); + + p->args[0] = -cabs(p->args[0]) * ngap->pos0; + p->pos = o + p->args[0]; + p->args[3] += 1; + } + } +} + +static int reimu_dream_ofuda(Projectile *p, int t) { + if(t >= 0) { + reimu_dream_bullet_warp(p, t); + } + + complex ov = p->args[0]; + double s = cabs(ov); + p->args[0] *= clamp(s * (1.5 - t / 10.0), s*1.0, 1.5*s) / s; + int r = reimu_common_ofuda(p, t); + p->args[0] = ov; + + return r; +} + +static int reimu_dream_needle(Projectile *p, int t) { + if(t >= 0) { + reimu_dream_bullet_warp(p, t); + } + + p->angle = carg(p->args[0]); + + if(t < 0) { + return ACTION_ACK; + } + + p->pos += p->args[0]; + + Color *c = color_mul(COLOR_COPY(&p->color), RGBA_MUL_ALPHA(0.75, 0.5, 1, 0.35)); + c->a = 0; + + PARTICLE( + .sprite_ptr = p->sprite, + .color = c, + .timeout = 12, + .pos = p->pos, + .args = { p->args[0] * 0.8, 0, 0+3*I }, + .rule = linear, + .draw_rule = ScaleFade, + .layer = LAYER_PARTICLE_LOW, + .flags = PFLAG_NOREFLECT, + ); + + return ACTION_NONE; +} + +static void reimu_dream_shot(Player *p) { + play_loop("generic_shot"); + int dmg = 50; + + if(!(global.frames % 6)) { + for(int i = -1; i < 2; i += 2) { + complex shot_dir = i * ((p->inputflags & INFLAG_FOCUS) ? 1 : I); + complex spread_dir = shot_dir * cexp(I*M_PI*0.5); + + for(int j = -1; j < 2; j += 2) { + PROJECTILE( + .proto = pp_ofuda, + .pos = p->pos + 10 * j * spread_dir, + .color = RGBA_MUL_ALPHA(1, 1, 1, 0.5), + .rule = reimu_dream_ofuda, + .args = { -20.0 * shot_dir }, + .type = PlrProj, + .damage = dmg, + .shader = "sprite_default", + ); + } + } + } +} + +static void reimu_dream_slave_visual(Enemy *e, int t, bool render) { + if(render) { + r_draw_sprite(&(SpriteParams) { + .sprite = "yinyang", + .shader = "sprite_yinyang", + .pos = { + creal(e->pos), + cimag(e->pos), + }, + .rotation.angle = global.frames * -6 * DEG2RAD, + .color = RGB(0.95, 0.75, 1.0), + .scale.both = 0.5, + }); + } +} + +static int reimu_dream_slave(Enemy *e, int t) { + if(t < 0) { + return ACTION_ACK; + } + + // double a = M_PI * psin(t * 0.1) + creal(e->args[0]) + M_PI/2; + double a = t * -0.1 + creal(e->args[0]) + M_PI/2; + complex ofs = e->pos0; + complex shotdir = e->args[1]; + + if(global.plr.inputflags & INFLAG_FOCUS) { + ofs = cimag(ofs) + I * creal(ofs); + shotdir = cimag(shotdir) + I * creal(shotdir); + } + + if(t == 0) { + e->pos = global.plr.pos; + } else { + double x = creal(ofs); + double y = cimag(ofs); + complex tpos = global.plr.pos + x * sin(a) + y * I * cos(a); + e->pos += (tpos - e->pos) * 0.5; + } + + if(player_should_shoot(&global.plr, true)) { + if(!(global.frames % 6)) { + PROJECTILE( + .proto = pp_needle2, + .pos = e->pos, + .color = RGBA_MUL_ALPHA(1, 1, 1, 0.35), + .rule = reimu_dream_needle, + .args = { 20.0 * shotdir }, + .type = PlrProj, + .damage = 35, + .shader = "sprite_default", + ); + } + } + + return ACTION_NONE; +} + +static Enemy* reimu_dream_spawn_slave(Player *plr, complex pos, complex a0, complex a1, complex a2, complex a3) { + Enemy *e = create_enemy_p(&plr->slaves, pos, ENEMY_IMMUNE, reimu_dream_slave_visual, reimu_dream_slave, a0, a1, a2, a3); + e->ent.draw_layer = LAYER_PLAYER_SLAVE; + return e; +} + +static void reimu_dream_kill_slaves(EnemyList *slaves) { + for(Enemy *e = slaves->first, *next; e; e = next) { + next = e->next; + + if(e->logic_rule == reimu_dream_slave) { + delete_enemy(slaves, e); + } + } +} + +static void reimu_dream_respawn_slaves(Player *plr, short npow) { + reimu_dream_kill_slaves(&plr->slaves); + + int p = 2 * (npow / 100); + double s = 1; + + for(int i = 0; i < p; ++i, s = -s) { + reimu_dream_spawn_slave(plr, 48+32*I, ((double)i/p)*(M_PI*2), s*I, 0, 0); + } +} + +static void reimu_dream_power(Player *p, short npow) { + if(p->power / 100 != npow / 100) { + reimu_dream_respawn_slaves(p, npow); + } +} + +static Enemy* reimu_dream_spawn_gap(Player *plr, complex pos, complex a0, complex a1, complex a2, complex a3) { + Enemy *gap = create_enemy_p(&plr->slaves, pos, ENEMY_IMMUNE, NULL, reimu_dream_gap, a0, a1, a2, a3); + gap->ent.draw_layer = LAYER_PLAYER_SLAVE; + return gap; +} + +static void reimu_dream_think(Player *plr) { + if(player_is_bomb_active(plr)) { + global.shake_view_fade = max(global.shake_view_fade, 5); + } +} + +static void reimu_dream_init(Player *plr) { + Enemy* left = reimu_dream_spawn_gap(plr, -1, I, 0, 0, 0); + Enemy* top = reimu_dream_spawn_gap(plr, -I, 1, 0, 0, 0); + Enemy* right = reimu_dream_spawn_gap(plr, 1, I, 0, 0, 0); + Enemy* bottom = reimu_dream_spawn_gap(plr, I, 1, 0, 0, 0); + + reimu_dream_gap_link(top, left); + reimu_dream_gap_link(bottom, right); + + gap_renderer= create_enemy_p(&plr->slaves, 0, ENEMY_IMMUNE, reimu_dream_gap_renderer_visual, reimu_dream_gap_renderer, 0, 0, 0, 0); + gap_renderer->ent.draw_layer = LAYER_PLAYER_FOCUS; + + int idx = 0; + FOR_EACH_GAP(gap) { + gap->args[3] = creal(gap->args[3]) + I*idx++; + } + + reimu_dream_respawn_slaves(plr, plr->power); + reimu_common_bomb_buffer_init(); +} + +PlayerMode plrmode_reimu_b = { + .name = "Dream Sign", + .character = &character_reimu, + .dialog = &dialog_reimu, + .shot_mode = PLR_SHOT_REIMU_DREAM, + .procs = { + .property = reimu_common_property, + .bomb = reimu_dream_bomb, + .bombbg = reimu_dream_bomb_bg, + .shot = reimu_dream_shot, + .power = reimu_dream_power, + .init = reimu_dream_init, + .preload = reimu_dream_preload, + .think = reimu_dream_think, + }, +}; diff --git a/src/plrmodes/youmu.c b/src/plrmodes/youmu.c index e840dbb2..5fe50565 100644 --- a/src/plrmodes/youmu.c +++ b/src/plrmodes/youmu.c @@ -79,6 +79,10 @@ void youmu_common_shot(Player *plr) { } void youmu_common_bombbg(Player *plr) { + if(!player_is_bomb_active(plr)) { + return; + } + float t = player_get_bomb_progress(&global.plr, NULL); float fade = 1; diff --git a/src/plrmodes/youmu_b.c b/src/plrmodes/youmu_b.c index c6c3bdb7..a14a48a3 100644 --- a/src/plrmodes/youmu_b.c +++ b/src/plrmodes/youmu_b.c @@ -15,28 +15,7 @@ #include "youmu.h" static complex youmu_homing_target(complex org, complex fallback) { - double mindst = DBL_MAX; - complex target = fallback; - - if(global.boss && boss_is_vulnerable(global.boss)) { - target = global.boss->pos; - mindst = cabs(target - org); - } - - for(Enemy *e = global.enemies.first; e; e = e->next) { - if(e->hp == ENEMY_IMMUNE){ - continue; - } - - double dst = cabs(e->pos - org); - - if(dst < mindst) { - mindst = dst; - target = e->pos; - } - } - - return target; + return plrutil_homing_target(org, fallback); } static void youmu_homing_draw_common(Projectile *p, float clrfactor, float scale, float alpha) { diff --git a/src/projectile.c b/src/projectile.c index 22ce1769..0045df5d 100644 --- a/src/projectile.c +++ b/src/projectile.c @@ -232,6 +232,7 @@ static Projectile* _create_projectile(ProjArgs *args) { p->ent.draw_func = ent_draw_projectile; projectile_set_prototype(p, args->proto); + // p->collision_size *= 10; // p->size *= 5; @@ -245,6 +246,7 @@ static Projectile* _create_projectile(ProjArgs *args) { // But in that case, code that uses this function's return value must be careful to not dereference a NULL pointer. proj_call_rule(p, EVENT_BIRTH); + return alist_append(args->dest, p); } @@ -368,9 +370,9 @@ void calc_projectile_collision(Projectile *p, ProjCollisionResult *out_col) { void apply_projectile_collision(ProjectileList *projlist, Projectile *p, ProjCollisionResult *col, ProjectileListInterface *out_list_pointers) { switch(col->type) { - case PCOL_NONE: { + case PCOL_NONE: + case PCOL_VOID: break; - } case PCOL_PLAYER_GRAZE: { if(p->flags & PFLAG_GRAZESPAM) { @@ -386,25 +388,17 @@ void apply_projectile_collision(ProjectileList *projlist, Projectile *p, ProjCol } case PCOL_ENTITY: { - if(ent_damage(col->entity, &col->damage) == DMG_RESULT_OK) { - player_register_damage(&global.plr, col->entity, &col->damage); - } - + ent_damage(col->entity, &col->damage); break; } - case PCOL_VOID: { - break; - } - - default: { - log_fatal("Invalid collision type %x", col->type); - } + default: + UNREACHABLE; } if(col->fatal) { delete_projectile(projlist, p, out_list_pointers); - } else { + } else if(out_list_pointers) { *&out_list_pointers->list_interface = p->list_interface; } } diff --git a/src/projectile_prototypes/player.inc.h b/src/projectile_prototypes/player.inc.h index 9f5bc7bd..c49f23e7 100644 --- a/src/projectile_prototypes/player.inc.h +++ b/src/projectile_prototypes/player.inc.h @@ -5,5 +5,9 @@ PP_PLAYER(marisa, 15, 50) PP_PLAYER(maristar, 25, 25) PP_PLAYER(youhoming, 50, 50) PP_PLAYER(youmu, 16, 24) +PP_PLAYER(ofuda, 16, 26) +PP_PLAYER(needle, 14, 72) +PP_PLAYER(needle2, 7, 65) +PP_PLAYER(hakurei_seal,16, 39) #undef PP_PLAYER diff --git a/src/stagedraw.c b/src/stagedraw.c index 21db6f1f..6648cbbb 100644 --- a/src/stagedraw.c +++ b/src/stagedraw.c @@ -23,6 +23,13 @@ #define OBJPOOLSTATS_DEFAULT 0 #endif +typedef struct CustomFramebuffer { + LIST_INTERFACE(struct CustomFramebuffer); + Framebuffer fb; + float scale; + StageFBPair scaling_base; +} CustomFramebuffer; + static struct { struct { ShaderProgram *shader; @@ -37,6 +44,7 @@ static struct { PostprocessShader *viewport_pp; FBPair fb_pairs[NUM_FBPAIRS]; + CustomFramebuffer *custom_fbs; bool framerate_graphs; bool objpool_stats; @@ -82,6 +90,21 @@ static void update_fb_size(StageFBPair fb_id) { set_fb_size(fb_id, &w, &h); fbpair_resize_all(stagedraw.fb_pairs + fb_id, w, h); fbpair_viewport(stagedraw.fb_pairs + fb_id, 0, 0, w, h); + + if(fb_id != FBPAIR_FG_AUX) { + for(CustomFramebuffer *cfb = stagedraw.custom_fbs; cfb; cfb = cfb->next) { + if(cfb->scaling_base == fb_id) { + int sw = w * cfb->scale; + int sh = h * cfb->scale; + + for(uint i = 0; i < FRAMEBUFFER_MAX_ATTACHMENTS; ++i) { + fbutil_resize_attachment(&cfb->fb, i, sw, sh); + } + + r_framebuffer_viewport(&cfb->fb, 0, 0, sw, sh); + } + } + } } static bool stage_draw_event(SDL_Event *e, void *arg) { @@ -167,6 +190,58 @@ static void stage_draw_setup_framebuffers(void) { fbpair_viewport(stagedraw.fb_pairs + FBPAIR_BG, 0, 0, bg_width, bg_height); } +static Framebuffer* add_custom_framebuffer(StageFBPair fbtype, float scale_factor, uint num_attachments, FBAttachmentConfig attachments[num_attachments]) { + CustomFramebuffer *cfb = calloc(1, sizeof(*cfb)); + list_push(&stagedraw.custom_fbs, cfb); + + FBAttachmentConfig cfg[num_attachments]; + memcpy(cfg, attachments, sizeof(cfg)); + + int width, height; + set_fb_size(fbtype, &width, &height); + cfb->scale = scale_factor; + width *= scale_factor; + height *= scale_factor; + + for(uint i = 0; i < num_attachments; ++i) { + cfg[i].tex_params.width = width; + cfg[i].tex_params.height = height; + } + + r_framebuffer_create(&cfb->fb); + fbutil_create_attachments(&cfb->fb, num_attachments, cfg); + r_framebuffer_viewport(&cfb->fb, 0, 0, width, height); + + r_state_push(); + r_framebuffer(&cfb->fb); + r_clear_color4(0, 0, 0, 0); + r_clear(CLEAR_ALL); + r_state_pop(); + + return &cfb->fb; +} + +Framebuffer* stage_add_foreground_framebuffer(float scale_factor, uint num_attachments, FBAttachmentConfig attachments[num_attachments]) { + return add_custom_framebuffer(FBPAIR_FG, scale_factor, num_attachments, attachments); +} + +Framebuffer* stage_add_background_framebuffer(float scale_factor, uint num_attachments, FBAttachmentConfig attachments[num_attachments]) { + return add_custom_framebuffer(FBPAIR_BG, scale_factor, num_attachments, attachments); +} + +static void stage_draw_destroy_framebuffers(void) { + for(uint i = 0; i < NUM_FBPAIRS; ++i) { + fbpair_destroy(stagedraw.fb_pairs + i); + } + + for(CustomFramebuffer *cfb = stagedraw.custom_fbs, *next; cfb; cfb = next) { + next = cfb->next; + fbutil_destroy_attachments(&cfb->fb); + r_framebuffer_destroy(&cfb->fb); + free(list_unlink(&stagedraw.custom_fbs, cfb)); + } +} + void stage_draw_init(void) { preload_resources(RES_POSTPROCESS, RESF_OPTIONAL, "viewport", @@ -234,10 +309,7 @@ void stage_draw_init(void) { void stage_draw_shutdown(void) { events_unregister_handler(stage_draw_event); - - for(uint i = 0; i < NUM_FBPAIRS; ++i) { - fbpair_destroy(stagedraw.fb_pairs + i); - } + stage_draw_destroy_framebuffers(); } FBPair* stage_get_fbpair(StageFBPair id) { @@ -622,7 +694,9 @@ void stage_draw_scene(StageInfo *stage) { r_shader_standard(); // draw bomb background - if(global.plr.mode->procs.bombbg && player_is_bomb_active(&global.plr)) { + // FIXME: we need a more flexible and consistent way for entities to hook + // into the various stages of scene drawing code. + if(global.plr.mode->procs.bombbg /*&& player_is_bomb_active(&global.plr)*/) { global.plr.mode->procs.bombbg(&global.plr); } } else if(!key_nobg) { diff --git a/src/stagedraw.h b/src/stagedraw.h index 75beaea3..f354311c 100644 --- a/src/stagedraw.h +++ b/src/stagedraw.h @@ -27,3 +27,5 @@ void stage_draw_scene(StageInfo *stage); bool stage_should_draw_particle(Projectile *p); FBPair* stage_get_fbpair(StageFBPair id) attr_returns_nonnull; +Framebuffer* stage_add_foreground_framebuffer(float scale_factor, uint num_attachments, FBAttachmentConfig attachments[num_attachments]); +Framebuffer* stage_add_background_framebuffer(float scale_factor, uint num_attachments, FBAttachmentConfig attachments[num_attachments]); diff --git a/src/util/fbpair.c b/src/util/fbpair.c index a653bdac..8c713a9f 100644 --- a/src/util/fbpair.c +++ b/src/util/fbpair.c @@ -11,47 +11,20 @@ #include "fbpair.h" #include "global.h" #include "util.h" +#include "util/graphics.h" static void fbpair_create_fb(Framebuffer *fb, uint num_attachments, FBAttachmentConfig attachments[num_attachments]) { r_framebuffer_create(fb); - - for(uint i = 0; i < num_attachments; ++i) { - Texture *tex = calloc(1, sizeof(Texture)); - r_texture_create(tex, &attachments[i].tex_params); - r_framebuffer_attach(fb, tex, 0, attachments[i].attachment); - } + fbutil_create_attachments(fb, num_attachments, attachments); } static void fbpair_destroy_fb(Framebuffer *fb) { - for(uint i = 0; i < FRAMEBUFFER_MAX_ATTACHMENTS; ++i) { - Texture *tex = r_framebuffer_get_attachment(fb, i); - - if(tex != NULL) { - r_texture_destroy(tex); - free(tex); - } - } - + fbutil_destroy_attachments(fb); r_framebuffer_destroy(fb); } static void fbpair_resize_fb(Framebuffer *fb, FramebufferAttachment attachment, uint width, uint height) { - Texture *tex = r_framebuffer_get_attachment(fb, attachment); - - if(tex == NULL || (tex->w == width && tex->h == height)) { - return; - } - - // TODO: We could render a rescaled version of the old texture contents here - - TextureParams params; - r_texture_get_params(tex, ¶ms); - r_texture_destroy(tex); - params.width = width; - params.height = height; - params.mipmaps = 0; // FIXME - r_texture_create(tex, ¶ms); - r_framebuffer_attach(fb, tex, 0, attachment); + fbutil_resize_attachment(fb, attachment, width, height); } void fbpair_create(FBPair *pair, uint num_attachments, FBAttachmentConfig attachments[num_attachments]) { diff --git a/src/util/graphics.c b/src/util/graphics.c index f89f7b10..ce4f3fd8 100644 --- a/src/util/graphics.c +++ b/src/util/graphics.c @@ -114,3 +114,42 @@ void draw_framebuffer_tex(Framebuffer *fb, double width, double height) { r_cull(cull_saved); } + +void fbutil_create_attachments(Framebuffer *fb, uint num_attachments, FBAttachmentConfig attachments[num_attachments]) { + for(uint i = 0; i < num_attachments; ++i) { + Texture *tex = calloc(1, sizeof(Texture)); + log_debug("%i %i", attachments[i].tex_params.width, attachments[i].tex_params.height); + r_texture_create(tex, &attachments[i].tex_params); + r_framebuffer_attach(fb, tex, 0, attachments[i].attachment); + } +} + +void fbutil_destroy_attachments(Framebuffer *fb) { + for(uint i = 0; i < FRAMEBUFFER_MAX_ATTACHMENTS; ++i) { + Texture *tex = r_framebuffer_get_attachment(fb, i); + + if(tex != NULL) { + r_texture_destroy(tex); + free(tex); + } + } +} + +void fbutil_resize_attachment(Framebuffer *fb, FramebufferAttachment attachment, uint width, uint height) { + Texture *tex = r_framebuffer_get_attachment(fb, attachment); + + if(tex == NULL || (tex->w == width && tex->h == height)) { + return; + } + + // TODO: We could render a rescaled version of the old texture contents here + + TextureParams params; + r_texture_get_params(tex, ¶ms); + r_texture_destroy(tex); + params.width = width; + params.height = height; + params.mipmaps = 0; // FIXME + r_texture_create(tex, ¶ms); + r_framebuffer_attach(fb, tex, 0, attachment); +} diff --git a/src/util/graphics.h b/src/util/graphics.h index 241554e2..64fbcf6a 100644 --- a/src/util/graphics.h +++ b/src/util/graphics.h @@ -16,3 +16,7 @@ void colorfill(float r, float g, float b, float a); void fade_out(float f); void draw_stars(int x, int y, int numstars, int numfrags, int maxstars, int maxfrags, float alpha, float star_width); void draw_framebuffer_tex(Framebuffer *fb, double width, double height); + +void fbutil_create_attachments(Framebuffer *fb, uint num_attachments, FBAttachmentConfig attachments[num_attachments]); +void fbutil_destroy_attachments(Framebuffer *fb); +void fbutil_resize_attachment(Framebuffer *fb, FramebufferAttachment attachment, uint width, uint height);