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