New dialogue system powered by coroutines

This commit is contained in:
Andrei Alexeyev 2020-01-23 02:23:35 +02:00
parent 4aa6ba5b3f
commit fb19028ed5
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
33 changed files with 1180 additions and 969 deletions

View file

@ -50,6 +50,10 @@ DEFINE_EXTERN_TASK(common_call_func) {
ARGS.func();
}
DEFINE_EXTERN_TASK(common_start_bgm) {
stage_start_bgm(ARGS.bgm);
}
cmplx common_wander(cmplx origin, double dist, Rect bounds) {
int attempts = 32;
double angle;

View file

@ -37,6 +37,11 @@ DECLARE_EXTERN_TASK(
{ void (*func)(void); }
);
DECLARE_EXTERN_TASK(
common_start_bgm,
{ const char *bgm; }
);
void common_move_loop(cmplx *restrict pos, MoveParams *restrict mp);
INLINE Rect viewport_bounds(double margin) {

View file

@ -11,132 +11,262 @@
#include "dialog.h"
#include "global.h"
Dialog *dialog_create(void) {
Dialog *d = calloc(1, sizeof(Dialog));
d->page_time = global.frames;
d->birthtime = global.frames;
return d;
void dialog_init(Dialog *d) {
memset(d, 0, sizeof(*d));
d->state = DIALOG_STATE_IDLE;
d->text.current = &d->text.buffers[0];
d->text.fading_out = &d->text.buffers[1];
COEVENT_INIT_ARRAY(d->events);
}
void dialog_set_base(Dialog *d, DialogSide side, const char *sprite) {
d->spr_base[side] = sprite ? get_sprite(sprite) : NULL;
d->valid_composites &= ~(1 << side);
void dialog_deinit(Dialog *d) {
COEVENT_CANCEL_ARRAY(d->events);
for(DialogActor *a = d->actors.first; a; a = a->next) {
if(a->composite.tex) {
r_texture_destroy(a->composite.tex);
}
}
}
void dialog_set_base_p(Dialog *d, DialogSide side, Sprite *sprite) {
d->spr_base[side] = sprite;
d->valid_composites &= ~(1 << side);
void dialog_add_actor(Dialog *d, DialogActor *a, const char *name, DialogSide side) {
memset(a, 0, sizeof(*a));
a->name = name;
a->face = "normal";
a->side = side;
a->target_opacity = 1;
a->composite_dirty = true;
if(side == DIALOG_SIDE_RIGHT) {
a->speech_color = *RGB(0.6, 0.6, 1.0);
} else {
a->speech_color = *RGB(1.0, 1.0, 1.0);
}
alist_append(&d->actors, a);
}
void dialog_set_face(Dialog *d, DialogSide side, const char *sprite) {
d->spr_face[side] = sprite ? get_sprite(sprite) : NULL;
d->valid_composites &= ~(1 << side);
void dialog_actor_set_face(DialogActor *a, const char *face) {
log_debug("[%s] %s --> %s", a->name, a->face, face);
if(a->face != face) {
a->face = face;
a->composite_dirty = true;
}
}
void dialog_set_face_p(Dialog *d, DialogSide side, Sprite *sprite) {
d->spr_face[side] = sprite;
d->valid_composites &= ~(1 << side);
void dialog_actor_set_variant(DialogActor *a, const char *variant) {
log_debug("[%s] %s --> %s", a->name, a->variant, variant);
if(a->variant != variant) {
a->variant = variant;
a->composite_dirty = true;
}
}
void dialog_set_char(Dialog *d, DialogSide side, const char *char_name, const char *char_face, const char *char_variant) {
size_t name_len = strlen(char_name);
size_t face_len = strlen(char_face);
void dialog_update(Dialog *d) {
if(d->text.current->text) {
fapproach_p(&d->text.current->opacity, 1, 1/120.0f);
} else {
d->text.current->opacity = 0;
}
if(d->text.fading_out->text) {
fapproach_p(&d->text.fading_out->opacity, 0, 1/60.0f);
} else {
d->text.fading_out->opacity = 0;
}
if(
d->state == DIALOG_STATE_FADEOUT ||
(
d->text.current->opacity == 0 &&
d->text.fading_out->opacity < 0.25
)
) {
fapproach_asymptotic_p(&d->opacity, 0, 0.1, 1e-3);
} else {
fapproach_asymptotic_p(&d->opacity, 1, 0.05, 1e-3);
}
for(DialogActor *a = d->actors.first; a; a = a->next) {
if(d->state == DIALOG_STATE_FADEOUT) {
fapproach_asymptotic_p(&a->opacity, 0, 0.12, 1e-3);
} else {
fapproach_asymptotic_p(&a->opacity, a->target_opacity, 0.04, 1e-3);
}
fapproach_asymptotic_p(&a->focus, a->target_focus, 0.12, 1e-3);
}
}
void dialog_skippable_wait(Dialog *d, int timeout) {
CoEventSnapshot snap = coevent_snapshot(&d->events.skip_requested);
assert(d->state == DIALOG_STATE_IDLE);
d->state = DIALOG_STATE_WAITING_FOR_SKIP;
while(timeout > 0) {
dialog_update(d);
--timeout;
YIELD;
if(coevent_poll(&d->events.skip_requested, &snap) != CO_EVENT_PENDING) {
log_debug("Skipped with %i remaining", timeout);
break;
}
}
assert(d->state == DIALOG_STATE_WAITING_FOR_SKIP);
d->state = DIALOG_STATE_IDLE;
if(timeout == 0) {
log_debug("Timed out");
}
}
int dialog_util_estimate_wait_timeout_from_text(const char *text) {
return 1800;
}
static void dialog_set_text(Dialog *d, const char *text, const Color *clr) {
DialogTextBuffer *temp = d->text.current;
d->text.current = d->text.fading_out;
d->text.fading_out = temp;
d->text.current->color = *clr;
d->text.current->text = text;
}
void dialog_focus_actor(Dialog *d, DialogActor *actor) {
for(DialogActor *a = d->actors.first; a; a = a->next) {
a->target_focus = 0;
}
actor->target_focus = 1;
// make focused actor drawn on top of everyone else
alist_unlink(&d->actors, actor);
alist_append(&d->actors, actor);
}
void dialog_message_ex(Dialog *d, const DialogMessageParams *params) {
assume(params->actor != NULL);
assume(params->text != NULL);
log_debug("%s: %s", params->actor->name, params->text);
dialog_set_text(d, params->text, &params->actor->speech_color);
dialog_focus_actor(d, params->actor);
if(params->implicit_wait) {
assume(params->wait_timeout > 0);
if(params->wait_skippable) {
dialog_skippable_wait(d, params->wait_timeout);
} else {
WAIT(params->wait_timeout);
}
}
}
static void _dialog_message(Dialog *d, DialogActor *actor, const char *text, bool skippable, int delay) {
DialogMessageParams p = { 0 };
p.actor = actor;
p.text = text;
p.implicit_wait = true;
p.wait_skippable = skippable;
p.wait_timeout = delay;
dialog_message_ex(d, &p);
}
void dialog_message(Dialog *d, DialogActor *actor, const char *text) {
_dialog_message(d, actor, text, true, dialog_util_estimate_wait_timeout_from_text(text));
}
void dialog_message_unskippable(Dialog *d, DialogActor *actor, const char *text, int delay) {
_dialog_message(d, actor, text, false, delay);
}
void dialog_end(Dialog *d) {
d->state = DIALOG_STATE_FADEOUT;
coevent_signal(&d->events.fadeout_began);
for(DialogActor *a = d->actors.first; a; a = a->next) {
a->target_opacity = 0;
}
wait_for_fadeout: {
if(d->opacity > 0) {
YIELD;
goto wait_for_fadeout;
}
for(DialogActor *a = d->actors.first; a; a = a->next) {
if(a->opacity > 0) {
YIELD;
goto wait_for_fadeout;
}
}
}
coevent_signal(&d->events.fadeout_ended);
dialog_deinit(d);
}
static void dialog_actor_update_composite(DialogActor *a) {
assume(a->name != NULL);
assume(a->face != NULL);
if(!a->composite_dirty) {
return;
}
log_debug("%s (%p) is dirty; face=%s; variant=%s", a->name, (void*)a, a->face, a->variant);
Sprite *spr_base, *spr_face;
size_t name_len = strlen(a->name);
size_t face_len = strlen(a->face);
size_t variant_len;
size_t lenfull_base = sizeof("dialog/") + name_len - 1;
size_t lenfull_face = lenfull_base + sizeof("_face_") + face_len - 1;
if(char_variant) {
variant_len = strlen(char_variant);
if(a->variant) {
variant_len = strlen(a->variant);
lenfull_base += sizeof("_variant_") + variant_len - 1;
}
char buf[imax(lenfull_base, lenfull_face) + 1];
char *dst = buf;
char *variant_dst;
dst = memcpy(dst, "dialog/", sizeof("dialog/") - 1);
dst = memcpy(dst + sizeof("dialog/") - 1, char_name, name_len + 1);
dst = memcpy(dst + sizeof("dialog/") - 1, a->name, name_len + 1);
if(char_variant) {
variant_dst = dst + name_len;
} else {
d->spr_base[side] = get_sprite(buf);
if(a->variant) {
char *tmp = dst;
dst = memcpy(dst + name_len, "_variant_", sizeof("_variant_") - 1);
dst = memcpy(dst + sizeof("_variant_") - 1, a->variant, variant_len + 1);
dst = tmp;
}
spr_base = get_sprite(buf);
log_debug("base: %s", buf);
assume(spr_base != NULL);
dst = memcpy(dst + name_len, "_face_", sizeof("_face_") - 1);
dst = memcpy(dst + sizeof("_face_") - 1, char_face, face_len + 1);
d->spr_face[side] = get_sprite(buf);
dst = memcpy(dst + sizeof("_face_") - 1, a->face, face_len + 1);
if(!char_variant) {
return;
spr_face = get_sprite(buf);
log_debug("face: %s", buf);
assume(spr_face != NULL);
if(a->composite.tex != NULL) {
log_debug("destroyed texture at %p", (void*)a->composite.tex);
r_texture_destroy(a->composite.tex);
}
dst = memcpy(variant_dst, "_variant_", sizeof("_variant_") - 1);
dst = memcpy(dst + sizeof("_variant_") - 1, char_variant, variant_len + 1);
d->spr_base[side] = get_sprite(buf);
d->valid_composites &= ~(1 << side);
}
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->actions[idx].type != DIALOG_MSG_LEFT &&
d->actions[idx].type != DIALOG_MSG_RIGHT
) {
--idx;
}
return idx;
}
DialogAction *dialog_add_action(Dialog *d, const DialogAction *action) {
d->actions = realloc(d->actions, (++d->count)*sizeof(DialogAction));
d->actions[d->count - 1] = *action;
return d->actions + d->count - 1;
}
static void update_composite(Dialog *d, DialogSide side) {
if(d->valid_composites & (1 << side)) {
return;
}
Sprite *composite = d->spr_composite + side;
Sprite *spr_base = d->spr_base[side];
Sprite *spr_face = d->spr_face[side];
if(composite->tex != NULL) {
r_texture_destroy(composite->tex);
}
if(spr_base != NULL) {
assert(spr_face != NULL);
render_character_portrait(spr_base, spr_face, composite);
} else {
composite->tex = NULL;
}
d->valid_composites |= (1 << side);
}
static void update_composites(Dialog *d) {
update_composite(d, DIALOG_LEFT);
update_composite(d, DIALOG_RIGHT);
}
void dialog_destroy(Dialog *d) {
memset(&d->spr_base, 0, sizeof(d->spr_base));
memset(&d->spr_face, 0, sizeof(d->spr_face));
d->valid_composites = 0;
update_composites(d);
free(d->actions);
free(d);
render_character_portrait(spr_base, spr_face, &a->composite);
log_debug("created texture at %p", (void*)a->composite.tex);
a->composite_dirty = false;
}
void dialog_draw(Dialog *dialog) {
@ -146,12 +276,10 @@ void dialog_draw(Dialog *dialog) {
float o = dialog->opacity;
if(o == 0) {
return;
for(DialogActor *a = dialog->actors.first; a; a = a->next) {
dialog_actor_update_composite(a);
}
update_composites(dialog);
r_state_push();
r_state_push();
r_shader("sprite_default");
@ -164,76 +292,47 @@ void dialog_draw(Dialog *dialog) {
r_mat_mv_push();
r_mat_mv_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->actions[cur_idx].type;
int pre_side = pre_idx >= 0 ? dialog->actions[pre_idx].type : 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) {
Sprite *portrait = dialog->spr_composite + i;
if(portrait->tex == NULL) {
for(DialogActor *a = dialog->actors.first; a; a = a->next) {
if(a->opacity <= 0) {
continue;
}
dialog_actor_update_composite(a);
Sprite *portrait = &a->composite;
assume(portrait->tex != NULL);
float portrait_w = sprite_padded_width(portrait);
float portrait_h = sprite_padded_height(portrait);
r_mat_mv_push();
if(i == DIALOG_MSG_LEFT) {
if(a->side == DIALOG_SIDE_LEFT) {
r_cull(CULL_FRONT);
r_mat_mv_scale(-1, 1, 1);
} else {
r_cull(CULL_BACK);
}
if(o < 1) {
r_mat_mv_translate(120 * (1 - o), 0, 0);
if(a->opacity < 1) {
r_mat_mv_translate(120 * (1 - a->opacity), 0, 0);
}
float dir = (1 - 2 * (i == cur_side));
float ofs = 10 * dir;
float ofs = 10 * (1 - a->focus);
r_mat_mv_translate(ofs, ofs, 0);
float brightness = 0.5 + 0.5 * a->focus;
clr.r = clr.g = clr.b = brightness;
clr.a = 1;
if(page_alpha < 10 && ((i != pre_side && i == cur_side) || (i == pre_side && i != cur_side))) {
r_mat_mv_translate(ofs * page_alpha, ofs * page_alpha, 0);
float brightness = min(1.0 - 0.5 * page_alpha * dir, 1);
clr.r = clr.g = clr.b = brightness;
clr.a = 1;
} else {
r_mat_mv_translate(ofs, ofs, 0);
clr = *RGB(1 - (dir > 0) * 0.5, 1 - (dir > 0) * 0.5, 1 - (dir > 0) * 0.5);
}
color_mul_scalar(&clr, o);
color_mul_scalar(&clr, a->opacity);
r_flush_sprites();
r_draw_sprite(&(SpriteParams) {
.blend = BLEND_PREMUL_ALPHA,
.color = &clr,
.pos.x = (dialog_width - portrait_w) / 2 + 32,
.pos.y = VIEWPORT_H - portrait_h / 2,
.pos.x = (dialog_width - portrait_w) / 2 + 32 + a->offset.x,
.pos.y = VIEWPORT_H - portrait_h / 2 + a->offset.y,
.sprite_ptr = portrait,
});
@ -243,8 +342,6 @@ void dialog_draw(Dialog *dialog) {
r_mat_mv_pop();
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 },
@ -265,27 +362,19 @@ void dialog_draw(Dialog *dialog) {
Font *font = get_font("standard");
r_mat_tex_push();
// r_mat_tex_scale(2, 0.2, 0);
// r_mat_tex_translate(0, -global.frames/page_text_time, 0);
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 == DIALOG_MSG_RIGHT) {
clr = *RGB(0.6, 0.6, 1.0);
} else {
clr = *RGB(1.0, 1.0, 1.0);
}
if(dialog->text.fading_out->opacity > 0) {
clr = dialog->text.fading_out->color;
color_mul_scalar(&clr, o);
text_draw_wrapped(dialog->actions[pre_idx].data, VIEWPORT_W * 0.86, &(TextParams) {
text_draw_wrapped(dialog->text.fading_out->text, dialog_bg_rect.w, &(TextParams) {
.shader = "text_dialog",
.aux_textures = { get_tex("cell_noise") },
.shader_params = &(ShaderCustomParams) {{ o * (1.0 - (0.2 + 0.8 * page_text_alpha)), 1 }},
.shader_params = &(ShaderCustomParams) {{ o * (1.0 - (0.2 + 0.8 * (1 - dialog->text.fading_out->opacity))), 1 }},
.color = &clr,
.pos = { VIEWPORT_W/2, VIEWPORT_H-110 + font_get_lineskip(font) },
.align = ALIGN_CENTER,
@ -294,121 +383,39 @@ void dialog_draw(Dialog *dialog) {
});
}
if(cur_side == DIALOG_MSG_RIGHT) {
clr = *RGB(0.6, 0.6, 1.0);
} else {
clr = *RGB(1.0, 1.0, 1.0);
if(dialog->text.current->opacity > 0) {
clr = dialog->text.current->color;
color_mul_scalar(&clr, o);
text_draw_wrapped(dialog->text.current->text, dialog_bg_rect.w, &(TextParams) {
.shader = "text_dialog",
.aux_textures = { get_tex("cell_noise") },
.shader_params = &(ShaderCustomParams) {{ o * dialog->text.current->opacity, 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,
});
}
color_mul_scalar(&clr, o);
text_draw_wrapped(dialog->actions[cur_idx].data, 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,
});
r_mat_tex_pop();
r_mat_mv_pop();
r_mat_mv_pop();
r_state_pop();
}
bool dialog_page(Dialog **pdialog) {
Dialog *d = *pdialog;
if(!d || d->pos >= d->count) {
return false;
bool dialog_page(Dialog *d) {
if(d->state == DIALOG_STATE_WAITING_FOR_SKIP) {
coevent_signal(&d->events.skip_requested);
return true;
}
int to = d->actions[d->pos].timeout;
if(to && to > global.frames) {
assert(d->actions[d->pos].type == DIALOG_MSG_LEFT || d->actions[d->pos].type == DIALOG_MSG_RIGHT);
return false;
}
DialogAction *a = d->actions + d->pos;
d->pos++;
d->page_time = global.frames;
if(d->pos >= d->count) {
// XXX: maybe this can be handled elsewhere?
if(!global.boss)
global.timer++;
}
switch(a->type) {
case DIALOG_SET_BGM:
stage_start_bgm(a->data);
break;
case DIALOG_SET_FACE_RIGHT:
case DIALOG_SET_FACE_LEFT: {
DialogSide side = (
a->type == DIALOG_SET_FACE_RIGHT
? DIALOG_RIGHT
: DIALOG_LEFT
);
dialog_set_face(d, side, a->data);
break;
}
default:
break;
}
return true;
}
void dialog_update(Dialog **d) {
if(!*d) {
return;
}
if(dialog_is_active(*d)) {
while((*d)->pos < (*d)->count) {
if(
(*d)->actions[(*d)->pos].type != DIALOG_MSG_LEFT &&
(*d)->actions[(*d)->pos].type != DIALOG_MSG_RIGHT
) {
dialog_page(d);
} else {
int to = (*d)->actions[(*d)->pos].timeout;
if(
(to && to >= global.frames) ||
((global.plr.inputflags & INFLAG_SKIP) && global.frames - (*d)->page_time > 3)
) {
dialog_page(d);
}
break;
}
}
}
// important to check this again; the dialog_page 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) {
dialog_destroy(*d);
*d = NULL;
}
}
return false;
}
bool dialog_is_active(Dialog *d) {
return d && (d->pos < d->count);
return d && d->state != DIALOG_STATE_FADEOUT;
}
void dialog_preload(void) {

View file

@ -11,94 +11,129 @@
#include "taisei.h"
#include "color.h"
#include "resource/sprite.h"
#include "coroutine.h"
struct DialogAction;
typedef enum {
DIALOG_RIGHT,
DIALOG_LEFT,
typedef enum DialogSide {
DIALOG_SIDE_RIGHT,
DIALOG_SIDE_LEFT,
} DialogSide;
typedef enum {
DIALOG_MSG_RIGHT = DIALOG_RIGHT,
DIALOG_MSG_LEFT = DIALOG_LEFT,
DIALOG_SET_BGM,
DIALOG_SET_FACE_RIGHT,
DIALOG_SET_FACE_LEFT,
} DialogActionType;
typedef enum DialogState {
DIALOG_STATE_IDLE,
DIALOG_STATE_WAITING_FOR_SKIP,
DIALOG_STATE_FADEOUT,
} DialogState;
typedef struct DialogAction {
DialogActionType type;
const char *data;
int timeout;
} DialogAction;
typedef struct DialogActor {
LIST_INTERFACE(struct DialogActor);
const char *name;
const char *variant;
const char *face;
Sprite composite;
float opacity;
float target_opacity;
float focus;
float target_focus;
Color speech_color;
FloatOffset offset;
DialogSide side;
bool composite_dirty;
} DialogActor;
typedef struct DialogTextBuffer {
const char *text;
Color color;
float opacity;
} DialogTextBuffer;
typedef struct Dialog {
DialogAction *actions;
LIST_ANCHOR(DialogActor) actors;
Sprite *spr_base[2];
Sprite *spr_face[2];
struct {
DialogTextBuffer buffers[2];
DialogTextBuffer *current;
DialogTextBuffer *fading_out;
} text;
Sprite spr_composite[2];
uint valid_composites;
COEVENTS_ARRAY(
skip_requested,
fadeout_began,
fadeout_ended
) events;
int count;
int pos;
int page_time;
int birthtime;
DialogState state;
float opacity;
} Dialog;
Dialog *dialog_create(void)
attr_returns_allocated;
typedef struct DialogMessageParams {
const char *text;
DialogActor *actor;
int wait_timeout;
bool implicit_wait;
bool wait_skippable;
} DialogMessageParams;
void dialog_set_base(Dialog *d, DialogSide side, const char *sprite)
void dialog_init(Dialog *d)
attr_nonnull_all;
void dialog_deinit(Dialog *d)
attr_nonnull_all;
void dialog_add_actor(Dialog *d, DialogActor *a, const char *name, DialogSide side)
attr_nonnull_all;
void dialog_actor_set_face(DialogActor *a, const char *face)
attr_nonnull_all;
void dialog_actor_set_variant(DialogActor *a, const char *variant)
attr_nonnull(1);
void dialog_set_base_p(Dialog *d, DialogSide side, Sprite *sprite)
attr_nonnull(1);
attr_nonnull_all INLINE void dialog_actor_show(DialogActor *a) { a->target_opacity = 1; }
attr_nonnull_all INLINE void dialog_actor_hide(DialogActor *a) { a->target_opacity = 0; }
void dialog_set_face(Dialog *d, DialogSide side, const char *sprite)
attr_nonnull(1);
void dialog_skippable_wait(Dialog *d, int timeout)
attr_nonnull_all;
void dialog_set_face_p(Dialog *d, DialogSide side, Sprite *sprite)
attr_nonnull(1);
int dialog_util_estimate_wait_timeout_from_text(const char *text)
attr_nonnull_all;
void dialog_set_char(Dialog *d, DialogSide side, const char *char_name, const char *char_face, const char *char_variant)
attr_nonnull(1, 3, 4);
void dialog_message(Dialog *d, DialogActor *actor, const char *text)
attr_nonnull_all;
DialogAction *dialog_add_action(Dialog *d, const DialogAction *action)
attr_nonnull(1, 2);
void dialog_message_unskippable(Dialog *d, DialogActor *actor, const char *text, int delay)
attr_nonnull_all;
void dialog_destroy(Dialog *d)
attr_nonnull(1);
void dialog_message_ex(Dialog *d, const DialogMessageParams *params)
attr_nonnull_all;
void dialog_draw(Dialog *dialog);
void dialog_focus_actor(Dialog *d, DialogActor *actor)
attr_nonnull_all;
bool dialog_page(Dialog **d) attr_nonnull(1);
void dialog_update(Dialog **d) attr_nonnull(1);
void dialog_end(Dialog *d)
attr_nonnull_all;
void dialog_update(Dialog *d)
attr_nonnull_all;
void dialog_draw(Dialog *d);
bool dialog_page(Dialog *d)
attr_nonnull_all;
bool dialog_is_active(Dialog *d);
void dialog_preload(void);
// FIXME: might not be the best place for this
typedef struct PlayerDialogProcs {
void (*stage1_pre_boss)(Dialog *d);
void (*stage1_post_boss)(Dialog *d);
void (*stage2_pre_boss)(Dialog *d);
void (*stage2_post_boss)(Dialog *d);
void (*stage3_pre_boss)(Dialog *d);
void (*stage3_post_boss)(Dialog *d);
void (*stage4_pre_boss)(Dialog *d);
void (*stage4_post_boss)(Dialog *d);
void (*stage5_post_midboss)(Dialog *d);
void (*stage5_pre_boss)(Dialog *d);
void (*stage5_post_boss)(Dialog *d);
void (*stage6_pre_boss)(Dialog *d);
void (*stage6_pre_final)(Dialog *d);
} PlayerDialogProcs;
#include "dialog/dialog_interface.h"
#endif // IGUARD_dialog_h

View file

@ -0,0 +1,79 @@
/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#ifndef IGUARD_stages_dialog_interface_h
#define IGUARD_stages_dialog_interface_h
#include "taisei.h"
#include "dialog.h"
#define DIALOG_SCRIPTS \
WITH_EVENTS (Stage1PreBoss, (boss_appears, music_changes)) \
WITHOUT_EVENTS (Stage1PostBoss) \
WITH_EVENTS (Stage2PreBoss, (boss_appears, music_changes)) \
WITHOUT_EVENTS (Stage2PostBoss) \
WITH_EVENTS (Stage3PreBoss, (boss_appears, music_changes)) \
WITHOUT_EVENTS (Stage3PostBoss) \
WITH_EVENTS (Stage4PreBoss, (boss_appears, music_changes)) \
WITHOUT_EVENTS (Stage4PostBoss) \
WITH_EVENTS (Stage5PreBoss, (boss_appears, music_changes)) \
WITHOUT_EVENTS (Stage5PostMidBoss) \
WITHOUT_EVENTS (Stage5PostBoss) \
WITH_EVENTS (Stage6PreBoss, (boss_appears, music_changes)) \
WITHOUT_EVENTS (Stage6PreFinal) \
#define WITH_EVENTS(_name, _events) \
typedef COEVENTS_ARRAY _events _name##DialogEvents; \
DEFINE_TASK_INTERFACE(_name##Dialog, { _name##DialogEvents **out_events; });
#define WITHOUT_EVENTS(_name) \
WITH_EVENTS(_name, (_dummy_fake_event_))
/*
#define WITHOUT_EVENTS(_name) \
typedef struct { char there_are_no_events; } _name##DialogEvents; \
DEFINE_TASK_INTERFACE(_name##Dialog, { _name##DialogEvents **out_events; });
*/
DIALOG_SCRIPTS
#undef WITH_EVENTS
#undef WITHOUT_EVENTS
#define WITH_EVENTS(_name, _events) WITHOUT_EVENTS(_name)
#define WITHOUT_EVENTS(_name) \
TASK_INDIRECT_TYPE(_name##Dialog) _name;
typedef struct PlayerDialogTasks {
DIALOG_SCRIPTS
} PlayerDialogTasks;
#undef WITH_EVENTS
#undef WITHOUT_EVENTS
// FIXME: might not be the best place for this
typedef struct PlayerDialogProcs {
void (*stage1_pre_boss)(Dialog *d);
void (*stage1_post_boss)(Dialog *d);
void (*stage2_pre_boss)(Dialog *d);
void (*stage2_post_boss)(Dialog *d);
void (*stage3_pre_boss)(Dialog *d);
void (*stage3_post_boss)(Dialog *d);
void (*stage4_pre_boss)(Dialog *d);
void (*stage4_post_boss)(Dialog *d);
void (*stage5_post_midboss)(Dialog *d);
void (*stage5_pre_boss)(Dialog *d);
void (*stage5_post_boss)(Dialog *d);
void (*stage6_pre_boss)(Dialog *d);
void (*stage6_pre_final)(Dialog *d);
} PlayerDialogProcs;
#endif // IGUARD_stages_dialog_interface_h

View file

@ -12,33 +12,39 @@
#include "taisei.h"
#include "dialog.h"
#include "stage.h"
#define DIALOG_SCRIPT(name) static void dialog_##name(Dialog *d)
#define MSG(_side, _text) \
dialog_add_action(d, &(DialogAction) { .type = (_side), .data = (_text) })
#define FACE(_side, _chr, _face) \
dialog_add_action(d, &(DialogAction) { .type = (_side), .data = "dialog/" #_chr "_face_" #_face })
#define LEFT(text) MSG(DIALOG_MSG_LEFT, text)
#define RIGHT(text) MSG(DIALOG_MSG_RIGHT, text)
#define DIALOG_EVAL(a, b) a b
#define LEFT_FACE(face) DIALOG_EVAL(FACE, (DIALOG_SET_FACE_LEFT, LEFT_BASE, face))
#define RIGHT_FACE(face) DIALOG_EVAL(FACE, (DIALOG_SET_FACE_RIGHT, RIGHT_BASE, face))
#define DIALOG_BEGIN(_interface) \
Dialog dialog; \
stage_begin_dialog(&dialog); \
_interface##DialogEvents events = { 0 }; \
COEVENT_INIT_ARRAY(events); \
if(ARGS.out_events) *ARGS.out_events = &events; \
YIELD \
#define EXPORT_DIALOG_SCRIPT(name) \
PlayerDialogProcs dialog_##name = { \
.stage1_pre_boss = dialog_##name##_stage1_pre_boss, \
.stage1_post_boss = dialog_##name##_stage1_post_boss, \
.stage2_pre_boss = dialog_##name##_stage2_pre_boss, \
.stage2_post_boss = dialog_##name##_stage2_post_boss, \
.stage3_pre_boss = dialog_##name##_stage3_pre_boss, \
.stage3_post_boss = dialog_##name##_stage3_post_boss, \
.stage4_pre_boss = dialog_##name##_stage4_pre_boss, \
.stage4_post_boss = dialog_##name##_stage4_post_boss, \
.stage5_post_midboss = dialog_##name##_stage5_post_midboss, \
.stage5_pre_boss = dialog_##name##_stage5_pre_boss, \
.stage5_post_boss = dialog_##name##_stage5_post_boss, \
.stage6_pre_boss = dialog_##name##_stage6_pre_boss, \
.stage6_pre_final = dialog_##name##_stage6_pre_final, \
};
#define DIALOG_END() \
dialog_end(&dialog); \
COEVENT_CANCEL_ARRAY(events)
#define ACTOR(_name, _side) \
DialogActor _name; \
dialog_add_actor(&dialog, &_name, #_name, _side)
#define ACTOR_LEFT(_name) ACTOR(_name, DIALOG_SIDE_LEFT)
#define ACTOR_RIGHT(_name) ACTOR(_name, DIALOG_SIDE_RIGHT)
#define MSG(_actor, _text) dialog_message(&dialog, &_actor, _text)
#define MSG_UNSKIPPABLE(_actor, _delay, _text) dialog_message_unskippable(&dialog, &_actor, _text, _delay)
#define FACE(_actor, _face) dialog_actor_set_face(&_actor, #_face)
#define VARIANT(_actor, _variant) dialog_actor_set_variant(&_actor, #_variant)
#define SHOW(_actor) dialog_actor_show(&_actor)
#define HIDE(_actor) dialog_actor_hide(&_actor)
#define FOCUS(_actor) dialog_focus_actor(&dialog, &_actor)
#define WAIT_SKIPPABLE(_delay) dialog_skippable_wait(&dialog, _delay)
#define EVENT(_name) coevent_signal(&events._name)
#define TITLE(_actor, _name, _title) log_warn("TITLE(%s, %s, %s) not yet implemented", #_actor, #_name, #_title)
#define DIALOG_TASK(_protag, _interface) \
TASK_WITH_INTERFACE(_protag##_##_interface##Dialog, _interface##Dialog)
#endif // IGUARD_dialog_dialog_macros_h

View file

@ -0,0 +1,28 @@
// this needs an extra indirection to expand EXPORT_DIALOG_TASKS_CHARACTER
#define EDT_CONCAT(a, b) MACROHAX_CONCAT(a, b)
// Fill the global dialog_tasks_<character> struct
#define WITH_EVENTS(_name, _events) WITHOUT_EVENTS(_name)
#define WITHOUT_EVENTS(_name) \
._name = MACROHAX_EXPAND(MACROHAX_DEFER(TASK_INDIRECT_INIT)(_name##Dialog, EDT_CONCAT(EXPORT_DIALOG_TASKS_CHARACTER, _##_name##Dialog))),
PlayerDialogTasks EDT_CONCAT(dialog_tasks_, EXPORT_DIALOG_TASKS_CHARACTER) = {
DIALOG_SCRIPTS
};
// Let the compiler know that the tasks are "used". TASK_INDIRECT_INIT can't do that.
#undef WITHOUT_EVENTS
#define WITHOUT_EVENTS(_name) \
attr_unused static char MACROHAX_EXPAND(MACROHAX_DEFER(EDT_CONCAT)(COTASK_UNUSED_CHECK_, EDT_CONCAT(EXPORT_DIALOG_TASKS_CHARACTER, _##_name##Dialog)));
DIALOG_SCRIPTS
#undef WITH_EVENTS
#undef WITHOUT_EVENTS
#undef EDT_CONTAT
#undef EXPORT_DIALOG_TASKS_CHARACTER

View file

@ -10,190 +10,183 @@
#include "dialog_macros.h"
#define LEFT_BASE marisa
/*
* Stage 1
*/
#define RIGHT_BASE cirno
DIALOG_TASK(marisa, Stage1PreBoss) {
DIALOG_BEGIN(Stage1PreBoss);
DIALOG_SCRIPT(marisa_stage1_pre_boss) {
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(angry);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
ACTOR_LEFT(marisa);
ACTOR_RIGHT(cirno);
EVENT(boss_appears);
EVENT(music_changes);
DIALOG_END();
}
DIALOG_SCRIPT(marisa_stage1_post_boss) {
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(happy);
LEFT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage1PostBoss) {
DIALOG_BEGIN(Stage1PostBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(cirno);
VARIANT(cirno, defeated);
FACE(cirno, defeated);
DIALOG_END();
}
#undef RIGHT_BASE
#define RIGHT_BASE hina
/*
* Stage 2
*/
DIALOG_SCRIPT(marisa_stage2_pre_boss) {
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(concerned);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(unamused);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(serious);
LEFT_FACE(puzzled);
RIGHT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage2PreBoss) {
DIALOG_BEGIN(Stage2PreBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(hina);
EVENT(boss_appears);
EVENT(music_changes);
DIALOG_END();
}
DIALOG_SCRIPT(marisa_stage2_post_boss) {
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
LEFT_FACE(normal);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(happy);
LEFT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage2PostBoss) {
DIALOG_BEGIN(Stage2PostBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(hina);
VARIANT(hina, defeated);
FACE(hina, defeated);
DIALOG_END();
}
#undef RIGHT_BASE
#define RIGHT_BASE wriggle
/*
* Stage 3
*/
DIALOG_SCRIPT(marisa_stage3_pre_boss) {
LEFT_FACE(puzzled);
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(outraged);
RIGHT("FIXME: Removed due to issue #179");
RIGHT_FACE(proud);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(normal);
RIGHT_FACE(calm);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(proud);
RIGHT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage3PreBoss) {
DIALOG_BEGIN(Stage3PreBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(wriggle);
EVENT(boss_appears);
EVENT(music_changes);
DIALOG_END();
}
DIALOG_SCRIPT(marisa_stage3_post_boss) {
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(normal);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(outraged_unlit);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(happy);
RIGHT_FACE(defeated);
LEFT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage3PostBoss) {
DIALOG_BEGIN(Stage3PostBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(wriggle);
VARIANT(wriggle, defeated);
FACE(wriggle, defeated);
DIALOG_END();
}
#undef RIGHT_BASE
#define RIGHT_BASE kurumi
/*
* Stage 4
*/
DIALOG_SCRIPT(marisa_stage4_pre_boss) {
LEFT_FACE(surprised);
RIGHT_FACE(tsun);
RIGHT("FIXME: Removed due to issue #179");
RIGHT_FACE(normal);
LEFT_FACE(normal);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(tsun_blush);
RIGHT("FIXME: Removed due to issue #179");
RIGHT_FACE(dissatisfied);
LEFT_FACE(happy);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(tsun);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(sweat_smile);
LEFT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage4PreBoss) {
DIALOG_BEGIN(Stage4PreBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(kurumi);
EVENT(boss_appears);
EVENT(music_changes);
DIALOG_END();
}
DIALOG_SCRIPT(marisa_stage4_post_boss) {
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(dissatisfied);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(happy);
LEFT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage4PostBoss) {
DIALOG_BEGIN(Stage4PostBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(kurumi);
VARIANT(kurumi, defeated);
FACE(kurumi, defeated);
DIALOG_END();
}
#undef RIGHT_BASE
#define RIGHT_BASE iku
/*
* Stage 5
*/
DIALOG_SCRIPT(marisa_stage5_pre_boss) {
LEFT("FIXME: Removed due to issue #179");
LEFT_FACE(puzzled);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(serious);
RIGHT("FIXME: Removed due to issue #179");
RIGHT_FACE(eyes_closed);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(happy);
RIGHT_FACE(normal);
LEFT("FIXME: Removed due to issue #179");
LEFT_FACE(normal);
RIGHT_FACE(eyes_closed);
RIGHT("FIXME: Removed due to issue #179");
RIGHT_FACE(smile);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(surprised);
RIGHT_FACE(serious);
RIGHT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage5PreBoss) {
DIALOG_BEGIN(Stage5PreBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(iku);
EVENT(boss_appears);
EVENT(music_changes);
DIALOG_END();
}
DIALOG_SCRIPT(marisa_stage5_post_midboss) {
// this dialog must contain only one page
LEFT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage5PostMidBoss) {
DIALOG_BEGIN(Stage5PostMidBoss);
ACTOR_LEFT(marisa);
FACE(marisa, surprised);
// should be only one message with a fixed 120-frames timeout
MSG_UNSKIPPABLE(marisa, 120, "changeme");
DIALOG_END();
}
DIALOG_SCRIPT(marisa_stage5_post_boss) {
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(happy);
LEFT("FIXME: Removed due to issue #179");
LEFT_FACE(normal);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(sweat_smile);
LEFT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage5PostBoss) {
DIALOG_BEGIN(Stage5PostBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(iku);
VARIANT(iku, defeated);
FACE(iku, defeated);
DIALOG_END();
}
#undef RIGHT_BASE
#define RIGHT_BASE elly
/*
* Stage 6
*/
DIALOG_SCRIPT(marisa_stage6_pre_boss) {
LEFT("FIXME: Removed due to issue #179");
LEFT_FACE(surprised);
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(puzzled);
RIGHT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(normal);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(angry);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(smug);
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage6PreBoss) {
DIALOG_BEGIN(Stage6PreBoss);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(elly);
EVENT(boss_appears);
EVENT(music_changes);
DIALOG_END();
}
DIALOG_SCRIPT(marisa_stage6_pre_final) {
RIGHT_FACE(angry);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(puzzled);
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(surprised);
RIGHT_FACE(shouting);
RIGHT("FIXME: Removed due to issue #179");
DIALOG_TASK(marisa, Stage6PreFinal) {
DIALOG_BEGIN(Stage6PreFinal);
ACTOR_LEFT(marisa);
ACTOR_RIGHT(elly);
VARIANT(elly, beaten);
FACE(elly, angry);
DIALOG_END();
}
EXPORT_DIALOG_SCRIPT(marisa)
/*
* Register the tasks
*/
#define EXPORT_DIALOG_TASKS_CHARACTER marisa
#include "export_dialog_tasks.inc.h"

View file

@ -13,6 +13,6 @@
#include "dialog.h"
extern PlayerDialogProcs dialog_marisa;
extern PlayerDialogTasks dialog_tasks_marisa;
#endif // IGUARD_dialog_marisa_h

View file

@ -10,196 +10,248 @@
#include "dialog_macros.h"
#define LEFT_BASE reimu
/*
* Stage 1
*/
#define RIGHT_BASE cirno
DIALOG_TASK(reimu, Stage1PreBoss) {
// Initialization, must be at the very top.
DIALOG_BEGIN(Stage1PreBoss);
DIALOG_SCRIPT(reimu_stage1_pre_boss) {
LEFT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
LEFT_FACE(unamused);
LEFT("FIXME: Removed due to issue #179");
RIGHT_FACE(angry);
RIGHT("FIXME: Removed due to issue #179");
RIGHT("FIXME: Removed due to issue #179");
ACTOR_LEFT(reimu);
ACTOR_RIGHT(cirno);
// Hide cirno for now, to be revealed later.
HIDE(cirno);
// "normal" is the default face.
// FACE(reimu, normal);
// FOCUS() to make an actor stand out.
// Focused actors are fully bright and appear in front of the rest.
// Only one actor can be focused at a time.