#include #include #include #include #include #include #include #include #include #include #include #include #include #include "renderer.h" #include "defer.h" #include "stb_image.h" #include "change_directory.h" #pragma clang diagnostic ignored "-Waddress-of-temporary" using namespace glm; #define ORG ("ammerhai") #define APP ("mikemon") #define ASSETS_PATH "./assets/" #define NEAR_PLANE (0.01f) #define TILE_SIZE (32) #define TILE_ATLAS_SIZE (512) static SDL_Window *window; static R_Texture framebuffer; static R_Texture player_texture; static R_Texture tile_textures_atlas; static R_Texture tile_textures_atlas_imgui; static R_Buffer view_projection_matrix_buffer; static R_Buffer per_frame_buffer; static R_Buffer tint_color_buffer; static R_Buffer vertex_buffer; static R_Buffer index_buffer; static R_Buffer player_instance_buffer; static R_Buffer tile_uvs_buffer; static i32vec2 window_size = { 1280, 720 }; static MIX_Mixer *mixer; static MIX_Track *music_track; static MIX_Track *sfx_track; static MIX_Audio *music_setting_off_piano; static float volume_master = 50.0f; static float volume_music = 50.0f; static float volume_sfx = 50.0f; static bool Running = true; static float camera_fovy_degrees = 31.0f; static float camera_tilt = 25.5f; static float camera_distance = 13.5f; static mat4x4 view_matrix; static mat4x4 inverse_view_matrix; static mat4x4 projection_matrix; static mat4x4 inverse_projection_matrix; static SDL_Time last_time; static SDL_Time current_time; static bool enable_time_tints = true; static bool use_actual_time = true; static SDL_DateTime calendar_time; static vec2 mouse_pos; static bool in_editor; static bool show_grid = true; static vec2 editor_camera_position; static float editor_camera_distance = 30.0f; static bool show_demo_window; static bool show_tile_picker; static bool show_settings; static float character_speed = 4.0f; #define log_error(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) float remap(float in_a, float in_b, float out_a, float out_b, float v) { return mix(out_a, out_b, (v - in_a) / (in_b - in_a)); } vec2 remap(vec2 in_a, vec2 in_b, vec2 out_a, vec2 out_b, vec2 v) { return mix(out_a, out_b, (v - in_a) / (in_b - in_a)); } mat4x4 view(vec3 player_pos, float tilt, float camera_distance) { float s = sinf(tilt); float c = cosf(tilt); return { 1, 0, 0, -player_pos.x, 0, c, s, c * -player_pos.y, 0, -s, c, s * player_pos.y - camera_distance, 0, 0, 0, 1, }; } mat4x4 inverse_view(vec3 player_pos, float tilt, float camera_distance) { float s = sinf(tilt); float c = cosf(tilt); return { 1, 0, 0, player_pos.x, 0, c, -s, -s * camera_distance + player_pos.y, 0, s, c, c * camera_distance, 0, 0, 0, 1, }; } mat4x4 projection(float fovy, float aspect, float near) { float g = 1.0 / tanf(fovy * 0.5); return { g / aspect, 0, 0, 0, 0, g, 0, 0, 0, 0, 0, near, 0, 0, -1, 0, }; } mat4x4 inverse_projection(float fovy, float aspect, float near) { float g = 1.0 / tanf(fovy * 0.5); return { aspect / g, 0, 0, 0, 0, 1 / g, 0, 0, 0, 0, 0, -1, 0, 0, 1 / near, 0, }; } #define MAX_TINT_TIMES 32 static int num_used_tint_times = 4; static int time_tints_times[MAX_TINT_TIMES][3] = { { 4, 0, 0 }, { 9, 0, 0 }, { 19, 0, 0 }, { 21, 0, 0 }, }; static vec3 time_tints[MAX_TINT_TIMES] = { { 0.314f, 0.369f, 0.455f }, { 1.0f, 0.891f, 0.868f }, { 1.0f, 0.465f, 0.373f }, { 0.314f, 0.369f, 0.455f }, }; enum Settings_Category { SETTINGS_UNKNOWN, SETTINGS_AUDIO, }; struct Vertex { vec3 pos; vec2 uv; }; static Vertex vertices[] = { {{ -0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f }}, {{ -0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f }}, {{ 0.5f, -0.5f, 0.0f }, { 1.0f, 1.0f }}, {{ 0.5f, 0.5f, 0.0f }, { 1.0f, 0.0f }}, }; static Uint16 indices[] = { 0, 1, 2, 0, 2, 3, }; struct Instance { vec2 pos; }; static Instance player_instance = {{ 0.0f, 0.0f }}; struct Map { Uint32 version; i32vec2 size; Uint16 *tiles; char name[64]; R_Texture texture; }; static Map current_map; struct Player { i32vec2 position; i32vec2 target_position; bool is_moving; vec2 visual_position; }; static Player player; struct alignas(16) PerFrame { i32vec2 drag_start; i32vec2 mouse; vec2 grid_offset; Uint32 grid_width; Uint32 map_width; Uint32 grid_height; }; static PerFrame per_frame = {}; typedef enum : Uint8 { DIR_DOWN, DIR_UP, DIR_LEFT, DIR_RIGHT, } Direction; typedef enum : Uint8 { TILEKIND_ERROR = 0, TILEKIND_NONE = 1, TILEKIND_GRASS = 2, TILEKIND_DIRT = 3, TILEKIND_WATER = 4, } TileKind; #define TILE_CORNER_INFO(rotation, top_left, top_right, bottom_right, bottom_left) (rotation), std::rotl((Uint32)(((TILEKIND_##top_left) << 24) | ((TILEKIND_##top_right) << 16) | ((TILEKIND_##bottom_right) << 8) | (TILEKIND_##bottom_left)), (rotation) * 8) typedef struct { Uint16 serialization_id; const char *asset_path; Uint8 rotation; Uint32 corner_info; } TileInfo; // TILE_CORNER_INFO ORDER: // 0 1 2 3 // +---+---+ +---+---+ +---+---+ +---+---+ // | 1 | 2 | | 2 | 3 | | 3 | 4 | | 4 | 1 | // +---+---+ +---+---+ +---+---+ +---+---+ // | 4 | 3 | | 1 | 4 | | 2 | 1 | | 3 | 2 | // +---+---+ +---+---+ +---+---+ +---+---+ static TileInfo tile_infos[] = { { 1, "tiles/error.png", TILE_CORNER_INFO(0, ERROR, ERROR, ERROR, ERROR) }, { 0, "tiles/empty.png", TILE_CORNER_INFO(0, NONE, NONE, NONE, NONE ) }, // GRASS { 2, "tiles/grass_3.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) }, { 3, "tiles/grass_1.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) }, { 4, "tiles/grass_2.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) }, { 5, "tiles/grass_4.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) }, // DIRT { 6, "tiles/dirt_3.png", TILE_CORNER_INFO(0, DIRT, DIRT, DIRT, DIRT ) }, { 7, "tiles/dirt_1.png", TILE_CORNER_INFO(0, DIRT, DIRT, DIRT, DIRT ) }, { 8, "tiles/dirt_2.png", TILE_CORNER_INFO(0, DIRT, DIRT, DIRT, DIRT ) }, // WATER { 9, "tiles/water_1.png", TILE_CORNER_INFO(0, WATER, WATER, WATER, WATER) }, { 10, "tiles/water_2.png", TILE_CORNER_INFO(0, WATER, WATER, WATER, WATER) }, // GRASS / DIRT { 11, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, GRASS) }, { 12, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, GRASS) }, { 13, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, GRASS) }, { 14, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, GRASS) }, { 15, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, GRASS) }, { 16, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, GRASS) }, { 17, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, GRASS) }, { 18, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, GRASS) }, { 19, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, GRASS) }, { 20, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, GRASS) }, { 21, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, GRASS) }, { 22, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, GRASS) }, { 23, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, DIRT ) }, { 24, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, DIRT ) }, { 25, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, DIRT ) }, { 26, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, DIRT ) }, { 27, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, DIRT ) }, { 28, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, DIRT ) }, { 29, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, DIRT ) }, { 30, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, DIRT ) }, { 31, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, GRASS) }, { 32, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, GRASS) }, { 33, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(2, GRASS, DIRT, GRASS, GRASS) }, { 34, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(3, GRASS, DIRT, GRASS, GRASS) }, { 35, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, GRASS) }, { 36, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, GRASS) }, { 37, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(2, GRASS, DIRT, GRASS, GRASS) }, { 38, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(3, GRASS, DIRT, GRASS, GRASS) }, { 39, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, GRASS) }, { 40, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, GRASS) }, { 41, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(2, GRASS, DIRT, GRASS, GRASS) }, { 42, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(3, GRASS, DIRT, GRASS, GRASS) }, { 43, "tiles/grass_dirt_two_corner.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, DIRT ) }, { 44, "tiles/grass_dirt_two_corner.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, DIRT ) }, // GRASS / WATER { 47, "tiles/water_grass.png", TILE_CORNER_INFO(0, WATER, WATER, GRASS, GRASS) }, { 48, "tiles/water_grass.png", TILE_CORNER_INFO(1, WATER, WATER, GRASS, GRASS) }, { 49, "tiles/water_grass.png", TILE_CORNER_INFO(2, WATER, WATER, GRASS, GRASS) }, { 50, "tiles/water_grass.png", TILE_CORNER_INFO(3, WATER, WATER, GRASS, GRASS) }, { 51, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(0, WATER, WATER, WATER, GRASS) }, { 52, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(1, WATER, WATER, WATER, GRASS) }, { 53, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(2, WATER, WATER, WATER, GRASS) }, { 54, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(3, WATER, WATER, WATER, GRASS) }, { 55, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(0, GRASS, WATER, GRASS, GRASS) }, { 56, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(1, GRASS, WATER, GRASS, GRASS) }, { 57, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(2, GRASS, WATER, GRASS, GRASS) }, { 58, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(3, GRASS, WATER, GRASS, GRASS) }, { 59, "tiles/grass_water_grass_water.png", TILE_CORNER_INFO(0, GRASS, WATER, GRASS, WATER) }, { 60, "tiles/grass_water_grass_water.png", TILE_CORNER_INFO(1, GRASS, WATER, GRASS, WATER) }, // DIRT / WATER { 61, "tiles/dirt_water.png", TILE_CORNER_INFO(0, DIRT, DIRT, WATER, WATER) }, { 62, "tiles/dirt_water.png", TILE_CORNER_INFO(1, DIRT, DIRT, WATER, WATER) }, { 63, "tiles/dirt_water.png", TILE_CORNER_INFO(2, DIRT, DIRT, WATER, WATER) }, { 64, "tiles/dirt_water.png", TILE_CORNER_INFO(3, DIRT, DIRT, WATER, WATER) }, { 65, "tiles/PH_water_water_dirt_water.png", TILE_CORNER_INFO(0, WATER, WATER, DIRT, WATER) }, { 66, "tiles/PH_water_water_dirt_water.png", TILE_CORNER_INFO(1, WATER, WATER, DIRT, WATER) }, { 67, "tiles/PH_water_water_dirt_water.png", TILE_CORNER_INFO(2, WATER, WATER, DIRT, WATER) }, { 68, "tiles/PH_water_water_dirt_water.png", TILE_CORNER_INFO(3, WATER, WATER, DIRT, WATER) }, { 69, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(0, DIRT, WATER, DIRT, DIRT) }, { 70, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(1, DIRT, WATER, DIRT, DIRT) }, { 71, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(2, DIRT, WATER, DIRT, DIRT) }, { 72, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(3, DIRT, WATER, DIRT, DIRT) }, { 73, "tiles/dirt_water_dirt_water.png", TILE_CORNER_INFO(0, DIRT, WATER, DIRT, WATER) }, { 74, "tiles/dirt_water_dirt_water.png", TILE_CORNER_INFO(1, DIRT, WATER, DIRT, WATER) }, // GRASS / DIRT / WATER { 75, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(0, DIRT, WATER, GRASS, WATER) }, { 76, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(1, DIRT, WATER, GRASS, WATER) }, { 77, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(2, DIRT, WATER, GRASS, WATER) }, { 78, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(3, DIRT, WATER, GRASS, WATER) }, { 79, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(0, WATER, WATER, DIRT, GRASS) }, { 80, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(1, WATER, WATER, DIRT, GRASS) }, { 81, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(2, WATER, WATER, DIRT, GRASS) }, { 82, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(3, WATER, WATER, DIRT, GRASS) }, { 83, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(0, WATER, WATER, GRASS, DIRT) }, { 84, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(1, WATER, WATER, GRASS, DIRT) }, { 85, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(2, WATER, WATER, GRASS, DIRT) }, { 86, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(3, WATER, WATER, GRASS, DIRT) }, { 87, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(0, GRASS, GRASS, WATER, DIRT) }, { 88, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(1, GRASS, GRASS, WATER, DIRT) }, { 89, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(2, GRASS, GRASS, WATER, DIRT) }, { 90, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(3, GRASS, GRASS, WATER, DIRT) }, { 91, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(0, GRASS, GRASS, DIRT, WATER) }, { 92, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(1, GRASS, GRASS, DIRT, WATER) }, { 93, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(2, GRASS, GRASS, DIRT, WATER) }, { 94, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(3, GRASS, GRASS, DIRT, WATER) }, { 95, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(0, GRASS, WATER, GRASS, DIRT) }, { 96, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(1, GRASS, WATER, GRASS, DIRT) }, { 97, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(2, GRASS, WATER, GRASS, DIRT) }, { 98, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(3, GRASS, WATER, GRASS, DIRT) }, { 99, "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(0, DIRT, WATER, DIRT, GRASS) }, { 100, "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(1, DIRT, WATER, DIRT, GRASS) }, { 101, "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(2, DIRT, WATER, DIRT, GRASS) }, { 102, "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(3, DIRT, WATER, DIRT, GRASS) }, { 103, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(0, DIRT, DIRT, WATER, GRASS) }, { 104, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(1, DIRT, DIRT, WATER, GRASS) }, { 105, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(2, DIRT, DIRT, WATER, GRASS) }, { 106, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(3, DIRT, DIRT, WATER, GRASS) }, { 107, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, WATER) }, { 108, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, WATER) }, { 109, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, WATER) }, { 110, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, WATER) }, }; static vec4 tile_uvs[SDL_arraysize(tile_infos)]; static Sint32 selected_tile_kind = -1; static Sint32 selected_tile = -1; static bool dragging_tile_change; static bool dragging_camera_change; static vec2 drag_start_pos; static bool queued_movement; static Direction queued_movement_direction; static R_Texture create_texture(const char *path) { char path_to_load[256] = ASSETS_PATH; SDL_strlcat(path_to_load, path, SDL_arraysize(path_to_load)); int width = 0, height = 0, channels = 0; stbi_uc *data = stbi_load(path_to_load, &width, &height, &channels, 0); if (!data) { log_error("Failed to load texture (\"%s\").", path_to_load); return NULL; } R_Texture result = renderer_texture_create(channels == 4 ? R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB : R_TEXTURE_FORMAT_R8_UNORM, R_TEXTURE_USAGE_SAMPLED, false, width, height, data, path); if (!result) { log_error("Failed to load texture (\"%s\").", path_to_load); stbi_image_free(data); return NULL; } stbi_image_free(data); return result; } #define MAP_FILE_VERSION (2u) static bool save_map(Map map) { char path[256] = ASSETS_PATH "maps/"; SDL_strlcat(path, map.name, SDL_arraysize(path)); SDL_IOStream *file = SDL_IOFromFile(path, "wb"); if (!file) { log_error("Failed to open map file for writing."); return false; } defer(SDL_CloseIO(file)); if (!SDL_WriteU32LE(file, MAP_FILE_VERSION)) { log_error("Failed to write version to map file."); return false; } if (!SDL_WriteS32LE(file, map.size.x)) { log_error("Failed to write width to map file."); return false; } if (!SDL_WriteS32LE(file, map.size.y)) { log_error("Failed to write height to map file."); return false; } for (int i = 0; i < map.size.x * map.size.y; i++) { Uint16 id = tile_infos[map.tiles[i]].serialization_id; if (!SDL_WriteU16LE(file, id)) { log_error("Failed to write tile to map file."); return false; } } if(!SDL_FlushIO(file)) { log_error("Failed to flush data to map file."); return false; }; SDL_Log("Saved map file."); return true; } static bool load_map(const char *name, Map *result) { char path[256] = ASSETS_PATH "maps/"; SDL_strlcat(path, name, SDL_arraysize(path)); SDL_IOStream *file = SDL_IOFromFile(path, "rb"); if (!file) { log_error("Failed to open map file for reading."); return false; } defer(SDL_CloseIO(file)); SDL_memcpy(result->name, name, SDL_min(strlen(name), SDL_arraysize(result->name) - 1)); if (!SDL_ReadU32LE(file, &result->version)) { log_error("Failed read version from map file."); return false; } if (result->version > MAP_FILE_VERSION) { log_error("Map file version (%u) is higher than the highest supported.", result->version); return false; } if (!SDL_ReadS32LE(file, &result->size.x)) { log_error("Failed read width from map file."); return false; } if (!SDL_ReadS32LE(file, &result->size.y)) { log_error("Failed read height from map file."); return false; } result->tiles = (Uint16*)malloc(result->size.x * result->size.y * sizeof(Uint16)); for (int i = 0; i < result->size.x * result->size.y; i++) { if (result->version == 2) { Uint16 serialization_id = 0; if (!SDL_ReadU16LE(file, &serialization_id)) { free(result->tiles); return false; } Uint16 info_index = 0; for (int i = 0; i < SDL_arraysize(tile_infos); i++) { if (tile_infos[i].serialization_id == serialization_id) { info_index = i; break; } } result->tiles[i] = info_index; } else { assert(false && "Tried to load an unsupported map version."); log_error("Tried to load an unsupported map version. Aborting."); free(result->tiles); return false; } } char buffer_name[256] = "Map "; SDL_strlcat(buffer_name, result->name, SDL_arraysize(buffer_name)); result->texture = renderer_texture_create(R_TEXTURE_FORMAT_R16_UINT, R_TEXTURE_USAGE_STORAGE, false, result->size.x, result->size.y, result->tiles, "map_texture"); SDL_Log("Loaded map file."); return true; } static void unload_map(Map *map) { map->size = i32vec2(0, 0); free(map->tiles); SDL_free(map->name); renderer_texture_destroy(map->texture); } static void change_map_size(Map *map, char direction, int amount) { renderer_texture_destroy(map->texture); Uint16*old_map = map->tiles; Sint32 old_map_width = map->size.x; Sint32 old_map_height = map->size.y; Sint32 new_x_offset = 0; Sint32 new_y_offset = 0; Sint32 old_x_offset = 0; Sint32 old_y_offset = 0; Sint32 to_fill_width = map->size.x; Sint32 to_fill_height = map->size.y; Sint32 to_fill_x_offset = 0; Sint32 to_fill_y_offset = 0; if (direction == 'W') { player.position.x = player.position.x + amount; map->size.x += amount; to_fill_width = amount; if (amount < 0) old_x_offset = -amount; else new_x_offset = amount; } if (direction == 'N') { player.position.y = player.position.y + amount; map->size.y += amount; to_fill_height = amount; if (amount < 0) old_y_offset = -amount; else new_y_offset = amount; } if (direction == 'E') { map->size.x += amount; to_fill_width = amount; to_fill_x_offset = old_map_width; } if (direction == 'S') { map->size.y += amount; to_fill_height = amount; to_fill_y_offset = old_map_height; } map->tiles = (Uint16 *)malloc(map->size.x * map->size.y * sizeof(Uint16)); for (int y = 0; y < min(old_map_height, map->size.y); y++) { for (int x = 0; x < min(old_map_width, map->size.x); x++) { map->tiles[(y + new_y_offset) * map->size.x + (x + new_x_offset)] = old_map[(y + old_y_offset) * old_map_width + (x + old_x_offset)]; } } free(old_map); for (int y = 0; y < to_fill_height; y++) { for (int x = 0; x < to_fill_width; x++) { map->tiles[(y + to_fill_y_offset) * map->size.x + (x + to_fill_x_offset)] = 1; } } player.position = clamp(player.position, i32vec2(0, 0), map->size - 2); map->texture = renderer_texture_create(R_TEXTURE_FORMAT_R16_UINT, R_TEXTURE_USAGE_SAMPLED, false, map->size.x, map->size.y, map->tiles, "map_texture"); } static void blit(char *dst, Sint32 dst_pitch, Sint32 dst_x, Sint32 dst_y, char *src, Sint32 src_pitch, Sint32 width, Sint32 height, int components = 4) { for (Sint32 y = 0; y < height; y++) memmove(&dst[((dst_y + y) * dst_pitch + dst_x) * components], &src[y * src_pitch * components], width * components); } static bool SelectableTile(const char *label, bool selected, Uint32 tile_index, const ImVec2& image_size) { const ImGuiContext *context = ImGui::GetCurrentContext(); const ImVec2 padding = context->Style.FramePadding; bool pressed = ImGui::Selectable(label, selected, 0, image_size + padding * 2.0f); ImVec2 min = ImGui::GetItemRectMin(); ImVec2 max = ImGui::GetItemRectMax(); vec4 uv = tile_uvs[tile_index] / (float)TILE_ATLAS_SIZE; context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)ImGui_ImplRenderer_GetTextureID(tile_textures_atlas_imgui), min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv.x, uv.y), ImVec2(uv.z, uv.y), ImVec2(uv.z, uv.w), ImVec2(uv.x, uv.w)); return pressed; } static ImVec4 linear_to_sRGB(ImVec4 linear) { float red = linear.x <= 0.0031308f ? 12.92f * linear.x : 1.055f * powf(linear.x, 1.0f / 2.4f) - 0.055; float green = linear.y <= 0.0031308f ? 12.92f * linear.y : 1.055f * powf(linear.y, 1.0f / 2.4f) - 0.055; float blue = linear.z <= 0.0031308f ? 12.92f * linear.z : 1.055f * powf(linear.z, 1.0f / 2.4f) - 0.055; return ImVec4(red, green, blue, linear.w); } static ImVec4 sRGB_to_linear(ImVec4 linear) { float red = linear.x <= 0.0031308f ? linear.x / 12.92f : powf((linear.x + 0.055) / 1.055, 2.4f); float green = linear.y <= 0.0031308f ? linear.y / 12.92f : powf((linear.y + 0.055) / 1.055, 2.4f); float blue = linear.z <= 0.0031308f ? linear.z / 12.92f : powf((linear.z + 0.055) / 1.055, 2.4f); return ImVec4(red, green, blue, linear.w); } static vec3 Unproject(vec3 screen_pos) { vec4 result = vec4(screen_pos, 1.0f) * inverse_projection_matrix * inverse_view_matrix; result.x /= result.w; result.y /= result.w; result.z /= result.w; return result.xyz(); } static vec2 get_floor_intersection_of_mouse(vec2 mouse_pos) { vec2 mouse = remap(vec2(0, 0), window_size, vec2(-1, 1), vec2(1, -1), mouse_pos); vec3 camera_position = (vec4(0, 0, 0, 1) * inverse_view_matrix).xyz(); vec3 probe = Unproject(vec3(mouse, .5)); vec3 ray_dir = normalize(probe - camera_position); float t = -camera_position.z / ray_dir.z; vec3 floor_intersection = camera_position + (t * ray_dir); return floor_intersection.xy(); } #ifdef TRACY_ENABLE static SDL_malloc_func sdl_malloc = NULL; static SDL_calloc_func sdl_calloc = NULL; static SDL_realloc_func sdl_realloc = NULL; static SDL_free_func sdl_free = NULL; static void setup_memory_functions() { SDL_GetMemoryFunctions(&sdl_malloc, &sdl_calloc, &sdl_realloc, &sdl_free); SDL_SetMemoryFunctions( [](size_t size) -> void * { void *result = sdl_malloc(size); TracyAllocN(result, size, "SDL"); return result; }, [](size_t nmemb, size_t size) -> void * { void *result = sdl_calloc(nmemb, size); TracyAllocN(result, nmemb * size, "SDL"); return result; }, [](void *mem, size_t size) -> void * { void *result = sdl_realloc(mem, size); TracyFreeN(mem, "SDL"); TracyAllocN(result, size, "SDL"); return result; }, [](void *mem) { TracyFreeN(mem, "SDL"); sdl_free(mem); } ); } #else static void setup_memory_functions() {} #endif static bool recreate_tile_textures() { tile_textures_atlas = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB, R_TEXTURE_USAGE_SAMPLED, false, TILE_ATLAS_SIZE, TILE_ATLAS_SIZE, NULL, "tile_atlas_texture"); if (!tile_textures_atlas) { log_error("Failed to create texture."); return false; } tile_textures_atlas_imgui = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM, R_TEXTURE_USAGE_SAMPLED, false, TILE_ATLAS_SIZE, TILE_ATLAS_SIZE, NULL, "tile_atlas_texture imgui"); if (!tile_textures_atlas) { log_error("Failed to create texture."); return false; } for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) { char path[256] = ASSETS_PATH; SDL_strlcat(path, tile_infos[i].asset_path, SDL_arraysize(path)); int width = 0, height = 0; stbi_uc *data = stbi_load(path, &width, &height, NULL, 4); if (!data) { log_error("Failed to load texture (\"%s\"). Exiting.", path); renderer_texture_destroy(tile_textures_atlas); tile_textures_atlas = NULL; renderer_texture_destroy(tile_textures_atlas_imgui); tile_textures_atlas_imgui = NULL; return false; } SDL_assert_always(width == TILE_SIZE); SDL_assert_always(height == TILE_SIZE); Uint32 x = (i * TILE_SIZE) % TILE_ATLAS_SIZE; Uint32 y = ((i * TILE_SIZE) / TILE_ATLAS_SIZE) * TILE_SIZE; tile_uvs[i].x = x; tile_uvs[i].y = y; tile_uvs[i].z = x + TILE_SIZE; tile_uvs[i].w = y + TILE_SIZE; if (tile_infos[i].rotation) { Uint32 copy[TILE_SIZE * TILE_SIZE]; memcpy(copy, data, TILE_SIZE * TILE_SIZE * 4); for (int y = 0; y < TILE_SIZE; y++) { for (int x = 0; x < TILE_SIZE; x++) { switch(tile_infos[i].rotation) { case 1: ((Uint32 *)data)[(TILE_SIZE - x - 1) * TILE_SIZE + (y)] = copy[y * TILE_SIZE + x]; break; case 2: ((Uint32 *)data)[(TILE_SIZE - y - 1) * TILE_SIZE + (TILE_SIZE - x - 1)] = copy[y * TILE_SIZE + x]; break; case 3: ((Uint32 *)data)[(x) * TILE_SIZE + (TILE_SIZE - y - 1)] = copy[y * TILE_SIZE + x]; break; default: assert(false); break; }; } } } renderer_texture_update(tile_textures_atlas, x, y, TILE_SIZE, TILE_SIZE, data, TILE_SIZE * 4); renderer_texture_update(tile_textures_atlas_imgui, x, y, TILE_SIZE, TILE_SIZE, data, TILE_SIZE * 4); stbi_image_free(data); } tile_uvs_buffer = renderer_buffer_create(R_BUFFER_USAGE_STORAGE, sizeof(tile_uvs), tile_uvs, "tile_uvs_buffer"); return true; } static int real_mod(int a, int b) { int result = a % b; return result >= 0 ? result : result + b; } static bool imgui_time_picker(const char *label, int time[3]) { bool result = ImGui::DragScalarN(label, ImGuiDataType_S32, time, 3); time[1] += time[2] >= 0 ? time[2] / 60 : -1; time[0] += time[1] >= 0 ? time[1] / 60 : -1; time[2] = real_mod(time[2], 60); time[1] = real_mod(time[1], 60); time[0] = real_mod(time[0], 24); return result; } static Uint32 get_corner_info(Sint32 tile_pos_x, Sint32 tile_pos_y) { if (tile_pos_x < 0 || tile_pos_x >= current_map.size.x || tile_pos_y < 0 || tile_pos_y >= current_map.size.y) return 0; return tile_infos[current_map.tiles[tile_pos_y * current_map.size.x + tile_pos_x]].corner_info; } static Uint32 find_matching_tile(Uint32 corner_info) { for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) { if (corner_info == tile_infos[i].corner_info) return i; } return 0; } static void change_map_tile(Sint32 pos_x, Sint32 pos_y, TileKind kind) { const Uint32 INFO_NONE = ((TILEKIND_NONE << 24) | (TILEKIND_NONE << 16) | (TILEKIND_NONE << 8) | TILEKIND_NONE); const Uint32 INFO_ERROR = ((TILEKIND_ERROR << 24) | (TILEKIND_ERROR << 16) | (TILEKIND_ERROR << 8) | TILEKIND_ERROR); const Uint32 INFO_MASKS[4] = { 0x0000ff00, 0x000000ff, 0xff000000, 0x00ff0000 }; Uint32 corner_infos[4] = { get_corner_info(pos_x + 0, pos_y + 1), get_corner_info(pos_x + 1, pos_y + 1), get_corner_info(pos_x + 1, pos_y + 0), get_corner_info(pos_x + 0, pos_y + 0) }; for (int i = 0; i < 4; i++) { Uint32 replace_mask = INFO_MASKS[i]; if ((corner_infos[i] == INFO_NONE) | (corner_infos[i] == INFO_ERROR)) replace_mask = 0xffffffff; corner_infos[i] = corner_infos[i] ^ ((corner_infos[i] ^ ((kind << 24) | (kind << 16) | (kind << 8) | kind)) & replace_mask); } if (0 <= pos_x + 0 && pos_x + 0 < current_map.size.x && 0 <= pos_y + 1 && pos_y + 1 < current_map.size.y) current_map.tiles[(pos_y + 1) * current_map.size.x + pos_x + 0] = find_matching_tile(corner_infos[0]); if (0 <= pos_x + 1 && pos_x + 1 < current_map.size.x && 0 <= pos_y + 1 && pos_y + 1 < current_map.size.y) current_map.tiles[(pos_y + 1) * current_map.size.x + pos_x + 1] = find_matching_tile(corner_infos[1]); if (0 <= pos_x + 1 && pos_x + 1 < current_map.size.x && 0 <= pos_y + 0 && pos_y + 0 < current_map.size.y) current_map.tiles[(pos_y + 0) * current_map.size.x + pos_x + 1] = find_matching_tile(corner_infos[2]); if (0 <= pos_x + 0 && pos_x + 0 < current_map.size.x && 0 <= pos_y + 0 && pos_y + 0 < current_map.size.y) current_map.tiles[(pos_y + 0) * current_map.size.x + pos_x + 0] = find_matching_tile(corner_infos[3]); renderer_texture_update(current_map.texture, 0, 0, current_map.size.x, current_map.size.y, current_map.tiles, current_map.size.x * sizeof(Uint16)); } static void SameLineOrWrap(const ImVec2& size) { ImGuiWindow *window = ImGui::GetCurrentWindow(); ImVec2 pos = ImVec2(window->DC.CursorPosPrevLine.x + ImGui::GetStyle().ItemSpacing.x, window->DC.CursorPosPrevLine.y); if (window->WorkRect.Contains(ImRect(pos, pos + size))) ImGui::SameLine(); } static i32vec2 grid_tile_pos_from_floor_intersection(vec2 floor_intersection) { return { (Sint32)SDL_floorf(floor_intersection.x + (selected_tile == -1 ? 0.5f : 1.0f)), (Sint32)SDL_floorf(floor_intersection.y + (selected_tile == -1 ? 0.5f : 1.0f)), }; } static bool init_resources() { view_projection_matrix_buffer = renderer_buffer_create(R_BUFFER_USAGE_UNIFORM, sizeof(mat4x4), NULL, "view_projection_matrix_buffer"); if (!view_projection_matrix_buffer) { log_error("Failed to create buffer."); return false; } per_frame_buffer = renderer_buffer_create(R_BUFFER_USAGE_UNIFORM, sizeof(per_frame), NULL, "per_frame_buffer"); if (!per_frame_buffer) { log_error("Failed to create buffer."); return false; } tint_color_buffer = renderer_buffer_create(R_BUFFER_USAGE_UNIFORM, sizeof(vec3), NULL, "tint_color_buffer"); if (!tint_color_buffer) { log_error("Failed to create buffer."); return false; } vertex_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, sizeof(vertices), vertices, "vertex_buffer"); if (!vertex_buffer) { log_error("Failed to create buffer."); return false; } index_buffer = renderer_buffer_create(R_BUFFER_USAGE_INDEX, sizeof(indices), indices, "index_buffer"); if (!index_buffer) { log_error("Failed to create buffer."); return false; } player_instance_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, sizeof(player_instance), &player_instance, "player_instance_buffer"); if (!player_instance_buffer) { log_error("Failed to create buffer."); return false; } player_texture = create_texture("decorations/strawberry.png"); if (!player_texture) { log_error("Failed to create shader texture."); return false; } if (!recreate_tile_textures()) { log_error("Failed to create tile textures."); return false; } return true; } static void setup_working_directory() { if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return; const char *current_directory = SDL_GetCurrentDirectory(); change_directory(SDL_GetBasePath()); if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return; change_directory(".."); if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return; change_directory(current_directory); } static void process_event_editor(SDL_Event event) { ZoneScopedN("process_event_editor"); ImGuiIO &io = ImGui::GetIO(); ImGui_ImplSDL3_ProcessEvent(&event); switch (event.type) { case SDL_EVENT_KEY_DOWN: { if (event.key.key == SDLK_F9) { in_editor = !in_editor; } if (io.WantCaptureKeyboard) return; } break; case SDL_EVENT_MOUSE_WHEEL: { if (io.WantCaptureMouse) return; vec2 floor_pos_before = get_floor_intersection_of_mouse(vec2(event.wheel.mouse_x, event.wheel.mouse_y)); editor_camera_distance = SDL_max(0.2, editor_camera_distance - event.wheel.y * SDL_sqrt(editor_camera_distance)); view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance); inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance); vec2 floor_pos_after = get_floor_intersection_of_mouse(vec2(event.wheel.mouse_x, event.wheel.mouse_y)); editor_camera_position += floor_pos_before - floor_pos_after; view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance); inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance); } break; case SDL_EVENT_MOUSE_BUTTON_DOWN: { if (io.WantCaptureMouse) return; vec2 floor_intersection = get_floor_intersection_of_mouse(vec2(event.button.x, event.button.y)); i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); drag_start_pos = floor_intersection; if (event.button.button == SDL_BUTTON_RIGHT) { dragging_camera_change = true; } if (event.button.button == SDL_BUTTON_LEFT) { if (selected_tile_kind != -1) { change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind); if (-1 <= tile_pos.x && tile_pos.x < current_map.size.x && -1 <= tile_pos.y && tile_pos.y < current_map.size.y) { dragging_tile_change = true; } } if (0 <= tile_pos.x && tile_pos.x < current_map.size.x && 0 <= tile_pos.y && tile_pos.y < current_map.size.y) { dragging_tile_change = true; } SDL_Keymod modifiers = SDL_GetModState(); if (modifiers & SDL_KMOD_SHIFT && tile_pos.x <= -1) { if(modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'W', -1); else change_map_size(¤t_map, 'W', 1); } if (modifiers & SDL_KMOD_SHIFT && tile_pos.x == current_map.size.x) { if (modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'E', -1); else change_map_size(¤t_map, 'E', 1); } if (modifiers & SDL_KMOD_SHIFT && tile_pos.y <= -1) { if (modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'N', -1); else change_map_size(¤t_map, 'N', 1); } if (modifiers & SDL_KMOD_SHIFT && tile_pos.y == current_map.size.y) { if (modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'S', -1); else change_map_size(¤t_map, 'S', 1); } } } break; case SDL_EVENT_MOUSE_BUTTON_UP: { if (io.WantCaptureMouse) return; if (event.button.button == SDL_BUTTON_RIGHT) { dragging_camera_change = false; } if (event.button.button == SDL_BUTTON_LEFT) { if (selected_tile != -1 && dragging_tile_change) { vec2 floor_intersection = get_floor_intersection_of_mouse(vec2(event.button.x, event.button.y)); i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); Sint32 tile_x = clamp(0, tile_pos.x, current_map.size.x - 1); Sint32 tile_y = clamp(0, tile_pos.y, current_map.size.y - 1); i32vec2 drag_start = grid_tile_pos_from_floor_intersection(drag_start_pos); Sint32 start_x = min(tile_x, drag_start.x); Sint32 start_y = min(tile_y, drag_start.y); Sint32 end_x = max(tile_x, drag_start.x); Sint32 end_y = max(tile_y, drag_start.y); for (Sint32 y = start_y; y <= end_y; y++) { for (Sint32 x = start_x; x <= end_x; x++) { current_map.tiles[x + current_map.size.x * y] = selected_tile; } } renderer_texture_update(current_map.texture, 0, 0, current_map.size.x, current_map.size.y, current_map.tiles, current_map.size.x * sizeof(Uint16)); } dragging_tile_change = false; } } break; case SDL_EVENT_MOUSE_MOTION: { mouse_pos = vec2(event.motion.x, event.motion.y); vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos); if (dragging_camera_change) { editor_camera_position -= (floor_intersection - drag_start_pos); view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance); inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance); } if (selected_tile_kind != -1 && dragging_tile_change) { vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos); i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind); } } break; } } static void update_state_editor() { ZoneScopedN("update_state_editor"); ImGui_ImplRenderer_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); const ImGuiViewport *viewport = ImGui::GetMainViewport(); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Reload")) { recreate_tile_textures(); } if (ImGui::MenuItem("Save")) { save_map(current_map); } ImGui::Separator(); ImGui::MenuItem("Settings", NULL, &show_settings); ImGui::MenuItem("Demo Window", NULL, &show_demo_window); ImGui::Separator(); if (ImGui::MenuItem("Exit")) { Running = false; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Edit")) { ImGui::MenuItem("Tile Picker", NULL, &show_tile_picker); ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } ImGuiID main_viewport_dock = ImGui::GetID("main_viewport_dock"); if (!ImGui::DockBuilderGetNode(main_viewport_dock)) { ImGui::DockBuilderAddNode (main_viewport_dock, (ImGuiDockNodeFlags)ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); ImGui::DockBuilderSetNodePos (main_viewport_dock, ImGui::GetMainViewport()->WorkPos); ImGui::DockBuilderSetNodeSize(main_viewport_dock, ImGui::GetMainViewport()->WorkSize); ImGuiID left_dock = ImGui::DockBuilderSplitNode(main_viewport_dock, ImGuiDir_Left, 0.2f, NULL, NULL); ImGui::DockBuilderDockWindow("Tile Picker", left_dock); ImGui::DockBuilderFinish(main_viewport_dock); } ImGui::DockSpaceOverViewport(main_viewport_dock, ImGui::GetMainViewport(), ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); if (show_settings) { ImGui::SetNextWindowSize(ImVec2(400, 0), ImGuiCond_FirstUseEver); if (ImGui::Begin("Settings", &show_settings)) { if (ImGui::DragFloat("Master", &volume_master, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) { MIX_SetMixerGain(mixer, volume_master / 100.0f); }; if (ImGui::DragFloat("Music", &volume_music, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) { MIX_SetTrackGain(music_track, volume_music / 100.0f); } if (ImGui::DragFloat("SFX", &volume_sfx, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) { MIX_SetTrackGain(sfx_track, volume_sfx / 100.0f); } ImGui::NewLine(); ImGui::DragFloat("fovy", &camera_fovy_degrees); ImGui::DragFloat("camera_distance", &camera_distance, 0.25f, 1.0f, INFINITY); ImGui::DragFloat("camera_tilt", &camera_tilt, 0.25f, 0.0f, 89.0f); ImGui::NewLine(); ImGui::BeginDisabled(use_actual_time); ImGui::DragScalarN("Time", ImGuiDataType_S32, &calendar_time.hour, 3); ImGui::EndDisabled(); ImGui::Checkbox("use actual time", &use_actual_time); ImGui::Checkbox("enable time based tinting", &enable_time_tints); ImGui::NewLine(); for (int i = 0; i < num_used_tint_times; i++) { ImGui::PushID(i); imgui_time_picker("##time", time_tints_times[i]); ImGui::PopID(); } if (ImGui::Button("Add")) num_used_tint_times = clamp(1, num_used_tint_times + 1, MAX_TINT_TIMES); ImGui::SameLine(); if (ImGui::Button("Remove")) num_used_tint_times = clamp(1, num_used_tint_times - 1, MAX_TINT_TIMES); ImGui::NewLine(); for (int i = 0; i < num_used_tint_times; i++) { ImGui::PushID(i); ImGui::ColorEdit3("##color", glm::value_ptr(time_tints[i])); ImGui::PopID(); } if (!ImGui::IsAnyItemActive()) { for (int j = 0; j < num_used_tint_times; j++) { for (int i = 0; i < num_used_tint_times - 1; i++) { if (time_tints_times[i][0] > time_tints_times[i + 1][0] || time_tints_times[i][0] == time_tints_times[i + 1][0] && time_tints_times[i][1] > time_tints_times[i + 1][1] || time_tints_times[i][0] == time_tints_times[i + 1][0] && time_tints_times[i][1] == time_tints_times[i + 1][1] && time_tints_times[i][2] > time_tints_times[i + 1][2]) { int temp_time[3] = { time_tints_times[i][0], time_tints_times[i][1], time_tints_times[i][2] }; vec3 temp_color = time_tints[i]; time_tints_times[i][0] = time_tints_times[i + 1][0]; time_tints_times[i][1] = time_tints_times[i + 1][1]; time_tints_times[i][2] = time_tints_times[i + 1][2]; time_tints[i] = time_tints[i + 1]; time_tints_times[i + 1][0] = temp_time[0]; time_tints_times[i + 1][1] = temp_time[1]; time_tints_times[i + 1][2] = temp_time[2]; time_tints[i + 1] = temp_color; } } } } } ImGui::End(); } if (show_tile_picker) { if (ImGui::Begin("Tile Picker", &show_tile_picker, ImGuiWindowFlags_NoFocusOnAppearing)) { if (ImGui::Selectable("None", selected_tile_kind == -1 && selected_tile == -1)) { selected_tile_kind = -1; selected_tile = -1; } if (ImGui::Selectable("Grass", selected_tile_kind == TILEKIND_GRASS)) { selected_tile_kind = TILEKIND_GRASS; selected_tile = -1; } if (ImGui::Selectable("Dirt", selected_tile_kind == TILEKIND_DIRT)) { selected_tile_kind = TILEKIND_DIRT; selected_tile = -1; } if (ImGui::Selectable("Water", selected_tile_kind == TILEKIND_WATER)) { selected_tile_kind = TILEKIND_WATER; selected_tile = -1; } for (int i = 0; i < SDL_arraysize(tile_infos); i++) { ImGui::PushID(i); if (i != 0) SameLineOrWrap(ImVec2(32, 32)); if (SelectableTile("##tile", selected_tile == i, i, ImVec2(32, 32))) { selected_tile_kind = -1; selected_tile = i; } ImGui::PopID(); } } ImGui::End(); } else { selected_tile = -1; selected_tile_kind = -1; } ImGui::SetNextWindowPos({ viewport->WorkPos.x + viewport->WorkSize.x - 10.0f, viewport->WorkPos.y + 10.0f }, ImGuiCond_Always, { 1.0f, 0.0f }); if (ImGui::Begin("Overlay", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { ImGui::Checkbox("Grid", &show_grid); } ImGui::End(); if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); } static void render_editor() { ZoneScopedN("render_editor"); { ZoneScopedN("update buffers"); { ZoneScopedN("player_instance_buffer"); player_instance.pos = player.position; renderer_buffer_update(player_instance_buffer, 0, sizeof(player_instance), &player_instance); } { ZoneScopedN("per_frame"); float aspect_ratio = ((float)window_size.x / (float)window_size.y); view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance); inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance); projection_matrix = projection (radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE); inverse_projection_matrix = inverse_projection(radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE); mat4x4 view_projection_matrix = view_matrix * projection_matrix; renderer_buffer_update(view_projection_matrix_buffer, 0, sizeof(view_projection_matrix), &view_projection_matrix); vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos); i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); per_frame.map_width = current_map.size.x; per_frame.grid_width = selected_tile != -1 ? current_map.size.x : current_map.size.x + 1; per_frame.grid_height = selected_tile != -1 ? current_map.size.y : current_map.size.y + 1; per_frame.grid_offset = selected_tile != -1 ? vec2(-0.5f, -0.5f) : vec2(-1.0f, -1.0f); per_frame.mouse = tile_pos; if (dragging_tile_change && selected_tile != -1) { i32vec2 grid_tile_pos = grid_tile_pos_from_floor_intersection(drag_start_pos); per_frame.drag_start = grid_tile_pos; } else { per_frame.drag_start = tile_pos; } renderer_buffer_update(per_frame_buffer, 0, sizeof(per_frame), &per_frame); } { ZoneScopedN("tint color"); Sint64 tint_times_ns[MAX_TINT_TIMES]; for (int i = 0; i < num_used_tint_times; i++) tint_times_ns[i] = (time_tints_times[i][0] * 60 * 60 + time_tints_times[i][1] * 60 + time_tints_times[i][2]) * SDL_NS_PER_SECOND; tint_times_ns[num_used_tint_times] = (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND + tint_times_ns[0]; Sint64 calendar_time_ns = (calendar_time.hour * 60 * 60 + calendar_time.minute * 60 + calendar_time.second) * SDL_NS_PER_SECOND + (Sint64)calendar_time.nanosecond; int last_time_index = num_used_tint_times - 1; for (int i = 0; i < num_used_tint_times; i++) { if (calendar_time_ns > tint_times_ns[i]) last_time_index = i; } if (calendar_time_ns <= tint_times_ns[0]) calendar_time_ns += (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND; Sint64 v = calendar_time_ns - tint_times_ns[last_time_index]; Sint64 time_between = tint_times_ns[last_time_index + 1] - tint_times_ns[last_time_index]; double t = v / (double)time_between; vec3 tint_color = mix(time_tints[last_time_index], time_tints[(last_time_index + 1) % num_used_tint_times], t); if (!enable_time_tints) tint_color = vec3(1, 1, 1); renderer_buffer_update(tint_color_buffer, 0, sizeof(tint_color), &tint_color); } } renderer_frame_set_target(framebuffer, R_surface, R_LOAD_OP_CLEAR, R_STORE_OP_STORE, { 0.01f, 0.01f, 0.01f, 0.01f }); { ZoneScopedN("Draw Map"); renderer_frame_set_shader(R_SHADER_WORLD); renderer_frame_draw(NULL, NULL, NULL, 6, current_map.size.y * current_map.size.x, &(R_Draw_Resources){ .vertex_storage_textures = (R_Texture[]){ current_map.texture }, .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer }, .num_vertex_storage_textures = 1, .num_vertex_uniform_buffers = 2, .fragment_sampled_textures = (R_Texture[]){ tile_textures_atlas }, .fragment_storage_buffers = (R_Buffer[]){ tile_uvs_buffer }, .fragment_uniform_buffers = (R_Buffer[]){ tint_color_buffer }, .num_fragment_sampled_textures = 1, .num_fragment_storage_buffers = 1, .num_fragment_uniform_buffers = 1, }); } if (show_grid) { ZoneScopedN("Draw Grid"); renderer_frame_set_shader(R_SHADER_GRID); renderer_frame_draw(NULL, NULL, NULL, per_frame.grid_width * 2 + per_frame.grid_height * 2 + 4, 1, &(R_Draw_Resources){ .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer }, .num_vertex_uniform_buffers = 2, }); } { ZoneScopedN("ImGui Render"); ImGui::Render(); ImDrawData *draw_data = ImGui::GetDrawData(); ImGui_ImplRenderer_RenderDrawData(draw_data); } } static void process_event_game(SDL_Event event) { ZoneScopedN("process_event"); switch (event.type) { case SDL_EVENT_KEY_DOWN: { if (event.key.repeat) return; if (event.key.key == SDLK_UP || event.key.key == SDLK_W) { queued_movement = true; queued_movement_direction = DIR_UP; } if (event.key.key == SDLK_LEFT || event.key.key == SDLK_A) { queued_movement = true; queued_movement_direction = DIR_LEFT; } if (event.key.key == SDLK_DOWN || event.key.key == SDLK_S) { queued_movement = true; queued_movement_direction = DIR_DOWN; } if (event.key.key == SDLK_RIGHT || event.key.key == SDLK_D) { queued_movement = true; queued_movement_direction = DIR_RIGHT; } if (event.key.key == SDLK_F9) { in_editor = !in_editor; } } break; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP) { queued_movement = true; queued_movement_direction = DIR_UP; } if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) { queued_movement = true; queued_movement_direction = DIR_LEFT; } if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) { queued_movement = true; queued_movement_direction = DIR_DOWN; } if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) { queued_movement = true; queued_movement_direction = DIR_RIGHT; } } break; } } static void update_state_game() { ZoneScopedN("update_state_game"); SDL_Time delta_time = current_time - last_time; float delta_t = delta_time / (float)SDL_NS_PER_SECOND; const bool *keyboard_state = SDL_GetKeyboardState(NULL); if (player.is_moving) { vec2 leftover_movement = vec2(player.target_position) - player.visual_position; vec2 movement = clamp(min(vec2(0, 0), leftover_movement), normalize(leftover_movement) * (delta_t * character_speed * (keyboard_state[SDL_SCANCODE_LSHIFT] ? 2.0f : 1.0f)), max(vec2(0, 0), leftover_movement)); player.visual_position += movement; if (movement == leftover_movement) { player.is_moving = false; player.position = player.target_position; } } if (!player.is_moving && (queued_movement || keyboard_state[SDL_SCANCODE_W] || keyboard_state[SDL_SCANCODE_A] || keyboard_state[SDL_SCANCODE_S] || keyboard_state[SDL_SCANCODE_D])) { switch (queued_movement_direction) { case DIR_UP: { player.target_position = player.position + ivec2( 0, 1); if (player.target_position != player.position) player.is_moving = true; } break; case DIR_LEFT: { player.target_position = player.position + ivec2(-1, 0); if (player.target_position != player.position) player.is_moving = true; } break; case DIR_DOWN: { player.target_position = player.position + ivec2( 0, -1); if (player.target_position != player.position) player.is_moving = true; } break; case DIR_RIGHT: { player.target_position = player.position + ivec2( 1, 0); if (player.target_position != player.position) player.is_moving = true; } break; } } queued_movement = false; if (!MIX_TrackPlaying(music_track)) { MIX_SetTrackAudio(music_track, music_setting_off_piano); MIX_PlayTrack(music_track, 0); } } static void render_game() { ZoneScopedN("render_game"); { ZoneScopedN("update buffers"); { ZoneScopedN("player_instance_buffer"); player_instance.pos = player.visual_position; renderer_buffer_update(player_instance_buffer, 0, sizeof(player_instance), &player_instance); } { ZoneScopedN("per_frame"); float aspect_ratio = ((float)window_size.x / (float)window_size.y); view_matrix = view (vec3(player.visual_position, 0), radians(camera_tilt), camera_distance); inverse_view_matrix = inverse_view(vec3(player.visual_position, 0), radians(camera_tilt), camera_distance); projection_matrix = projection (radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE); inverse_projection_matrix = inverse_projection(radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE); mat4x4 view_projection_matrix = view_matrix * projection_matrix; renderer_buffer_update(view_projection_matrix_buffer, 0, sizeof(view_projection_matrix), &view_projection_matrix); per_frame.map_width = current_map.size.x; renderer_buffer_update(per_frame_buffer, 0, sizeof(per_frame), &per_frame); } { ZoneScopedN("tint color"); Sint64 tint_times_ns[MAX_TINT_TIMES]; for (int i = 0; i < num_used_tint_times; i++) tint_times_ns[i] = (time_tints_times[i][0] * 60 * 60 + time_tints_times[i][1] * 60 + time_tints_times[i][2]) * SDL_NS_PER_SECOND; tint_times_ns[num_used_tint_times] = (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND + tint_times_ns[0]; Sint64 calendar_time_ns = (calendar_time.hour * 60 * 60 + calendar_time.minute * 60 + calendar_time.second) * SDL_NS_PER_SECOND + (Sint64)calendar_time.nanosecond; int last_time_index = num_used_tint_times - 1; for (int i = 0; i < num_used_tint_times; i++) { if (calendar_time_ns > tint_times_ns[i]) last_time_index = i; } if (calendar_time_ns <= tint_times_ns[0]) calendar_time_ns += (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND; Sint64 v = calendar_time_ns - tint_times_ns[last_time_index]; Sint64 time_between = tint_times_ns[last_time_index + 1] - tint_times_ns[last_time_index]; double t = v / (double)time_between; vec3 tint_color = mix(time_tints[last_time_index], time_tints[(last_time_index + 1) % num_used_tint_times], t); if (!enable_time_tints) tint_color = vec3(1, 1, 1); renderer_buffer_update(tint_color_buffer, 0, sizeof(tint_color), &tint_color); } } renderer_frame_set_target(framebuffer, R_surface, R_LOAD_OP_CLEAR, R_STORE_OP_STORE, { 0.01f, 0.01f, 0.01f, 0.01f }); { ZoneScopedN("Draw Map"); renderer_frame_set_shader(R_SHADER_WORLD); renderer_frame_draw(NULL, NULL, NULL, 6, current_map.size.y * current_map.size.x, &(R_Draw_Resources){ .vertex_storage_textures = (R_Texture[]){ current_map.texture }, .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer }, .num_vertex_storage_textures = 1, .num_vertex_uniform_buffers = 2, .fragment_sampled_textures = (R_Texture[]){ tile_textures_atlas }, .fragment_storage_buffers = (R_Buffer[]){ tile_uvs_buffer }, .fragment_uniform_buffers = (R_Buffer[]){ tint_color_buffer }, .num_fragment_sampled_textures = 1, .num_fragment_storage_buffers = 1, .num_fragment_uniform_buffers = 1, }); } { ZoneScopedN("Draw Player"); renderer_frame_set_shader(R_SHADER_BASIC); renderer_frame_draw(vertex_buffer, index_buffer, player_instance_buffer, 6, 1, &(R_Draw_Resources){ .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer }, .num_vertex_uniform_buffers = 1, .fragment_sampled_textures = (R_Texture[]){ player_texture }, .fragment_uniform_buffers = (R_Buffer[]){ tint_color_buffer }, .num_fragment_sampled_textures = 1, .num_fragment_uniform_buffers = 1, }); } } static void process_events() { ZoneScopedN("process_events"); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: { Running = false; } break; case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: { window_size.x = event.window.data1; window_size.y = event.window.data2; } break; } if (in_editor) { process_event_editor(event); } else { process_event_game(event); } } } static void update_state() { ZoneScopedN("update_state"); last_time = current_time; SDL_GetCurrentTime(¤t_time); if (use_actual_time) SDL_TimeToDateTime(current_time, &calendar_time, true); calendar_time.minute += calendar_time.second >= 0 ? calendar_time.second / 60 : calendar_time.second / 60 - 1; calendar_time.hour += calendar_time.minute >= 0 ? calendar_time.minute / 60 : calendar_time.minute / 60 - 1; calendar_time.second = real_mod(calendar_time.second, 60); calendar_time.minute = real_mod(calendar_time.minute, 60); calendar_time.hour = real_mod(calendar_time.hour, 24); if (in_editor) { update_state_editor(); } else { update_state_game(); } } static void render() { ZoneScopedN("render"); renderer_frame_begin(); if (!framebuffer || renderer_texture_get_width(framebuffer) != renderer_texture_get_width(R_surface) || renderer_texture_get_height(framebuffer) != renderer_texture_get_height(R_surface)) { if (framebuffer) { renderer_texture_destroy(framebuffer); framebuffer = NULL; } framebuffer = renderer_texture_create(renderer_texture_get_format(R_surface), R_TEXTURE_USAGE_TARGET, true, renderer_texture_get_width(R_surface), renderer_texture_get_height(R_surface), NULL, "framebuffer"); } if (in_editor) { render_editor(); } else { render_game(); } renderer_frame_end(); } void save_settings() { SDL_Storage *user_storage = SDL_OpenUserStorage(ORG, APP, 0); if (!user_storage) return; defer(SDL_CloseStorage(user_storage)); SDL_IOStream *io = SDL_IOFromDynamicMem(); SDL_IOprintf(io, "[Audio]\n"); SDL_IOprintf(io, "Master=%.0f\n", volume_master); SDL_IOprintf(io, "Music=%.0f\n", volume_music); SDL_IOprintf(io, "SFX=%.0f\n", volume_sfx); SDL_PropertiesID properties = SDL_GetIOProperties(io); void *data = SDL_GetPointerProperty(properties, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); Sint64 size = SDL_GetIOSize(io); SDL_WriteStorageFile(user_storage, "settings.ini", data, size); SDL_CloseIO(io); } void load_settings() { SDL_Storage *user_storage = SDL_OpenUserStorage(ORG, APP, 0); if (!user_storage) return; defer(SDL_CloseStorage(user_storage)); Uint64 file_size = 0; if (SDL_GetStorageFileSize(user_storage, "settings.ini", &file_size)) { char *file_data = (char *)SDL_calloc(1, file_size + 1); defer(SDL_free(file_data)); if (!SDL_ReadStorageFile(user_storage, "settings.ini", file_data, file_size)) return; Settings_Category current_category = SETTINGS_UNKNOWN; char *line_end = NULL; for (char *line = file_data; line < file_data + file_size; line = line_end + 1) { while (line[0] == '\n' || line[0] == '\r') line++; line_end = line; while(line_end < file_data + file_size && line_end[0] != '\n' && line_end[0] != '\r') line_end++; line_end[0] = 0; if (line[0] == '[' && line_end > line && line_end[-1] == ']') { line_end[-1] = 0; const char *category_name = line + 1; if (SDL_strcasecmp(category_name, "AUDIO") == 0) { current_category = SETTINGS_AUDIO; } else { SDL_Log("Unknown category '%s' in settings.ini.", category_name); } } else { switch (current_category) { case SETTINGS_UNKNOWN: { SDL_Log("Unknown value '%s' outside of any category in settings.ini.", line); } break; case SETTINGS_AUDIO: { if (SDL_strncasecmp(line, "Master", sizeof("Master") - 1) == 0) { line = line + sizeof("Master") - 1; SDL_sscanf(line, "=%f", &volume_master); } else if (SDL_strncasecmp(line, "Music", sizeof("Music") - 1) == 0) { line = line + sizeof("Music") - 1; SDL_sscanf(line, "=%f", &volume_music); } else if (SDL_strncasecmp(line, "SFX", sizeof("SFX") - 1) == 0) { line = line + sizeof("SFX") - 1; SDL_sscanf(line, "=%f", &volume_sfx); } else { SDL_Log("Unknown value '%s' in category 'AUDIO' in settings.ini.", line); } } break; } } } MIX_SetMixerGain(mixer, volume_master / 100.0f); MIX_SetTrackGain(music_track, volume_music / 100.0f); MIX_SetTrackGain(sfx_track, volume_sfx / 100.0f); } else { SDL_Log("No settings found. Using default values."); } } int main(int argc, char **argv) { setup_memory_functions(); setup_working_directory(); #ifdef SDL_PLATFORM_LINUX if (getenv("ENABLE_VULKAN_RENDERDOC_CAPTURE")) SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland"); #endif if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) { log_error("Failed to initialize SDL (%s). Exiting.", SDL_GetError()); return 1; } if (!MIX_Init()) { log_error("Failed to init SDL_mixer. Exiting."); return 1; } window = SDL_CreateWindow("Mikemon", window_size.x, window_size.y, SDL_WINDOW_RESIZABLE); if (!window) { log_error("Failed to create window (%s). Exiting.", SDL_GetError()); return 1; } if (!renderer_init(window)) { log_error("Failed to initialize renderer. Exiting."); return 1; } if (!init_resources()) { log_error("Failed to init resources. Exiting."); return 1; } if (!load_map("map.sv", ¤t_map)) { log_error("Failed to load initial map. Exiting."); return 1; } mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); if (!mixer) { log_error("Failed to open default audio device. Ignoring."); } music_track = MIX_CreateTrack(mixer); if (!music_track) { log_error("Failed to create music track. Ignoring."); } sfx_track = MIX_CreateTrack(mixer); if (!sfx_track) { log_error("Failed to create sfx track. Ignoring."); } music_setting_off_piano = MIX_LoadAudio(mixer, ASSETS_PATH "music/setting_off_piano.opus", false); if (!music_setting_off_piano) { log_error("Failed to load music setting_off_piano.opus. Ignoring."); } load_settings(); save_settings(); IMGUI_CHECKVERSION(); ImGuiContext *imgui_context = ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; char *storage_path = SDL_GetPrefPath(ORG, APP); size_t imgui_ini_path_len = SDL_strlen(storage_path) + SDL_strlen("imgui.ini") + 1; char *imgui_ini_path = (char *)SDL_calloc(imgui_ini_path_len, 1); SDL_strlcat(imgui_ini_path, storage_path, imgui_ini_path_len); SDL_strlcat(imgui_ini_path, "imgui.ini", imgui_ini_path_len); SDL_free(storage_path); io.IniFilename = imgui_ini_path; io.Fonts->AddFontDefaultVector(); ImGui::StyleColorsDark(); ImGui_ImplSDL3_InitForOther(window); ImGui_ImplRenderer_Init(); ImGuiSettingsHandler time_tints_settings_handler = {}; time_tints_settings_handler.TypeName = "TimeTints"; time_tints_settings_handler.TypeHash = ImHashStr("TimeTints"); time_tints_settings_handler.ReadOpenFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, const char *name) -> void * { if (strcmp(name, "Settings") == 0) return (void *)-1; int num = atoi(name) + 1; return (void *)(Sint64)num; }; time_tints_settings_handler.ReadLineFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, void *entry, const char *line) { if (entry == (void *)-1) { if (strncmp(line, "num", 3) == 0) { SDL_sscanf(line, "num=%d", &num_used_tint_times); } else if (strncmp(line, "enable", 6) == 0) { Uint32 enable = 0; SDL_sscanf(line, "enable=%d", &enable); enable_time_tints = !!enable; } return; } if (strncmp(line, "time", 4) == 0) { SDL_sscanf(line, "time=%d %d %d", &time_tints_times[(size_t)entry - 1][0], &time_tints_times[(size_t)entry - 1][1], &time_tints_times[(size_t)entry - 1][2]); } else if(strncmp(line, "color", 5) == 0) { SDL_sscanf(line, "color=%g %g %g", &time_tints[(size_t)entry - 1][0], &time_tints[(size_t)entry - 1][1], &time_tints[(size_t)entry - 1][2]); } }; time_tints_settings_handler.WriteAllFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buffer) { buffer->append("[TimeTints][Settings]\n"); buffer->appendf("enable=%u\n", enable_time_tints ? 1 : 0); buffer->appendf("num=%d\n\n", num_used_tint_times); for (int i = 0; i < num_used_tint_times; i++) { buffer->appendf("[TimeTints][%d]\n", i); buffer->appendf("time=%d %d %d\n", time_tints_times[i][0], time_tints_times[i][1], time_tints_times[i][2]); buffer->appendf("color=%g %g %g\n\n", time_tints[i][0], time_tints[i][1], time_tints[i][2]); } }; ImGui::AddSettingsHandler(&time_tints_settings_handler); SDL_GetWindowSizeInPixels(window, &window_size.x, &window_size.y); while (Running) { ZoneScopedN("main_loop"); process_events(); update_state(); render(); FrameMark; } save_settings(); ImGui_ImplRenderer_Shutdown(); ImGui_ImplSDL3_Shutdown(); ImGui::DestroyContext(); return 0; }