1 /*
2 * This software is licensed under the terms of the MIT License.
3 * See COPYING for further information.
4 * ---
5 * Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
6 * Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
7 */
8
9 #include "taisei.h"
10
11 #include "dialog.h"
12 #include "global.h"
13
dialog_create(void)14 Dialog *dialog_create(void) {
15 Dialog *d = calloc(1, sizeof(Dialog));
16 d->page_time = global.frames;
17 d->birthtime = global.frames;
18 return d;
19 }
20
dialog_set_image(Dialog * d,DialogSide side,const char * img)21 void dialog_set_image(Dialog *d, DialogSide side, const char *img) {
22 d->images[side] = img ? get_sprite(img) : NULL;
23 }
24
message_index(Dialog * d,int offset)25 static int message_index(Dialog *d, int offset) {
26 int idx = d->pos + offset;
27
28 if(idx >= d->count) {
29 idx = d->count - 1;
30 }
31
32 while(
33 idx >= 0 &&
34 d->actions[idx].type != DIALOG_MSG_LEFT &&
35 d->actions[idx].type != DIALOG_MSG_RIGHT
36 ) {
37 --idx;
38 }
39
40 return idx;
41 }
42
dialog_add_action(Dialog * d,DialogActionType side,const char * msg)43 DialogAction * dialog_add_action(Dialog *d, DialogActionType side, const char *msg) {
44 d->actions = realloc(d->actions, (++d->count)*sizeof(DialogAction));
45 d->actions[d->count-1].type = side;
46 d->actions[d->count-1].msg = malloc(strlen(msg) + 1);
47 d->actions[d->count-1].timeout = 0;
48 strlcpy(d->actions[d->count-1].msg, msg, strlen(msg) + 1);
49 return &d->actions[d->count-1];
50 }
51
dialog_destroy(Dialog * d)52 void dialog_destroy(Dialog *d) {
53 for(int i = 0; i < d->count; i++) {
54 free(d->actions[i].msg);
55 }
56
57 free(d->actions);
58 free(d);
59 }
60
dialog_draw(Dialog * dialog)61 void dialog_draw(Dialog *dialog) {
62 if(dialog == NULL) {
63 return;
64 }
65
66 float o = dialog->opacity;
67
68 if(o == 0) {
69 return;
70 }
71
72 r_state_push();
73 r_state_push();
74 r_shader("sprite_default");
75
76 r_mat_mv_push();
77 r_mat_mv_translate(VIEWPORT_X, 0, 0);
78
79 const double dialog_width = VIEWPORT_W * 1.2;
80
81 r_mat_mv_push();
82 r_mat_mv_translate(dialog_width/2.0, 64, 0);
83
84 int cur_idx = message_index(dialog, 0);
85 int pre_idx = message_index(dialog, -1);
86
87 assume(cur_idx >= 0);
88
89 int cur_side = dialog->actions[cur_idx].type;
90 int pre_side = pre_idx >= 0 ? dialog->actions[pre_idx].type : 2;
91
92 Color clr = { 0 };
93
94 const float page_time = 10;
95 float page_alpha = min(global.frames - (dialog->page_time), page_time) / page_time;
96
97 const float page_text_time = 60;
98 float page_text_alpha = min(global.frames - dialog->page_time, page_text_time) / page_text_time;
99
100 int loop_start = 1;
101 int loop_incr = 1;
102
103 if(cur_side == 0) {
104 loop_start = 1;
105 loop_incr = -1;
106 } else {
107 loop_start = 0;
108 loop_incr = 1;
109 }
110
111 for(int i = loop_start; i < 2 && i >= 0; i += loop_incr) {
112 Sprite *portrait = dialog->images[i];
113
114 if(!portrait) {
115 continue;
116 }
117
118 float portrait_w = sprite_padded_width(portrait);
119 float portrait_h = sprite_padded_height(portrait);
120
121 r_mat_mv_push();
122
123 if(i == DIALOG_MSG_LEFT) {
124 r_cull(CULL_FRONT);
125 r_mat_mv_scale(-1, 1, 1);
126 } else {
127 r_cull(CULL_BACK);
128 }
129
130 if(o < 1) {
131 r_mat_mv_translate(120 * (1 - o), 0, 0);
132 }
133
134 float dir = (1 - 2 * (i == cur_side));
135 float ofs = 10 * dir;
136
137 if(page_alpha < 10 && ((i != pre_side && i == cur_side) || (i == pre_side && i != cur_side))) {
138 r_mat_mv_translate(ofs * page_alpha, ofs * page_alpha, 0);
139 float brightness = min(1.0 - 0.7 * page_alpha * dir, 1);
140 clr.r = clr.g = clr.b = brightness;
141 clr.a = 1;
142 } else {
143 r_mat_mv_translate(ofs, ofs, 0);
144 clr = *RGB(1 - (dir > 0) * 0.7, 1 - (dir > 0) * 0.7, 1 - (dir > 0) * 0.7);
145 }
146
147 color_mul_scalar(&clr, o);
148
149 r_draw_sprite(&(SpriteParams) {
150 .blend = BLEND_PREMUL_ALPHA,
151 .color = &clr,
152 .pos.x = (dialog_width - portrait_w) / 2 + 32,
153 .pos.y = VIEWPORT_H - portrait_h / 2,
154 .sprite_ptr = portrait,
155 });
156
157 r_mat_mv_pop();
158 }
159
160 r_mat_mv_pop();
161 r_state_pop();
162
163 o *= smooth(clamp((global.frames - dialog->birthtime - 10) / 30.0, 0, 1));
164
165 FloatRect dialog_bg_rect = {
166 .extent = { VIEWPORT_W-40, 110 },
167 .offset = { VIEWPORT_W/2, VIEWPORT_H-55 },
168 };
169
170 r_mat_mv_push();
171 if(o < 1) {
172 r_mat_mv_translate(0, 100 * (1 - o), 0);
173 }
174 r_color4(0, 0, 0, 0.8 * o);
175 r_mat_mv_push();
176 r_mat_mv_translate(dialog_bg_rect.x, dialog_bg_rect.y, 0);
177 r_mat_mv_scale(dialog_bg_rect.w, dialog_bg_rect.h, 1);
178 r_shader_standard_notex();
179 r_draw_quad();
180 r_mat_mv_pop();
181
182 Font *font = get_font("standard");
183
184 r_mat_tex_push();
185 // r_mat_tex_scale(2, 0.2, 0);
186 // r_mat_tex_translate(0, -global.frames/page_text_time, 0);
187
188 dialog_bg_rect.w = VIEWPORT_W * 0.86;
189 dialog_bg_rect.x -= dialog_bg_rect.w * 0.5;
190 dialog_bg_rect.y -= dialog_bg_rect.h * 0.5;
191 // dialog_bg_rect.h = dialog_bg_rect.w;
192
193 if(pre_idx >= 0 && page_text_alpha < 1) {
194 if(pre_side == DIALOG_MSG_RIGHT) {
195 clr = *RGB(0.6, 0.6, 1.0);
196 } else {
197 clr = *RGB(1.0, 1.0, 1.0);
198 }
199
200 color_mul_scalar(&clr, o);
201
202 text_draw_wrapped(dialog->actions[pre_idx].msg, VIEWPORT_W * 0.86, &(TextParams) {
203 .shader = "text_dialog",
204 .aux_textures = { r_texture_get("cell_noise") },
205 .shader_params = &(ShaderCustomParams) {{ o * (1.0 - (0.2 + 0.8 * page_text_alpha)), 1 }},
206 .color = &clr,
207 .pos = { VIEWPORT_W/2, VIEWPORT_H-110 + font_get_lineskip(font) },
208 .align = ALIGN_CENTER,
209 .font_ptr = font,
210 .overlay_projection = &dialog_bg_rect,
211 });
212 }
213
214 if(cur_side == DIALOG_MSG_RIGHT) {
215 clr = *RGB(0.6, 0.6, 1.0);
216 } else {
217 clr = *RGB(1.0, 1.0, 1.0);
218 }
219
220 color_mul_scalar(&clr, o);
221
222 text_draw_wrapped(dialog->actions[cur_idx].msg, VIEWPORT_W * 0.86, &(TextParams) {
223 .shader = "text_dialog",
224 .aux_textures = { r_texture_get("cell_noise") },
225 .shader_params = &(ShaderCustomParams) {{ o * page_text_alpha, 0 }},
226 .color = &clr,
227 .pos = { VIEWPORT_W/2, VIEWPORT_H-110 + font_get_lineskip(font) },
228 .align = ALIGN_CENTER,
229 .font_ptr = font,
230 .overlay_projection = &dialog_bg_rect,
231 });
232
233 r_mat_tex_pop();
234 r_mat_mv_pop();
235 r_mat_mv_pop();
236 r_state_pop();
237 }
238
dialog_page(Dialog ** d)239 bool dialog_page(Dialog **d) {
240 if(!*d || (*d)->pos >= (*d)->count) {
241 return false;
242 }
243
244 int to = (*d)->actions[(*d)->pos].timeout;
245
246 if(to && to > global.frames) {
247 return false;
248 }
249
250 (*d)->pos++;
251 (*d)->page_time = global.frames;
252
253 if((*d)->pos >= (*d)->count) {
254 // XXX: maybe this can be handled elsewhere?
255 if(!global.boss)
256 global.timer++;
257 } else if((*d)->actions[(*d)->pos].type == DIALOG_SET_BGM) {
258 stage_start_bgm((*d)->actions[(*d)->pos].msg);
259 return dialog_page(d);
260 }
261
262 return true;
263 }
264
dialog_update(Dialog ** d)265 void dialog_update(Dialog **d) {
266 if(!*d) {
267 return;
268 }
269
270 if(dialog_is_active(*d)) {
271 int to = (*d)->actions[(*d)->pos].timeout;
272
273 if(
274 (to && to >= global.frames) ||
275 ((global.plr.inputflags & INFLAG_SKIP) && global.frames - (*d)->page_time > 3)
276 ) {
277 dialog_page(d);
278 }
279 }
280
281 // important to check this again; the page_dialog call may have ended the dialog
282
283 if(dialog_is_active(*d)) {
284 fapproach_asymptotic_p(&(*d)->opacity, 1, 0.05, 1e-3);
285 } else {
286 fapproach_asymptotic_p(&(*d)->opacity, 0, 0.1, 1e-3);
287 if((*d)->opacity == 0) {
288 dialog_destroy(*d);
289 *d = NULL;
290 }
291 }
292 }
293
dialog_is_active(Dialog * d)294 bool dialog_is_active(Dialog *d) {
295 return d && (d->pos < d->count);
296 }
297
dialog_preload(void)298 void dialog_preload(void) {
299 preload_resource(RES_SHADER_PROGRAM, "text_dialog", RESF_DEFAULT);
300 }
301