New character art by Afensorm + other visual tweaks (#170)

* implement player spellcard declarations on bomb

* stagedraw: move "bottom text" to a separate (higher) layer

* update boss spell declaration effect

* import afensorm's character portrait art

* improve dialog visuals

* acknowledge afensorm in credits and COPYING

* 'alphamap' functionality; effects for wriggle and youmu portraits

* update afens art

* add cirno alphamap

* dialog: draw active speaker above other other

* charselect: use r_draw_sprite
This commit is contained in:
Andrei Alexeyev 2019-07-03 20:50:43 +03:00 committed by GitHub
parent 5f6ada9e7e
commit c2cd91463c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 696 additions and 230 deletions

View file

@ -23,10 +23,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The original soundtrack is composed by Tuck V and available under CC-BY 4.0.
It comprises the audio files under resources/bgm/.
It comprises the audio files under resources/00-taisei.pkgdir/bgm/.
https://www.youtube.com/channel/UCaw73cuHLnFCSpjOtt_9pyg
https://creativecommons.org/licenses/by/4.0/
The character portraits under atlas/portraits/ are created by afensorm as a
commission, and are available under CC-BY 4.0.
https://afens.surge.sh
https://gensokyo.social/@afensorm
https://pixiv.me/afens
The photos used in the Spellcard and menu backgrounds are generally in the
public domain or CC0 and were not taken by us. Here are some honorary mentions.

View file

@ -43,7 +43,7 @@ atlases = [
['common_ui', ['--width=1024', '--height=1024']],
['gray16', [preset_png]],
['huge', []],
['portraits', ['--width=2048', '--height=4096']],
['portraits', ['--width=3600', '--height=4096']],
]
atlas_profiles = [

View file

@ -1,3 +1,3 @@
w = 336.5
h = 500
w = 326.5
h = 478

View file

@ -1,3 +1,3 @@
w = 324
w = 388
h = 500

View file

@ -1,3 +1,3 @@
w = 284
h = 500
w = 338.5
h = 493.5

View file

@ -1,3 +1,3 @@
w = 348
h = 500
w = 340
h = 513

View file

@ -1,3 +1,3 @@
w = 277.5
h = 500
w = 402.5
h = 478

View file

@ -1,3 +1,3 @@
w = 290.5
h = 500
w = 257
h = 498.5

View file

@ -1,3 +1,3 @@
w = 350.5
h = 500
w = 311
h = 491.5

View file

@ -0,0 +1,4 @@
w = 263.5
h = 474
offset_x = -16

View file

@ -1,3 +1,3 @@
w = 333.5
h = 500
w = 322
h = 545.5

View file

@ -1,3 +1,4 @@
w = 293
h = 500
w = 288.5
h = 491
offset_x = 32

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -1,6 +1,7 @@
# Autogenerated by the atlas packer, do not modify
source = res/gfx/atlas_portraits_0.webp
alphamap = res/gfx/atlas_portraits_0.alphamap.webp
# -- Pasted from the global override file --

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View file

@ -2,11 +2,11 @@
texture = atlas_portraits_0
region_x = 2
region_y = 1006
region_w = 673
region_h = 1000
region_y = 962
region_w = 653
region_h = 956
# -- Pasted from the override file --
w = 336.5
h = 500
w = 326.5
h = 478

View file

@ -1,12 +1,12 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 1350
region_y = 1006
region_w = 648
region_x = 811
region_y = 2
region_w = 776
region_h = 1000
# -- Pasted from the override file --
w = 324
w = 388
h = 500

View file

@ -1,12 +1,12 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 587
region_y = 2010
region_w = 568
region_h = 1000
region_x = 2275
region_y = 2
region_w = 677
region_h = 987
# -- Pasted from the override file --
w = 284
h = 500
w = 338.5
h = 493.5

View file

@ -1,12 +1,12 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 707
region_x = 1591
region_y = 2
region_w = 696
region_h = 1000
region_w = 680
region_h = 1026
# -- Pasted from the override file --
w = 348
h = 500
w = 340
h = 513

View file

@ -1,12 +1,12 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 1159
region_y = 2010
region_w = 555
region_h = 1000
region_x = 2
region_y = 2
region_w = 805
region_h = 956
# -- Pasted from the override file --
w = 277.5
h = 500
w = 402.5
h = 478

View file

@ -1,12 +1,12 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 2
region_y = 2010
region_w = 581
region_h = 1000
region_x = 1190
region_y = 1032
region_w = 514
region_h = 997
# -- Pasted from the override file --
w = 290.5
h = 500
w = 257
h = 498.5

View file

@ -1,12 +1,12 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 2
region_x = 2956
region_y = 2
region_w = 701
region_h = 1000
region_w = 622
region_h = 983
# -- Pasted from the override file --
w = 350.5
h = 500
w = 311
h = 491.5

View file

@ -0,0 +1,13 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 659
region_y = 1006
region_w = 527
region_h = 948
# -- Pasted from the override file --
w = 263.5
h = 474
offset_x = -16

View file

@ -1,12 +1,12 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 679
region_y = 1006
region_w = 667
region_h = 1000
region_x = 2275
region_y = 993
region_w = 644
region_h = 1091
# -- Pasted from the override file --
w = 333.5
h = 500
w = 322
h = 545.5

View file

@ -1,12 +1,13 @@
# Autogenerated by the atlas packer, do not modify
texture = atlas_portraits_0
region_x = 1407
region_y = 2
region_w = 586
region_h = 1000
region_x = 2956
region_y = 989
region_w = 577
region_h = 982
# -- Pasted from the override file --
w = 293
h = 500
w = 288.5
h = 491
offset_x = 32

View file

@ -70,6 +70,8 @@ glsl_files = files(
'standardnotex.vert.glsl',
'text_default.frag.glsl',
'text_default.vert.glsl',
'text_dialog.frag.glsl',
'text_dialog.vert.glsl',
'text_example.frag.glsl',
'text_example.vert.glsl',
'text_hud.frag.glsl',

View file

@ -0,0 +1,29 @@
#version 330 core
#include "lib/render_context.glslh"
#include "lib/sprite_main.frag.glslh"
#include "lib/util.glslh"
float sampleNoise(vec2 tc) {
tc.y *= fwidth(tc.x) / fwidth(tc.y);
return texture(tex_aux[0], tc).r;
}
void spriteMain(out vec4 fragColor) {
float t = customParams.x;
float r = customParams.y;
vec4 glyph = texture(tex, texCoord);
float noise = sampleNoise(texCoordOverlay);
float d = 0.5;
float x = 1 - t;
float slope = x;
float f = smoothstep(0.5 - d * (1.0 - x), 0.5 + d * x, r + (1 - 2 * r) * mix(texCoordOverlay.y, 1.0 - texCoordOverlay.x, slope) - x + 0.5);
vec4 textfrag = vec4(vec3(glyph.r + 0.25 * glyph.g), glyph.g);
textfrag *= smoothstep(1 - 2 * f, 1-f, noise);
textfrag *= color;
fragColor = textfrag;
}

View file

@ -0,0 +1,2 @@
objects = text_dialog.vert text_dialog.frag

View file

@ -0,0 +1,8 @@
#version 330 core
#define SPRITE_OUT_COLOR
#define SPRITE_OUT_TEXCOORD
#define SPRITE_OUT_TEXCOORD_OVERLAY
#define SPRITE_OUT_CUSTOM
#include "lib/sprite_default.vert.glslh"

View file

@ -30,7 +30,7 @@ void spriteMain(out vec4 fragColor) {
fragColor = mix(shadowfrag, textfrag, sqrt(textfrag.a));
// The overlay coordinates are outside of [0,1] in the padding region, so we make sure there are no wrap around artifacts when a bit of text is distorted to this region.
tc_overlay = clamp(tc_overlay, 0.01, 0.99);
tc_overlay = clamp(tc_overlay, 0.01, 0.99);
fragColor *= clamp((texture(tex_aux[0], tc_overlay).r + 0.5) * 2.5 * t-0.5, 0.0, 1.0);
}

View file

@ -2,7 +2,14 @@
#include "interface/standard.glslh"
UNIFORM(1) sampler2D alphamap;
UNIFORM(2) int have_alphamap;
void main(void) {
fragColor = textureLod(tex, texCoordRaw, 0);
fragColor.rgb *= fragColor.a;
if(have_alphamap != 0) {
fragColor.a *= textureLod(alphamap, texCoordRaw, 0).r;
}
}

View file

@ -79,7 +79,7 @@ def write_sprite_def(dst, texture, region, overrides=None):
update_text_file(dst, text)
def write_texture_def(dst, texture, texture_fmt, global_overrides=None, local_overrides=None):
def write_texture_def(dst, texture, texture_fmt, global_overrides=None, local_overrides=None, have_alphamap=False):
dst.parent.mkdir(exist_ok=True, parents=True)
text = (
@ -87,6 +87,9 @@ def write_texture_def(dst, texture, texture_fmt, global_overrides=None, local_ov
f'source = res/gfx/{texture}.{texture_fmt}\n'
)
if have_alphamap:
text += f'alphamap = res/gfx/{texture}.alphamap.{texture_fmt}\n'
if global_overrides is not None:
text += f'\n# -- Pasted from the global override file --\n\n{global_overrides.strip()}\n'
@ -140,6 +143,14 @@ def get_override_file_name(basename):
return f'{basename}.spr'
def find_alphamap(basepath):
for fmt in texture_formats:
mpath = basepath.with_suffix(f'.alphamap.{fmt}')
if mpath.is_file():
return mpath
def gen_atlas(overrides, src, dst, binsize, atlasname, tex_format=texture_formats[0], border=1, force_single=False, crop=True, leanify=True):
overrides = Path(overrides).resolve()
src = Path(src).resolve()
@ -165,7 +176,7 @@ def gen_atlas(overrides, src, dst, binsize, atlasname, tex_format=texture_format
rects = []
for path in src.glob('**/*.*'):
if path.is_file() and path.suffix[1:].lower() in texture_formats:
if path.is_file() and path.suffix[1:].lower() in texture_formats and path.with_suffix('').suffix != '.alphamap':
img = Image.open(path)
sprite_name = path.relative_to(src).with_suffix('').as_posix()
sprite_config_path = overrides / (get_override_file_name(sprite_name) + '.conf')
@ -237,10 +248,10 @@ def gen_atlas(overrides, src, dst, binsize, atlasname, tex_format=texture_format
# dstfile = temp_dst / f'{textureid}.{tex_format}'
# NOTE: we always save PNG first and convert with an external tool later if needed.
dstfile = temp_dst / f'{textureid}.png'
dstfile_alphamap = temp_dst / f'{textureid}.alphamap.png'
print(dstfile)
dstfile_meta = temp_dst / f'{textureid}.tex'
write_texture_def(dstfile_meta, textureid, tex_format, texture_global_overrides, texture_local_overrides)
actual_size = [0, 0]
@ -254,21 +265,32 @@ def gen_atlas(overrides, src, dst, binsize, atlasname, tex_format=texture_format
else:
actual_size = (bin.width, bin.height)
composite_cmd = [
base_composite_cmd = [
'convert',
'-verbose',
'-size', f'{actual_size[0]}x{actual_size[1]}',
'xc:none',
]
composite_cmd = base_composite_cmd.copy() + ['xc:none']
alphamap_composite_cmd = None
for rect in bin:
img_path, name = rect.rid
border = get_border(name)
alphamap_path = find_alphamap(img_path)
composite_cmd += [
str(img_path), '-geometry', '{:+}{:+}'.format(rect.x + border, rect.y + border), '-composite'
]
if alphamap_path:
if alphamap_composite_cmd is None:
alphamap_composite_cmd = base_composite_cmd.copy() + ['xc:white']
alphamap_composite_cmd += [
str(alphamap_path), '-geometry', '{:+}{:+}'.format(rect.x + border, rect.y + border), '-composite'
]
override_path = overrides / get_override_file_name(name)
if override_path.exists():
@ -286,8 +308,16 @@ def gen_atlas(overrides, src, dst, binsize, atlasname, tex_format=texture_format
composite_cmd += [str(dstfile)]
@executor.submit
def process(dstfile=dstfile):
write_texture_def(
dstfile_meta,
textureid,
tex_format,
texture_global_overrides,
texture_local_overrides,
have_alphamap=alphamap_composite_cmd is not None
)
def process(composite_cmd, dstfile):
subprocess.check_call(composite_cmd)
oldfmt = dstfile.suffix[1:].lower()
@ -316,14 +346,18 @@ def gen_atlas(overrides, src, dst, binsize, atlasname, tex_format=texture_format
if leanify:
subprocess.check_call(['leanify', '-v', str(dstfile)])
futures.append(process)
futures.append(executor.submit(lambda: process(composite_cmd, dstfile)))
if alphamap_composite_cmd is not None:
alphamap_composite_cmd += [str(dstfile_alphamap)]
futures.append(executor.submit(lambda: process(alphamap_composite_cmd, dstfile_alphamap)))
# Wait for subprocesses to complete.
wait_for_futures(futures)
executor.shutdown(wait=True)
# Only now, if everything is ok so far, copy everything to the destination, possibly overwriting previous results
pattern = re.compile(rf'^atlas_{re.escape(atlasname)}_\d+.({"|".join(texture_formats + ["tex"])})$')
pattern = re.compile(rf'^atlas_{re.escape(atlasname)}_\d+(?:\.alphamap)?.({"|".join(texture_formats + ["tex"])})$')
for path in dst.iterdir():
if pattern.match(path.name):
path.unlink()

View file

@ -14,6 +14,7 @@
#include "stagetext.h"
#include "stagedraw.h"
#include "entity.h"
#include "util/glm.h"
static void ent_draw_boss(EntityInterface *ent);
static DamageResult ent_damage_boss(EntityInterface *ent, const DamageInfo *dmg);
@ -186,7 +187,7 @@ static void update_healthbar(Boss *boss) {
boss_is_fleeing(boss) ||
!boss->current ||
boss->current->type == AT_Move ||
global.dialog
dialog_is_active(global.dialog)
) {
target_opacity = 0.0;
}
@ -310,7 +311,7 @@ static void update_hud(Boss *boss) {
target_opacity = 0.0;
}
if(!boss->current || boss->current->type == AT_Move || global.dialog) {
if(!boss->current || boss->current->type == AT_Move || dialog_is_active(global.dialog)) {
target_opacity = 0.0;
}
@ -409,7 +410,7 @@ static void draw_spell_name(Boss *b, int time, bool healthbar_radial) {
r_draw_sprite(&(SpriteParams) {
.sprite = "spell",
.pos = { (VIEWPORT_W - 128) * (1 - pow(1 - f2, 5)) -256 * pow(1 - f2, 2), y_offset },
.pos = { (VIEWPORT_W - 128), y_offset * (1 - pow(1 - f2, 5)) + VIEWPORT_H * pow(1 - f2, 2) },
.color = color_mul_scalar(RGBA(1, 1, 1, f2 * 0.5), opacity * f2) ,
.scale.both = 3 - 2 * (1 - pow(1 - f2, 3)),
});
@ -424,7 +425,7 @@ static void draw_spell_name(Boss *b, int time, bool healthbar_radial) {
r_mat_translate(creal(x), cimag(x),0);
float scale = f+1.*(1-f)*(1-f)*(1-f);
r_mat_scale(scale,scale,1);
r_mat_rotate_deg(360*f,1,1,0);
r_mat_rotate(glm_ease_quad_out(f) * 2 * M_PI, 0.8, -0.2, 0);
float spellname_opacity_noplr = opacity_noplr * min(1, warn_progress/0.6);
float spellname_opacity = spellname_opacity_noplr * b->hud.plrproximity_opacity;
@ -501,6 +502,72 @@ static void draw_spell_name(Boss *b, int time, bool healthbar_radial) {
}
}
static void draw_spell_portrait(Boss *b, int time) {
const int anim_time = 200;
if(time <= 0 || time >= anim_time) {
return;
}
if(!b->dialog) {
return;
}
if(b->current->draw_rule == NULL) {
// No spell background? Assume not cut-in is intended, either.
return;
}
// NOTE: Mostly copypasted from player.c::player_draw_overlay()
// TODO: Maybe somehow generalize and make it more intelligible
float a = time / (float)anim_time;
r_state_push();
r_shader("sprite_default");
float char_in = clamp(a * 1.5, 0, 1);
float char_out = min(1, 2 - (2 * a));
float char_opacity_in = 0.75 * min(1, a * 5);
float char_opacity = char_opacity_in * char_out * char_out;
float char_xofs = -20 * a;
Sprite *char_spr = b->dialog;
r_mat_push();
r_mat_scale(-1, 1, 1);
r_mat_translate(-VIEWPORT_W, 0, 0);
r_cull(CULL_FRONT);
for(int i = 1; i <= 3; ++i) {
float t = a * 200;
float dur = 20;
float start = 200 - dur * 5;
float end = start + dur;
float ofs = 0.2 * dur * (i - 1);
float o = 1 - smoothstep(start + ofs, end + ofs, t);
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = char_spr,
.pos = { char_spr->w * 0.5 + VIEWPORT_W * pow(1 - char_in, 4 - i * 0.3) - i + char_xofs, VIEWPORT_H - char_spr->h * 0.5 },
.color = color_mul_scalar(color_add(RGBA(0.2, 0.2, 0.2, 0), RGBA(i==1, i==2, i==3, 0)), char_opacity_in * (1 - char_in * o) * o),
.flip.x = true,
.scale.both = 1.0 + 0.02 * (min(1, a * 1.2)) + i * 0.5 * pow(1 - o, 2),
});
}
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = char_spr,
.pos = { char_spr->w * 0.5 + VIEWPORT_W * pow(1 - char_in, 4) + char_xofs, VIEWPORT_H - char_spr->h * 0.5 },
.color = RGBA_MUL_ALPHA(1, 1, 1, char_opacity * min(1, char_in * 2) * (1 - min(1, (1 - char_out) * 5))),
.flip.x = true,
.scale.both = 1.0 + 0.1 * (1 - char_out),
});
r_mat_pop();
r_state_pop();
}
static void BossGlow(Projectile *p, int t) {
float s = 1.0+t/(double)p->timeout*0.5;
float fade = 1 - (1.5 - s);
@ -638,7 +705,17 @@ void draw_boss_overlay(Boss *boss) {
draw_boss_text(ALIGN_LEFT, 10, 20 + 8 * !radial_style, boss->name, get_font("standard"), RGBA(o, o, o, o));
if(ATTACK_IS_SPELL(boss->current->type)) {
draw_spell_name(boss, global.frames - boss->current->starttime, radial_style);
int t_portrait, t_spell;
t_portrait = t_spell = global.frames - boss->current->starttime;
if(boss->current->type == AT_ExtraSpell) {
t_portrait += ATTACK_START_DELAY_EXTRA;
} else {
t_portrait += ATTACK_START_DELAY;
}
draw_spell_portrait(boss, t_portrait);
draw_spell_name(boss, t_spell, radial_style);
}
float remaining = boss->hud.attack_timer;
@ -919,7 +996,7 @@ void process_boss(Boss **pboss) {
spawn_particle_effects(boss);
if(!boss->current || global.dialog) {
if(!boss->current || dialog_is_active(global.dialog)) {
return;
}
@ -1098,7 +1175,7 @@ void process_boss(Boss **pboss) {
boss->current->starttime = global.frames;
boss->current->rule(boss, EVENT_BIRTH);
if(global.dialog) {
if(dialog_is_active(global.dialog)) {
break;
}

View file

@ -36,7 +36,7 @@ static struct {
#define CREDITS_FADEOUT 180
#define ENTRY_TIME 345
#define ENTRY_TIME 322
static void credits_add(char *data, int time);
@ -70,6 +70,13 @@ static void credits_fill(void) {
"Original soundtrack"
), ENTRY_TIME);
credits_add((
"afensorm\n"
"https://gensokyo.social/@afensorm\n"
"https://pixiv.me/afens\n\n"
"Character art"
), ENTRY_TIME);
credits_add((
"Lalasa\n"
"Ola Kruzel\n"
@ -127,7 +134,7 @@ static void credits_fill(void) {
"http://www.libpng.org/\n\n"
"Ogg Opus\n"
"https://www.opus-codec.org/"
), 350);
), 327);
credits_add((
"\n"
@ -138,7 +145,7 @@ static void credits_fill(void) {
"M cross environment\n"
"http://mxe.cc/\n\n"
"and many other projects"
), 350);
), 327);
credits_add((
"Mochizuki Ado\n"

View file

@ -12,8 +12,7 @@
#include "global.h"
Dialog *create_dialog(const char *left, const char *right) {
Dialog *d = malloc(sizeof(Dialog));
memset(d, 0, sizeof(Dialog));
Dialog *d = calloc(1, sizeof(Dialog));
if(left) {
d->images[Left] = get_sprite(left);
@ -28,6 +27,20 @@ Dialog *create_dialog(const char *left, const char *right) {
return d;
}
static int message_index(Dialog *d, int offset) {
int idx = d->pos + offset;
if(idx >= d->count) {
idx = d->count - 1;
}
while(idx >= 0 && d->messages[idx].side == BGM) {
--idx;
}
return idx;
}
void dset_image(Dialog *d, Side side, const char *name) {
d->images[side] = get_sprite(name);
}
@ -42,22 +55,65 @@ DialogMessage* dadd_msg(Dialog *d, Side side, const char *msg) {
}
void delete_dialog(Dialog *d) {
int i;
for(i = 0; i < d->count; i++)
for(int i = 0; i < d->count; i++) {
free(d->messages[i].msg);
}
free(d->messages);
free(d);
}
void draw_dialog(Dialog *dialog) {
CullFaceMode cull_saved = r_cull_current();
if(dialog == NULL) {
return;
}
float o = dialog->opacity;
if(o == 0) {
return;
}
r_state_push();
r_state_push();
r_shader("sprite_default");
r_mat_push();
r_mat_translate(VIEWPORT_W/2.0, VIEWPORT_H*3.0/4.0, 0);
r_mat_translate(VIEWPORT_X, 0, 0);
int i;
for(i = 0; i < 2; i++) {
const double dialog_width = VIEWPORT_W * 1.2;
r_mat_push();
r_mat_translate(dialog_width/2.0, 64, 0);
int cur_idx = message_index(dialog, 0);
int pre_idx = message_index(dialog, -1);
assume(cur_idx >= 0);
int cur_side = dialog->messages[cur_idx].side;
int pre_side = pre_idx >= 0 ? dialog->messages[pre_idx].side : 2;
Color clr = { 0 };
const float page_time = 10;
float page_alpha = min(global.frames - (dialog->page_time), page_time) / page_time;
const float page_text_time = 60;
float page_text_alpha = min(global.frames - dialog->page_time, page_text_time) / page_text_time;
int loop_start = 1;
int loop_incr = 1;
if(cur_side == 0) {
loop_start = 1;
loop_incr = -1;
} else {
loop_start = 0;
loop_incr = 1;
}
for(int i = loop_start; i < 2 && i >= 0; i += loop_incr) {
r_mat_push();
if(i == Left) {
@ -67,72 +123,130 @@ void draw_dialog(Dialog *dialog) {
r_cull(CULL_BACK);
}
if(global.frames - dialog->birthtime < 30)
r_mat_translate(120 - (global.frames - dialog->birthtime)*4, 0, 0);
int cur = dialog->messages[dialog->pos].side;
int pre = 2;
if(dialog->pos > 0)
pre = dialog->messages[dialog->pos-1].side;
short dir = (1 - 2*(i == dialog->messages[dialog->pos].side));
if(global.frames - dialog->page_time < 10 && ((i != pre && i == cur) || (i == pre && i != cur))) {
int time = (global.frames - dialog->page_time) * dir;
r_mat_translate(time, time, 0);
float clr = min(1.0 - 0.07*time,1);
r_color3(clr, clr, clr);
} else {
r_mat_translate(dir*10, dir*10, 0);
r_color3(1 - (dir>0)*0.7, 1 - (dir>0)*0.7, 1 - (dir>0)*0.7);
if(o < 1) {
r_mat_translate(120 * (1 - o), 0, 0);
}
r_mat_translate(VIEWPORT_W*7.0/18.0, 0, 0);
if(dialog->images[i])
draw_sprite_batched_p(0, 0, dialog->images[i]);
r_mat_pop();
float dir = (1 - 2 * (i == cur_side));
float ofs = 10 * dir;
r_color3(1,1,1);
if(page_alpha < 10 && ((i != pre_side && i == cur_side) || (i == pre_side && i != cur_side))) {
r_mat_translate(ofs * page_alpha, ofs * page_alpha, 0);
float brightness = min(1.0 - 0.7 * page_alpha * dir, 1);
clr.r = clr.g = clr.b = brightness;
clr.a = 1;
} else {
r_mat_translate(ofs, ofs, 0);
clr = *RGB(1 - (dir > 0) * 0.7, 1 - (dir > 0) * 0.7, 1 - (dir > 0) * 0.7);
}
color_mul_scalar(&clr, o);
r_color(&clr);
if(dialog->images[i]) {
Sprite *s = dialog->images[i];
r_mat_translate((dialog_width - s->w)/2 + 32, VIEWPORT_H - s->h/2, 0);
draw_sprite_batched_p(0, 0, s);
r_state_push();
r_shader_standard_notex();
r_mat_push();
r_mat_scale(s->w, s->h, 1);
// r_mat_translate(0.5, 0.5, 0);
r_color4(0.2, 0.2, 0.2, 0.2);
// r_draw_quad();
r_mat_pop();
r_state_pop();
}
r_mat_pop();
}
r_mat_pop();
r_cull(cull_saved);
r_state_pop();
o *= smooth(clamp((global.frames - dialog->birthtime - 10) / 30.0, 0, 1));
FloatRect dialog_bg_rect = {
.extent = { VIEWPORT_W-40, 110 },
.offset = { VIEWPORT_W/2, VIEWPORT_H-55 },
};
r_mat_push();
if(global.frames - dialog->birthtime < 25)
r_mat_translate(0, 100-(global.frames-dialog->birthtime)*4, 0);
r_color4(0,0,0,0.8);
if(o < 1) {
r_mat_translate(0, 100 * (1 - o), 0);
}
r_color4(0, 0, 0, 0.8 * o);
r_mat_push();
r_mat_translate(VIEWPORT_W/2, VIEWPORT_H-75, 0);
r_mat_scale(VIEWPORT_W-40, 110, 1);
r_mat_translate(dialog_bg_rect.x, dialog_bg_rect.y, 0);
r_mat_scale(dialog_bg_rect.w, dialog_bg_rect.h, 1);
r_shader_standard_notex();
r_draw_quad();
r_shader_standard();
r_mat_pop();
r_color4(1,1,1,1);
if(dialog->messages[dialog->pos].side == Right)
r_color3(0.6,0.6,1);
Font *font = get_font("standard");
r_shader("text_default");
text_draw_wrapped(dialog->messages[dialog->pos].msg, VIEWPORT_W * 0.85, &(TextParams) {
.pos = { VIEWPORT_W/2, VIEWPORT_H-110 },
.align = ALIGN_CENTER
r_mat_mode(MM_TEXTURE);
r_mat_push();
// r_mat_scale(2, 0.2, 0);
// r_mat_translate(0, -global.frames/page_text_time, 0);
r_mat_mode(MM_MODELVIEW);
dialog_bg_rect.w = VIEWPORT_W * 0.86;
dialog_bg_rect.x -= dialog_bg_rect.w * 0.5;
dialog_bg_rect.y -= dialog_bg_rect.h * 0.5;
// dialog_bg_rect.h = dialog_bg_rect.w;
if(pre_idx >= 0 && page_text_alpha < 1) {
if(pre_side == Right) {
clr = *RGB(0.6, 0.6, 1.0);
} else {
clr = *RGB(1.0, 1.0, 1.0);
}
color_mul_scalar(&clr, o);
text_draw_wrapped(dialog->messages[pre_idx].msg, VIEWPORT_W * 0.86, &(TextParams) {
.shader = "text_dialog",
.aux_textures = { get_tex("cell_noise") },
.shader_params = &(ShaderCustomParams) {{ o * (1.0 - (0.2 + 0.8 * page_text_alpha)), 1 }},
.color = &clr,
.pos = { VIEWPORT_W/2, VIEWPORT_H-110 + font_get_lineskip(font) },
.align = ALIGN_CENTER,
.font_ptr = font,
.overlay_projection = &dialog_bg_rect,
});
}
if(cur_side == Right) {
clr = *RGB(0.6, 0.6, 1.0);
} else {
clr = *RGB(1.0, 1.0, 1.0);
}
color_mul_scalar(&clr, o);
text_draw_wrapped(dialog->messages[cur_idx].msg, VIEWPORT_W * 0.86, &(TextParams) {
.shader = "text_dialog",
.aux_textures = { get_tex("cell_noise") },
.shader_params = &(ShaderCustomParams) {{ o * page_text_alpha, 0 }},
.color = &clr,
.pos = { VIEWPORT_W/2, VIEWPORT_H-110 + font_get_lineskip(font) },
.align = ALIGN_CENTER,
.font_ptr = font,
.overlay_projection = &dialog_bg_rect,
});
if(dialog->messages[dialog->pos].side == Right)
r_color3(1,1,1);
r_mat_mode(MM_TEXTURE);
r_mat_pop();
r_mat_mode(MM_MODELVIEW);
r_mat_pop();
r_shader("sprite_default");
r_mat_pop();
r_state_pop();
}
bool page_dialog(Dialog **d) {
if(!*d) {
if(!*d || (*d)->pos >= (*d)->count) {
return false;
}
@ -146,9 +260,6 @@ bool page_dialog(Dialog **d) {
(*d)->page_time = global.frames;
if((*d)->pos >= (*d)->count) {
delete_dialog(*d);
*d = NULL;
// XXX: maybe this can be handled elsewhere?
if(!global.boss)
global.timer++;
@ -165,12 +276,30 @@ void process_dialog(Dialog **d) {
return;
}
int to = (*d)->messages[(*d)->pos].timeout;
if(dialog_is_active(*d)) {
int to = (*d)->messages[(*d)->pos].timeout;
if(
(to && to >= global.frames) ||
((global.plr.inputflags & INFLAG_SKIP) && global.frames - (*d)->page_time > 3)
) {
page_dialog(d);
if(
(to && to >= global.frames) ||
((global.plr.inputflags & INFLAG_SKIP) && global.frames - (*d)->page_time > 3)
) {
page_dialog(d);
}
}
// important to check this again; the page_dialog call may have ended the dialog
if(dialog_is_active(*d)) {
fapproach_asymptotic_p(&(*d)->opacity, 1, 0.05, 1e-3);
} else {
fapproach_asymptotic_p(&(*d)->opacity, 0, 0.1, 1e-3);
if((*d)->opacity == 0) {
delete_dialog(*d);
*d = NULL;
}
}
}
bool dialog_is_active(Dialog *d) {
return d && (d->pos < d->count);
}

View file

@ -14,7 +14,6 @@
#include "resource/sprite.h"
struct DialogMessage;
struct DialogSpeaker;
typedef enum {
Right,
@ -34,10 +33,10 @@ typedef struct Dialog {
int count;
int pos;
int page_time;
int birthtime;
float opacity;
} Dialog;
Dialog *create_dialog(const char *left, const char *right)
@ -52,12 +51,13 @@ DialogMessage* dadd_msg(Dialog *d, Side side, const char *msg)
void delete_dialog(Dialog *d)
attr_nonnull(1);
void draw_dialog(Dialog *dialog)
attr_nonnull(1);
void draw_dialog(Dialog *dialog);
bool page_dialog(Dialog **d) attr_nonnull(1);
void process_dialog(Dialog **d) attr_nonnull(1);
bool dialog_is_active(Dialog *d);
// FIXME: might not be the best place for this
typedef struct PlayerDialogProcs {
void (*stage1_pre_boss)(Dialog *d);

View file

@ -68,7 +68,7 @@ MenuData* create_char_menu(void) {
}