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
This commit is contained in:
Andrei Alexeyev 2018-08-11 22:13:48 +03:00 committed by GitHub
parent 157a59dbbd
commit 3615b95f13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 2267 additions and 86 deletions

View file

@ -8,6 +8,7 @@ atlases = [
'common_ui',
'huge',
'portraits',
'reimu',
]
atlas_common_args = [

View file

@ -0,0 +1,3 @@
w = 128
h = 128

View file

@ -0,0 +1,4 @@
w = 20
h = 30

View file

@ -1,6 +1,3 @@
#w = 76
#h = 95
w = 48
h = 60

View file

@ -0,0 +1,3 @@
w = 54
h = 66

View file

@ -0,0 +1,4 @@
w = 16
h = 39

View file

@ -0,0 +1,4 @@
w = 14
h = 72

View file

@ -0,0 +1,3 @@
w = 7
h = 65

View file

@ -0,0 +1,4 @@
w = 16
h = 26

View file

@ -0,0 +1,3 @@
w = 24
h = 24

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
atlas/reimu/proj/needle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
atlas/reimu/proj/ofuda.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
atlas/reimu/yinyang.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -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 sleeps 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? Cant 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 Reimus 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 cant fix it, then?”
“Of course not. Its too… logical. Even though I created this barrier with
mathematics in mind, my parameters were fictional. Its 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 Yukaris 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 shrines 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 elses. And if nothing else, the shrine maiden was loath to become
homeless. That simply would not do.
“Yukari, youll take me there, wont 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
resources/gfx/gaplight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

View file

@ -0,0 +1,2 @@
wrap_t = mirror
wrap_s = clamp

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

12
resources/gfx/yinyang.spr Normal file
View 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

Binary file not shown.

View file

@ -33,3 +33,4 @@ warp = 80
damage_feedback = 120
hit0 = 120
hit1 = 120
bomb_reimu_a = 128

View file

@ -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

View file

@ -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',

View 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);
}

View file

@ -0,0 +1,2 @@
glsl_objects = standard.vert reimu_bomb_bg.frag

View 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
}

View file

@ -0,0 +1,2 @@
glsl_objects = standard.vert reimu_gap.frag

View 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;
}

View file

@ -0,0 +1,2 @@
glsl_objects = standard.vert reimu_gap_light.frag

View 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;
}

View file

@ -0,0 +1 @@
glsl_objects = sprite_default.vert sprite_yinyang.frag

View file

@ -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 });
}
}

View file

@ -1,4 +1,5 @@
dialog_src = files(
'marisa.c',
'reimu.c',
'youmu.c',
)

View file

@ -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 didnt 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 dont even remember who I fought. Does it even matter?”", NULL);
add_ending_entry(e, -1, "“Yes, it does! Because Wriggle shouldnt 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, "“Thats not true. Youre upset because you blame Gensōkyō for rejecting you when the reality is that you simply got lost, didnt you? Gensōkyō welcomes everyone, and it will still welcome you,” Reimu replied, kicking aside Ellys scythe before reaching out to pull her up. Elly hesitantly took it, her knees still shaking from Reimus 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 Ill just have to figure out the rest in your stead and then deal with it in any way that Ill need to. Seems like I cant go home yet…” Reimu declared, breaking it off with a sigh at the end.", NULL);
add_ending_entry(e, -1, "“Oh well, Ill 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 cant 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));

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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;
}

View file

@ -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);

View file

@ -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,

View file

@ -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 {

View file

@ -11,7 +11,6 @@
#include "global.h"
#include "plrmodes.h"
#include "marisa.h"
#include "renderer/api.h"
PlayerCharacter character_marisa = {
.id = PLR_CHAR_MARISA,

View file

@ -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;

View file

@ -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");

View file

@ -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
View 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,