From 622380f077e073fd4525ab36e706ce194887d0b5 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Thu, 13 Apr 2023 12:51:18 -0700 Subject: [PATCH] Talk while time is stopped, fix anim change when time stopped --- main.c | 417 +++++++++++++++++++++++++++------------------------ makeprompt.h | 10 +- 2 files changed, 231 insertions(+), 196 deletions(-) diff --git a/main.c b/main.c index ada6e58..9fcf238 100644 --- a/main.c +++ b/main.c @@ -43,13 +43,13 @@ double clamp(double d, double min, double max) { - const double t = d < min ? min : d; - return t > max ? max : t; + const double t = d < min ? min : d; + return t > max ? max : t; } float clampf(float d, float min, float max) { - const float t = d < min ? min : d; - return t > max ? max : t; + const float t = d < min ? min : d; + return t > max ? max : t; } float clamp01(float f) @@ -63,18 +63,18 @@ float clamp01(float f) int min(int a, int b) { - if(a < b) return a; - else return b; + if(a < b) return a; + else return b; } // so can be grep'd and removed #define dbgprint(...) { printf("Debug | %s:%d | ", __FILE__, __LINE__); printf(__VA_ARGS__); } Vec2 RotateV2(Vec2 v, float theta) { - return V2( - v.X * cosf(theta) - v.Y * sinf(theta), - v.X * sinf(theta) + v.Y * cosf(theta) - ); + return V2( + v.X * cosf(theta) - v.Y * sinf(theta), + v.X * sinf(theta) + v.Y * cosf(theta) + ); } Vec2 ReflectV2(Vec2 v, Vec2 normal) @@ -257,13 +257,13 @@ bool receiving_text_input = false; Sentence text_input_buffer = {0}; #else #ifdef WEB -EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void stop_controlling_input() { _sapp_emsc_unregister_eventhandlers(); // stop getting input, hand it off to text input } -EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void start_controlling_input() { memset(keydown, 0, ARRLEN(keydown)); @@ -287,8 +287,8 @@ Arena make_arena(size_t max_size) return (Arena) { .data = calloc(max_size, 1), - .data_size = max_size, - .cur = 0, + .data_size = max_size, + .cur = 0, }; } void reset(Arena *a) @@ -318,9 +318,9 @@ char *tprint(const char *format, ...) vsnprintf(to_return, size, format, argptr); - va_end(argptr); + va_end(argptr); - return to_return; + return to_return; } AABB entity_sword_aabb(Entity *e, float width, float height) @@ -328,14 +328,14 @@ AABB entity_sword_aabb(Entity *e, float width, float height) if(e->facing_left) { return (AABB){ - .upper_left = AddV2(e->pos, V2(-width, height)), + .upper_left = AddV2(e->pos, V2(-width, height)), .lower_right = AddV2(e->pos, V2(0.0, -height)), }; } else { return (AABB){ - .upper_left = AddV2(e->pos, V2(0.0, height)), + .upper_left = AddV2(e->pos, V2(0.0, height)), .lower_right = AddV2(e->pos, V2(width, -height)), }; } @@ -481,12 +481,12 @@ AABB centered_aabb(Vec2 at, Vec2 size) AABB entity_aabb(Entity *e) { Vec2 at = e->pos; -/* following doesn't work because in move_and_slide I'm not using this function - if(e->is_character) // aabb near feet - { - at = AddV2(at, V2(0.0f, -50.0f)); - } -*/ + /* following doesn't work because in move_and_slide I'm not using this function + if(e->is_character) // aabb near feet + { + at = AddV2(at, V2(0.0f, -50.0f)); + } + */ return centered_aabb(at, entity_aabb_size(e)); } @@ -631,14 +631,14 @@ void reset_level() #ifdef WEB -EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void dump_save_data() { EM_ASM({ save_game_data = new Int8Array(Module.HEAP8.buffer, $0, $1); - }, (char*)(&gs), sizeof(gs)); + }, (char*)(&gs), sizeof(gs)); } -EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void read_from_save_data(char *data, size_t length) { GameState read_data = {0}; @@ -698,17 +698,17 @@ void end_text_input(char *what_player_said) } } /* -AnimatedSprite moose_idle = -{ - .img = &image_moose, - .time_per_frame = 0.15, - .num_frames = 8, - .start = {0.0, 0.0}, - .horizontal_diff_btwn_frames = 347.0f, - .region_size = {347.0f, 160.0f}, - .offset = {-1.5f, -10.0f}, -}; -*/ + AnimatedSprite moose_idle = + { + .img = &image_moose, + .time_per_frame = 0.15, + .num_frames = 8, + .start = {0.0, 0.0}, + .horizontal_diff_btwn_frames = 347.0f, + .region_size = {347.0f, 160.0f}, + .offset = {-1.5f, -10.0f}, + }; + */ sg_image image_font = {0}; @@ -819,18 +819,18 @@ void init(void) #ifdef WEB EM_ASM({ set_server_url(UTF8ToString($0)); - }, SERVER_URL); + }, SERVER_URL); #endif Log("Size of entity struct: %zu\n", sizeof(Entity)); Log("Size of %d gs.entities: %zu kb\n", (int)ARRLEN(gs.entities), sizeof(gs.entities)/1024); sg_setup(&(sg_desc){ .context = sapp_sgcontext(), - }); + }); stm_setup(); saudio_setup(&(saudio_desc){ .stream_cb = audio_stream_callback, - .logger.func = slog_func, - }); + .logger.func = slog_func, + }); scratch = make_arena(1024 * 10); @@ -840,7 +840,7 @@ void init(void) #ifdef WEB EM_ASM({ load_all(); - }); + }); #endif // load font @@ -878,7 +878,7 @@ void init(void) .ptr = font_bitmap_rgba, .size = (size_t)(512 * 512 * 4), } - } ); + } ); stbtt_fontinfo font; stbtt_InitFont(&font, fontBuffer, 0); @@ -895,10 +895,10 @@ void init(void) state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc) { - .usage = SG_USAGE_STREAM, - //.data = SG_RANGE(vertices), - .size = 1024*500, - .label = "quad-vertices" + .usage = SG_USAGE_STREAM, + //.data = SG_RANGE(vertices), + .size = 1024*900, + .label = "quad-vertices" }); const sg_shader_desc *desc = quad_program_shader_desc(sg_query_backend()); @@ -908,28 +908,28 @@ void init(void) Color clearcol = colhex(0x98734c); state.pip = sg_make_pipeline(&(sg_pipeline_desc) { - .shader = shd, - .depth = { - .compare = SG_COMPAREFUNC_LESS_EQUAL, - .write_enabled = true - }, - .layout = { - .attrs = - { - [ATTR_quad_vs_position].format = SG_VERTEXFORMAT_FLOAT3, - [ATTR_quad_vs_texcoord0].format = SG_VERTEXFORMAT_FLOAT2, - } - }, - .colors[0].blend = (sg_blend_state) { // allow transparency - .enabled = true, - .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, - .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - .op_rgb = SG_BLENDOP_ADD, - .src_factor_alpha = SG_BLENDFACTOR_ONE, - .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - .op_alpha = SG_BLENDOP_ADD, - }, - .label = "quad-pipeline", + .shader = shd, + .depth = { + .compare = SG_COMPAREFUNC_LESS_EQUAL, + .write_enabled = true + }, + .layout = { + .attrs = + { + [ATTR_quad_vs_position].format = SG_VERTEXFORMAT_FLOAT3, + [ATTR_quad_vs_texcoord0].format = SG_VERTEXFORMAT_FLOAT2, + } + }, + .colors[0].blend = (sg_blend_state) { // allow transparency + .enabled = true, + .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, + .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .op_rgb = SG_BLENDOP_ADD, + .src_factor_alpha = SG_BLENDFACTOR_ONE, + .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .op_alpha = SG_BLENDOP_ADD, + }, + .label = "quad-pipeline", }); state.pass_action = (sg_pass_action) @@ -1083,7 +1083,7 @@ AABB aabb_at(Vec2 at, Vec2 size) { return (AABB){ .upper_left = at, - .lower_right = AddV2(at, V2(size.x, -size.y)), + .lower_right = AddV2(at, V2(size.x, -size.y)), }; } @@ -1091,7 +1091,7 @@ AABB aabb_at_yplusdown(Vec2 at, Vec2 size) { return (AABB){ .upper_left = at, - .lower_right = AddV2(at, V2(size.x, size.y)), + .lower_right = AddV2(at, V2(size.x, size.y)), }; } @@ -1151,9 +1151,9 @@ Quad quad_aabb(AABB aabb) assert(aabb_is_valid(aabb)); return (Quad) { .ul = aabb.upper_left, - .ur = AddV2(aabb.upper_left, V2(size_vec.X, 0.0f)), - .lr = AddV2(aabb.upper_left, size_vec), - .ll = AddV2(aabb.upper_left, V2(0.0f, size_vec.Y)), + .ur = AddV2(aabb.upper_left, V2(size_vec.X, 0.0f)), + .lr = AddV2(aabb.upper_left, size_vec), + .ll = AddV2(aabb.upper_left, V2(0.0f, size_vec.Y)), }; } @@ -2136,7 +2136,6 @@ void draw_dialog_panel(Entity *talking_to, float alpha) dbgrect(dialog_panel); } } - } #define ROLL_KEY SAPP_KEYCODE_LEFT_SHIFT @@ -2145,6 +2144,7 @@ double last_frame_processing_time = 0.0; uint64_t last_frame_time; Vec2 mouse_pos = {0}; // in screen space bool interact_just_pressed = false; +bool mouse_just_clicked = false; float learned_shift = 0.0; float learned_space = 0.0; float learned_e = 0.0; @@ -2155,16 +2155,17 @@ void frame(void) { static float speed_factor = 1.0f; // elapsed_time - double dt_double = 0.0; + double unwarped_dt_double = 0.0; { - dt_double = stm_sec(stm_diff(stm_now(), last_frame_time)); - dt_double = fmin(dt_double, 5.0 / 60.0); // clamp dt at maximum 5 frames, avoid super huge dt - elapsed_time += dt_double*speed_factor; + unwarped_dt_double = stm_sec(stm_diff(stm_now(), last_frame_time)); + unwarped_dt_double = fmin(unwarped_dt_double, MINIMUM_TIMESTEP * 5.0); // clamp dt at maximum 5 frames, avoid super huge dt + elapsed_time += unwarped_dt_double*speed_factor; last_frame_time = stm_now(); } + double dt_double = unwarped_dt_double*speed_factor; + float unwarped_dt = (float)unwarped_dt_double; float dt = (float)dt_double; - #if 0 { printf("Frametime: %.1f ms\n", dt*1000.0); @@ -2330,23 +2331,31 @@ void frame(void) static Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on static bool player_in_combat = false; const float dialog_interact_size = 2.5f * TILE_SIZE; - if(player->has_paused_time) + + float speed_target; + if(player->in_conversation_mode) { - speed_factor = Lerp(speed_factor, dt*10.0f, 0.0f); + speed_target = 0.0f; } else { - speed_factor = Lerp(speed_factor, dt*10.0f, 1.0f); + speed_target = 1.0f; + } + speed_factor = Lerp(speed_factor, unwarped_dt*15.0f, speed_target); + if(fabsf(speed_factor - speed_target) <= 0.05f) + { + speed_factor = speed_target; } int num_timestep_loops = 0; { - unprocessed_gameplay_time += dt; - float timestep = fminf(dt, (float)MINIMUM_TIMESTEP); + unprocessed_gameplay_time += unwarped_dt; + float timestep = fminf(unwarped_dt, (float)MINIMUM_TIMESTEP); while(unprocessed_gameplay_time >= timestep) { num_timestep_loops++; unprocessed_gameplay_time -= timestep; - float dt = timestep*speed_factor; + float unwarped_dt = timestep; + float dt = unwarped_dt*speed_factor; // process gs.entities player_in_combat = false; // in combat set by various enemies when they fight the player @@ -2420,6 +2429,8 @@ void frame(void) } } #endif + + if(fabsf(it->vel.x) > 0.01f) it->facing_left = it->vel.x < 0.0f; @@ -2428,6 +2439,21 @@ void frame(void) it->dead_time += dt; } + it->being_hovered = false; + if(player->in_conversation_mode) + { + + if(has_point(entity_aabb(it), screen_to_world(mouse_pos))) + { + it->being_hovered = true; + if(mouse_just_clicked) + { + player->talking_to = frome(it); + player->state = CHARACTER_TALKING; + } + } + } + if(it->is_npc) { // character speech animation text input @@ -2440,7 +2466,7 @@ void frame(void) if(last_said_sentence(it)) length = last_said_sentence(it)->cur_index; if((int)before < length) { - it->characters_said += characters_per_sec*dt; + it->characters_said += characters_per_sec*unwarped_dt; } else { @@ -2763,6 +2789,7 @@ void frame(void) PROFILE_SCOPE("process player") { + // do dialog Entity *closest_interact_with = 0; { @@ -2817,7 +2844,11 @@ void frame(void) if(interact) { - if(closest_interact_with) + if(player->in_conversation_mode) + { + player->in_conversation_mode = false; + } + else if(closest_interact_with) { if(closest_interact_with->is_npc) { @@ -2835,7 +2866,6 @@ void frame(void) { assert(false); } - } else { @@ -2850,100 +2880,102 @@ void frame(void) } else { - player->has_paused_time = !player->has_paused_time; + player->in_conversation_mode = true; } } } - if(roll && !player->is_rolling && player->time_not_rolling > 0.3f && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) - { - player->is_rolling = true; - player->roll_progress = 0.0; - } - - if(attack && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) - { - player->state = CHARACTER_ATTACK; - BUFF_CLEAR(&player->done_damage_to_this_swing); - player->swing_progress = 0.0; - } - // after images - BUFF_ITER(PlayerAfterImage, &player->after_images) - { - it->alive_for += dt; - } - if(player->after_images.data[0].alive_for >= AFTERIMAGE_LIFETIME) - { - BUFF_REMOVE_FRONT(&player->after_images); - } - // roll processing + float speed = 0.0f; { - if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING) + if(roll && !player->is_rolling && player->time_not_rolling > 0.3f && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) { + player->is_rolling = true; player->roll_progress = 0.0; - player->is_rolling = false; } - if(player->is_rolling) + if(attack && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) + { + player->state = CHARACTER_ATTACK; + BUFF_CLEAR(&player->done_damage_to_this_swing); + player->swing_progress = 0.0; + } + // after images + BUFF_ITER(PlayerAfterImage, &player->after_images) + { + it->alive_for += dt; + } + if(player->after_images.data[0].alive_for >= AFTERIMAGE_LIFETIME) { - player->after_image_timer += dt; - player->time_not_rolling = 0.0f; - player->roll_progress += dt; - if(player->roll_progress > anim_sprite_duration(ANIM_knight_rolling)) + BUFF_REMOVE_FRONT(&player->after_images); + } + + // roll processing + { + if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING) { + player->roll_progress = 0.0; player->is_rolling = false; } + if(player->is_rolling) + { + player->after_image_timer += dt; + player->time_not_rolling = 0.0f; + player->roll_progress += dt; + if(player->roll_progress > anim_sprite_duration(ANIM_knight_rolling)) + { + player->is_rolling = false; + } + } + if(!player->is_rolling) player->time_not_rolling += dt; } - if(!player->is_rolling) player->time_not_rolling += dt; - } - Vec2 target_vel = {0}; - float speed = 0.0f; - - if(LenV2(movement) > 0.01f) player->to_throw_direction = NormV2(movement); - if(player->state == CHARACTER_WALKING) - { - speed = PLAYER_SPEED; - if(player->is_rolling) speed = PLAYER_ROLL_SPEED; + Vec2 target_vel = {0}; - if(gete(player->holding_item) && gete(player->holding_item)->item_kind == ITEM_Boots) + if(LenV2(movement) > 0.01f) player->to_throw_direction = NormV2(movement); + if(player->state == CHARACTER_WALKING) { - speed *= 2.0f; - } + speed = PLAYER_SPEED; + if(player->is_rolling) speed = PLAYER_ROLL_SPEED; - if(LenV2(movement) == 0.0) + if(gete(player->holding_item) && gete(player->holding_item)->item_kind == ITEM_Boots) + { + speed *= 2.0f; + } + + if(LenV2(movement) == 0.0) + { + player->state = CHARACTER_IDLE; + } + else + { + } + } + else if(player->state == CHARACTER_IDLE) { - player->state = CHARACTER_IDLE; + if(LenV2(movement) > 0.01) player->state = CHARACTER_WALKING; } - else + else if(player->state == CHARACTER_ATTACK) { + AABB weapon_aabb = entity_sword_aabb(player, 40.0f, 25.0f); + dbgrect(weapon_aabb); + SwordToDamage to_damage = entity_sword_to_do_damage(player, get_overlapping(cur_level, weapon_aabb)); + BUFF_ITER(Entity*, &to_damage) + { + request_do_damage(*it, player, DAMAGE_SWORD); + } + player->swing_progress += dt; + if(player->swing_progress > anim_sprite_duration(ANIM_knight_attack)) + { + player->state = CHARACTER_IDLE; + } } - } - else if(player->state == CHARACTER_IDLE) - { - if(LenV2(movement) > 0.01) player->state = CHARACTER_WALKING; - } - else if(player->state == CHARACTER_ATTACK) - { - AABB weapon_aabb = entity_sword_aabb(player, 40.0f, 25.0f); - dbgrect(weapon_aabb); - SwordToDamage to_damage = entity_sword_to_do_damage(player, get_overlapping(cur_level, weapon_aabb)); - BUFF_ITER(Entity*, &to_damage) + else if(player->state == CHARACTER_TALKING) { - request_do_damage(*it, player, DAMAGE_SWORD); } - player->swing_progress += dt; - if(player->swing_progress > anim_sprite_duration(ANIM_knight_attack)) + else { - player->state = CHARACTER_IDLE; + assert(false); // unknown character state? not defined how to process } - } - else if(player->state == CHARACTER_TALKING) - { - } - else - { - assert(false); // unknown character state? not defined how to process - } + } // not time stopped // velocity processing { @@ -2958,13 +2990,14 @@ void frame(void) } } interact_just_pressed = false; + mouse_just_clicked = false; interact = false; } // while loop } - PROFILE_SCOPE("render player") - { + PROFILE_SCOPE("render player") + { DrawnAnimatedSprite to_draw = {0}; Vec2 character_sprite_pos = AddV2(player->pos, V2(0.0, 20.0f)); // if somebody, show their dialog panel @@ -3023,6 +3056,14 @@ void frame(void) draw_quad((DrawParams){false, (Quad){.ul=V2(0.0f, screen_size().Y), .ur = screen_size(), .lr = V2(screen_size().X, 0.0f)}, image_hurt_vignette, full_region(image_hurt_vignette), (Color){1.0f, 1.0f, 1.0f, player->damage}, .y_coord_sorting = Y_COORD_IN_FRONT, .queue_for_translucent = true}); } + player->anim_change_timer += dt; + if(player->anim_change_timer >= 0.05f) + { + player->anim_change_timer = 0.0f; + player->cur_animation = to_draw.anim; + } + to_draw.anim = player->cur_animation; + Vec2 target_sprite_pos = to_draw.pos; BUFF_ITER(PlayerAfterImage, &player->after_images) { @@ -3069,38 +3110,6 @@ void frame(void) } #endif - // draw drop shadow - //if(it->is_character || it->is_npc || it->is_prop) - if(false) - { - //if(it->npc_kind != DEATH) - { - float shadow_size = GET_TABLE(sprites, ANIM_knight_rolling).region_size.x * 0.5f; - Vec2 shadow_offset = V2(0.0f, -20.0f); - if(npc_is_knight_sprite(it)) - { - shadow_offset = V2(0.5f, -10.0f); - } -#if 0 - if(it->npc_kind == MERCHANT) - { - shadow_offset = V2(-4.5f, -15.0f); - } - else if(it->npc_kind == OLD_MAN) - { - shadow_offset = V2(-1.5f, -8.0f); - shadow_size *= 0.5f; - } -#endif - if(it->is_prop) - { - shadow_size *= 2.5f; - shadow_offset = V2(-5.0f, -8.0f); - } - draw_quad((DrawParams){true, quad_centered(AddV2(it->pos, shadow_offset), V2(shadow_size, shadow_size)),IMG(image_drop_shadow), (Color){1.0f,1.0f,1.0f,0.5f}}); - } - } - Color col = LerpV4(WHITE, it->damage, RED); if(it->is_npc) { @@ -3111,7 +3120,14 @@ void frame(void) float max_dist = dialog_interact_size/2.0f; float alpha = 1.0f - (float)clamp(dist/max_dist, 0.0, 1.0); if(gete(player->talking_to) == it && player->state == CHARACTER_TALKING) alpha = 1.0f; - draw_dialog_panel(it, alpha); + if(it->being_hovered) + { + draw_quad((DrawParams){true, quad_centered(it->pos, V2(TILE_SIZE, TILE_SIZE)), IMG(image_hovering_circle), WHITE}); + alpha = 1.0f; + } + + it->dialog_panel_opacity = Lerp(it->dialog_panel_opacity, unwarped_dt*10.0f, alpha); + draw_dialog_panel(it, it->dialog_panel_opacity); } if(it->npc_kind == NPC_OldMan) @@ -3326,7 +3342,7 @@ void frame(void) } else { - cam.pos = LerpV2(cam.pos, dt*8.0f, target); + cam.pos = LerpV2(cam.pos, unwarped_dt*8.0f, target); } } @@ -3466,7 +3482,18 @@ void event(const sapp_event *e) } } + if(e->type == SAPP_EVENTTYPE_MOUSE_DOWN) + { + if(e->mouse_button == SAPP_MOUSEBUTTON_LEFT) + { + mouse_just_clicked = true; + } + } + if(e->type == SAPP_EVENTTYPE_KEY_DOWN) +#ifdef DESKTOP + if(!receiving_text_input) +#endif { mobile_controls = false; assert(e->key_code < sizeof(keydown)/sizeof(*keydown)); diff --git a/makeprompt.h b/makeprompt.h index 963de05..0f62847 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -179,9 +179,11 @@ typedef struct Entity // npcs bool is_npc; + bool being_hovered; bool perceptions_dirty; BUFF(Perception, REMEMBERED_PERCEPTIONS) remembered_perceptions; bool direction_of_spiral_pattern; + float dialog_panel_opacity; double characters_said; NPCPlayerStanding standing; NpcKind npc_kind; @@ -199,12 +201,18 @@ typedef struct Entity // character bool is_character; EntityRef holding_item; - bool has_paused_time; + bool in_conversation_mode; Vec2 to_throw_direction; + CharacterState state; EntityRef talking_to; // Maybe should be generational index, but I dunno. No death yet bool is_rolling; // can only roll in idle or walk states double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible + + // so doesn't change animations while time is stopped + AnimKind cur_animation; + float anim_change_timer; + BUFF(PlayerAfterImage, MAX_AFTERIMAGES) after_images; double after_image_timer; double roll_progress;