Add Reimu Hakurei as a playable character (#106)
* Reimu (#101) * add the reimu * Add Reimu story * account for various blunders * add reimu dialog picture * Reimu: WIP yin-yang orbs * reimu: fix up indents * Reimu: swap the shotmode names to match the kanji order in her Japanese name * Reimu: compatibility with the latest system * WIP ReimuA crap * ReimuA homing trails * more ReimuA stuff * more ReimuA adjustments + enhanced DPS stats * Reimu: stubs for new player animation sequences * Reimu: occupy the 0th character slot * Reimu: tweak needle sprite * Reimu: buff movement speed to better match Touhou * Reimu: fixup for the recent projectile changes * ReimuA: make homing shots a bit smaller; give them custom effect on collision * Reimu: add intermediate frames; move some loose sprites to the atlas * Reimu: fix compile errors * replace DBL_MAX by INFINITY * Don’t draw reimu orbs twice fixes #127 * add new reimu dialog pic * ReimuA adjustments (mostly homing); it's still OP * wip ReimuB gaps * still not sure where i'm going with these gaps * meh * Reimu: premultiplied alpha fixups after rebase * reimuB shot pattern with basic power scaling (not balanced at all) * reimuB: some lame-ass particle effects * ReimuB bomb effect prototype * reimuA bomb prototype * fix reimu shots for the new damage system * Reimu: use the new player_is_bomb_active() function, add placeholder BG for ReimuB * some reimuB bomb projectiles * ReimuB bomb bg and some framebuffer utils required to support it. Might reuse this at least in part for ReimuA unless we come up with something better. * hack to fix ReimuB bomb fade; refactoring needed * reimuA damaging bombs * fix ub * prevent nan when reimuA bombs without enemies present * add a bomb_bg to reimuA * ... * various fantasy seal tweaks * Reimu: placeholder bomb sounds; slight fantasy seal buff * fix null pointer dereference * Reimu "balance" adjustments; minor fixes * putting bandaids over gunshot wounds * Add aoe damage and bullet cancel to ReimuB's bomb * more exorcism porn * make reimu bomb bg runes better visible on dark backgrounds * More ReimuA shot changes
|
@ -8,6 +8,7 @@ atlases = [
|
|||
'common_ui',
|
||||
'huge',
|
||||
'portraits',
|
||||
'reimu',
|
||||
]
|
||||
|
||||
atlas_common_args = [
|
||||
|
|
3
atlas/overrides/part/fantasyseal_impact.spr
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
w = 128
|
||||
h = 128
|
4
atlas/overrides/part/ofuda_glow.spr
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
w = 20
|
||||
h = 30
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
|
||||
#w = 76
|
||||
#h = 95
|
||||
|
||||
w = 48
|
||||
h = 60
|
||||
|
|
3
atlas/overrides/player/reimu.framegroup.spr
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
w = 54
|
||||
h = 66
|
4
atlas/overrides/proj/hakurei_seal.spr
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
w = 16
|
||||
h = 39
|
||||
|
4
atlas/overrides/proj/needle.spr
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
w = 14
|
||||
h = 72
|
||||
|
3
atlas/overrides/proj/needle2.spr
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
w = 7
|
||||
h = 65
|
4
atlas/overrides/proj/ofuda.spr
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
w = 16
|
||||
h = 26
|
||||
|
3
atlas/overrides/yinyang.spr
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
w = 24
|
||||
h = 24
|
BIN
atlas/reimu/part/fantasyseal_impact.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
atlas/reimu/part/ofuda_glow.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
atlas/reimu/player/reimu.frame0000.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
atlas/reimu/player/reimu.frame0001.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
atlas/reimu/player/reimu.frame0002.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
atlas/reimu/player/reimu.frame0003.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
atlas/reimu/player/reimu.frame0004.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
atlas/reimu/player/reimu.frame0005.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
atlas/reimu/player/reimu.frame0006.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
atlas/reimu/player/reimu.frame0007.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
atlas/reimu/player/reimu.frame0008.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
atlas/reimu/player/reimu.frame0009.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
atlas/reimu/player/reimu.frame0010.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
atlas/reimu/player/reimu.frame0011.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
atlas/reimu/proj/glowball.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
atlas/reimu/proj/hakurei_seal.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
atlas/reimu/proj/needle.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
atlas/reimu/proj/needle2.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
atlas/reimu/proj/ofuda.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
atlas/reimu/yinyang.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
|
@ -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
|
||||
|
|
BIN
resources/gfx/atlas_reimu_0.png
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
resources/gfx/dialog/reimu.png
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
resources/gfx/gaplight.png
Normal file
After Width: | Height: | Size: 243 KiB |
2
resources/gfx/gaplight.tex
Normal file
|
@ -0,0 +1,2 @@
|
|||
wrap_t = mirror
|
||||
wrap_s = clamp
|
12
resources/gfx/part/fantasyseal_impact.spr
Normal file
|
@ -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
|
12
resources/gfx/part/ofuda_glow.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.ani
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0000.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0001.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0002.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0003.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0004.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0005.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0006.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0007.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0008.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0009.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0010.spr
Normal file
|
@ -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
|
12
resources/gfx/player/reimu.frame0011.spr
Normal file
|
@ -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
|
7
resources/gfx/proj/glowball.spr
Normal file
|
@ -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
|
12
resources/gfx/proj/hakurei_seal.spr
Normal file
|
@ -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
|
12
resources/gfx/proj/needle.spr
Normal file
|
@ -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
|
12
resources/gfx/proj/needle2.spr
Normal file
|
@ -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
|
12
resources/gfx/proj/ofuda.spr
Normal file
|
@ -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
|
BIN
resources/gfx/runes.png
Normal file
After Width: | Height: | Size: 473 KiB |
12
resources/gfx/yinyang.spr
Normal file
|
@ -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
|
BIN
resources/sfx/bomb_reimu_a.ogg
Normal file
|
@ -33,3 +33,4 @@ warp = 80
|
|||
damage_feedback = 120
|
||||
hit0 = 120
|
||||
hit1 = 120
|
||||
bomb_reimu_a = 128
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
122
resources/shader/reimu_bomb_bg.frag.glsl
Normal file
|
@ -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);
|
||||
}
|
2
resources/shader/reimu_bomb_bg.prog
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
glsl_objects = standard.vert reimu_bomb_bg.frag
|
77
resources/shader/reimu_gap.frag.glsl
Normal file
|
@ -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
|
||||
}
|
2
resources/shader/reimu_gap.prog
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
glsl_objects = standard.vert reimu_gap.frag
|
50
resources/shader/reimu_gap_light.frag.glsl
Normal file
|
@ -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;
|
||||
}
|
2
resources/shader/reimu_gap_light.prog
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
glsl_objects = standard.vert reimu_gap_light.frag
|
9
resources/shader/sprite_yinyang.frag.glsl
Normal file
|
@ -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;
|
||||
}
|
1
resources/shader/sprite_yinyang.prog
Normal file
|
@ -0,0 +1 @@
|
|||
glsl_objects = sprite_default.vert sprite_yinyang.frag
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
dialog_src = files(
|
||||
'marisa.c',
|
||||
'reimu.c',
|
||||
'youmu.c',
|
||||
)
|
||||
|
|
34
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));
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
27
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "global.h"
|
||||
#include "plrmodes.h"
|
||||
#include "marisa.h"
|
||||
#include "renderer/api.h"
|
||||
|
||||
PlayerCharacter character_marisa = {
|
||||
.id = PLR_CHAR_MARISA,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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',
|
||||
|
|
145
src/plrmodes/reimu.c
Normal file
|
@ -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 <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
23
src/plrmodes/reimu.h
Normal file
|
@ -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 <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#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);
|
653
src/plrmodes/reimu_a.c
Normal file
|
@ -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 <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#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,
|
||||
},
|
||||
};
|
558
src/plrmodes/reimu_b.c
Normal file
|
@ -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 <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#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,
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|