diff --git a/CMakeLists.txt b/CMakeLists.txt index c22cceb7cb..45624491e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -346,6 +346,7 @@ set(GAME_FILES ${PROJECT_SOURCE_DIR}/src/game/file.c ${PROJECT_SOURCE_DIR}/src/game/file_editor.c ${PROJECT_SOURCE_DIR}/src/game/file_io.c + ${PROJECT_SOURCE_DIR}/src/game/file_minimap.c ${PROJECT_SOURCE_DIR}/src/game/game.c ${PROJECT_SOURCE_DIR}/src/game/mission.c ${PROJECT_SOURCE_DIR}/src/game/orientation.c @@ -465,7 +466,6 @@ set(WIDGET_FILES ${PROJECT_SOURCE_DIR}/src/widget/map_editor.c ${PROJECT_SOURCE_DIR}/src/widget/map_editor_tool.c ${PROJECT_SOURCE_DIR}/src/widget/minimap.c - ${PROJECT_SOURCE_DIR}/src/widget/scenario_minimap.c ${PROJECT_SOURCE_DIR}/src/widget/top_menu.c ${PROJECT_SOURCE_DIR}/src/widget/top_menu_editor.c ${PROJECT_SOURCE_DIR}/src/widget/sidebar/city.c diff --git a/src/building/building.c b/src/building/building.c index 79dbf75c23..868cebc71b 100644 --- a/src/building/building.c +++ b/src/building/building.c @@ -35,6 +35,12 @@ building *building_get(int id) return &all_buildings[id]; } +void building_get_from_buffer(buffer *buf, int id, building *b) +{ + buffer_set(buf, 128 * id); + building_state_load_from_buffer(buf, b); +} + building *building_main(building *b) { for (int guard = 0; guard < 9; guard++) { diff --git a/src/building/building.h b/src/building/building.h index 093ff7e808..05288cfe5a 100644 --- a/src/building/building.h +++ b/src/building/building.h @@ -141,6 +141,8 @@ typedef struct { building *building_get(int id); +void building_get_from_buffer(buffer *buf, int id, building *b); + building *building_main(building *b); building *building_next(building *b); diff --git a/src/city/data.c b/src/city/data.c index beb43016de..5238cc41fb 100644 --- a/src/city/data.c +++ b/src/city/data.c @@ -1052,3 +1052,11 @@ void city_data_load_state(buffer *main, buffer *faction, buffer *faction_unknown load_entry_exit(entry_exit_xy, entry_exit_grid_offset); } + +void city_data_load_basic_info(buffer *main, int *population, int *treasury) +{ + buffer_skip(main, 18080); + *treasury = buffer_read_i32(main); + buffer_skip(main, 20); + *population = buffer_read_i32(main); +} diff --git a/src/city/data.h b/src/city/data.h index 63de75186f..03763fdc80 100644 --- a/src/city/data.h +++ b/src/city/data.h @@ -15,4 +15,6 @@ void city_data_save_state(buffer *main, buffer *faction, buffer *faction_unknown void city_data_load_state(buffer *main, buffer *faction, buffer *faction_unknown, buffer *graph_order, buffer *entry_exit_xy, buffer *entry_exit_grid_offset); +void city_data_load_basic_info(buffer *main, int *population, int *treasury); + #endif // CITY_DATA_H diff --git a/src/city/view.c b/src/city/view.c index 9db5dae10b..ccbc0259a8 100644 --- a/src/city/view.c +++ b/src/city/view.c @@ -6,6 +6,8 @@ #include "map/image.h" #include "widget/minimap.h" +#include + #define TILE_WIDTH_PIXELS 60 #define TILE_HEIGHT_PIXELS 30 #define HALF_TILE_WIDTH_PIXELS 30 @@ -172,6 +174,36 @@ void city_view_reset_orientation(void) calculate_lookup(); } +void city_view_set_custom_lookup(int start_offset, int width, int height, int border_size) +{ + reset_lookup(); + + int start_x = border_size / 2; + int end_x = GRID_SIZE - start_x; + int start_y = (start_offset - start_x) / GRID_SIZE; + int end_y = start_y + height; + + int x_view_start = VIEW_X_MAX - 1 - start_y; + int y_view_start = 1 + start_y; + + for (int y = start_y; y < end_y; y++) { + int x_view = x_view_start + start_x; + int y_view = y_view_start + start_x; + for (int x = start_x; x < end_x; x++) { + view_to_grid_offset_lookup[x_view / 2][y_view] = x + GRID_SIZE * y; + x_view++; + y_view++; + } + x_view_start--; + y_view_start++; + } +} + +void city_view_restore_lookup(void) +{ + calculate_lookup(); +} + void city_view_get_camera(int *x, int *y) { *x = data.camera.tile.x; diff --git a/src/city/view.h b/src/city/view.h index 24244e955a..be0544099e 100644 --- a/src/city/view.h +++ b/src/city/view.h @@ -20,6 +20,9 @@ int city_view_orientation(void); void city_view_reset_orientation(void); +void city_view_set_custom_lookup(int start_offset, int width, int height, int border_size); +void city_view_restore_lookup(void); + void city_view_get_camera(int *x, int *y); void city_view_get_camera_absolute(int *x_abs, int *y_abs); void city_view_get_pixel_offset(int *x, int *y); diff --git a/src/game/file.c b/src/game/file.c index 56238d1524..c74a201211 100644 --- a/src/game/file.c +++ b/src/game/file.c @@ -188,6 +188,26 @@ static void initialize_scenario_data(const uint8_t *scenario_name) game_state_unpause(); } +static void load_empire_data(int is_custom_scenario, int empire_id) +{ + empire_load(is_custom_scenario, empire_id); + scenario_distant_battle_set_roman_travel_months(); + scenario_distant_battle_set_enemy_travel_months(); +} + +static int load_scenario_data(const char *scenario_file) +{ + if (!game_file_io_read_scenario(scenario_file)) { + return 0; + } + + trade_prices_reset(); + load_empire_data(1, scenario_empire_id()); + city_view_reset_orientation(); + return 1; +} + + static int load_custom_scenario(const uint8_t *scenario_name, const char *scenario_file) { if (!file_exists(scenario_file, NOT_LOCALIZED)) { @@ -195,18 +215,13 @@ static int load_custom_scenario(const uint8_t *scenario_name, const char *scenar } clear_scenario_data(); - game_file_load_scenario_data(scenario_file); + if (!load_scenario_data(scenario_file)) { + return 0; + } initialize_scenario_data(scenario_name); return 1; } -static void load_empire_data(int is_custom_scenario, int empire_id) -{ - empire_load(is_custom_scenario, empire_id); - scenario_distant_battle_set_roman_travel_months(); - scenario_distant_battle_set_enemy_travel_months(); -} - static void initialize_saved_game(void) { load_empire_data(scenario_is_custom(), scenario_empire_id()); @@ -332,21 +347,10 @@ int game_file_start_scenario(const char *scenario_file) uint8_t scenario_name[FILE_NAME_MAX]; encoding_from_utf8(scenario_file, scenario_name, FILE_NAME_MAX); file_remove_extension(scenario_name); + scenario_set_custom(2); return start_scenario(scenario_name, scenario_file); } -int game_file_load_scenario_data(const char *scenario_file) -{ - if (!game_file_io_read_scenario(scenario_file)) { - return 0; - } - - trade_prices_reset(); - load_empire_data(1, scenario_empire_id()); - city_view_reset_orientation(); - return 1; -} - int game_file_load_saved_game(const char *filename) { if (!game_file_io_read_saved_game(filename, 0)) { diff --git a/src/game/file.h b/src/game/file.h index 1535b94b3c..1b4e7f90a0 100644 --- a/src/game/file.h +++ b/src/game/file.h @@ -17,13 +17,6 @@ int game_file_start_scenario_by_name(const uint8_t *scenario_name); */ int game_file_start_scenario(const char *scenario_file); -/** - * Load scenario data only, without starting it - * @param scenario_file File to load - * @return Boolean true on success, false on failure - */ -int game_file_load_scenario_data(const char *scenario_file); - /** * Load saved game * @param filename File to load diff --git a/src/game/file_io.c b/src/game/file_io.c index bca5285e98..4329afb67a 100644 --- a/src/game/file_io.c +++ b/src/game/file_io.c @@ -22,6 +22,7 @@ #include "figure/name.h" #include "figure/route.h" #include "figure/trader.h" +#include "game/file_minimap.h" #include "game/time.h" #include "game/tutorial.h" #include "map/aqueduct.h" @@ -79,6 +80,7 @@ static struct { int num_pieces; file_piece pieces[10]; scenario_state state; + char last_loaded_scenario[FILE_NAME_MAX]; } scenario_data = {0}; typedef struct { @@ -195,12 +197,20 @@ static buffer *create_savegame_piece(int size, int compressed) return &piece->buf; } +static int reset_scenario_pieces(void) +{ + if (scenario_data.num_pieces == 0) { + return 0; + } + for (int i = 0; i < scenario_data.num_pieces; i++) { + buffer_reset(&scenario_data.pieces[i].buf); + } + return 1; +} + static void init_scenario_data(void) { - if (scenario_data.num_pieces > 0) { - for (int i = 0; i < scenario_data.num_pieces; i++) { - buffer_reset(&scenario_data.pieces[i].buf); - } + if (reset_scenario_pieces()) { return; } scenario_state *state = &scenario_data.state; @@ -224,6 +234,7 @@ static void init_savegame_data(void) } return; } + savegame_state *state = &savegame_data.state; state->scenario_campaign_mission = create_savegame_piece(4, 0); state->file_version = create_savegame_piece(4, 0); @@ -504,9 +515,8 @@ static void savegame_save_to_state(savegame_state *state) buffer_skip(state->end_marker, 284); } -int game_file_io_read_scenario(const char *filename) +static int load_scenario_to_buffers(const char *filename) { - log_info("Loading scenario", filename, 0); init_scenario_data(); FILE *fp = file_open(dir_get_file(filename, NOT_LOCALIZED), "rb"); if (!fp) { @@ -521,8 +531,63 @@ int game_file_io_read_scenario(const char *filename) } } file_close(fp); + return 1; +} +int game_file_io_read_scenario(const char *filename) +{ + log_info("Loading scenario", filename, 0); + if (strcmp(scenario_data.last_loaded_scenario, filename) != 0) { + if (!load_scenario_to_buffers(filename)) { + return 0; + } + } scenario_load_from_state(&scenario_data.state); + scenario_data.last_loaded_scenario[0] = 0; + return 1; +} + +int game_file_io_read_scenario_info(const char *filename, scenario_info *info) +{ + if (strcmp(scenario_data.last_loaded_scenario, filename) != 0) { + if (!load_scenario_to_buffers(filename)) { + return 0; + } + } + + strncpy(scenario_data.last_loaded_scenario, filename, FILE_NAME_MAX - 1); + + const scenario_state *state = &scenario_data.state; + + scenario_description_from_buffer(state->scenario, info->description); + info->image_id = scenario_image_id_from_buffer(state->scenario); + info->climate = scenario_climate_from_buffer(state->scenario); + info->total_invasions = scenario_invasions_from_buffer(state->scenario); + info->player_rank = scenario_rank_from_buffer(state->scenario); + info->start_year = scenario_start_year_from_buffer(state->scenario); + scenario_open_play_info_from_buffer(state->scenario, &info->is_open_play, &info->open_play_id); + + if (!info->is_open_play) { + scenario_objectives_from_buffer(state->scenario, &info->win_criteria); + } + + file_buffers minimap_buffers; + minimap_buffers.buildings = 0; + minimap_buffers.map_building = 0; + minimap_buffers.map_bitfields = state->bitfields; + minimap_buffers.map_terrain = state->terrain; + minimap_buffers.map_edge = state->edge; + minimap_buffers.map_random = state->random; + minimap_buffers.climate = info->climate; + + scenario_map_data_from_buffer(state->scenario, &minimap_buffers.grid_width, &minimap_buffers.grid_height, + &minimap_buffers.grid_start, &minimap_buffers.grid_border_size); + + info->minimap_image = game_file_minimap_create(&minimap_buffers); + info->map_size = minimap_buffers.grid_width; + + reset_scenario_pieces(); + return 1; } @@ -583,6 +648,18 @@ static int read_compressed_chunk(FILE *fp, void *buffer, int bytes_to_read) return 1; } +static int skip_piece(FILE *fp, int size, int compressed) +{ + if (!compressed) { + return fseek(fp, size, SEEK_CUR) == 0; + } + int input_size = read_int32(fp); + if ((unsigned int) input_size == UNCOMPRESSED) { + return fseek(fp, size, SEEK_CUR) == 0; + } + return fseek(fp, input_size, SEEK_CUR) == 0; +} + static int write_compressed_chunk(FILE *fp, const void *buffer, int bytes_to_write) { if (bytes_to_write > COMPRESS_BUFFER_SIZE) { @@ -653,6 +730,161 @@ int game_file_io_read_saved_game(const char *filename, int offset) return 1; } +static void free_file_piece(file_piece *piece) +{ + buffer_reset(&piece->buf); + free(piece->buf.data); +} + +static int savegame_read_file_info(FILE *fp, saved_game_info *info) +{ + file_piece city_data, game_time, terrain_grid, random_grid, scenario; + file_piece bitfields_grid, edge_grid, building_grid, buildings; + + init_file_piece(&city_data, 36136, 0); + init_file_piece(&game_time, 20, 0); + init_file_piece(&terrain_grid, 52488, 0); + init_file_piece(&random_grid, 26244, 0); + init_file_piece(&edge_grid, 26244, 0); + init_file_piece(&bitfields_grid, 26244, 0); + init_file_piece(&scenario, 1720, 0); + init_file_piece(&building_grid, 52488, 0); + init_file_piece(&buildings, 256000, 0); + + file_buffers minimap_buffers; + minimap_buffers.map_terrain = &terrain_grid.buf; + minimap_buffers.map_random = &random_grid.buf; + minimap_buffers.map_edge = &edge_grid.buf; + minimap_buffers.map_bitfields = &bitfields_grid.buf; + minimap_buffers.map_building = &building_grid.buf; + minimap_buffers.buildings = &buildings.buf; + + info->mission = read_int32(fp); + + skip_piece(fp, 4, 0); + skip_piece(fp, 52488, 1); + + if (!read_compressed_chunk(fp, edge_grid.buf.data, edge_grid.buf.size)) { + return 0; + } + + if (!read_compressed_chunk(fp, building_grid.buf.data, building_grid.buf.size)) { + return 0; + } + + if (!read_compressed_chunk(fp, terrain_grid.buf.data, terrain_grid.buf.size)) { + return 0; + } + + skip_piece(fp, 26244, 1); + skip_piece(fp, 52488, 1); + + if (!read_compressed_chunk(fp, bitfields_grid.buf.data, bitfields_grid.buf.size)) { + return 0; + } + + skip_piece(fp, 26244, 1); + + if (fread(random_grid.buf.data, 1, random_grid.buf.size, fp) != random_grid.buf.size) { + return 0; + } + + skip_piece(fp, 26244, 1); + skip_piece(fp, 26244, 1); + skip_piece(fp, 26244, 1); + skip_piece(fp, 26244, 1); + skip_piece(fp, 26244, 1); + skip_piece(fp, 128000, 1); + skip_piece(fp, 1200, 1); + skip_piece(fp, 300000, 1); + skip_piece(fp, 6400, 1); + skip_piece(fp, 12, 0); + + if (!read_compressed_chunk(fp, city_data.buf.data, city_data.buf.size)) { + return 0; + } + + skip_piece(fp, 2, 0); + skip_piece(fp, 64, 0); + skip_piece(fp, 4, 0); + + if (!read_compressed_chunk(fp, buildings.buf.data, buildings.buf.size)) { + return 0; + } + + skip_piece(fp, 4, 0); + + if (fread(game_time.buf.data, 1, game_time.buf.size, fp) != game_time.buf.size) { + return 0; + } + + skip_piece(fp, 8, 0); + skip_piece(fp, 8, 0); + skip_piece(fp, 8, 0); + skip_piece(fp, 132, 0); + skip_piece(fp, 8, 0); + skip_piece(fp, 8, 0); + skip_piece(fp, 12, 0); + skip_piece(fp, 2706, 1); + skip_piece(fp, 128, 0); + skip_piece(fp, 128, 0); + skip_piece(fp, 84, 0); + skip_piece(fp, 60, 0); + + if (fread(scenario.buf.data, 1, scenario.buf.size, fp) != scenario.buf.size) { + return 0; + } + + skip_piece(fp, 4, 0); + skip_piece(fp, 60, 0); + skip_piece(fp, 4, 0); + skip_piece(fp, 16000, 1); + skip_piece(fp, 12, 0); + skip_piece(fp, 10, 0); + skip_piece(fp, 80, 0); + skip_piece(fp, 80, 0); + skip_piece(fp, 8, 0); + skip_piece(fp, 4, 0); + skip_piece(fp, 12, 0); + skip_piece(fp, 3232, 1); + + info->custom_mission = read_int32(fp); + + city_data_load_basic_info(&city_data.buf, &info->population, &info->treasury); + game_time_load_basic_info(&game_time.buf, &info->month, &info->year); + minimap_buffers.climate = scenario_climate_from_buffer(&scenario.buf); + scenario_map_data_from_buffer(&scenario.buf, &minimap_buffers.grid_width, &minimap_buffers.grid_height, + &minimap_buffers.grid_start, &minimap_buffers.grid_border_size); + + info->minimap_image = game_file_minimap_create(&minimap_buffers); + + free_file_piece(&city_data); + free_file_piece(&game_time); + free_file_piece(&terrain_grid); + free_file_piece(&scenario); + free_file_piece(&random_grid); + free_file_piece(&edge_grid); + free_file_piece(&bitfields_grid); + free_file_piece(&building_grid); + free_file_piece(&buildings); + + return 1; +} + +int game_file_io_read_saved_game_info(const char *filename, saved_game_info *info) +{ + FILE *fp = file_open(dir_get_file(filename, NOT_LOCALIZED), "rb"); + if (!fp) { + return 0; + } + int result = savegame_read_file_info(fp, info); + file_close(fp); + if (!result) { + return 0; + } + return 1; +} + int game_file_io_write_saved_game(const char *filename) { init_savegame_data(); diff --git a/src/game/file_io.h b/src/game/file_io.h index 0cdbebbd07..ea245db43f 100644 --- a/src/game/file_io.h +++ b/src/game/file_io.h @@ -1,14 +1,45 @@ #ifndef GAME_FILE_IO_H #define GAME_FILE_IO_H +#include "graphics/color.h" +#include "scenario/data.h" + +typedef struct { + int mission; + int custom_mission; + int treasury; + int population; + int month; + int year; + const color_t *minimap_image; +} saved_game_info; + +typedef struct { + uint8_t description[MAX_BRIEF_DESCRIPTION]; + int image_id; + int start_year; + int climate; + int map_size; + int total_invasions; + int player_rank; + int is_open_play; + int open_play_id; + scenario_win_criteria win_criteria; + const color_t *minimap_image; +} scenario_info; + int game_file_io_read_scenario(const char *filename); +int game_file_io_read_scenario_info(const char *filename, scenario_info *info); + int game_file_io_write_scenario(const char *filename); int game_file_io_read_saved_game(const char *filename, int offset); int game_file_io_write_saved_game(const char *filename); +int game_file_io_read_saved_game_info(const char *filename, saved_game_info *info); + int game_file_io_delete_saved_game(const char *filename); #endif // GAME_FILE_IO_H diff --git a/src/widget/scenario_minimap.c b/src/game/file_minimap.c similarity index 55% rename from src/widget/scenario_minimap.c rename to src/game/file_minimap.c index 8f961d8ea2..6d882e6e3c 100644 --- a/src/widget/scenario_minimap.c +++ b/src/game/file_minimap.c @@ -1,13 +1,19 @@ -#include "scenario_minimap.h" +#include "file_minimap.h" +#include "building/building.h" #include "city/view.h" +#include "figure/figure.h" #include "graphics/graphics.h" #include "graphics/image.h" +#include "map/building.h" +#include "map/figure.h" #include "map/property.h" #include "map/random.h" #include "map/terrain.h" #include "scenario/property.h" +#include + typedef struct { color_t left; color_t right; @@ -64,56 +70,61 @@ const tile_color_set MINIMAP_COLOR_SETS[3] = { } }; +#define MINIMAP_WIDTH_TILES (FILE_MINIMAP_IMAGE_WIDTH / 2) +#define MINIMAP_HEIGHT_TILES FILE_MINIMAP_IMAGE_HEIGHT +#define MINIMAP_X_OFFSET ((VIEW_X_MAX - MINIMAP_WIDTH_TILES) / 2) +#define MINIMAP_Y_OFFSET (((VIEW_Y_MAX - MINIMAP_HEIGHT_TILES) / 2) & ~1) // ensure even height + static struct { - int absolute_x; - int absolute_y; - int width_tiles; - int height_tiles; - int x_offset; - int y_offset; + color_t screen[FILE_MINIMAP_IMAGE_WIDTH * FILE_MINIMAP_IMAGE_HEIGHT]; + color_t dst[FILE_MINIMAP_IMAGE_WIDTH * FILE_MINIMAP_IMAGE_HEIGHT]; + const file_buffers *buffers; } data; -static void foreach_map_tile(map_callback *callback) -{ - city_view_foreach_minimap_tile( - data.x_offset, data.y_offset, data.absolute_x, data.absolute_y, - data.width_tiles, data.height_tiles, callback); -} - -static void set_bounds(int x_offset, int y_offset, int width, int height) -{ - data.width_tiles = width / 2; - data.height_tiles = height; - data.x_offset = x_offset; - data.y_offset = y_offset; - data.absolute_x = (VIEW_X_MAX - data.width_tiles) / 2; - data.absolute_y = (VIEW_Y_MAX - data.height_tiles) / 2; - - // ensure even height - data.absolute_y &= ~1; -} - static void draw_minimap_tile(int x_view, int y_view, int grid_offset) { if (grid_offset < 0) { return; } - int terrain = map_terrain_get(grid_offset); + int terrain = map_terrain_get_from_buffer(data.buffers->map_terrain, grid_offset); + building b; + // exception for fort ground: display as empty land + if (terrain & TERRAIN_BUILDING && data.buffers->buildings) { + building_get_from_buffer(data.buffers->buildings, + map_building_from_buffer(data.buffers->map_building, grid_offset), &b); + if (b.type == BUILDING_FORT_GROUND) { + terrain = 0; + } + } if (terrain & TERRAIN_BUILDING) { - // Native huts/fields - if (map_property_is_draw_tile(grid_offset)) { - int image_id = image_group(GROUP_MINIMAP_BUILDING); - switch (map_property_multi_tile_size(grid_offset)) { + // For scenarios these can be Native huts/fields + if (map_property_is_draw_tile_from_buffer(data.buffers->map_edge, grid_offset)) { + int image_id; + if (data.buffers->buildings && b.house_size) { + image_id = image_group(GROUP_MINIMAP_HOUSE); + } else if (data.buffers->buildings && b.type == BUILDING_RESERVOIR) { + image_id = image_group(GROUP_MINIMAP_AQUEDUCT) - 1; + } else { + image_id = image_group(GROUP_MINIMAP_BUILDING); + } + switch (map_property_multi_tile_size_from_buffer(data.buffers->map_bitfields, grid_offset)) { case 1: image_draw(image_id, x_view, y_view); break; case 2: image_draw(image_id + 1, x_view, y_view - 1); break; + case 3: image_draw(image_id + 2, x_view, y_view - 2); break; + case 4: image_draw(image_id + 3, x_view, y_view - 3); break; + case 5: image_draw(image_id + 4, x_view, y_view - 4); break; } } + } else if (terrain & TERRAIN_AQUEDUCT) { + image_draw(image_group(GROUP_MINIMAP_AQUEDUCT), x_view, y_view); + } else if (terrain & TERRAIN_WALL) { + image_draw(image_group(GROUP_MINIMAP_WALL), x_view, y_view); } else { - int rand = map_random_get(grid_offset); + int rand = map_random_get_from_buffer(data.buffers->map_random, grid_offset); const tile_color *color; - const tile_color_set *set = &MINIMAP_COLOR_SETS[scenario_property_climate()]; + const tile_color_set *set = &MINIMAP_COLOR_SETS[data.buffers->climate]; if (terrain & TERRAIN_WATER) { color = &set->water[rand & 3]; } else if (terrain & (TERRAIN_SHRUB | TERRAIN_TREE)) { @@ -132,10 +143,21 @@ static void draw_minimap_tile(int x_view, int y_view, int grid_offset) } } -void widget_scenario_minimap_draw(int x_offset, int y_offset, int width, int height) +const color_t *game_file_minimap_create(const file_buffers *buffers) { - set_bounds(x_offset, y_offset, width + 2, height); - graphics_set_clip_rectangle(x_offset, y_offset, width, height); - foreach_map_tile(draw_minimap_tile); + data.buffers = buffers; + + graphics_save_to_buffer(0, 0, FILE_MINIMAP_IMAGE_WIDTH, FILE_MINIMAP_IMAGE_HEIGHT, data.screen); + graphics_fill_rect(0, 0, FILE_MINIMAP_IMAGE_WIDTH, FILE_MINIMAP_IMAGE_HEIGHT, 0); + city_view_set_custom_lookup(buffers->grid_start, buffers->grid_width, + buffers->grid_height, buffers->grid_border_size); + city_view_foreach_minimap_tile(0, 0, MINIMAP_X_OFFSET, MINIMAP_Y_OFFSET, + MINIMAP_WIDTH_TILES, MINIMAP_HEIGHT_TILES, draw_minimap_tile); + graphics_set_clip_rectangle(0, 0, FILE_MINIMAP_IMAGE_WIDTH, FILE_MINIMAP_IMAGE_HEIGHT); graphics_reset_clip_rectangle(); + city_view_restore_lookup(); + graphics_save_to_buffer(0, 0, FILE_MINIMAP_IMAGE_WIDTH, FILE_MINIMAP_IMAGE_HEIGHT, data.dst); + graphics_draw_from_buffer(0, 0, FILE_MINIMAP_IMAGE_WIDTH, FILE_MINIMAP_IMAGE_HEIGHT, data.screen); + + return data.dst; } diff --git a/src/game/file_minimap.h b/src/game/file_minimap.h new file mode 100644 index 0000000000..03ac16baf9 --- /dev/null +++ b/src/game/file_minimap.h @@ -0,0 +1,26 @@ +#ifndef GAME_FILE_MINIMAP_H +#define GAME_FILE_MINIMAP_H + +#include "core/buffer.h" +#include "graphics/color.h" + +#define FILE_MINIMAP_IMAGE_WIDTH 324 +#define FILE_MINIMAP_IMAGE_HEIGHT 324 + +typedef struct { + buffer *map_terrain; + buffer *map_bitfields; + buffer *map_edge; + buffer *map_random; + buffer *map_building; + buffer *buildings; + int climate; + int grid_width; + int grid_height; + int grid_start; + int grid_border_size; +} file_buffers; + +const color_t *game_file_minimap_create(const file_buffers *buffers); + +#endif // GAME_FILE_MINIMAP_H diff --git a/src/game/time.c b/src/game/time.c index ffb0574e8a..4b4872e738 100644 --- a/src/game/time.c +++ b/src/game/time.c @@ -87,3 +87,10 @@ void game_time_load_state(buffer *buf) data.year = buffer_read_i32(buf); data.total_days = buffer_read_i32(buf); } + +void game_time_load_basic_info(buffer *buf, int *month, int *year) +{ + buffer_skip(buf, 8); + *month = buffer_read_i32(buf); + *year = buffer_read_i32(buf); +} diff --git a/src/game/time.h b/src/game/time.h index 2e55e83257..7b24e55f51 100644 --- a/src/game/time.h +++ b/src/game/time.h @@ -73,4 +73,6 @@ void game_time_save_state(buffer *buf); */ void game_time_load_state(buffer *buf); +void game_time_load_basic_info(buffer *buf, int *month, int *year); + #endif // GAME_TIME_H diff --git a/src/graphics/graphics.c b/src/graphics/graphics.c index 6ccece8391..55a54cbfaa 100644 --- a/src/graphics/graphics.c +++ b/src/graphics/graphics.c @@ -195,6 +195,28 @@ void graphics_draw_from_buffer(int x, int y, int width, int height, const color_ } } +void graphics_blend_from_buffer(int x, int y, int width, int height, const color_t *buffer) +{ + const clip_info *current_clip = graphics_get_clip_info(x, y, width, height); + if (!current_clip->is_visible) { + return; + } + int min_dx = current_clip->clipped_pixels_left; + int max_dx = width - current_clip->clipped_pixels_right; + int min_dy = current_clip->clipped_pixels_top; + int max_dy = height - current_clip->clipped_pixels_bottom; + for (int dy = min_dy; dy < max_dy; dy++) { + for (int dx = min_dx; dx < max_dx; dx++) { + color_t src = buffer[dy * width + dx]; + if (src) { + color_t *dst = graphics_get_pixel(x + dx, y + dy); + *dst = src; + } + } + } +} + + color_t *graphics_get_pixel(int x, int y) { return &canvas.pixels[(translation.y + y) * canvas.width + (translation.x + x)]; diff --git a/src/graphics/graphics.h b/src/graphics/graphics.h index c7423e271f..f7ed023a8b 100644 --- a/src/graphics/graphics.h +++ b/src/graphics/graphics.h @@ -37,6 +37,7 @@ const clip_info *graphics_get_clip_info(int x, int y, int width, int height); void graphics_save_to_buffer(int x, int y, int width, int height, color_t *buffer); void graphics_draw_from_buffer(int x, int y, int width, int height, const color_t *buffer); +void graphics_blend_from_buffer(int x, int y, int width, int height, const color_t *buffer); color_t *graphics_get_pixel(int x, int y); diff --git a/src/map/building.c b/src/map/building.c index 712db94e9e..60446f2423 100644 --- a/src/map/building.c +++ b/src/map/building.c @@ -12,6 +12,12 @@ int map_building_at(int grid_offset) return map_grid_is_valid_offset(grid_offset) ? buildings_grid.items[grid_offset] : 0; } +int map_building_from_buffer(buffer *buildings, int grid_offset) +{ + buffer_set(buildings, grid_offset * sizeof(uint16_t)); + return buffer_read_u16(buildings); +} + void map_building_set(int grid_offset, int building_id) { buildings_grid.items[grid_offset] = building_id; diff --git a/src/map/building.h b/src/map/building.h index 4a6b8ad676..a2a9adf286 100644 --- a/src/map/building.h +++ b/src/map/building.h @@ -11,6 +11,8 @@ */ int map_building_at(int grid_offset); +int map_building_from_buffer(buffer *buildings, int grid_offset); + void map_building_set(int grid_offset, int building_id); /** diff --git a/src/map/property.c b/src/map/property.c index 7ad1a72faa..40e676ed3a 100644 --- a/src/map/property.c +++ b/src/map/property.c @@ -44,6 +44,12 @@ int map_property_is_draw_tile(int grid_offset) return edge_grid.items[grid_offset] & EDGE_LEFTMOST_TILE; } +int map_property_is_draw_tile_from_buffer(buffer *edge, int grid_offset) +{ + buffer_set(edge, grid_offset); + return buffer_read_u8(edge) & EDGE_LEFTMOST_TILE; +} + void map_property_mark_draw_tile(int grid_offset) { edge_grid.items[grid_offset] |= EDGE_LEFTMOST_TILE; @@ -115,6 +121,18 @@ int map_property_multi_tile_size(int grid_offset) } } +int map_property_multi_tile_size_from_buffer(buffer *bitfields, int grid_offset) +{ + buffer_set(bitfields, grid_offset); + switch (buffer_read_u8(bitfields) & BIT_SIZES) { + case BIT_SIZE2: return 2; + case BIT_SIZE3: return 3; + case BIT_SIZE4: return 4; + case BIT_SIZE5: return 5; + default: return 1; + } +} + void map_property_set_multi_tile_size(int grid_offset, int size) { bitfields_grid.items[grid_offset] &= BIT_NO_SIZES; diff --git a/src/map/property.h b/src/map/property.h index 7f44ab9c22..6ebb1b6ae6 100644 --- a/src/map/property.h +++ b/src/map/property.h @@ -16,6 +16,7 @@ enum { }; int map_property_is_draw_tile(int grid_offset); +int map_property_is_draw_tile_from_buffer(buffer *edge, int grid_offset); void map_property_mark_draw_tile(int grid_offset); void map_property_clear_draw_tile(int grid_offset); @@ -31,6 +32,7 @@ void map_property_set_multi_tile_xy(int grid_offset, int x, int y, int is_draw_t void map_property_clear_multi_tile_xy(int grid_offset); int map_property_multi_tile_size(int grid_offset); +int map_property_multi_tile_size_from_buffer(buffer *bitfields, int grid_offset); void map_property_set_multi_tile_size(int grid_offset, int size); void map_property_init_alternate_terrain(void); diff --git a/src/map/random.c b/src/map/random.c index e33f4eeca1..fb31d4a911 100644 --- a/src/map/random.c +++ b/src/map/random.c @@ -26,6 +26,12 @@ int map_random_get(int grid_offset) return random.items[grid_offset]; } +int map_random_get_from_buffer(buffer *buf, int grid_offset) +{ + buffer_set(buf, grid_offset); + return buffer_read_u8(buf); +} + void map_random_save_state(buffer *buf) { map_grid_save_state_u8(random.items, buf); diff --git a/src/map/random.h b/src/map/random.h index d2432d89f9..a6ac3a3b70 100644 --- a/src/map/random.h +++ b/src/map/random.h @@ -9,6 +9,8 @@ void map_random_init(void); int map_random_get(int grid_offset); +int map_random_get_from_buffer(buffer *buf, int grid_offset); + void map_random_save_state(buffer *buf); void map_random_load_state(buffer *buf); diff --git a/src/map/terrain.c b/src/map/terrain.c index 3d68fb1abb..b9d9a4e9ed 100644 --- a/src/map/terrain.c +++ b/src/map/terrain.c @@ -17,6 +17,12 @@ int map_terrain_get(int grid_offset) return terrain_grid.items[grid_offset]; } +int map_terrain_get_from_buffer(buffer *buf, int grid_offset) +{ + buffer_set(buf, grid_offset * sizeof(uint16_t)); + return buffer_read_u16(buf); +} + void map_terrain_set(int grid_offset, int terrain) { terrain_grid.items[grid_offset] = terrain; diff --git a/src/map/terrain.h b/src/map/terrain.h index 0b7baade89..23c7a133a7 100644 --- a/src/map/terrain.h +++ b/src/map/terrain.h @@ -34,6 +34,8 @@ int map_terrain_is(int grid_offset, int terrain); int map_terrain_get(int grid_offset); +int map_terrain_get_from_buffer(buffer *buf, int grid_offset); + void map_terrain_set(int grid_offset, int terrain); void map_terrain_add(int grid_offset, int terrain); diff --git a/src/scenario/data.h b/src/scenario/data.h index 80c6dad9a7..95a803dd80 100644 --- a/src/scenario/data.h +++ b/src/scenario/data.h @@ -123,6 +123,25 @@ typedef struct { int is_rise; } demand_change_t; +typedef struct { + struct win_criteria_t population; + struct win_criteria_t culture; + struct win_criteria_t prosperity; + struct win_criteria_t peace; + struct win_criteria_t favor; + struct { + int enabled; + int years; + } time_limit; + struct { + int enabled; + int years; + } survival_time; + int milestone25_year; + int milestone50_year; + int milestone75_year; +} scenario_win_criteria; + extern struct scenario_t { uint8_t scenario_name[MAX_SCENARIO_NAME]; @@ -141,24 +160,7 @@ extern struct scenario_t { int is_open_play; int open_play_scenario_id; - struct { - struct win_criteria_t population; - struct win_criteria_t culture; - struct win_criteria_t prosperity; - struct win_criteria_t peace; - struct win_criteria_t favor; - struct { - int enabled; - int years; - } time_limit; - struct { - int enabled; - int years; - } survival_time; - int milestone25_year; - int milestone50_year; - int milestone75_year; - } win_criteria; + scenario_win_criteria win_criteria; struct { int id; diff --git a/src/scenario/scenario.c b/src/scenario/scenario.c index 5960d01fb3..c1b9fcc0eb 100644 --- a/src/scenario/scenario.c +++ b/src/scenario/scenario.c @@ -437,6 +437,86 @@ void scenario_load_state(buffer *buf) scenario.is_saved = 1; } +void scenario_description_from_buffer(buffer *buf, uint8_t *description) +{ + buffer_set(buf, 404); + buffer_read_raw(buf, description, MAX_BRIEF_DESCRIPTION); +} + +int scenario_climate_from_buffer(buffer *buf) +{ + buffer_set(buf, 1704); + return buffer_read_u8(buf); +} + +int scenario_invasions_from_buffer(buffer *buf) +{ + buffer_set(buf, 214); + int num_invasions = 0; + for (int i = 0; i < MAX_INVASIONS; i++) { + if (buffer_read_i16(buf)) { + num_invasions++; + } + } + return num_invasions; +} + +int scenario_image_id_from_buffer(buffer *buf) +{ + buffer_set(buf, 1010); + return buffer_read_i16(buf); +} + +int scenario_rank_from_buffer(buffer *buf) +{ + buffer_set(buf, 1014); + return buffer_read_i16(buf); + +} + +void scenario_open_play_info_from_buffer(buffer *buf, int *is_open_play, int *open_play_id) +{ + buffer_set(buf, 1012); + *is_open_play = buffer_read_i16(buf); + buffer_set(buf, 1718); + *open_play_id = buffer_read_u8(buf); +} + +int scenario_start_year_from_buffer(buffer *buf) +{ + buffer_set(buf, 0); + return buffer_read_i16(buf); +} + +void scenario_objectives_from_buffer(buffer *buf, scenario_win_criteria *win_criteria) +{ + buffer_set(buf, 1572); + win_criteria->culture.goal = buffer_read_i32(buf); + win_criteria->prosperity.goal = buffer_read_i32(buf); + win_criteria->peace.goal = buffer_read_i32(buf); + win_criteria->favor.goal = buffer_read_i32(buf); + win_criteria->culture.enabled = buffer_read_u8(buf); + win_criteria->prosperity.enabled = buffer_read_u8(buf); + win_criteria->peace.enabled = buffer_read_u8(buf); + win_criteria->favor.enabled = buffer_read_u8(buf); + win_criteria->time_limit.enabled = buffer_read_i32(buf); + win_criteria->time_limit.years = buffer_read_i32(buf); + win_criteria->survival_time.enabled = buffer_read_i32(buf); + win_criteria->survival_time.years = buffer_read_i32(buf); + buffer_skip(buf, 8); + win_criteria->population.enabled = buffer_read_i32(buf); + win_criteria->population.goal = buffer_read_i32(buf); +} + +void scenario_map_data_from_buffer(buffer *buf, int *width, int *height, int *grid_start, int *grid_border_size) +{ + buffer_set(buf, 388); + *width = buffer_read_i32(buf); + *height = buffer_read_i32(buf); + *grid_start = buffer_read_i32(buf); + *grid_border_size = buffer_read_i32(buf); +} + void scenario_settings_init(void) { scenario.settings.campaign_mission = 0; diff --git a/src/scenario/scenario.h b/src/scenario/scenario.h index 26f6eb4656..6a0154f016 100644 --- a/src/scenario/scenario.h +++ b/src/scenario/scenario.h @@ -2,6 +2,7 @@ #define SCENARIO_SCENARIO_H #include "core/buffer.h" +#include "scenario/data.h" int scenario_is_saved(void); @@ -13,6 +14,16 @@ void scenario_save_state(buffer *buf); void scenario_load_state(buffer *buf); +void scenario_description_from_buffer(buffer *buf, uint8_t *description); +int scenario_climate_from_buffer(buffer *buf); +int scenario_image_id_from_buffer(buffer *buf); +int scenario_invasions_from_buffer(buffer *buf); +int scenario_rank_from_buffer(buffer *buf); +int scenario_start_year_from_buffer(buffer *buf); +void scenario_open_play_info_from_buffer(buffer *buf, int *is_open_play, int *open_play_id); +void scenario_objectives_from_buffer(buffer *buf, scenario_win_criteria *win_criteria); +void scenario_map_data_from_buffer(buffer *buf, int *width, int *height, int *grid_start, int *grid_border_size); + void scenario_settings_save_state( buffer *part1, buffer *part2, buffer *part3, buffer *player_name, buffer *scenario_name); diff --git a/src/translation/english.c b/src/translation/english.c index 0f6d3fcde5..37fc1f844a 100644 --- a/src/translation/english.c +++ b/src/translation/english.c @@ -109,6 +109,16 @@ static translation_string all_strings[] = { {TR_HOTKEY_DUPLICATE_TITLE, "Hotkey already used"}, {TR_HOTKEY_DUPLICATE_MESSAGE, "This key combination is already assigned to the following action:"}, {TR_WARNING_SCREENSHOT_SAVED, "Screenshot saved: "}, + {TR_SAVE_DIALOG_INVALID_FILE, "Invalid file"}, + {TR_SAVE_DIALOG_SELECT_FILE, "Select a file"}, + {TR_SAVE_DIALOG_FUNDS, "Funds:"}, + {TR_SAVE_DIALOG_POPULATION, "Population:"}, + {TR_SAVE_DIALOG_DATE, "Date:"}, + {TR_SAVE_DIALOG_CUSTOM_SCENARIO, "Custom scenario"}, + {TR_SAVE_DIALOG_FIRST_MISSION, "First mission"}, + {TR_SAVE_DIALOG_MISSION, "Mission"}, + {TR_SAVE_DIALOG_MILITARY, "Military"}, + {TR_SAVE_DIALOG_PEACEFUL, "Peaceful"} }; void translation_english(const translation_string **strings, int *num_strings) diff --git a/src/translation/translation.h b/src/translation/translation.h index 591abc7b8e..1ceb1ab634 100644 --- a/src/translation/translation.h +++ b/src/translation/translation.h @@ -101,6 +101,16 @@ typedef enum { TR_HOTKEY_DUPLICATE_TITLE, TR_HOTKEY_DUPLICATE_MESSAGE, TR_WARNING_SCREENSHOT_SAVED, + TR_SAVE_DIALOG_INVALID_FILE, + TR_SAVE_DIALOG_SELECT_FILE, + TR_SAVE_DIALOG_FUNDS, + TR_SAVE_DIALOG_POPULATION, + TR_SAVE_DIALOG_DATE, + TR_SAVE_DIALOG_CUSTOM_SCENARIO, + TR_SAVE_DIALOG_FIRST_MISSION, + TR_SAVE_DIALOG_MISSION, + TR_SAVE_DIALOG_MILITARY, + TR_SAVE_DIALOG_PEACEFUL, TRANSLATION_MAX_KEY } translation_key; diff --git a/src/widget/minimap.c b/src/widget/minimap.c index 4d76da107c..62bd12df4f 100644 --- a/src/widget/minimap.c +++ b/src/widget/minimap.c @@ -2,8 +2,10 @@ #include "building/building.h" #include "city/view.h" +#include "core/buffer.h" #include "figure/figure.h" #include "figure/formation.h" +#include "game/file_minimap.h" #include "graphics/graphics.h" #include "graphics/image.h" #include "map/building.h" @@ -307,6 +309,19 @@ void widget_minimap_draw(int x_offset, int y_offset, int width, int height, int } } +void widget_minimap_draw_from_buffer(int x, int y, int width, int height, const color_t *buffer) +{ + int x_offset = (FILE_MINIMAP_IMAGE_WIDTH - width) / 2; + int y_offset = (FILE_MINIMAP_IMAGE_HEIGHT - height) / 2; + + const color_t *dst = buffer + y_offset * FILE_MINIMAP_IMAGE_WIDTH + x_offset; + + for (int i = 0; i < height; i++) { + graphics_blend_from_buffer(x, y + i, width, 1, dst); + dst += FILE_MINIMAP_IMAGE_WIDTH; + } +} + static void update_mouse_grid_offset(int x_view, int y_view, int grid_offset) { if (data.mouse.y == y_view && (data.mouse.x == x_view || data.mouse.x == x_view + 1)) { diff --git a/src/widget/minimap.h b/src/widget/minimap.h index 608baf0321..e8ade0a039 100644 --- a/src/widget/minimap.h +++ b/src/widget/minimap.h @@ -1,12 +1,15 @@ #ifndef WIDGET_MINIMAP_H #define WIDGET_MINIMAP_H +#include "graphics/color.h" #include "input/mouse.h" void widget_minimap_invalidate(void); void widget_minimap_draw(int x_offset, int y_offset, int width, int height, int force); +void widget_minimap_draw_from_buffer(int x, int y, int width, int height, const color_t *buffer); + int widget_minimap_handle_mouse(const mouse *m); #endif // WIDGET_MINIMAP_H diff --git a/src/widget/scenario_minimap.h b/src/widget/scenario_minimap.h deleted file mode 100644 index 83d8385211..0000000000 --- a/src/widget/scenario_minimap.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef WIDGET_SCENARIO_MINIMAP_H -#define WIDGET_SCENARIO_MINIMAP_H - -void widget_scenario_minimap_draw(int x_offset, int y_offset, int width, int height); - -#endif // WIDGET_SCENARIO_MINIMAP_H diff --git a/src/window/cck_selection.c b/src/window/cck_selection.c index 8e8a1be445..3c9efc9797 100644 --- a/src/window/cck_selection.c +++ b/src/window/cck_selection.c @@ -5,6 +5,7 @@ #include "core/file.h" #include "core/image_group.h" #include "game/file.h" +#include "game/file_io.h" #include "graphics/generic_button.h" #include "graphics/graphics.h" #include "graphics/image.h" @@ -15,12 +16,8 @@ #include "graphics/text.h" #include "graphics/window.h" #include "input/input.h" -#include "scenario/criteria.h" -#include "scenario/invasion.h" -#include "scenario/map.h" -#include "scenario/property.h" #include "sound/music.h" -#include "widget/scenario_minimap.h" +#include "widget/minimap.h" #include "window/city.h" #include @@ -65,13 +62,13 @@ static struct { int show_minimap; char selected_scenario_filename[FILE_NAME_MAX]; uint8_t selected_scenario_display[FILE_NAME_MAX]; + scenario_info info; const dir_listing *scenarios; } data; static void init(void) { - scenario_set_custom(2); data.scenarios = dir_find_files_with_extension("map"); data.focus_button_id = 0; data.focus_toggle_button = 0; @@ -106,32 +103,33 @@ static void draw_scenario_info(void) const int scenario_info_width = 280; const int scenario_criteria_x = 420; - image_draw(image_group(GROUP_SCENARIO_IMAGE) + scenario_image_id(), 78, 36); + image_draw(image_group(GROUP_SCENARIO_IMAGE) + data.info.image_id, 78, 36); text_ellipsize(data.selected_scenario_display, FONT_LARGE_BLACK, scenario_info_width + 10); text_draw_centered(data.selected_scenario_display, scenario_info_x, 25, scenario_info_width + 10, FONT_LARGE_BLACK, 0); - text_draw_centered(scenario_brief_description(), scenario_info_x, 60, scenario_info_width, FONT_NORMAL_WHITE, 0); - lang_text_draw_year(scenario_property_start_year(), scenario_criteria_x, 90, FONT_LARGE_BLACK); + text_draw_centered(data.info.description, scenario_info_x, 60, scenario_info_width, FONT_NORMAL_WHITE, 0); + lang_text_draw_year(data.info.start_year, scenario_criteria_x, 90, FONT_LARGE_BLACK); if (data.show_minimap) { - widget_scenario_minimap_draw(332, 119, 286, 300); + widget_minimap_draw_from_buffer(332, 119, 286, 300, data.info.minimap_image); // minimap button: draw mission instructions image image_draw(image_group(GROUP_SIDEBAR_BRIEFING_ROTATE_BUTTONS), toggle_minimap_button.x + 3, toggle_minimap_button.y + 3); } else { // minimap button: draw minimap - widget_scenario_minimap_draw( + widget_minimap_draw_from_buffer( toggle_minimap_button.x + 3, toggle_minimap_button.y + 3, - toggle_minimap_button.width - 6, toggle_minimap_button.height - 6 + toggle_minimap_button.width - 6, toggle_minimap_button.height - 6, + data.info.minimap_image ); - lang_text_draw_centered(44, 77 + scenario_property_climate(), + lang_text_draw_centered(44, 77 + data.info.climate, scenario_info_x, 150, scenario_info_width, FONT_NORMAL_BLACK); // map size int text_id; - switch (scenario_map_size()) { + switch (data.info.map_size) { case 40: text_id = 121; break; case 60: text_id = 122; break; case 80: text_id = 123; break; @@ -142,7 +140,7 @@ static void draw_scenario_info(void) lang_text_draw_centered(44, text_id, scenario_info_x, 170, scenario_info_width, FONT_NORMAL_BLACK); // military - int num_invasions = scenario_invasion_count(); + int num_invasions = data.info.total_invasions; if (num_invasions <= 0) { text_id = 112; } else if (num_invasions <= 2) { @@ -156,48 +154,48 @@ static void draw_scenario_info(void) } lang_text_draw_centered(44, text_id, scenario_info_x, 190, scenario_info_width, FONT_NORMAL_BLACK); - lang_text_draw_centered(32, 11 + scenario_property_player_rank(), + lang_text_draw_centered(32, 11 + data.info.player_rank, scenario_info_x, 210, scenario_info_width, FONT_NORMAL_BLACK); - if (scenario_is_open_play()) { - if (scenario_open_play_id() < 12) { - lang_text_draw_multiline(145, scenario_open_play_id(), + if (data.info.is_open_play) { + if (data.info.open_play_id < 12) { + lang_text_draw_multiline(145, data.info.open_play_id, scenario_info_x + 10, 270, scenario_info_width - 10, FONT_NORMAL_BLACK); } } else { lang_text_draw_centered(44, 127, scenario_info_x, 262, scenario_info_width, FONT_NORMAL_BLACK); int width; - if (scenario_criteria_culture_enabled()) { - width = text_draw_number(scenario_criteria_culture(), '@', " ", + if (data.info.win_criteria.culture.enabled) { + width = text_draw_number(data.info.win_criteria.culture.goal, '@', " ", scenario_criteria_x, 290, FONT_NORMAL_BLACK); lang_text_draw(44, 129, scenario_criteria_x + width, 290, FONT_NORMAL_BLACK); } - if (scenario_criteria_prosperity_enabled()) { - width = text_draw_number(scenario_criteria_prosperity(), '@', " ", + if (data.info.win_criteria.prosperity.enabled) { + width = text_draw_number(data.info.win_criteria.prosperity.goal, '@', " ", scenario_criteria_x, 306, FONT_NORMAL_BLACK); lang_text_draw(44, 130, scenario_criteria_x + width, 306, FONT_NORMAL_BLACK); } - if (scenario_criteria_peace_enabled()) { - width = text_draw_number(scenario_criteria_peace(), '@', " ", + if (data.info.win_criteria.peace.enabled) { + width = text_draw_number(data.info.win_criteria.peace.goal, '@', " ", scenario_criteria_x, 322, FONT_NORMAL_BLACK); lang_text_draw(44, 131, scenario_criteria_x + width, 322, FONT_NORMAL_BLACK); } - if (scenario_criteria_favor_enabled()) { - width = text_draw_number(scenario_criteria_favor(), '@', " ", + if (data.info.win_criteria.favor.enabled) { + width = text_draw_number(data.info.win_criteria.favor.goal, '@', " ", scenario_criteria_x, 338, FONT_NORMAL_BLACK); lang_text_draw(44, 132, scenario_criteria_x + width, 338, FONT_NORMAL_BLACK); } - if (scenario_criteria_population_enabled()) { - width = text_draw_number(scenario_criteria_population(), '@', " ", + if (data.info.win_criteria.population.enabled) { + width = text_draw_number(data.info.win_criteria.population.goal, '@', " ", scenario_criteria_x, 354, FONT_NORMAL_BLACK); lang_text_draw(44, 133, scenario_criteria_x + width, 354, FONT_NORMAL_BLACK); } - if (scenario_criteria_time_limit_enabled()) { - width = text_draw_number(scenario_criteria_time_limit_years(), '@', " ", + if (data.info.win_criteria.time_limit.enabled) { + width = text_draw_number(data.info.win_criteria.time_limit.years, '@', " ", scenario_criteria_x, 370, FONT_NORMAL_BLACK); lang_text_draw(44, 134, scenario_criteria_x + width, 370, FONT_NORMAL_BLACK); } - if (scenario_criteria_survival_enabled()) { - width = text_draw_number(scenario_criteria_survival_years(), '@', " ", + if (data.info.win_criteria.survival_time.enabled) { + width = text_draw_number(data.info.win_criteria.survival_time.years, '@', " ", scenario_criteria_x, 386, FONT_NORMAL_BLACK); lang_text_draw(44, 135, scenario_criteria_x + width, 386, FONT_NORMAL_BLACK); } @@ -260,7 +258,7 @@ static void button_select_item(int index, int param2) } data.selected_item = scrollbar.scroll_position + index; strcpy(data.selected_scenario_filename, data.scenarios->files[data.selected_item]); - game_file_load_scenario_data(data.selected_scenario_filename); + game_file_io_read_scenario_info(data.selected_scenario_filename, &data.info); encoding_from_utf8(data.selected_scenario_filename, data.selected_scenario_display, FILE_NAME_MAX); file_remove_extension(data.selected_scenario_display); window_invalidate(); diff --git a/src/window/file_dialog.c b/src/window/file_dialog.c index 0d53f98e1b..bc7c0b094a 100644 --- a/src/window/file_dialog.c +++ b/src/window/file_dialog.c @@ -10,6 +10,7 @@ #include "core/time.h" #include "game/file.h" #include "game/file_editor.h" +#include "game/file_io.h" #include "graphics/generic_button.h" #include "graphics/graphics.h" #include "graphics/image.h" @@ -21,14 +22,16 @@ #include "graphics/window.h" #include "input/input.h" #include "platform/file_manager.h" +#include "translation/translation.h" #include "widget/input_box.h" +#include "widget/minimap.h" #include "window/city.h" #include "window/editor/map.h" #include -#define NUM_FILES_IN_VIEW 12 -#define MAX_FILE_WINDOW_TEXT_WIDTH (18 * BLOCK_SIZE) +#define NUM_FILES_IN_VIEW 21 +#define MAX_FILE_WINDOW_TEXT_WIDTH (16 * BLOCK_SIZE) static const time_millis NOT_EXIST_MESSAGE_TIMEOUT = 500; @@ -37,25 +40,35 @@ static void button_select_file(int index, int param2); static void on_scroll(void); static image_button image_buttons[] = { - {344, 335, 39, 26, IB_NORMAL, GROUP_OK_CANCEL_SCROLL_BUTTONS, 0, button_ok_cancel, button_none, 1, 0, 1}, - {392, 335, 39, 26, IB_NORMAL, GROUP_OK_CANCEL_SCROLL_BUTTONS, 4, button_ok_cancel, button_none, 0, 0, 1}, + {536, 440, 39, 26, IB_NORMAL, GROUP_OK_CANCEL_SCROLL_BUTTONS, 0, button_ok_cancel, button_none, 1, 0, 1}, + {584, 440, 39, 26, IB_NORMAL, GROUP_OK_CANCEL_SCROLL_BUTTONS, 4, button_ok_cancel, button_none, 0, 0, 1}, }; + static generic_button file_buttons[] = { - {160, 128, 288, 16, button_select_file, button_none, 0, 0}, - {160, 144, 288, 16, button_select_file, button_none, 1, 0}, - {160, 160, 288, 16, button_select_file, button_none, 2, 0}, - {160, 176, 288, 16, button_select_file, button_none, 3, 0}, - {160, 192, 288, 16, button_select_file, button_none, 4, 0}, - {160, 208, 288, 16, button_select_file, button_none, 5, 0}, - {160, 224, 288, 16, button_select_file, button_none, 6, 0}, - {160, 240, 288, 16, button_select_file, button_none, 7, 0}, - {160, 256, 288, 16, button_select_file, button_none, 8, 0}, - {160, 272, 288, 16, button_select_file, button_none, 9, 0}, - {160, 288, 288, 16, button_select_file, button_none, 10, 0}, - {160, 304, 288, 16, button_select_file, button_none, 11, 0}, + {32, 88 + 16 * 0, 256, 16, button_select_file, button_none, 0, 0}, + {32, 88 + 16 * 1, 256, 16, button_select_file, button_none, 1, 0}, + {32, 88 + 16 * 2, 256, 16, button_select_file, button_none, 2, 0}, + {32, 88 + 16 * 3, 256, 16, button_select_file, button_none, 3, 0}, + {32, 88 + 16 * 4, 256, 16, button_select_file, button_none, 4, 0}, + {32, 88 + 16 * 5, 256, 16, button_select_file, button_none, 5, 0}, + {32, 88 + 16 * 6, 256, 16, button_select_file, button_none, 6, 0}, + {32, 88 + 16 * 7, 256, 16, button_select_file, button_none, 7, 0}, + {32, 88 + 16 * 8, 256, 16, button_select_file, button_none, 8, 0}, + {32, 88 + 16 * 9, 256, 16, button_select_file, button_none, 9, 0}, + {32, 88 + 16 * 10, 256, 16, button_select_file, button_none, 10, 0}, + {32, 88 + 16 * 11, 256, 16, button_select_file, button_none, 11, 0}, + {32, 88 + 16 * 12, 256, 16, button_select_file, button_none, 12, 0}, + {32, 88 + 16 * 13, 256, 16, button_select_file, button_none, 13, 0}, + {32, 88 + 16 * 14, 256, 16, button_select_file, button_none, 14, 0}, + {32, 88 + 16 * 15, 256, 16, button_select_file, button_none, 15, 0}, + {32, 88 + 16 * 16, 256, 16, button_select_file, button_none, 16, 0}, + {32, 88 + 16 * 17, 256, 16, button_select_file, button_none, 17, 0}, + {32, 88 + 16 * 18, 256, 16, button_select_file, button_none, 18, 0}, + {32, 88 + 16 * 19, 256, 16, button_select_file, button_none, 19, 0}, + {32, 88 + 16 * 20, 256, 16, button_select_file, button_none, 20, 0}, }; -static scrollbar_type scrollbar = {464, 120, 206, 320, NUM_FILES_IN_VIEW, on_scroll, 1}; +static scrollbar_type scrollbar = {304, 80, 350, 320, NUM_FILES_IN_VIEW, on_scroll, 1}; typedef struct { char extension[4]; @@ -74,9 +87,16 @@ static struct { uint8_t typed_name[FILE_NAME_MAX]; uint8_t previously_seen_typed_name[FILE_NAME_MAX]; char selected_file[FILE_NAME_MAX]; + saved_game_info info; + int has_valid_info; + int redraw_full_window; } data; -static input_box file_name_input = {144, 80, 20, 2, FONT_NORMAL_WHITE, 0, data.typed_name, FILE_NAME_MAX}; +static const int MISSION_ID_TO_CITY_ID[] = { + 0, 3, 2, 1, 7, 10, 18, 4, 30, 6, 12, 14, 16, 27, 31, 23, 36, 38, 28, 25 +}; + +static input_box file_name_input = {16, 40, 38, 2, FONT_NORMAL_WHITE, 0, data.typed_name, FILE_NAME_MAX}; static file_type_data saved_game_data = {"sav"}; static file_type_data scenario_data = {"map"}; @@ -149,38 +169,96 @@ static void init(file_type type, file_dialog_type dialog_type) input_box_start(&file_name_input); } -static void draw_foreground(void) +static void draw_mission_info(int x_offset, int y_offset, int box_size) { - graphics_in_dialog(); - uint8_t file[FILE_NAME_MAX]; + if (data.info.custom_mission) { + text_draw_centered(translation_for(TR_SAVE_DIALOG_CUSTOM_SCENARIO), + x_offset, y_offset, box_size, FONT_NORMAL_BLACK, 0); + return; + } + if (data.info.mission == 0) { + text_draw_centered(translation_for(TR_SAVE_DIALOG_FIRST_MISSION), + x_offset, y_offset, box_size, FONT_NORMAL_BLACK, 0); + return; + } - outer_panel_draw(128, 40, 24, 21); - input_box_draw(&file_name_input); - inner_panel_draw(144, 120, 20, 13); + translation_key mission_type; - // title - if (data.message_not_exist_start_time - && time_get_millis() - data.message_not_exist_start_time < NOT_EXIST_MESSAGE_TIMEOUT) { - lang_text_draw_centered(43, 2, 160, 50, 304, FONT_LARGE_BLACK); - } else if (data.dialog_type == FILE_DIALOG_DELETE) { - lang_text_draw_centered(43, 6, 160, 50, 304, FONT_LARGE_BLACK); + if (data.info.mission == 1) { + mission_type = TR_SAVE_DIALOG_MISSION; + } else if (data.info.mission % 2) { + mission_type = TR_SAVE_DIALOG_MILITARY; } else { - int text_id = data.dialog_type + (data.type == FILE_TYPE_SCENARIO ? 3 : 0); - lang_text_draw_centered(43, text_id, 160, 50, 304, FONT_LARGE_BLACK); + mission_type = TR_SAVE_DIALOG_PEACEFUL; } - lang_text_draw(43, 5, 224, 342, FONT_NORMAL_BLACK); - for (int i = 0; i < NUM_FILES_IN_VIEW; i++) { - font_t font = FONT_NORMAL_GREEN; - if (data.focus_button_id == i + 1) { - font = FONT_NORMAL_WHITE; + int width = text_draw(translation_for(mission_type), x_offset, y_offset, FONT_NORMAL_BLACK, 0); + width += text_draw_number(data.info.mission / 2 + 2, '\0', " -", x_offset + width, y_offset, FONT_NORMAL_BLACK); + lang_text_draw(21, MISSION_ID_TO_CITY_ID[data.info.mission], x_offset + width, y_offset, FONT_NORMAL_BLACK); +} + +static void draw_background(void) +{ + window_draw_underlying_window(); + data.redraw_full_window = 1; +} + +static void draw_foreground(void) +{ + graphics_in_dialog(); + + if (data.redraw_full_window) { + uint8_t file[FILE_NAME_MAX]; + + outer_panel_draw(0, 0, 40, 30); + inner_panel_draw(16, 80, 18, 22); + + // title + if (data.message_not_exist_start_time + && time_get_millis() - data.message_not_exist_start_time < NOT_EXIST_MESSAGE_TIMEOUT) { + lang_text_draw_centered(43, 2, 32, 10, 554, FONT_LARGE_BLACK); + } else if (data.dialog_type == FILE_DIALOG_DELETE) { + lang_text_draw_centered(43, 6, 32, 10, 554, FONT_LARGE_BLACK); + } else { + int text_id = data.dialog_type + (data.type == FILE_TYPE_SCENARIO ? 3 : 0); + lang_text_draw_centered(43, text_id, 32, 10, 554, FONT_LARGE_BLACK); } - encoding_from_utf8(data.file_list->files[scrollbar.scroll_position + i], file, FILE_NAME_MAX); - file_remove_extension(file); - text_ellipsize(file, font, MAX_FILE_WINDOW_TEXT_WIDTH); - text_draw(file, 160, 130 + 16 * i, font, 0); + lang_text_draw_centered(43, 5, 362, 447, 164, FONT_NORMAL_BLACK); + + for (int i = 0; i < NUM_FILES_IN_VIEW; i++) { + font_t font = FONT_NORMAL_GREEN; + if (data.focus_button_id == i + 1) { + font = FONT_NORMAL_WHITE; + } + encoding_from_utf8(data.file_list->files[scrollbar.scroll_position + i], file, FILE_NAME_MAX); + file_remove_extension(file); + text_ellipsize(file, font, MAX_FILE_WINDOW_TEXT_WIDTH); + text_draw(file, 32, 90 + 16 * i, font, 0); + } + + // Saved game info + if (*data.selected_file) { + if (data.has_valid_info) { + draw_mission_info(362, 356, 246); + text_draw(translation_for(TR_SAVE_DIALOG_FUNDS), 362, 376, FONT_NORMAL_BLACK, 0); + text_draw_money(data.info.treasury, 494, 376, FONT_NORMAL_BLACK); + text_draw(translation_for(TR_SAVE_DIALOG_DATE), 362, 396, FONT_NORMAL_BLACK, 0); + lang_text_draw_month_year_max_width(data.info.month, data.info.year, + 500, 396, 108, FONT_NORMAL_BLACK, 0); + text_draw(translation_for(TR_SAVE_DIALOG_POPULATION), 362, 416, FONT_NORMAL_BLACK, 0); + text_draw_number(data.info.population, '\0', "", 500, 416, FONT_NORMAL_BLACK); + widget_minimap_draw_from_buffer(352, 80, 266, 272, data.info.minimap_image); + } else { + text_draw_centered(translation_for(TR_SAVE_DIALOG_INVALID_FILE), 362, 241, 246, FONT_LARGE_BLACK, 0); + } + } else { + text_draw_centered(translation_for(TR_SAVE_DIALOG_SELECT_FILE), 362, 246, 246, FONT_NORMAL_BLACK, 0); + } + data.redraw_full_window = 0; } + input_box_draw(&file_name_input); + image_buttons_draw(0, 0, image_buttons, 2); scrollbar_draw(&scrollbar); @@ -210,12 +288,21 @@ static void handle_input(const mouse *m, const hotkeys *h) return; } + // title + if (data.message_not_exist_start_time + && time_get_millis() - data.message_not_exist_start_time >= NOT_EXIST_MESSAGE_TIMEOUT) { + data.redraw_full_window = 1; + data.message_not_exist_start_time = 0; + } + const mouse *m_dialog = mouse_in_dialog(m); - data.focus_button_id = 0; - if (scrollbar_handle_mouse(&scrollbar, m_dialog) || - input_box_handle_mouse(m_dialog, &file_name_input) || + int focus_id = data.focus_button_id; + int scroll_position = scrollbar.scroll_position; + if (input_box_handle_mouse(m_dialog, &file_name_input) || generic_buttons_handle_mouse(m_dialog, 0, 0, file_buttons, NUM_FILES_IN_VIEW, &data.focus_button_id) || - image_buttons_handle_mouse(m_dialog, 0, 0, image_buttons, 2, 0)) { + image_buttons_handle_mouse(m_dialog, 0, 0, image_buttons, 2, 0) || + scrollbar_handle_mouse(&scrollbar, m_dialog)) { + data.redraw_full_window = 1; return; } if (input_go_back_requested(m, h)) { @@ -225,6 +312,10 @@ static void handle_input(const mouse *m, const hotkeys *h) if (should_scroll_to_typed_text()) { scroll_to_typed_text(); + data.redraw_full_window = 1; + } + if (focus_id != data.focus_button_id || scroll_position != scrollbar.scroll_position) { + data.redraw_full_window = 1; } } @@ -310,13 +401,15 @@ static void on_scroll(void) static void button_select_file(int index, int param2) { - if (index < data.file_list->num_files) { + if (index < data.file_list->num_files && + strcmp(data.selected_file, data.file_list->files[scrollbar.scroll_position + index])) { strncpy(data.selected_file, data.file_list->files[scrollbar.scroll_position + index], FILE_NAME_MAX - 1); encoding_from_utf8(data.selected_file, data.typed_name, FILE_NAME_MAX); file_remove_extension(data.typed_name); string_copy(data.typed_name, data.previously_seen_typed_name, FILE_NAME_MAX); input_box_refresh_text(&file_name_input); data.message_not_exist_start_time = 0; + data.has_valid_info = game_file_io_read_saved_game_info(get_chosen_filename(), &data.info); } if (data.dialog_type != FILE_DIALOG_DELETE && data.double_click) { data.double_click = 0; @@ -328,7 +421,7 @@ void window_file_dialog_show(file_type type, file_dialog_type dialog_type) { window_type window = { WINDOW_FILE_DIALOG, - window_draw_underlying_window, + draw_background, draw_foreground, handle_input }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a6b4f5df8d..688d3a7448 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,6 +20,8 @@ except_file(TEST_CORE_FILES "core/image.c" ${CORE_FILES}) except_file(TEST_CORE_FILES "core/lang.c" ${TEST_CORE_FILES}) except_file(TEST_CORE_FILES "core/speed.c" ${TEST_CORE_FILES}) except_file(TEST_BUILDING_FILES "building/model.c" ${BUILDING_FILES}) +except_file(TEST_GAME_FILES "game/file_minimap.c" ${GAME_FILES}) + add_executable(translationcheck translation/check.c @@ -54,11 +56,11 @@ add_executable(autopilot ${PROJECT_SOURCE_DIR}/src/platform/file_manager.c ${TEST_CORE_FILES} ${TEST_BUILDING_FILES} + ${TEST_GAME_FILES} ${CITY_FILES} ${EMPIRE_FILES} ${FIGURE_FILES} ${FIGURETYPE_FILES} - ${GAME_FILES} ${MAP_FILES} ${SCENARIO_FILES} ${SOUND_FILES} diff --git a/test/stub/ui.c b/test/stub/ui.c index 3411261b48..f539fe26a3 100644 --- a/test/stub/ui.c +++ b/test/stub/ui.c @@ -63,6 +63,11 @@ void window_popup_dialog_show(popup_dialog_type type, void (*okFunc)(int), int h void widget_minimap_invalidate(void) {} +const void *game_file_minimap_create(const void *buffers) +{ + return 0; +} + int window_building_info_get_building_type(void) { return 0;