diff --git a/assets/Roboto-Regular.ttf b/assets/Roboto-Regular.ttf new file mode 100644 index 0000000..3033308 Binary files /dev/null and b/assets/Roboto-Regular.ttf differ diff --git a/assets/new_level.json b/assets/new_level.json index df7244c..6e7042d 100644 --- a/assets/new_level.json +++ b/assets/new_level.json @@ -382,8 +382,8 @@ "rotation":0, "visible":true, "width":32, - "x":2087.33333333334, - "y":2247.66666666667 + "x":1506.00000000001, + "y":2563.66666666667 }, { "class":"", @@ -443,8 +443,8 @@ "rotation":0, "visible":true, "width":32, - "x":1470, - "y":2534 + "x":1572.66666666667, + "y":2226 }], "opacity":1, "type":"objectgroup", diff --git a/main.c b/main.c index 0c37eb8..e49d21a 100644 --- a/main.c +++ b/main.c @@ -1196,6 +1196,13 @@ void reset_level() { if (it->npc_kind == NPC_TheBlacksmith) { + Memory test_memory = {0}; + test_memory.context.author_npc_kind = NPC_TheBlacksmith; + MD_String8 speech = MD_S8Lit("This is some very important testing dialog. Too important to count. Very very very very important. Super caliafradgalisticexpelaladosis"); + memcpy(test_memory.speech, speech.str, speech.size); + test_memory.speech_length = (int)speech.size; + RANGE_ITER(0, 15) + BUFF_APPEND(&it->memories, test_memory); //RANGE_ITER(0, 20) //BUFF_APPEND(&it->remembered_perceptions, ((Perception) { .type = PlayerDialog, .player_dialog = SENTENCE_CONST("Testing dialog") })); @@ -1405,7 +1412,7 @@ void init(void) // load font { - FILE* fontFile = fopen("assets/orange kid.ttf", "rb"); + FILE* fontFile = fopen("assets/Roboto-Regular.ttf", "rb"); fseek(fontFile, 0, SEEK_END); size_t size = ftell(fontFile); /* how long is the file ? */ fseek(fontFile, 0, SEEK_SET); /* reset */ @@ -1431,8 +1438,8 @@ void init(void) .width = 512, .height = 512, .pixel_format = SG_PIXELFORMAT_RGBA8, - .min_filter = SG_FILTER_NEAREST, - .mag_filter = SG_FILTER_NEAREST, + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, .data.subimage[0][0] = { .ptr = font_bitmap_rgba, @@ -1637,6 +1644,11 @@ Vec2 screen_to_world(Vec2 screen) return to_return; } +AABB aabb_screen_to_world(AABB screen) +{ + return (AABB) { .upper_left = screen_to_world(screen.upper_left), .lower_right = screen_to_world(screen.lower_right ), }; +} + AABB aabb_at(Vec2 at, Vec2 size) { return (AABB) { @@ -2003,14 +2015,11 @@ Vec2 NormV2_or_zero(Vec2 v) } } - -// in world coordinates -bool in_screen_space = false; -void line(Vec2 from, Vec2 to, float line_width, Color color) +Quad line_quad(Vec2 from, Vec2 to, float line_width) { Vec2 normal = rotate_counter_clockwise(NormV2_or_zero(SubV2(to, from))); - Quad line_quad = { + return (Quad){ .points = { AddV2(from, MulV2F(normal, line_width)), // upper left AddV2(to, MulV2F(normal, line_width)), // upper right @@ -2018,7 +2027,13 @@ void line(Vec2 from, Vec2 to, float line_width, Color color) AddV2(from, MulV2F(normal, -line_width)), // lower left } }; - colorquad(!in_screen_space, line_quad, color); +} + +// in world coordinates +bool in_screen_space = false; +void line(Vec2 from, Vec2 to, float line_width, Color color) +{ + colorquad(!in_screen_space, line_quad(from, to, line_width), color); } #ifdef DEVTOOLS @@ -2510,75 +2525,62 @@ float character_width(int ascii_letter, float text_scale) return (float)advanceWidth * font_scale * text_scale; } -typedef struct +typedef struct PlacedWord { - bool dry_run; - Vec2 at_point; - float max_width; + struct PlacedWord *next; + struct PlacedWord *prev; MD_String8 text; - Color *colors; - float text_scale; - AABB clip_to; - bool do_clipping; + Vec2 lower_left_corner; +} PlacedWord; - bool screen_space; -} WrappedTextParams; +typedef struct +{ + PlacedWord *first; + PlacedWord *last; +} PlacedWordList; -// returns next vertical cursor position -float draw_wrapped_text(WrappedTextParams p) +PlacedWordList place_wrapped_words(MD_Arena *arena, MD_String8 text, float text_scale, float maximum_width) { - char * sentence_to_draw = (char*)p.text.str; - size_t sentence_len = p.text.size; + PlacedWordList to_return = {0}; + MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); - Vec2 cursor = p.at_point; - while (sentence_len > 0) + MD_String8 word_delimeters[] = { MD_S8Lit(" ") }; + MD_String8List words = MD_S8Split(scratch.arena, text, ARRLEN(word_delimeters), word_delimeters); + Vec2 at_position = V2(0.0, 0.0); + Vec2 cur = at_position; + float space_size = character_width((int)' ', text_scale); + float current_vertical_offset = 0.0f; // goes negative + for(MD_String8Node *next_word = words.first; next_word; next_word = next_word->next) { - char line_to_draw[MAX_SENTENCE_LENGTH] = { 0 }; - Color colors_to_draw[MAX_SENTENCE_LENGTH] = { 0 }; - size_t chars_from_sentence = 0; - AABB line_bounds = { 0 }; - float current_line_width = 0.0f; - while (chars_from_sentence <= sentence_len) - { - //line_bounds = draw_text((TextParams) { !p.screen_space, true, MD_S8CString(line_to_draw), cursor, BLACK, p.text_scale, p.clip_to, .do_clipping = p.do_clipping}); - if (current_line_width >= p.max_width) - { - // too big - if (chars_from_sentence <= 0) chars_from_sentence = 1; // @CREDIT(warehouse56) always draw at least one character, if there's not enough room - chars_from_sentence -= 1; - break; - } - chars_from_sentence += 1; - memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); - memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); - //AABB next_character_bounds = draw_text((TextParams) { !p.screen_space, true, MD_S8((MD_u8*)line_to_draw + chars_from_sentence - 1, 1), cursor, BLACK, p.text_scale, p.clip_to, .do_clipping = p.do_clipping}); - //current_line_width += aabb_size(next_character_bounds).x; - current_line_width += character_width( *(line_to_draw + chars_from_sentence - 1), p.text_scale); - } - if (chars_from_sentence > sentence_len) chars_from_sentence--; - memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); - memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); - memcpy(colors_to_draw, p.colors, chars_from_sentence*sizeof(Color)); - - //float line_height = line_bounds.upper_left.Y - line_bounds.lower_right.Y; - float line_height = font_line_advance * p.text_scale; - AABB drawn_bounds = draw_text((TextParams) { !p.screen_space, p.dry_run, MD_S8CString(line_to_draw), AddV2(cursor, V2(0.0f, -line_height)), BLACK, p.text_scale, p.clip_to, colors_to_draw, .do_clipping = p.do_clipping}); - if (!p.dry_run) dbgrect(drawn_bounds); - - // caught a random infinite loop in the debugger, this will stop it - assert(chars_from_sentence >= 0); // defensive programming - if (chars_from_sentence == 0) + AABB word_bounds = draw_text((TextParams){false, true, next_word->string, V2(0.0, 0.0), .scale = text_scale}); + word_bounds.lower_right.x += space_size; + float next_x_position = cur.x + aabb_size(word_bounds).x; + if(next_x_position - at_position.x > maximum_width) { - break; + current_vertical_offset -= font_line_advance*text_scale*1.1f; // the 1.1 is just arbitrary padding because it looks too crowded otherwise + cur = AddV2(at_position, V2(0.0f, current_vertical_offset)); + next_x_position = cur.x + aabb_size(word_bounds).x; } - sentence_len -= chars_from_sentence; - sentence_to_draw += chars_from_sentence; - p.colors += chars_from_sentence; - cursor = V2(drawn_bounds.upper_left.X, drawn_bounds.lower_right.Y); + PlacedWord *new_placed = MD_PushArray(arena, PlacedWord, 1); + new_placed->text = next_word->string; + new_placed->lower_left_corner = cur; + + MD_DblPushBack(to_return.first, to_return.last, new_placed); + + cur.x = next_x_position; } - return cursor.Y; + MD_ReleaseScratch(scratch); + return to_return; +} + +void translate_words_by(PlacedWordList words, Vec2 translation) +{ + for(PlacedWord *cur = words.first; cur; cur = cur->next) + { + cur->lower_left_corner = AddV2(cur->lower_left_corner, translation); + } } MD_String8 last_said_sentence(Entity *npc) @@ -2710,6 +2712,8 @@ Vec2 mouse_pos = { 0 }; // in screen space void draw_dialog_panel(Entity *talking_to, float alpha) { + MD_ArenaTemp scratch = MD_GetScratch(0, 0); + float panel_width = 250.0f; float panel_height = 150.0f; float panel_vert_offset = 30.0f; @@ -2759,39 +2763,48 @@ void draw_dialog_panel(Entity *talking_to, float alpha) { DialogElement *it = &dialog.data[i]; { - Color *colors = calloc(sizeof(*colors), it->speech_length); - for (int char_i = 0; char_i < it->speech_length; char_i++) + Color color; + // decide color { if(it->was_eavesdropped) { - colors[char_i] = colhex(0x9341a3); + color = colhex(0x9341a3); } else { if (it->kind == DELEM_PLAYER) { - colors[char_i] = BLACK; + color = BLACK; } else if (it->kind == DELEM_NPC) { - colors[char_i] = colhex(0x345e22); + color = colhex(0x345e22); } else if (it->kind == DELEM_ACTION_DESCRIPTION) { - colors[char_i] = colhex(0xb5910e); + color = colhex(0xb5910e); } else { assert(false); } } - colors[char_i] = blendalpha(colors[char_i], alpha); } - float measured_line_height = draw_wrapped_text((WrappedTextParams) { true, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, MD_S8(it->speech, it->speech_length), colors, 0.5f, .clip_to = dialog_panel, .do_clipping = true}); - new_line_height += (new_line_height - measured_line_height); - draw_wrapped_text((WrappedTextParams) { false, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, MD_S8(it->speech, it->speech_length), colors, 0.5f, dialog_panel, .do_clipping = true }); - free(colors); + color = blendalpha(color, alpha); + const float text_scale = 0.5f; + PlacedWordList wrapped = place_wrapped_words(scratch.arena, MD_S8(it->speech, it->speech_length), text_scale, dialog_panel.lower_right.x - dialog_panel.upper_left.x); + float line_vertical_offset = -wrapped.last->lower_left_corner.y; + translate_words_by(wrapped, V2(0.0, line_vertical_offset)); + translate_words_by(wrapped, V2(dialog_panel.upper_left.x, new_line_height)); + new_line_height += line_vertical_offset + font_line_advance * text_scale; + + AABB no_clip_curly_things = dialog_panel; + no_clip_curly_things.lower_right.y -= padding; + for(PlacedWord *cur = wrapped.first; cur; cur = cur->next) + { + draw_text((TextParams){true, false, cur->text, cur->lower_left_corner, color, text_scale, .clip_to = no_clip_curly_things, .do_clipping = true,}); + } } } } @@ -2799,6 +2812,8 @@ void draw_dialog_panel(Entity *talking_to, float alpha) dbgrect(dialog_panel); } } + + MD_ReleaseScratch(scratch); } @@ -4277,54 +4292,75 @@ void frame(void) const float dialog_text_scale = 1.0f; float button_grid_height = button_size.y; - AABB dialog_text_aabb = panel_aabb; - dialog_text_aabb.lower_right.y += button_grid_height + 20.0f; // a little bit of padding because the buttons go up - float new_line_height = dialog_text_aabb.lower_right.y; + AABB dialog_panel = panel_aabb; + dialog_panel.lower_right.y += button_grid_height + 20.0f; // a little bit of padding because the buttons go up + float new_line_height = dialog_panel.lower_right.y; - if (talking_to) + // talking to dialog text + if (talking_to && aabb_is_valid(dialog_panel)) { + MD_ArenaTemp scratch = MD_GetScratch(0, 0); Dialog dialog = produce_dialog(talking_to, true); { for (int i = dialog.cur_index - 1; i >= 0; i--) { DialogElement *it = &dialog.data[i]; { - Color *colors = calloc(sizeof(*colors), it->speech_length); - for (int char_i = 0; char_i < it->speech_length; char_i++) + Color color; + if(it->was_eavesdropped) + { + color = colhex(0xcb40e6); + } + else { - if(it->was_eavesdropped) + if (it->kind == DELEM_PLAYER) + { + color = WHITE; + } + else if (it->kind == DELEM_NPC) { - colors[char_i] = colhex(0xcb40e6); + color = colhex(0x34e05c); + } + else if (it->kind == DELEM_ACTION_DESCRIPTION) + { + color = colhex(0xebc334); } else { - if (it->kind == DELEM_PLAYER) - { - colors[char_i] = WHITE; - } - else if (it->kind == DELEM_NPC) - { - colors[char_i] = colhex(0x34e05c); - } - else if (it->kind == DELEM_ACTION_DESCRIPTION) - { - colors[char_i] = colhex(0xebc334); - } - else - { - assert(false); - } + assert(false); } - colors[char_i] = blendalpha(colors[char_i], alpha); } - float measured_line_height = draw_wrapped_text((WrappedTextParams) { true, V2(dialog_text_aabb.upper_left.X, new_line_height), dialog_text_aabb.lower_right.X - dialog_text_aabb.upper_left.X, MD_S8(it->speech, it->speech_length), colors, dialog_text_scale, dialog_text_aabb, .screen_space = true, .do_clipping = true}); - new_line_height += (new_line_height - measured_line_height); - draw_wrapped_text((WrappedTextParams) { false, V2(dialog_text_aabb.upper_left.X, new_line_height), dialog_text_aabb.lower_right.X - dialog_text_aabb.upper_left.X, MD_S8(it->speech, it->speech_length), colors, dialog_text_scale, dialog_text_aabb, .screen_space = true, .do_clipping = true}); + color = blendalpha(color, alpha); + + const float text_scale = 1.0f; + PlacedWordList wrapped = place_wrapped_words(scratch.arena, MD_S8(it->speech, it->speech_length), text_scale, dialog_panel.lower_right.x - dialog_panel.upper_left.x); + float line_vertical_offset = -wrapped.last->lower_left_corner.y; + translate_words_by(wrapped, V2(0.0, line_vertical_offset)); + translate_words_by(wrapped, V2(dialog_panel.upper_left.x, new_line_height)); + new_line_height += line_vertical_offset + font_line_advance * text_scale; + + for(PlacedWord *cur = wrapped.first; cur; cur = cur->next) + { + AABB clipping_aabb = dialog_panel; + clipping_aabb.lower_right.y -= 50.0f; + draw_text((TextParams){false, false, cur->text, cur->lower_left_corner, color, text_scale, .clip_to = clipping_aabb, .do_clipping = true,}); + } + + if(i != 0) + { + float separator_height = 40.0f; // how much vertical space the whole separation, including padding, takes + float line_height = 1.0f; + Vec2 line_from = AddV2(wrapped.first->lower_left_corner, V2(0, font_line_advance*text_scale + separator_height/2.0f)); + Vec2 line_to = AddV2(line_from, V2(aabb_size(dialog_panel).x, 0)); + draw_quad((DrawParams){false, line_quad(line_from, line_to, line_height), IMG(image_white_square), blendalpha(WHITE, 0.6f), .clip_to = dialog_panel, .do_clipping = true}); + + new_line_height += separator_height; + } - free(colors); } } } + MD_ReleaseScratch(scratch); } } } @@ -4483,6 +4519,7 @@ void frame(void) } #ifdef DEVTOOLS + dbgsquare(screen_to_world(mouse_pos)); // tile coord