From 4ccbf1959b74ff36d96a283fca8a1fb488d15fe8 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sat, 25 Feb 2023 20:30:51 -0800 Subject: [PATCH] Stuff (sorry future person, forgot to isolate): - Text input for desktop - Buff macro - Camera snapping bug fixes - Bunch of state dialog bug fixes --- main.c | 300 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 222 insertions(+), 78 deletions(-) diff --git a/main.c b/main.c index 964e2a5..6ccadff 100644 --- a/main.c +++ b/main.c @@ -1,9 +1,11 @@ #define SOKOL_IMPL #if defined(WIN32) || defined(_WIN32) +#define DESKTOP #define SOKOL_D3D11 #endif #if defined(__EMSCRIPTEN__) +#define WEB #define SOKOL_GLES2 #endif @@ -89,6 +91,7 @@ typedef enum CharacterState CHARACTER_WALKING, CHARACTER_IDLE, CHARACTER_ATTACK, + CHARACTER_TALKING, } CharacterState; typedef enum EntityKind @@ -100,19 +103,28 @@ typedef enum EntityKind ENTITY_BULLET, } EntityKind; +#define BUFF(type, max_size) struct { type data[max_size]; int cur_index; } +#define BUFF_HAS_SPACE(buff_ptr) ( (buff_ptr)->cur_index < ARRLEN((buff_ptr)->data) ) +#define BUFF_APPEND(buff_ptr, element) { (buff_ptr)->data[(buff_ptr)->cur_index++] = element; assert((buff_ptr)->cur_index < ARRLEN((buff_ptr)->data)); } +#define BUFF_ITER(type, buff_ptr) for(type *it = &((buff_ptr)->data[0]); it < (buff_ptr)->data + (buff_ptr)->cur_index; it++) +#define BUFF_CLEAR(buff_ptr) {memset((buff_ptr), 0, sizeof(*(buff_ptr))); ((buff_ptr)->cur_index = 0);} + #define MAX_SENTENCE_LENGTH 400 -typedef struct { char text[MAX_SENTENCE_LENGTH]; } Sentence; +typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence; +#define SENTENCE_CONST(txt) (Sentence){.data=txt, .cur_index=sizeof(txt)} -typedef struct Dialog -{ - Sentence sentences[8]; -} Dialog; + +// even indexed dialogs (0,2,4) are player saying stuff, odds are the character from GPT +typedef BUFF(Sentence, 8) Dialog; typedef struct Entity { bool exists; EntityKind kind; + // for all NPCS (maybe make this allocated somewhere instead of in every entity? it's kind of big...) + Dialog player_dialog; + // fields for all entities Vec2 pos; Vec2 vel; // only used sometimes, like in old man and bullet @@ -125,6 +137,7 @@ typedef struct Entity // character CharacterState state; + struct Entity *talking_to; // Maybe should be generational index, but I dunno. No death yet bool is_rolling; // can only roll in idle or walk states float speed; // for lerping to the speed, so that roll gives speed boost which fades double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible @@ -139,9 +152,6 @@ typedef struct Overlap Entity *e; } Overlap; -#define BUFF(type, max_size) struct { type data[max_size]; int cur_index; } -#define BUFF_APPEND(buff_ptr, element) { (buff_ptr)->data[(buff_ptr)->cur_index++] = element; assert((buff_ptr)->cur_index < ARRLEN((buff_ptr)->data)); } -#define BUFF_ITER(type, buff_ptr) for(type *it = &((buff_ptr)->data[0]); it < (buff_ptr)->data + (buff_ptr)->cur_index; it++) typedef BUFF(Overlap, 16) Overlapping; @@ -170,6 +180,57 @@ typedef struct Arena size_t cur; } Arena; +Entity *player = NULL; // up here, used in text backend callback + +Sentence text_input_buffer; +void begin_text_input(); // called when player engages in dialog, must say something and fill text_input_buffer +// a callback, when 'text backend' has finished making text +void end_text_input() +{ + // the new elements wouldn't fit! + Dialog *to_append = &player->talking_to->player_dialog; + if(to_append->cur_index + 2 >= ARRLEN(to_append->data)) + { + // do it twice + for(int i = 0; i < 2; i++) + { + // shift array elements backwards, once + assert(ARRLEN(to_append->data) >= 1); + for(int i = 0; i < ARRLEN(to_append->data) - 1; i++) + { + to_append->data[i] = to_append->data[i + 1]; + } + to_append->cur_index--; + } + } + BUFF_APPEND(to_append, text_input_buffer); + BUFF_APPEND(to_append, SENTENCE_CONST("NPC response")); + player->state = CHARACTER_IDLE; +} + +#ifdef DESKTOP +bool receiving_text_input = false; +void begin_text_input() +{ + receiving_text_input = true; + BUFF_CLEAR(&text_input_buffer); +} +#else +#ifdef WEB +void begin_text_input() +{ +} +#else +#error "No platform defined for text input! +#endif // web + +#endif // desktop + +Vec2 FloorV2(Vec2 v) +{ + return V2(floorf(v.x), floorf(v.y)); +} + Arena make_arena(size_t max_size) { return (Arena) @@ -179,13 +240,11 @@ Arena make_arena(size_t max_size) .cur = 0, }; } - void reset(Arena *a) { memset(a->data, 0, a->data_size); a->cur = 0; } - char *get(Arena *a, size_t of_size) { assert(a->data != NULL); @@ -409,7 +468,6 @@ static struct AABB level_aabb = { .upper_left = {0.0f, 0.0f}, .lower_right = {2000.0f, -2000.0f} }; Entity entities[MAX_ENTITIES] = {0}; -Entity *player = NULL; Entity *new_entity() { @@ -573,9 +631,8 @@ Camera cam = {.scale = 2.0f }; Vec2 cam_offset() { Vec2 to_return = AddV2(cam.pos, MulV2F(screen_size(), 0.5f)); - to_return.X = (float)(int)to_return.X; - to_return.Y = (float)(int)to_return.Y; - return to_return; + to_return = FloorV2(to_return); // avoid pixel glitching on tilemap atlas + return to_return; } // in pixels @@ -751,7 +808,8 @@ void draw_quad(bool world_space, Quad quad, sg_image image, AABB image_region, C params.tint[2] = tint.B; params.tint[3] = tint.A; - if(image.id != cur_batch_image.id) + // if the rendering call is different, and the batch must be flushed + if(image.id != cur_batch_image.id || memcmp(¶ms,&cur_batch_params,sizeof(params)) != 0 ) { flush_quad_batch(); cur_batch_image = image; @@ -1170,17 +1228,19 @@ float draw_wrapped_text(Vec2 at_point, float max_width, char *text, float text_s return cursor.Y; } - +#define ROLL_KEY SAPP_KEYCODE_K double elapsed_time = 0.0; double last_frame_processing_time = 0.0; uint64_t last_frame_time; Vec2 mouse_pos = {0}; // in screen space bool keydown[SAPP_KEYCODE_MENU] = {0}; +bool roll_just_pressed = false; // to use to initiate dialog, shouldn't initiate dialog if the button is simply held down #ifdef DEVTOOLS bool mouse_frozen = false; #endif void frame(void) { + dbgprint("%f %f\n", cam_offset().x, cam.pos.x); #if 0 { sg_begin_default_pass(&state.pass_action, sapp_width(), sapp_height()); @@ -1215,7 +1275,7 @@ void frame(void) (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S] ); bool attack = keydown[SAPP_KEYCODE_J]; - bool roll = keydown[SAPP_KEYCODE_K]; + bool roll = keydown[ROLL_KEY]; if(LenV2(movement) > 1.0) { movement = NormV2(movement); @@ -1371,36 +1431,128 @@ void frame(void) { Vec2 character_sprite_pos = AddV2(player->pos, V2(0.0, 20.0f)); - if(attack && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) + + // do dialog + Entity *closest_talkto = NULL; { - player->state = CHARACTER_ATTACK; - player->swing_progress = 0.0; - } + // find closest to talk to + { + AABB dialog_rect = centered_aabb(player->pos, V2(TILE_SIZE*2.0f, TILE_SIZE*2.0f)); + dbgrect(dialog_rect); + Overlapping possible_dialogs = get_overlapping(cur_level, dialog_rect); + float closest_talkto_dist = INFINITY; + BUFF_ITER(Overlap, &possible_dialogs) + { + if(!it->is_tile && it->e->kind == ENTITY_OLD_MAN && !it->e->aggressive) + { + float dist = LenV2(SubV2(it->e->pos, player->pos)); + if(dist < closest_talkto_dist) + { + closest_talkto_dist = dist; + closest_talkto = it->e; + } + } + } + } + + Entity *talking_to = closest_talkto; + if(player->state == CHARACTER_TALKING) + { + talking_to = player->talking_to; + assert(talking_to); + } + + // if somebody, show their dialog panel + if(talking_to) { + // talking to them feedback + draw_quad(true, quad_centered(talking_to->pos, V2(TILE_SIZE, TILE_SIZE)), image_dialog_circle, full_region(image_dialog_circle), WHITE); + float panel_width = 250.0f; + float panel_height = 100.0f; + float panel_vert_offset = 30.0f; + AABB dialog_panel = (AABB){ + .upper_left = AddV2(talking_to->pos, V2(-panel_width/2.0f, panel_vert_offset+panel_height)), + .lower_right = AddV2(talking_to->pos, V2(panel_width/2.0f, panel_vert_offset)), + }; + colorquad(true, quad_aabb(dialog_panel), (Color){1.0f, 1.0f, 1.0f, 0.2f}); + + float new_line_height = dialog_panel.upper_left.Y; + int i = 0; + BUFF_ITER(Sentence, &talking_to->player_dialog) + { + new_line_height = draw_wrapped_text(V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->data, 0.5f, i % 2 == 0 ? GREEN : WHITE); + i++; + } + + dbgrect(dialog_panel); + + } + - // rolling - if(roll && !player->is_rolling && player->time_not_rolling > 0.3f && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) + // process dialog and display dialog box when talking to NPC + if(player->state == CHARACTER_TALKING) + { + assert(player->talking_to != NULL); + + if(player->talking_to->aggressive || !player->exists) + { + player->state = CHARACTER_IDLE; + } + } + } + // roll input management, sometimes means talk to the npc + if(player->state != CHARACTER_TALKING && roll_just_pressed && closest_talkto != NULL) { - player->is_rolling = true; - player->roll_progress = 0.0; - player->speed = PLAYER_ROLL_SPEED; + // begin dialog with closest npc + player->state = CHARACTER_TALKING; + player->talking_to = closest_talkto; + begin_text_input(); } - if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING) + else { - player->roll_progress = 0.0; - player->is_rolling = false; + // rolling trigger from input + 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->speed = PLAYER_ROLL_SPEED; + } } - if(player->is_rolling) + + if(attack && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) { - player->time_not_rolling = 0.0f; - player->roll_progress += dt; - if(player->roll_progress > anim_sprite_duration(&knight_rolling)) + player->state = CHARACTER_ATTACK; + player->swing_progress = 0.0; + } + + + // 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->time_not_rolling = 0.0f; + player->roll_progress += dt; + if(player->roll_progress > anim_sprite_duration(&knight_rolling)) + { + player->is_rolling = false; + } + } + if(!player->is_rolling) player->time_not_rolling += dt; } - if(!player->is_rolling) player->time_not_rolling += dt; - cam.pos = LerpV2(cam.pos, dt*8.0f, MulV2F(player->pos, -1.0f * cam.scale)); + Vec2 target = MulV2F(player->pos, -1.0f * cam.scale); + if(LenV2(SubV2(target, cam.pos)) <= 0.2) + { + cam.pos = target; + } + else + { + cam.pos = LerpV2(cam.pos, dt*8.0f, target); + } if(player->state == CHARACTER_WALKING) { if(player->speed <= 0.01f) player->speed = PLAYER_SPEED; @@ -1443,14 +1595,14 @@ void frame(void) { weapon_aabb = (AABB){ .upper_left = AddV2(player->pos, V2(-40.0, 25.0)), - .lower_right = AddV2(player->pos, V2(0.0, -25.0)), + .lower_right = AddV2(player->pos, V2(0.0, -25.0)), }; } else { weapon_aabb = (AABB){ .upper_left = AddV2(player->pos, V2(0.0, 25.0)), - .lower_right = AddV2(player->pos, V2(40.0, -25.0)), + .lower_right = AddV2(player->pos, V2(40.0, -25.0)), }; } dbgrect(weapon_aabb); @@ -1474,6 +1626,14 @@ void frame(void) player->state = CHARACTER_IDLE; } } + else if(player->state == CHARACTER_TALKING) + { + draw_animated_sprite(&knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE); + } + else + { + assert(false); // unknown character state? not defined how to draw + } // health if(player->damage >= 1.0) @@ -1486,47 +1646,6 @@ void frame(void) } } - // do dialog - AABB dialog_rect = centered_aabb(player->pos, V2(TILE_SIZE*2.0f, TILE_SIZE*2.0f)); - dbgrect(dialog_rect); - Overlapping possible_dialogs = get_overlapping(cur_level, dialog_rect); - Entity *closest_talkto = NULL; - float closest_talkto_dist = INFINITY; - BUFF_ITER(Overlap, &possible_dialogs) - { - if(!it->is_tile && it->e->kind == ENTITY_OLD_MAN && !it->e->aggressive) - { - float dist = LenV2(SubV2(it->e->pos, player->pos)); - if(dist < closest_talkto_dist) - { - closest_talkto_dist = dist; - closest_talkto = it->e; - } - } - } - if(closest_talkto != NULL) - { - draw_quad(true, quad_centered(closest_talkto->pos, V2(TILE_SIZE, TILE_SIZE)), image_dialog_circle, full_region(image_dialog_circle), WHITE); - - Dialog dialog = { - .sentences[0].text = "I'm an old man. fjdslfdasljfla dsfjdsalkf adskjfdlskfkladsjfkljdskljsadlkfjdsaklfjldsajf", - .sentences[1].text = "I'm the player. I have lots of things to say. Bla bla bla. All I do is say things. How cringe and terrible", - }; - float panel_width = 250.0f; - float panel_height = 100.0f; - float panel_vert_offset = 30.0f; - AABB dialog_panel = (AABB){ - .upper_left = AddV2(closest_talkto->pos, V2(-panel_width/2.0f, panel_vert_offset+panel_height)), - .lower_right = AddV2(closest_talkto->pos, V2(panel_width/2.0f, panel_vert_offset)), - }; - colorquad(true, quad_aabb(dialog_panel), (Color){1.0f, 1.0f, 1.0f, 0.2f}); - - float new_line_height = draw_wrapped_text(dialog_panel.upper_left, dialog_panel.lower_right.X - dialog_panel.upper_left.X, dialog.sentences[0].text, 0.5f, WHITE); - new_line_height = draw_wrapped_text(V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, dialog.sentences[1].text, 0.5f, GREEN); - - dbgrect(dialog_panel); - } - flush_quad_batch(); sg_end_pass(); sg_commit(); @@ -1534,6 +1653,7 @@ void frame(void) last_frame_processing_time = stm_sec(stm_diff(stm_now(),time_start_frame)); reset(&scratch); + roll_just_pressed = false; } void cleanup(void) @@ -1543,10 +1663,34 @@ void cleanup(void) void event(const sapp_event *e) { +#ifdef DESKTOP + // the desktop text backend, for debugging purposes + if(receiving_text_input) + { + if(e->type == SAPP_EVENTTYPE_CHAR) + { + if(BUFF_HAS_SPACE(&text_input_buffer)) + { + BUFF_APPEND(&text_input_buffer, (char)e->char_code); + } + } + if(e->type == SAPP_EVENTTYPE_KEY_DOWN && e->key_code == SAPP_KEYCODE_ENTER) + { + receiving_text_input = false; + end_text_input(); + } + } +#endif if(e->type == SAPP_EVENTTYPE_KEY_DOWN) { assert(e->key_code < sizeof(keydown)/sizeof(*keydown)); keydown[e->key_code] = true; + + if(e->key_code == ROLL_KEY) + { + roll_just_pressed = true; + } + if(e->key_code == SAPP_KEYCODE_ESCAPE) { sapp_quit();