From 11a38dbc42eb2532fb65500e7b66259980008809 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 14:16:14 +0100 Subject: [PATCH 01/22] Rough version of terrain generator --- CMakeLists.txt | 1 + src/building/model.c | 362 ++++++++ src/building/model.h | 70 ++ src/empire/xml.c | 848 ++++++++++++++++++ src/empire/xml.h | 8 + src/game/file_editor.c | 23 +- src/game/file_editor.h | 1 + src/game/game.c | 18 + src/game/game.h | 1 + .../event/conditions/comparison_helper.h | 6 + .../conditions/condition_comparison_helper.c | 23 + .../event/conditions/condition_handler.c | 169 ++++ .../event/conditions/condition_handler.h | 21 + .../event/conditions/condition_types.c | 471 ++++++++++ .../event/conditions/condition_types.h | 55 ++ src/scenario/terrain_generator.c | 129 +++ src/scenario/terrain_generator.h | 12 + src/translation/english.c | 3 + src/translation/french.c | 3 + src/translation/german.c | 3 + src/translation/greek.c | 3 + src/translation/italian.c | 3 + src/translation/korean.c | 3 + src/translation/translation.h | 3 + src/window/main_menu.c | 93 +- 25 files changed, 2308 insertions(+), 24 deletions(-) create mode 100644 src/building/model.c create mode 100644 src/building/model.h create mode 100644 src/empire/xml.c create mode 100644 src/empire/xml.h create mode 100644 src/scenario/event/conditions/comparison_helper.h create mode 100644 src/scenario/event/conditions/condition_comparison_helper.c create mode 100644 src/scenario/event/conditions/condition_handler.c create mode 100644 src/scenario/event/conditions/condition_handler.h create mode 100644 src/scenario/event/conditions/condition_types.c create mode 100644 src/scenario/event/conditions/condition_types.h create mode 100644 src/scenario/terrain_generator.c create mode 100644 src/scenario/terrain_generator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab42393215..c9c318bfaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -535,6 +535,7 @@ set(SCENARIO_FILES ${PROJECT_SOURCE_DIR}/src/scenario/editor.c ${PROJECT_SOURCE_DIR}/src/scenario/editor_events.c ${PROJECT_SOURCE_DIR}/src/scenario/editor_map.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator.c ${PROJECT_SOURCE_DIR}/src/scenario/emperor_change.c ${PROJECT_SOURCE_DIR}/src/scenario/empire.c ${PROJECT_SOURCE_DIR}/src/scenario/gladiator_revolt.c diff --git a/src/building/model.c b/src/building/model.c new file mode 100644 index 0000000000..74178886b6 --- /dev/null +++ b/src/building/model.c @@ -0,0 +1,362 @@ +#include "building/model.h" + +#include "city/resource.h" +#include "core/io.h" +#include "core/log.h" +#include "core/string.h" + +#include +#include + +#define TMP_BUFFER_SIZE 100000 + +#define NUM_BUILDINGS 130 +#define NUM_HOUSES 20 + +static const uint8_t ALL_BUILDINGS[] = { 'A', 'L', 'L', ' ', 'B', 'U', 'I', 'L', 'D', 'I', 'N', 'G', 'S', 0 }; +static const uint8_t ALL_HOUSES[] = { 'A', 'L', 'L', ' ', 'H', 'O', 'U', 'S', 'E', 'S', 0 }; + +static model_building buildings[NUM_BUILDINGS]; +static model_house houses[NUM_HOUSES]; + +static int strings_equal(const uint8_t *a, const uint8_t *b, int len) +{ + for (int i = 0; i < len; i++, a++, b++) { + if (*a != *b) { + return 0; + } + } + return 1; +} + +static int index_of_string(const uint8_t *haystack, const uint8_t *needle, int haystack_length) +{ + int needle_length = string_length(needle); + for (int i = 0; i < haystack_length; i++) { + if (haystack[i] == needle[0] && strings_equal(&haystack[i], needle, needle_length)) { + return i + 1; + } + } + return 0; +} + +static int index_of(const uint8_t *haystack, uint8_t needle, int haystack_length) +{ + for (int i = 0; i < haystack_length; i++) { + if (haystack[i] == needle) { + return i + 1; + } + } + return 0; +} + +static const uint8_t *skip_non_digits(const uint8_t *str) +{ + int safeguard = 0; + while (1) { + if (++safeguard >= 1000) { + break; + } + if ((*str >= '0' && *str <= '9') || *str == '-') { + break; + } + str++; + } + return str; +} + +static const uint8_t *get_value(const uint8_t *ptr, const uint8_t *end_ptr, int *value) +{ + ptr = skip_non_digits(ptr); + *value = string_to_int(ptr); + ptr += index_of(ptr, ',', (int) (end_ptr - ptr)); + return ptr; +} + +static void override_model_data(void) +{ + buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_value = 14; + buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_step = 2; + buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_step_size = -2; + buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_range = 5; + + buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_value = 14; + buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_step = 2; + buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_step_size = -2; + buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_range = 5; + + buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_value = 14; + buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_step = 2; + buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_step_size = -2; + buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_range = 5; + + buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_value = 14; + buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_step = 2; + buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_step_size = -2; + buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_range = 5; + + buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_value = 14; + buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_step = 2; + buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_step_size = -2; + buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_range = 5; + + buildings[BUILDING_WELL].laborers = 0; + buildings[BUILDING_GATEHOUSE].laborers = 0; + buildings[BUILDING_FORT_JAVELIN].laborers = 0; + buildings[BUILDING_FORT_LEGIONARIES].laborers = 0; + buildings[BUILDING_FORT_MOUNTED].laborers = 0; + buildings[BUILDING_FORT].laborers = 0; +} + +int model_load(void) +{ + uint8_t *buffer = (uint8_t *) malloc(TMP_BUFFER_SIZE); + if (!buffer) { + log_error("No memory for model", 0, 0); + return 0; + } + memset(buffer, 0, TMP_BUFFER_SIZE); + int filesize = io_read_file_into_buffer("c3_model.txt", NOT_LOCALIZED, buffer, TMP_BUFFER_SIZE); + if (filesize == 0) { + log_error("No c3_model.txt file", 0, 0); + free(buffer); + return 0; + } + + int num_lines = 0; + int guard = 200; + int brace_index; + const uint8_t *ptr = &buffer[index_of_string(buffer, ALL_BUILDINGS, filesize)]; + do { + guard--; + brace_index = index_of(ptr, '{', filesize); + if (brace_index) { + ptr += brace_index; + num_lines++; + } + } while (brace_index && guard > 0); + + if (num_lines != NUM_BUILDINGS + NUM_HOUSES) { + log_error("Model has incorrect no of lines ", 0, num_lines + 1); + free(buffer); + return 0; + } + + int dummy; + ptr = &buffer[index_of_string(buffer, ALL_BUILDINGS, filesize)]; + const uint8_t *end_ptr = &buffer[filesize]; + for (int i = 0; i < NUM_BUILDINGS; i++) { + ptr += index_of(ptr, '{', filesize); + + ptr = get_value(ptr, end_ptr, &buildings[i].cost); + ptr = get_value(ptr, end_ptr, &buildings[i].desirability_value); + ptr = get_value(ptr, end_ptr, &buildings[i].desirability_step); + ptr = get_value(ptr, end_ptr, &buildings[i].desirability_step_size); + ptr = get_value(ptr, end_ptr, &buildings[i].desirability_range); + ptr = get_value(ptr, end_ptr, &buildings[i].laborers); + ptr = get_value(ptr, end_ptr, &dummy); + ptr = get_value(ptr, end_ptr, &dummy); + } + + ptr = &buffer[index_of_string(buffer, ALL_HOUSES, filesize)]; + + for (int i = 0; i < NUM_HOUSES; i++) { + ptr += index_of(ptr, '{', filesize); + + ptr = get_value(ptr, end_ptr, &houses[i].devolve_desirability); + ptr = get_value(ptr, end_ptr, &houses[i].evolve_desirability); + ptr = get_value(ptr, end_ptr, &houses[i].entertainment); + ptr = get_value(ptr, end_ptr, &houses[i].water); + ptr = get_value(ptr, end_ptr, &houses[i].religion); + ptr = get_value(ptr, end_ptr, &houses[i].education); + ptr = get_value(ptr, end_ptr, &dummy); + ptr = get_value(ptr, end_ptr, &houses[i].barber); + ptr = get_value(ptr, end_ptr, &houses[i].bathhouse); + ptr = get_value(ptr, end_ptr, &houses[i].health); + ptr = get_value(ptr, end_ptr, &houses[i].food_types); + ptr = get_value(ptr, end_ptr, &houses[i].pottery); + ptr = get_value(ptr, end_ptr, &houses[i].oil); + ptr = get_value(ptr, end_ptr, &houses[i].furniture); + ptr = get_value(ptr, end_ptr, &houses[i].wine); + ptr = get_value(ptr, end_ptr, &dummy); + ptr = get_value(ptr, end_ptr, &dummy); + ptr = get_value(ptr, end_ptr, &houses[i].prosperity); + ptr = get_value(ptr, end_ptr, &houses[i].max_people); + ptr = get_value(ptr, end_ptr, &houses[i].tax_multiplier); + } + + override_model_data(); + + log_info("Model loaded", 0, 0); + free(buffer); + return 1; +} + +const model_building MODEL_ROADBLOCK = { 12,0,0,0,0 }; +const model_building MODEL_WORK_CAMP = { 150,-10,2,3,4,20 }; +const model_building MODEL_ARCHITECT_GUILD = { 200,-8,1,2,4,12 }; +const model_building MODEL_GRAND_TEMPLE_CERES = { 2500,20,2,-4,5,50 }; +const model_building MODEL_GRAND_TEMPLE_NEPTUNE = { 2500,20,2,-4,5,50 }; +const model_building MODEL_GRAND_TEMPLE_MERCURY = { 2500,20,2,-4,5,50 }; +const model_building MODEL_GRAND_TEMPLE_MARS = { 2500,20,2,-4,5,50 }; +const model_building MODEL_GRAND_TEMPLE_VENUS = { 2500,20,2,-4,5,50 }; +const model_building MODEL_PANTHEON = { 3500,20,2,-4,5,50 }; +const model_building MODEL_LIGHTHOUSE = { 1000,6,1,-1,4,20 }; +const model_building MODEL_MESS_HALL = { 100,-8,1,2,4,10 }; +const model_building MODEL_TAVERN = { 40,-2,1,1,6,8 }; +const model_building MODEL_GRAND_GARDEN = { 400,0,0,0,0,0 }; +const model_building MODEL_ARENA = { 500,-3,1,1,3,25 }; +const model_building MODEL_COLOSSEUM = { 1500,-3,1,1,3,100 }; +const model_building MODEL_HIPPODROME = { 3500,-3,1,1,3,150 }; +const model_building MODEL_NULL = { 0,0,0,0,0 }; +const model_building MODEL_LARARIUM = { 45, 4, 1, -1, 3, 0 }; +const model_building MODEL_NYMPHAEUM = { 400,12,2,-1,6,0 }; +const model_building MODEL_SMALL_MAUSOLEUM = { 250,-8,1,3,5,0 }; +const model_building MODEL_LARGE_MAUSOLEUM = { 500,-10,1,3,6,0 }; +const model_building MODEL_WATCHTOWER = { 100,-6,1,2,3,8, }; +const model_building MODEL_CARAVANSERAI = { 500,-10,2,3,4,20 }; +const model_building MODEL_PALISADE = { 6,0,0,0,0,0 }; +const model_building MODEL_HIGHWAY = { 100,-4,1,2,3,0 }; +const model_building MODEL_GOLD_MINE = { 100,-6,1,1,4,30 }; +const model_building MODEL_STONE_QUARRY = { 60,-6,1,1,4,10 }; +const model_building MODEL_SAND_PIT = { 40,-6,1,1,4,10 }; +const model_building MODEL_BRICKWORKS = { 80,-3,1,1,4,10 }; +const model_building MODEL_CONCRETE_MAKER = { 60,-3,1,1,4,10 }; +const model_building MODEL_CITY_MINT = { 250,-3,1,1,3,40 }; +const model_building MODEL_DEPOT = { 100,-3,1,1,2,15 }; +const model_building MODEL_ARMOURY = { 50,-5,1,1,4,6 }; + +const model_building *model_get_building(building_type type) +{ + switch (type) { + case BUILDING_ROADBLOCK: + case BUILDING_ROOFED_GARDEN_WALL_GATE: + case BUILDING_LOOPED_GARDEN_GATE: + case BUILDING_PANELLED_GARDEN_GATE: + case BUILDING_HEDGE_GATE_DARK: + case BUILDING_HEDGE_GATE_LIGHT: + return &MODEL_ROADBLOCK; + case BUILDING_WORKCAMP: + return &MODEL_WORK_CAMP; + case BUILDING_ARCHITECT_GUILD: + return &MODEL_ARCHITECT_GUILD; + case BUILDING_GRAND_TEMPLE_CERES: + return &MODEL_GRAND_TEMPLE_CERES; + case BUILDING_GRAND_TEMPLE_NEPTUNE: + return &MODEL_GRAND_TEMPLE_NEPTUNE; + case BUILDING_GRAND_TEMPLE_MERCURY: + return &MODEL_GRAND_TEMPLE_MERCURY; + case BUILDING_GRAND_TEMPLE_MARS: + return &MODEL_GRAND_TEMPLE_MARS; + case BUILDING_GRAND_TEMPLE_VENUS: + return &MODEL_GRAND_TEMPLE_VENUS; + case BUILDING_PANTHEON: + return &MODEL_PANTHEON; + case BUILDING_MESS_HALL: + return &MODEL_MESS_HALL; + case BUILDING_LIGHTHOUSE: + return &MODEL_LIGHTHOUSE; + case BUILDING_TAVERN: + return &MODEL_TAVERN; + case BUILDING_GRAND_GARDEN: + return &MODEL_GRAND_GARDEN; + case BUILDING_ARENA: + return &MODEL_ARENA; + case BUILDING_COLOSSEUM: + return &MODEL_COLOSSEUM; + case BUILDING_HIPPODROME: + return &MODEL_HIPPODROME; + case BUILDING_LARARIUM: + case BUILDING_SHRINE_CERES: + case BUILDING_SHRINE_MARS: + case BUILDING_SHRINE_MERCURY: + case BUILDING_SHRINE_NEPTUNE: + case BUILDING_SHRINE_VENUS: + return &MODEL_LARARIUM; + case BUILDING_NYMPHAEUM: + return &MODEL_NYMPHAEUM; + case BUILDING_WATCHTOWER: + return &MODEL_WATCHTOWER; + case BUILDING_SMALL_MAUSOLEUM: + return &MODEL_SMALL_MAUSOLEUM; + case BUILDING_LARGE_MAUSOLEUM: + return &MODEL_LARGE_MAUSOLEUM; + case BUILDING_CARAVANSERAI: + return &MODEL_CARAVANSERAI; + case BUILDING_PALISADE: + case BUILDING_PALISADE_GATE: + return &MODEL_PALISADE; + case BUILDING_HIGHWAY: + return &MODEL_HIGHWAY; + case BUILDING_GOLD_MINE: + return &MODEL_GOLD_MINE; + case BUILDING_STONE_QUARRY: + return &MODEL_STONE_QUARRY; + case BUILDING_SAND_PIT: + return &MODEL_SAND_PIT; + case BUILDING_BRICKWORKS: + return &MODEL_BRICKWORKS; + case BUILDING_CONCRETE_MAKER: + return &MODEL_CONCRETE_MAKER; + case BUILDING_CITY_MINT: + return &MODEL_CITY_MINT; + case BUILDING_DEPOT: + return &MODEL_DEPOT; + case BUILDING_OVERGROWN_GARDENS: + return &buildings[BUILDING_GARDENS]; + case BUILDING_ARMOURY: + return &MODEL_ARMOURY; + default: + break; + } + + if ((type >= BUILDING_PINE_TREE && type <= BUILDING_SENATOR_STATUE) || + type == BUILDING_HEDGE_DARK || type == BUILDING_HEDGE_LIGHT || + type == BUILDING_DECORATIVE_COLUMN || type == BUILDING_LOOPED_GARDEN_WALL || + type == BUILDING_COLONNADE || type == BUILDING_LOOPED_GARDEN_WALL || + type == BUILDING_ROOFED_GARDEN_WALL || type == BUILDING_GARDEN_PATH || + type == BUILDING_PANELLED_GARDEN_WALL || type == BUILDING_GLADIATOR_STATUE) { + return &buildings[BUILDING_SMALL_STATUE]; + } + + if (type == BUILDING_SMALL_POND || type == BUILDING_OBELISK || + type == BUILDING_LEGION_STATUE || type == BUILDING_DOLPHIN_FOUNTAIN) { + return &buildings[BUILDING_MEDIUM_STATUE]; + } + + if (type == BUILDING_LARGE_POND || type == BUILDING_HORSE_STATUE) { + return &buildings[BUILDING_LARGE_STATUE]; + } + + if (type == BUILDING_FORT_AUXILIA_INFANTRY || type == BUILDING_FORT_ARCHERS) { + return &buildings[BUILDING_FORT_LEGIONARIES]; + } + + if (type > 129) { + return &MODEL_NULL; + } else { + return &buildings[type]; + } +} + +const model_house *model_get_house(house_level level) +{ + return &houses[level]; +} + +int model_house_uses_inventory(house_level level, resource_type inventory) +{ + const model_house *house = model_get_house(level); + switch (inventory) { + case RESOURCE_WINE: + return house->wine; + case RESOURCE_OIL: + return house->oil; + case RESOURCE_FURNITURE: + return house->furniture; + case RESOURCE_POTTERY: + return house->pottery; + default: + return 0; + } +} diff --git a/src/building/model.h b/src/building/model.h new file mode 100644 index 0000000000..bb6ff0381f --- /dev/null +++ b/src/building/model.h @@ -0,0 +1,70 @@ +#ifndef BUILDING_MODEL_H +#define BUILDING_MODEL_H + +#include "building/type.h" +#include "city/resource.h" + +/** + * @file + * Building models. + */ + +/** + * Building model + */ +typedef struct { + int cost; /**< Cost of structure or of one tile of a structure (for walls) */ + int desirability_value; /**< Initial desirability value */ + int desirability_step; /**< Desirability step (in tiles) */ + int desirability_step_size; /**< Desirability step size */ + int desirability_range; /**< Max desirability range */ + int laborers; /**< Number of people a building employs */ +} model_building; + +/** + * House model + */ +typedef struct { + int devolve_desirability; /**< Desirability at which the house devolves */ + int evolve_desirability; /**< Desirability at which the house evolves */ + int entertainment; /**< Entertainment points required */ + int water; /**< Water required: 1 = well, 2 = fountain */ + int religion; /**< Number of gods required */ + int education; /**< Education required: + 1 = school or library, 2 = school and library, 3 = school, library and academy */ + int barber; /**< Barber required (boolean) */ + int bathhouse; /**< Bathhouse required (boolean) */ + int health; /**< Health required: 1 = doctor or hospital, 2 = doctor and hospital */ + int food_types; /**< Number of food types required */ + int pottery; /**< Pottery required */ + int oil; /**< Oil required */ + int furniture; /**< Furniture required */ + int wine; /**< Wine types required: 1 = any wine, 2 = two types of wine */ + int prosperity; /**< Prosperity contribution */ + int max_people; /**< Maximum people per tile (medium insula and lower) or per house (large insula and up) */ + int tax_multiplier; /**< Tax rate multiplier */ +} model_house; + +/** + * Loads the model from file + * @return boolean true if model was loaded successfully, false otherwise + */ +int model_load(void); + +/** + * Gets the model for a building + * @param type Building type + * @return Read-only model + */ +const model_building *model_get_building(building_type type); + +/** + * Gets the model for a house + * @param level House level + * @return Read-only model + */ +const model_house *model_get_house(house_level level); + +int model_house_uses_inventory(house_level level, resource_type inventory); + +#endif // BUILDING_MODEL_H diff --git a/src/empire/xml.c b/src/empire/xml.c new file mode 100644 index 0000000000..8635d0354d --- /dev/null +++ b/src/empire/xml.c @@ -0,0 +1,848 @@ +#include "xml.h" + +#include "assets/assets.h" +#include "core/array.h" +#include "core/buffer.h" +#include "core/calc.h" +#include "core/file.h" +#include "core/image_group.h" +#include "core/image_group_editor.h" +#include "core/log.h" +#include "core/string.h" +#include "core/xml_parser.h" +#include "core/zlib_helper.h" +#include "editor/editor.h" +#include "empire/city.h" +#include "empire/empire.h" +#include "empire/object.h" +#include "empire/trade_route.h" +#include "scenario/data.h" +#include "scenario/empire.h" + +#include +#include +#include + +#define XML_TOTAL_ELEMENTS 19 +#define BASE_BORDER_FLAG_IMAGE_ID 3323 +#define BASE_ORNAMENT_IMAGE_ID 3356 +#define BORDER_EDGE_DEFAULT_SPACING 50 +#define ORIGINAL_ORNAMENTS 20 + +typedef enum { + LIST_NONE = -1, + LIST_BUYS = 1, + LIST_SELLS = 2, + LIST_TRADE_WAYPOINTS = 3 +} city_list; + +typedef enum { + DISTANT_BATTLE_PATH_NONE, + DISTANT_BATTLE_PATH_ROMAN, + DISTANT_BATTLE_PATH_ENEMY +} distant_battle_path_type; + +enum { + BORDER_STATUS_NONE = 0, + BORDER_STATUS_CREATING, + BORDER_STATUS_DONE +}; + +typedef struct { + int x; + int y; + int num_months; +} waypoint; + +static const char *ORNAMENTS[] = { + "The Stonehenge", + "Gallic Wheat", + "The Pyrenees", + "Iberian Aqueduct", + "Triumphal Arch", + "West Desert Wheat", + "Lighthouse of Alexandria", + "West Desert Palm Trees", + "Trade Ship", + "Waterside Palm Trees", + "Colosseum|The Colosseum", + "The Alps", + "Roman Tree", + "Greek Mountain Range", + "The Parthenon", + "The Pyramids", + "The Hagia Sophia", + "East Desert Palm Trees", + "East Desert Wheat", + "Trade Camel", + "Mount Etna", + "Colossus of Rhodes" +}; + +#define TOTAL_ORNAMENTS (sizeof(ORNAMENTS) / sizeof(const char *)) + +map_point ORNAMENT_POSITIONS[TOTAL_ORNAMENTS] = { + { 247, 81 }, { 361, 356 }, { 254, 428 }, { 199, 590 }, { 275, 791 }, + { 423, 802 }, { 1465, 883 }, { 518, 764 }, { 691, 618 }, { 742, 894 }, + { 726, 468 }, { 502, 280 }, { 855, 551 }, { 1014, 443 }, { 1158, 698 }, + { 1431, 961 }, { 1300, 500 }, { 1347, 648 }, { 1707, 783 }, { 1704, 876 }, + { 829, 720 }, { 1347, 745 } +}; + +static struct { + int success; + int version; + int current_city_id; + city_list current_city_list; + int has_vulnerable_city; + int current_invasion_path_id; + array(int) invasion_path_ids; + distant_battle_path_type distant_battle_path_type; + array(waypoint) distant_battle_waypoints; + int border_status; + char added_ornaments[TOTAL_ORNAMENTS]; +} data; + +static int xml_start_empire(void); +static int xml_start_map(void); +static int xml_start_coords(void); +static int xml_start_ornament(void); +static int xml_start_border(void); +static int xml_start_border_edge(void); +static int xml_start_city(void); +static int xml_start_buys(void); +static int xml_start_sells(void); +static int xml_start_waypoints(void); +static int xml_start_resource(void); +static int xml_start_trade_point(void); +static int xml_start_invasion_path(void); +static int xml_start_battle(void); +static int xml_start_distant_battle_path(void); +static int xml_start_distant_battle_waypoint(void); + +static void xml_end_border(void); +static void xml_end_city(void); +static void xml_end_sells_buys_or_waypoints(void); +static void xml_end_invasion_path(void); +static void xml_end_distant_battle_path(void); + +static const xml_parser_element xml_elements[XML_TOTAL_ELEMENTS] = { + { "empire", xml_start_empire }, + { "map", xml_start_map, 0, "empire" }, + { "coordinates", xml_start_coords, 0, "map" }, + { "ornament", xml_start_ornament, 0, "empire|map" }, + { "border", xml_start_border, xml_end_border, "empire" }, + { "edge", xml_start_border_edge, 0, "border" }, + { "cities", 0, 0, "empire" }, + { "city", xml_start_city, xml_end_city, "cities" }, + { "buys", xml_start_buys, xml_end_sells_buys_or_waypoints, "city" }, + { "sells", xml_start_sells, xml_end_sells_buys_or_waypoints, "city" }, + { "resource", xml_start_resource, 0, "buys|sells" }, + { "trade_points", xml_start_waypoints, xml_end_sells_buys_or_waypoints, "city" }, + { "point", xml_start_trade_point, 0, "trade_points" }, + { "invasion_paths", 0, 0, "empire" }, + { "path", xml_start_invasion_path, xml_end_invasion_path, "invasion_paths"}, + { "battle", xml_start_battle, 0, "path"}, + { "distant_battle_paths", 0, 0, "empire" }, + { "path", xml_start_distant_battle_path, xml_end_distant_battle_path, "distant_battle_paths" }, + { "waypoint", xml_start_distant_battle_waypoint, 0, "path" }, +}; + +static resource_type get_resource_from_attr(const char *key) +{ + const char *value = xml_parser_get_attribute_string(key); + if (!value) { + return RESOURCE_NONE; + } + for (resource_type i = RESOURCE_MIN; i < RESOURCE_MAX; i++) { + const char *resource_name = resource_get_data(i)->xml_attr_name; + if (xml_parser_compare_multiple(resource_name, value)) { + return i; + } + } + return RESOURCE_NONE; +} + +static int xml_start_empire(void) +{ + data.version = xml_parser_get_attribute_int("version"); + if (!data.version) { + data.success = 0; + log_error("No version set", 0, 0); + return 0; + } + if (data.version == 1 && xml_parser_get_attribute_bool("show_ireland")) { + full_empire_object *obj = empire_object_get_new(); + obj->in_use = 1; + obj->obj.type = EMPIRE_OBJECT_ORNAMENT; + obj->obj.image_id = -1; + } + return 1; +} + +static int xml_start_map(void) +{ + if (data.version < 2) { + log_info("Custom maps only work on version 2 and higher", 0, 0); + return 1; + } + const char *filename = xml_parser_get_attribute_string("image"); + int x_offset = xml_parser_get_attribute_int("x_offset"); + int y_offset = xml_parser_get_attribute_int("y_offset"); + int width = xml_parser_get_attribute_int("width"); + int height = xml_parser_get_attribute_int("height"); + + empire_set_custom_map(filename, x_offset, y_offset, width, height); + + if (xml_parser_get_attribute_bool("show_ireland")) { + if (empire_get_image_id() != image_group(editor_is_active() ? GROUP_EDITOR_EMPIRE_MAP : GROUP_EMPIRE_MAP)) { + log_info("Ireland image cannot be enabled on custom maps", 0, 0); + return 1; + } + full_empire_object *obj = empire_object_get_new(); + obj->in_use = 1; + obj->obj.type = EMPIRE_OBJECT_ORNAMENT; + obj->obj.image_id = -1; + } + + return 1; +} + +static int xml_start_coords(void) +{ + int relative = xml_parser_get_attribute_bool("relative"); + int x_offset = xml_parser_get_attribute_int("x_offset"); + int y_offset = xml_parser_get_attribute_int("y_offset"); + + empire_set_coordinates(relative, x_offset, y_offset); + return 1; +} + +static void add_ornament(int ornament_id) +{ + if (data.added_ornaments[ornament_id]) { + return; + } + data.added_ornaments[ornament_id] = 1; + full_empire_object *obj = empire_object_get_new(); + if (!obj) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return; + } + obj->in_use = 1; + obj->obj.type = EMPIRE_OBJECT_ORNAMENT; + if (ornament_id < ORIGINAL_ORNAMENTS) { + obj->obj.image_id = BASE_ORNAMENT_IMAGE_ID + ornament_id; + } else { + obj->obj.image_id = ORIGINAL_ORNAMENTS - ornament_id - 2; + } + obj->obj.x = ORNAMENT_POSITIONS[ornament_id].x; + obj->obj.y = ORNAMENT_POSITIONS[ornament_id].y; +} + +static int xml_start_ornament(void) +{ + const char *parent_name = xml_parser_get_parent_element_name(); + if (data.version >= 2 && parent_name && strcmp(parent_name, "empire") == 0) { + log_info("Ornaments should go inside the map tag on version 2 and later", 0, 0); + return 1; + } + if (empire_get_image_id() != image_group(editor_is_active() ? GROUP_EDITOR_EMPIRE_MAP : GROUP_EMPIRE_MAP)) { + log_info("Ornaments are not shown on custom maps", 0, 0); + return 1; + } + if (!xml_parser_has_attribute("type")) { + log_info("No ornament type specified", 0, 0); + return 1; + } + int ornament_id = xml_parser_get_attribute_enum("type", ORNAMENTS, TOTAL_ORNAMENTS, 0); + if (ornament_id == -1) { + if (strcmp("all", xml_parser_get_attribute_string("type")) == 0) { + for (int i = 0; i < (int) TOTAL_ORNAMENTS; i++) { + add_ornament(i); + } + } else { + log_info("Invalid ornament type specified", 0, 0); + } + } else { + add_ornament(ornament_id); + } + return 1; +} + +static int xml_start_border(void) +{ + if (data.border_status != BORDER_STATUS_NONE) { + data.success = 0; + log_error("Border is being set twice", 0, 0); + return 0; + } + full_empire_object *obj = empire_object_get_new(); + if (!obj) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + obj->in_use = 1; + obj->obj.type = EMPIRE_OBJECT_BORDER; + obj->obj.width = xml_parser_get_attribute_int("density"); + if (obj->obj.width == 0) { + obj->obj.width = BORDER_EDGE_DEFAULT_SPACING; + } + data.border_status = BORDER_STATUS_CREATING; + return 1; +} + +static int xml_start_border_edge(void) +{ + if (data.border_status != BORDER_STATUS_CREATING) { + data.success = 0; + log_error("Border edge is being wrongly added", 0, 0); + return 0; + } + full_empire_object *obj = empire_object_get_new(); + if (!obj) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + obj->in_use = 1; + obj->obj.type = EMPIRE_OBJECT_BORDER_EDGE; + obj->obj.x = xml_parser_get_attribute_int("x"); + obj->obj.y = xml_parser_get_attribute_int("y"); + empire_transform_coordinates(&obj->obj.x, &obj->obj.y); + obj->obj.image_id = xml_parser_get_attribute_bool("hidden") ? 0 : BASE_BORDER_FLAG_IMAGE_ID; + + return 1; +} + +static int xml_start_city(void) +{ + full_empire_object *city_obj = empire_object_get_new(); + + if (!city_obj) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + + data.current_city_id = city_obj->obj.id; + city_obj->in_use = 1; + city_obj->obj.type = EMPIRE_OBJECT_CITY; + city_obj->city_type = EMPIRE_CITY_TRADE; + city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_TRADE); + city_obj->trade_route_cost = 500; + + static const char *city_types[6] = { "roman", "ours", "trade", "future_trade", "distant", "vulnerable" }; + static const char *trade_route_types[2] = { "land", "sea" }; + + const char *name = xml_parser_get_attribute_string("name"); + if (name) { + string_copy(string_from_ascii(name), city_obj->city_custom_name, sizeof(city_obj->city_custom_name)); + } + + int city_type = xml_parser_get_attribute_enum("type", city_types, 6, EMPIRE_CITY_DISTANT_ROMAN); + if (city_type < EMPIRE_CITY_DISTANT_ROMAN) { + city_obj->city_type = EMPIRE_CITY_TRADE; + } else { + city_obj->city_type = city_type; + } + switch (city_obj->city_type) { + case EMPIRE_CITY_OURS: + city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY); + break; + case EMPIRE_CITY_FUTURE_TRADE: + case EMPIRE_CITY_DISTANT_ROMAN: + city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_DISTANT_ROMAN); + break; + case EMPIRE_CITY_DISTANT_FOREIGN: + city_obj->obj.image_id = image_group(GROUP_EMPIRE_FOREIGN_CITY); + break; + case EMPIRE_CITY_VULNERABLE_ROMAN: + city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_DISTANT_ROMAN); + data.has_vulnerable_city = 1; + break; + default: + city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_TRADE); + break; + } + + const image *img = image_get(city_obj->obj.image_id); + + city_obj->obj.width = img->width; + city_obj->obj.height = img->height; + + city_obj->obj.x = xml_parser_get_attribute_int("x") - city_obj->obj.width / 2; + city_obj->obj.y = xml_parser_get_attribute_int("y") - city_obj->obj.height / 2; + empire_transform_coordinates(&city_obj->obj.x, &city_obj->obj.y); + + if (city_obj->city_type == EMPIRE_CITY_TRADE || city_obj->city_type == EMPIRE_CITY_FUTURE_TRADE) { + full_empire_object *route_obj = empire_object_get_new(); + if (!route_obj) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + route_obj->in_use = 1; + route_obj->obj.type = EMPIRE_OBJECT_LAND_TRADE_ROUTE; + + route_obj->obj.type = xml_parser_get_attribute_enum("trade_route_type", + trade_route_types, 2, EMPIRE_OBJECT_LAND_TRADE_ROUTE); + if (route_obj->obj.type < EMPIRE_OBJECT_LAND_TRADE_ROUTE) { + route_obj->obj.type = EMPIRE_OBJECT_LAND_TRADE_ROUTE; + } + if (route_obj->obj.type == EMPIRE_OBJECT_SEA_TRADE_ROUTE) { + route_obj->obj.image_id = image_group(GROUP_EMPIRE_TRADE_ROUTE_TYPE); + } else { + route_obj->obj.image_id = image_group(GROUP_EMPIRE_TRADE_ROUTE_TYPE) + 1; + } + + city_obj->trade_route_cost = xml_parser_get_attribute_int("trade_route_cost"); + if (!city_obj->trade_route_cost) { + city_obj->trade_route_cost = 500; + } + } + + return 1; +} + +static int xml_start_buys(void) +{ + data.current_city_list = LIST_BUYS; + return 1; +} + +static int xml_start_sells(void) +{ + data.current_city_list = LIST_SELLS; + return 1; +} + +static int xml_start_waypoints(void) +{ + data.current_city_list = LIST_TRADE_WAYPOINTS; + return 1; +} + +static int xml_start_resource(void) +{ + if (data.current_city_id == -1) { + data.success = 0; + log_error("No active city when parsing resource", 0, 0); + return 0; + } else if (data.current_city_list != LIST_BUYS && data.current_city_list != LIST_SELLS) { + data.success = 0; + log_error("Resource not in buy or sell tag", 0, 0); + return 0; + } + + full_empire_object *city_obj = empire_object_get_full(data.current_city_id); + + if (!xml_parser_has_attribute("type")) { + data.success = 0; + log_error("Unable to find resource type attribute", 0, 0); + return 0; + } + resource_type resource = get_resource_from_attr("type"); + if (resource == RESOURCE_NONE) { + data.success = 0; + log_error("Unable to determine resource type", xml_parser_get_attribute_string("type"), 0); + return 0; + } + + int amount = xml_parser_has_attribute("amount") ? + xml_parser_get_attribute_int("amount") : 1; + + if (data.current_city_list == LIST_BUYS) { + city_obj->city_buys_resource[resource] = amount; + } else if (data.current_city_list == LIST_SELLS) { + city_obj->city_sells_resource[resource] = amount; + } + + return 1; +} + +static int xml_start_trade_point(void) +{ + if (data.current_city_id == -1) { + data.success = 0; + log_error("No active city when parsing trade point", 0, 0); + return 0; + } else if (data.current_city_list != LIST_TRADE_WAYPOINTS) { + data.success = 0; + log_error("Trade point not trade_points tag", 0, 0); + return 0; + } else if (!empire_object_get_full(data.current_city_id)->trade_route_cost) { + data.success = 0; + log_error("Attempting to parse trade point in a city that can't trade", 0, 0); + return 0; + } + + full_empire_object *obj = empire_object_get_new(); + if (!obj) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + obj->in_use = 1; + obj->obj.type = EMPIRE_OBJECT_TRADE_WAYPOINT; + obj->obj.x = xml_parser_get_attribute_int("x"); + obj->obj.y = xml_parser_get_attribute_int("y"); + empire_transform_coordinates(&obj->obj.x, &obj->obj.y); + + return 1; +} + +static int xml_start_invasion_path(void) +{ + data.current_invasion_path_id++; + return 1; +} + +static int xml_start_battle(void) +{ + if (!data.current_invasion_path_id) { + data.success = 0; + log_error("Battle not in path tag", 0, 0); + return 0; + } + int *battle_id = array_advance(data.invasion_path_ids); + if (!battle_id) { + data.success = 0; + log_error("Error creating invasion path - out of memory", 0, 0); + return 0; + } + + full_empire_object *battle_obj = empire_object_get_new(); + if (!battle_obj) { + data.invasion_path_ids.size--; + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + *battle_id = battle_obj->obj.id; + battle_obj->in_use = 1; + battle_obj->obj.type = EMPIRE_OBJECT_BATTLE_ICON; + battle_obj->obj.invasion_path_id = data.current_invasion_path_id; + battle_obj->obj.image_id = image_group(GROUP_EMPIRE_BATTLE); + battle_obj->obj.x = xml_parser_get_attribute_int("x"); + battle_obj->obj.y = xml_parser_get_attribute_int("y"); + empire_transform_coordinates(&battle_obj->obj.x, &battle_obj->obj.y); + + return 1; +} + +static int xml_start_distant_battle_path(void) +{ + if (!data.has_vulnerable_city) { + data.success = 0; + log_error("Must have a vulnerable city to set up distant battle paths", 0, 0); + return 0; + } else if (!xml_parser_has_attribute("type")) { + data.success = 0; + log_error("Unable to find type attribute on distant battle path", 0, 0); + return 0; + } else if (!xml_parser_has_attribute("start_x")) { + data.success = 0; + log_error("Unable to find start_x attribute on distant battle path", 0, 0); + return 0; + } else if (!xml_parser_has_attribute("start_y")) { + data.success = 0; + log_error("Unable to find start_y attribute on distant battle path", 0, 0); + return 0; + } + + const char *type = xml_parser_get_attribute_string("type"); + if (strcmp(type, "roman") == 0) { + data.distant_battle_path_type = DISTANT_BATTLE_PATH_ROMAN; + } else if (strcmp(type, "enemy") == 0) { + data.distant_battle_path_type = DISTANT_BATTLE_PATH_ENEMY; + } else { + data.success = 0; + log_error("Distant battle path type must be \"roman\" or \"enemy\"", type, 0); + return 0; + } + + waypoint *w = array_advance(data.distant_battle_waypoints); + if (!w) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + w->x = xml_parser_get_attribute_int("start_x"); + w->y = xml_parser_get_attribute_int("start_y"); + empire_transform_coordinates(&w->x, &w->y); + w->num_months = 0; + return 1; +} + +static int xml_start_distant_battle_waypoint(void) +{ + if (!xml_parser_has_attribute("num_months")) { + data.success = 0; + log_error("Unable to find num_months attribute on distant battle path", 0, 0); + return 0; + } else if (!xml_parser_has_attribute("x")) { + data.success = 0; + log_error("Unable to find x attribute on distant battle path", 0, 0); + return 0; + } else if (!xml_parser_has_attribute("y")) { + data.success = 0; + log_error("Unable to find y attribute on distant battle path", 0, 0); + return 0; + } + + waypoint *w = array_advance(data.distant_battle_waypoints); + if (!w) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return 0; + } + w->x = xml_parser_get_attribute_int("x"); + w->y = xml_parser_get_attribute_int("y"); + empire_transform_coordinates(&w->x, &w->y); + w->num_months = xml_parser_get_attribute_int("num_months"); + return 1; +} + +static void xml_end_border(void) +{ + if (data.border_status == BORDER_STATUS_CREATING) { + data.border_status = BORDER_STATUS_DONE; + } +} + +static void xml_end_city(void) +{ + data.current_city_id = -1; + data.current_city_list = LIST_NONE; +} + +static void xml_end_sells_buys_or_waypoints(void) +{ + data.current_city_list = LIST_NONE; +} + +static void xml_end_invasion_path(void) +{ + for (int i = data.invasion_path_ids.size - 1; i >= 0; i--) { + full_empire_object *battle = empire_object_get_full(*array_item(data.invasion_path_ids, i)); + battle->obj.invasion_years = i + 1; + } + data.invasion_path_ids.size = 0; +} + +static void xml_end_distant_battle_path(void) +{ + empire_object_type obj_type = 0; + int image_id = 0; + if (data.distant_battle_path_type == DISTANT_BATTLE_PATH_ROMAN) { + obj_type = EMPIRE_OBJECT_ROMAN_ARMY; + image_id = GROUP_EMPIRE_ROMAN_ARMY; + } else if (data.distant_battle_path_type == DISTANT_BATTLE_PATH_ENEMY) { + obj_type = EMPIRE_OBJECT_ENEMY_ARMY; + image_id = GROUP_EMPIRE_ENEMY_ARMY; + } else { + data.success = 0; + log_error("Invalid distant battle path type", 0, data.distant_battle_path_type); + return; + } + + int month = 1; + for (int i = 1; i < data.distant_battle_waypoints.size; i++) { + waypoint *last = array_item(data.distant_battle_waypoints, i - 1); + waypoint *current = array_item(data.distant_battle_waypoints, i); + int x_diff = current->x - last->x; + int y_diff = current->y - last->y; + for (int j = 0; j < current->num_months; j++) { + full_empire_object *army_obj = empire_object_get_new(); + if (!army_obj) { + data.success = 0; + log_error("Error creating new object - out of memory", 0, 0); + return; + } + army_obj->in_use = 1; + army_obj->obj.type = obj_type; + army_obj->obj.image_id = image_group(image_id); + army_obj->obj.x = (int) ((double) j / current->num_months * x_diff + last->x); + army_obj->obj.y = (int) ((double)j / current->num_months * y_diff + last->y); + army_obj->obj.distant_battle_travel_months = month; + month++; + } + } + data.distant_battle_path_type = DISTANT_BATTLE_PATH_NONE; + data.distant_battle_waypoints.size = 0; +} + +static void reset_data(void) +{ + data.success = 1; + data.current_city_id = -1; + data.current_city_list = LIST_NONE; + data.has_vulnerable_city = 0; + data.current_invasion_path_id = 0; + array_init(data.invasion_path_ids, 10, 0, 0); + data.distant_battle_path_type = DISTANT_BATTLE_PATH_NONE; + array_init(data.distant_battle_waypoints, 50, 0, 0); + data.border_status = BORDER_STATUS_NONE; + memset(data.added_ornaments, 0, sizeof(data.added_ornaments)); +} + +static void set_trade_coords(const empire_object *our_city) +{ + int *section_distances = 0; + for (int i = 0; i < empire_object_count(); i++) { + full_empire_object *trade_city = empire_object_get_full(i); + if ( + !trade_city->in_use || + trade_city->obj.type != EMPIRE_OBJECT_CITY || + trade_city->city_type == EMPIRE_CITY_OURS || + (trade_city->city_type != EMPIRE_CITY_TRADE && trade_city->city_type != EMPIRE_CITY_FUTURE_TRADE) + ) { + continue; + } + empire_object *trade_route = empire_object_get(i + 1); + + if (!section_distances) { + section_distances = malloc(sizeof(int) * (empire_object_count() - 1)); + } + int sections = 0; + int distance = 0; + int last_x = our_city->x + 25; + int last_y = our_city->y + 25; + int x_diff, y_diff; + for (int j = i + 2; j < empire_object_count(); j++) { + empire_object *obj = empire_object_get(j); + if (obj->type != EMPIRE_OBJECT_TRADE_WAYPOINT) { + break; + } + x_diff = obj->x - last_x; + y_diff = obj->y - last_y; + section_distances[sections] = (int) sqrt(x_diff * x_diff + y_diff * y_diff); + distance += section_distances[sections]; + last_x = obj->x; + last_y = obj->y; + sections++; + } + x_diff = trade_city->obj.x + 25 - last_x; + y_diff = trade_city->obj.y + 25 - last_y; + section_distances[sections] = (int) sqrt(x_diff * x_diff + y_diff * y_diff); + distance += section_distances[sections]; + sections++; + + last_x = our_city->x + 25; + last_y = our_city->y + 25; + int next_x = trade_city->obj.x + 25; + int next_y = trade_city->obj.y + 25; + + if (sections == 1) { + trade_route->x = (next_x + last_x) / 2 - 16; + trade_route->y = (next_y + last_y) / 2 - 10; + continue; + } + int crossed_distance = 0; + int current_section = 0; + int remaining_distance = 0; + while (current_section < sections) { + if (current_section == sections - 1) { + next_x = trade_city->obj.x + 25; + next_y = trade_city->obj.y + 25; + } else { + empire_object *obj = empire_object_get(current_section + i + 2); + next_x = obj->x; + next_y = obj->y; + } + if (section_distances[current_section] + crossed_distance > distance / 2) { + remaining_distance = distance / 2 - crossed_distance; + break; + } + last_x = next_x; + last_y = next_y; + crossed_distance += section_distances[current_section]; + current_section++; + } + x_diff = next_x - last_x; + y_diff = next_y - last_y; + int x_factor = calc_percentage(x_diff, section_distances[current_section]); + int y_factor = calc_percentage(y_diff, section_distances[current_section]); + trade_route->x = calc_adjust_with_percentage(remaining_distance, x_factor) + last_x - 16; + trade_route->y = calc_adjust_with_percentage(remaining_distance, y_factor) + last_y - 10; + + i += sections; // We know the following objects are waypoints so we skip them + } + free(section_distances); +} + +static int parse_xml(char *buf, int buffer_length) +{ + reset_data(); + empire_clear(); + empire_object_clear(); + if (!xml_parser_init(xml_elements, XML_TOTAL_ELEMENTS, 0)) { + return 0; + } + if (!xml_parser_parse(buf, buffer_length, 1)) { + data.success = 0; + } + xml_parser_free(); + if (!data.success) { + return 0; + } + + const empire_object *our_city = empire_object_get_our_city(); + if (!our_city) { + log_error("No home city specified", 0, 0); + return 0; + } + + set_trade_coords(our_city); + empire_object_init_cities(SCENARIO_CUSTOM_EMPIRE); + + return data.success; +} + +static char *file_to_buffer(const char *filename, int *output_length) +{ + FILE *file = file_open(filename, "r"); + if (!file) { + log_error("Error opening empire file", filename, 0); + return 0; + } + fseek(file, 0, SEEK_END); + int size = ftell(file); + rewind(file); + + char *buf = malloc(size); + if (!buf) { + log_error("Unable to allocate buffer to read XML file", filename, 0); + file_close(file); + return 0; + } + memset(buf, 0, size); + *output_length = (int) fread(buf, 1, size, file); + if (*output_length > size) { + log_error("Unable to read file into buffer", filename, 0); + free(buf); + file_close(file); + *output_length = 0; + return 0; + } + file_close(file); + return buf; +} + +int empire_xml_parse_file(const char *filename) +{ + int output_length = 0; + char *xml_contents = file_to_buffer(filename, &output_length); + if (!xml_contents) { + return 0; + } + int success = parse_xml(xml_contents, output_length); + free(xml_contents); + if (!success) { + log_error("Error parsing file", filename, 0); + } + return success; +} diff --git a/src/empire/xml.h b/src/empire/xml.h new file mode 100644 index 0000000000..664907ad15 --- /dev/null +++ b/src/empire/xml.h @@ -0,0 +1,8 @@ +#ifndef EMPIRE_XML_H +#define EMPIRE_XML_H + +#include "core/buffer.h" + +int empire_xml_parse_file(const char *filename); + +#endif // EMPIRE_XML_H diff --git a/src/game/file_editor.c b/src/game/file_editor.c index b19a614770..7a8b923e20 100644 --- a/src/game/file_editor.c +++ b/src/game/file_editor.c @@ -50,6 +50,7 @@ #include "scenario/invasion.h" #include "scenario/map.h" #include "scenario/property.h" +#include "scenario/terrain_generator.h" #include "sound/city.h" #include "sound/music.h" #include "widget/map_editor.h" @@ -127,18 +128,7 @@ static void prepare_map_for_editing(void) figure_create_editor_flags(); figure_create_flotsam(); - map_tiles_update_all_elevation_editor(); - map_tiles_update_all_water(); - map_tiles_update_all_earthquake(); - map_tiles_update_all_rocks(); - map_tiles_update_all_empty_land(); - map_tiles_update_all_meadow(); - map_tiles_update_all_rubble(); - map_tiles_update_all_roads(); - map_tiles_update_all_highways(); - map_tiles_update_all_plazas(); - map_tiles_update_all_walls(); - map_tiles_update_all_aqueducts(0); + map_tiles_update_all(); widget_map_editor_custom_earthquake_request_refresh(); map_natives_init_editor(); map_routing_update_all(); @@ -157,6 +147,15 @@ void game_file_editor_create_scenario(int size) scenario_editor_set_custom_victory_message(0); } +void game_file_editor_create_scenario_generated(int size, int generator_algorithm) +{ + create_blank_map(size); + terrain_generator_generate((terrain_generator_algorithm) generator_algorithm); + prepare_map_for_editing(); + scenario_editor_set_custom_message_introduction(0); + scenario_editor_set_custom_victory_message(0); +} + int game_file_editor_load_scenario(const char *scenario_file) { clear_map_data(); diff --git a/src/game/file_editor.h b/src/game/file_editor.h index 7a78b150b3..542aba9b8b 100644 --- a/src/game/file_editor.h +++ b/src/game/file_editor.h @@ -11,6 +11,7 @@ void game_file_editor_clear_data(void); * @param size Size of the map to create */ void game_file_editor_create_scenario(int size); +void game_file_editor_create_scenario_generated(int size, int generator_algorithm); /** * Load scenario from disk and init it for using in the editor diff --git a/src/game/game.c b/src/game/game.c index 666c3e38fc..89531c732a 100644 --- a/src/game/game.c +++ b/src/game/game.c @@ -183,6 +183,24 @@ int game_init_editor(void) return 1; } +int game_init_editor_generated(int size, int generator_algorithm) +{ + if (!reload_language(1, 0)) { + return 0; + } + + game_file_editor_clear_data(); + game_file_editor_create_scenario_generated(size, generator_algorithm); + + if (city_view_is_sidebar_collapsed()) { + city_view_toggle_sidebar(); + } + + editor_set_active(1); + window_editor_map_show(); + return 1; +} + void game_exit_editor(void) { if (!reload_language(0, 0)) { diff --git a/src/game/game.h b/src/game/game.h index 49feed9734..38eb9af777 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -6,6 +6,7 @@ int game_pre_init(void); int game_init(void); int game_init_editor(void); +int game_init_editor_generated(int size, int generator_algorithm); int game_reload_language(void); diff --git a/src/scenario/event/conditions/comparison_helper.h b/src/scenario/event/conditions/comparison_helper.h new file mode 100644 index 0000000000..f7c81c8c4f --- /dev/null +++ b/src/scenario/event/conditions/comparison_helper.h @@ -0,0 +1,6 @@ +#ifndef COMPARISON_HELPER_H +#define COMPARISON_HELPER_H + +int comparison_helper_compare_values(int compare_type, int value1, int value2); + +#endif // COMPARISON_HELPER_H diff --git a/src/scenario/event/conditions/condition_comparison_helper.c b/src/scenario/event/conditions/condition_comparison_helper.c new file mode 100644 index 0000000000..3b2e2f363d --- /dev/null +++ b/src/scenario/event/conditions/condition_comparison_helper.c @@ -0,0 +1,23 @@ +#include "comparison_helper.h" + +#include "scenario/scenario_event_data.h" + +int comparison_helper_compare_values(int compare_type, int value1, int value2) +{ + switch (compare_type) { + case COMPARISON_TYPE_EQUAL: + return value1 == value2; + case COMPARISON_TYPE_EQUAL_OR_LESS: + return value1 <= value2; + case COMPARISON_TYPE_EQUAL_OR_MORE: + return value1 >= value2; + case COMPARISON_TYPE_NOT_EQUAL: + return value1 != value2; + case COMPARISON_TYPE_LESS_THAN: + return value1 < value2; + case COMPARISON_TYPE_GREATER_THAN: + return value1 > value2; + default: + return 0; + } +} diff --git a/src/scenario/event/conditions/condition_handler.c b/src/scenario/event/conditions/condition_handler.c new file mode 100644 index 0000000000..0b44dd10ec --- /dev/null +++ b/src/scenario/event/conditions/condition_handler.c @@ -0,0 +1,169 @@ +#include "condition_handler.h" + +#include "core/log.h" +#include "game/resource.h" +#include "scenario/condition_types/condition_types.h" + +static int condition_in_use(const scenario_condition_t *condition) +{ + return condition->type != CONDITION_TYPE_UNDEFINED; +} + +void scenario_condition_group_new(scenario_condition_group_t *group, unsigned int id) +{ + // This group has been moved and is not new, no need to setup the array + if (group->type == FULFILLMENT_TYPE_ANY) { + return; + } + group->type = id == 0 ? FULFILLMENT_TYPE_ALL : FULFILLMENT_TYPE_ANY; + if (!array_init(group->conditions, CONDITION_GROUP_ITEMS_ARRAY_SIZE_STEP, 0, condition_in_use)) { + log_error("Unable to allocate enough memory for the scenario condition group. The game will now crash.", 0, 0); + } +} + +int scenario_condition_group_in_use(const scenario_condition_group_t *group) +{ + return group->type == FULFILLMENT_TYPE_ALL || group->conditions.size > 0; +} + +void scenario_condition_type_init(scenario_condition_t *condition) +{ + switch (condition->type) { + case CONDITION_TYPE_TIME_PASSED: + scenario_condition_type_time_init(condition); + break; + default: + break; + } +} + +int scenario_condition_type_is_met(scenario_condition_t *condition) +{ + switch (condition->type) { + case CONDITION_TYPE_BUILDING_COUNT_ACTIVE: + return scenario_condition_type_building_count_active_met(condition); + case CONDITION_TYPE_BUILDING_COUNT_ANY: + return scenario_condition_type_building_count_any_met(condition); + case CONDITION_TYPE_BUILDING_COUNT_AREA: + return scenario_condition_type_building_count_area_met(condition); + case CONDITION_TYPE_CITY_POPULATION: + return scenario_condition_type_city_population_met(condition); + case CONDITION_TYPE_COUNT_OWN_TROOPS: + return scenario_condition_type_count_own_troops_met(condition); + case CONDITION_TYPE_CUSTOM_VARIABLE_CHECK: + return scenario_condition_type_custom_variable_check_met(condition); + case CONDITION_TYPE_DIFFICULTY: + return scenario_condition_type_difficulty_met(condition); + case CONDITION_TYPE_MONEY: + return scenario_condition_type_money_met(condition); + case CONDITION_TYPE_POPS_UNEMPLOYMENT: + return scenario_condition_type_population_unemployed_met(condition); + case CONDITION_TYPE_REQUEST_IS_ONGOING: + return scenario_condition_type_request_is_ongoing_met(condition); + case CONDITION_TYPE_RESOURCE_STORAGE_AVAILABLE: + return scenario_condition_type_resource_storage_available_met(condition); + case CONDITION_TYPE_RESOURCE_STORED_COUNT: + return scenario_condition_type_resource_stored_count_met(condition); + case CONDITION_TYPE_ROME_WAGES: + return scenario_condition_type_rome_wages_met(condition); + case CONDITION_TYPE_SAVINGS: + return scenario_condition_type_savings_met(condition); + case CONDITION_TYPE_STATS_CITY_HEALTH: + return scenario_condition_type_stats_city_health_met(condition); + case CONDITION_TYPE_STATS_CULTURE: + return scenario_condition_type_stats_culture_met(condition); + case CONDITION_TYPE_STATS_FAVOR: + return scenario_condition_type_stats_favor_met(condition); + case CONDITION_TYPE_STATS_PEACE: + return scenario_condition_type_stats_peace_met(condition); + case CONDITION_TYPE_STATS_PROSPERITY: + return scenario_condition_type_stats_prosperity_met(condition); + case CONDITION_TYPE_TIME_PASSED: + return scenario_condition_type_time_met(condition); + case CONDITION_TYPE_TRADE_ROUTE_OPEN: + return scenario_condition_type_trade_route_open_met(condition); + case CONDITION_TYPE_TRADE_ROUTE_PRICE: + return scenario_condition_type_trade_route_price_met(condition); + case CONDITION_TYPE_TRADE_SELL_PRICE: + return scenario_condition_type_trade_sell_price_met(condition); + case CONDITION_TYPE_TAX_RATE: + return scenario_condition_type_tax_rate_met(condition); + default: + // If we cannot figure condition type (such as with deleted conditions) then default to passed. + return 1; + } +} + +void scenario_condition_type_delete(scenario_condition_t *condition) +{ + memset(condition, 0, sizeof(scenario_condition_t)); + condition->type = CONDITION_TYPE_UNDEFINED; +} + +static void save_conditions_in_group(buffer *buf, const scenario_condition_group_t *group) +{ + const scenario_condition_t *condition; + array_foreach(group->conditions, condition) { + buffer_write_i16(buf, condition->type); + buffer_write_i32(buf, condition->parameter1); + buffer_write_i32(buf, condition->parameter2); + buffer_write_i32(buf, condition->parameter3); + buffer_write_i32(buf, condition->parameter4); + buffer_write_i32(buf, condition->parameter5); + } +} + +void scenario_condition_group_save_state(buffer *buf, const scenario_condition_group_t *group, int link_type, + int32_t link_id) +{ + buffer_write_i16(buf, link_type); + buffer_write_u32(buf, link_id); + buffer_write_u8(buf, group->type); + buffer_write_u32(buf, group->conditions.size); + save_conditions_in_group(buf, group); +} + +void scenario_condition_load_state(buffer *buf, scenario_condition_group_t *group, scenario_condition_t *condition) +{ + condition->type = buffer_read_i16(buf); + condition->parameter1 = buffer_read_i32(buf); + condition->parameter2 = buffer_read_i32(buf); + condition->parameter3 = buffer_read_i32(buf); + condition->parameter4 = buffer_read_i32(buf); + condition->parameter5 = buffer_read_i32(buf); + + if (condition->type == CONDITION_TYPE_TRADE_SELL_PRICE) { + condition->parameter1 = resource_remap(condition->parameter1); + } else if (condition->type == CONDITION_TYPE_RESOURCE_STORED_COUNT) { + condition->parameter1 = resource_remap(condition->parameter1); + } else if (condition->type == CONDITION_TYPE_RESOURCE_STORAGE_AVAILABLE) { + condition->parameter1 = resource_remap(condition->parameter1); + } +} + +void scenario_condition_group_load_state(buffer *buf, scenario_condition_group_t *group, + int *link_type, int32_t *link_id) +{ + *link_type = buffer_read_i16(buf); + *link_id = buffer_read_u32(buf); + group->type = buffer_read_u8(buf); + unsigned int total_conditions = buffer_read_u32(buf); + if (!array_init(group->conditions, CONDITION_GROUP_ITEMS_ARRAY_SIZE_STEP, 0, condition_in_use) || + !array_expand(group->conditions, total_conditions)) { + log_error("Unable to create condition group array. The game will now crash.", 0, 0); + } + for (unsigned int i = 0; i < total_conditions; i++) { + scenario_condition_t *condition = array_next(group->conditions); + scenario_condition_load_state(buf, group, condition); + } +} + +int scenario_condition_uses_custom_variable(const scenario_condition_t *condition, int custom_variable_id) +{ + switch (condition->type) { + case CONDITION_TYPE_CUSTOM_VARIABLE_CHECK: + return condition->parameter1 == custom_variable_id; + default: + return 0; + } +} diff --git a/src/scenario/event/conditions/condition_handler.h b/src/scenario/event/conditions/condition_handler.h new file mode 100644 index 0000000000..d548a3102e --- /dev/null +++ b/src/scenario/event/conditions/condition_handler.h @@ -0,0 +1,21 @@ +#ifndef CONDITION_HANDLER_H +#define CONDITION_HANDLER_H + +#include "core/buffer.h" +#include "scenario/scenario_event_data.h" + +void scenario_condition_group_new(scenario_condition_group_t *group, unsigned int id); +int scenario_condition_group_in_use(const scenario_condition_group_t *group); + +void scenario_condition_type_init(scenario_condition_t *condition); +int scenario_condition_type_is_met(scenario_condition_t *condition); + +void scenario_condition_type_delete(scenario_condition_t *condition); +void scenario_condition_group_save_state(buffer *buf, const scenario_condition_group_t *condition_group, int link_type, + int32_t link_id); +void scenario_condition_load_state(buffer *buf, scenario_condition_group_t *group, scenario_condition_t *condition); +void scenario_condition_group_load_state(buffer *buf, scenario_condition_group_t *condition_group, + int *link_type, int32_t *link_id); +int scenario_condition_uses_custom_variable(const scenario_condition_t *condition, int custom_variable_id); + +#endif // CONDITION_HANDLER_H diff --git a/src/scenario/event/conditions/condition_types.c b/src/scenario/event/conditions/condition_types.c new file mode 100644 index 0000000000..6fb59e518c --- /dev/null +++ b/src/scenario/event/conditions/condition_types.c @@ -0,0 +1,471 @@ +#include "condition_types.h" + +#include "building/count.h" +#include "building/type.h" +#include "city/data_private.h" +#include "city/emperor.h" +#include "city/finance.h" +#include "city/health.h" +#include "city/labor.h" +#include "city/military.h" +#include "city/ratings.h" +#include "core/random.h" +#include "empire/city.h" +#include "empire/trade_prices.h" +#include "empire/trade_route.h" +#include "game/settings.h" +#include "game/time.h" +#include "map/grid.h" +#include "scenario/custom_variable.h" +#include "scenario/request.h" +#include "scenario/scenario.h" +#include "scenario/condition_types/comparison_helper.h" + +int scenario_condition_type_building_count_active_met(const scenario_condition_t *condition) +{ + int comparison = condition->parameter1; + int value = condition->parameter2; + building_type type = condition->parameter3; + + int total_active_count = 0; + switch(type) { + case BUILDING_MENU_FARMS: + total_active_count = building_set_count_farms(1); + break; + case BUILDING_MENU_RAW_MATERIALS: + total_active_count = building_set_count_raw_materials(1); + break; + case BUILDING_MENU_WORKSHOPS: + total_active_count = building_set_count_workshops(1); + break; + case BUILDING_MENU_SMALL_TEMPLES: + total_active_count = building_set_count_small_temples(1); + break; + case BUILDING_MENU_LARGE_TEMPLES: + total_active_count = building_set_count_large_temples(1); + break; + case BUILDING_MENU_GRAND_TEMPLES: + total_active_count = building_count_grand_temples_active(); + break; + case BUILDING_MENU_TREES: + total_active_count = building_set_count_deco_trees(); + break; + case BUILDING_MENU_PATHS: + total_active_count = building_set_count_deco_paths(); + break; + case BUILDING_MENU_PARKS: + total_active_count = building_set_count_deco_statues(); + break; + case BUILDING_ANY: + total_active_count = building_count_any_total(1); + break; + case BUILDING_FORT_LEGIONARIES: + total_active_count += building_count_fort_type_total(FIGURE_FORT_LEGIONARY); + break; + case BUILDING_FORT_JAVELIN: + total_active_count += building_count_fort_type_total(FIGURE_FORT_JAVELIN); + break; + case BUILDING_FORT_MOUNTED: + total_active_count += building_count_fort_type_total(FIGURE_FORT_MOUNTED); + break; + default: + total_active_count = building_count_active(type); + break; + } + + return comparison_helper_compare_values(comparison, total_active_count, value); +} + +int scenario_condition_type_building_count_any_met(const scenario_condition_t *condition) +{ + int comparison = condition->parameter1; + int value = condition->parameter2; + building_type type = condition->parameter3; + + int total_active_count = 0; + switch(type) { + case BUILDING_MENU_FARMS: + total_active_count = building_set_count_farms(0); + break; + case BUILDING_MENU_RAW_MATERIALS: + total_active_count = building_set_count_raw_materials(0); + break; + case BUILDING_MENU_WORKSHOPS: + total_active_count = building_set_count_workshops(0); + break; + case BUILDING_MENU_SMALL_TEMPLES: + total_active_count = building_set_count_small_temples(0); + break; + case BUILDING_MENU_LARGE_TEMPLES: + total_active_count = building_set_count_large_temples(0); + break; + case BUILDING_MENU_GRAND_TEMPLES: + total_active_count = building_count_grand_temples(); + break; + case BUILDING_MENU_TREES: + total_active_count = building_set_count_deco_trees(); + break; + case BUILDING_MENU_PATHS: + total_active_count = building_set_count_deco_paths(); + break; + case BUILDING_MENU_PARKS: + total_active_count = building_set_count_deco_statues(); + break; + case BUILDING_ANY: + total_active_count = building_count_any_total(0); + break; + case BUILDING_FORT_LEGIONARIES: + total_active_count += building_count_fort_type_total(FIGURE_FORT_LEGIONARY); + break; + case BUILDING_FORT_JAVELIN: + total_active_count += building_count_fort_type_total(FIGURE_FORT_JAVELIN); + break; + case BUILDING_FORT_MOUNTED: + total_active_count += building_count_fort_type_total(FIGURE_FORT_MOUNTED); + break; + default: + total_active_count = building_count_total(type); + break; + } + + return comparison_helper_compare_values(comparison, total_active_count, value); +} + +int scenario_condition_type_building_count_area_met(const scenario_condition_t *condition) +{ + int grid_offset = condition->parameter1; + int block_radius = condition->parameter2; + building_type type = condition->parameter3; + int comparison = condition->parameter4; + int value = condition->parameter5; + + if (!map_grid_is_valid_offset(grid_offset)) { + return 0; + } + + int minx = map_grid_offset_to_x(grid_offset) - block_radius; + int miny = map_grid_offset_to_y(grid_offset) - block_radius; + int maxx = map_grid_offset_to_x(grid_offset) + block_radius; + int maxy = map_grid_offset_to_y(grid_offset) + block_radius; + int buildings_in_area = 0; + switch(type) { + case BUILDING_MENU_FARMS: + buildings_in_area = building_set_area_count_farms(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_RAW_MATERIALS: + buildings_in_area = building_set_area_count_raw_materials(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_WORKSHOPS: + buildings_in_area = building_set_area_count_workshops(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_SMALL_TEMPLES: + buildings_in_area = building_set_area_count_small_temples(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_LARGE_TEMPLES: + buildings_in_area = building_set_area_count_large_temples(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_GRAND_TEMPLES: + buildings_in_area = building_set_area_count_grand_temples(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_TREES: + buildings_in_area = building_set_area_count_deco_trees(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_PATHS: + buildings_in_area = building_set_area_count_deco_paths(minx, miny, maxx, maxy); + break; + case BUILDING_MENU_PARKS: + buildings_in_area = building_set_area_count_deco_statues(minx, miny, maxx, maxy); + break; + case BUILDING_FORT_LEGIONARIES: + buildings_in_area = building_count_fort_type_in_area(minx, miny, maxx, maxy, FIGURE_FORT_LEGIONARY); + break; + case BUILDING_FORT_JAVELIN: + buildings_in_area = building_count_fort_type_in_area(minx, miny, maxx, maxy, FIGURE_FORT_JAVELIN); + break; + case BUILDING_FORT_MOUNTED: + buildings_in_area = building_count_fort_type_in_area(minx, miny, maxx, maxy, FIGURE_FORT_MOUNTED); + break; + default: + buildings_in_area = building_count_in_area(type, minx, miny, maxx, maxy); + break; + } + + return comparison_helper_compare_values(comparison, buildings_in_area, value); +} + +int scenario_condition_type_city_population_met(const scenario_condition_t *condition) +{ + int comparison = condition->parameter1; + int value = condition->parameter2; + int class = condition->parameter3; + + int population_value_to_use = city_data.population.population; + if (class == POP_CLASS_PATRICIAN) { + population_value_to_use = city_data.population.people_in_villas_palaces; + } else if (class == POP_CLASS_PLEBEIAN) { + population_value_to_use = city_data.population.population - city_data.population.people_in_villas_palaces; + } else if (class == POP_CLASS_SLUMS) { + population_value_to_use = city_data.population.people_in_tents_shacks; + } + + return comparison_helper_compare_values(comparison, population_value_to_use, value); +} + +int scenario_condition_type_count_own_troops_met(const scenario_condition_t *condition) +{ + int comparison = condition->parameter1; + int value = condition->parameter2; + int in_city_only = condition->parameter3; + + int soldier_count = in_city_only ? city_military_total_soldiers_in_city() : city_military_total_soldiers(); + + return comparison_helper_compare_values(comparison, soldier_count, value); +} + +int scenario_condition_type_custom_variable_check_met(const scenario_condition_t *condition) +{ + int target_variable = scenario_custom_variable_get_value(condition->parameter1); + int comparison = condition->parameter2; + int value = condition->parameter3; + + return comparison_helper_compare_values(comparison, target_variable, value); +} + +int scenario_condition_type_difficulty_met(const scenario_condition_t *condition) +{ + int difficulty = setting_difficulty(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, difficulty, value); +} + +int scenario_condition_type_money_met(const scenario_condition_t *condition) +{ + int funds = city_finance_treasury(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, funds, value); +} + +int scenario_condition_type_population_unemployed_met(const scenario_condition_t *condition) +{ + int use_percentage = condition->parameter1; + int comparison = condition->parameter2; + int value = condition->parameter3; + + int unemployed_total = use_percentage ? city_labor_unemployment_percentage() : city_labor_workers_unemployed(); + + return comparison_helper_compare_values(comparison, unemployed_total, value); +} + +int scenario_condition_type_request_is_ongoing_met(const scenario_condition_t *condition) +{ + int request_id = condition->parameter1; + int check_for_ongoing = condition->parameter2; + int is_ongoing = scenario_request_is_ongoing(request_id); + + return check_for_ongoing ? is_ongoing : !is_ongoing; +} + +int scenario_condition_type_resource_storage_available_met(const scenario_condition_t *condition) +{ + int resource = condition->parameter1; + int comparison = condition->parameter2; + int value = condition->parameter3; + storage_types storage_type = condition->parameter4; + int respect_settings = condition->parameter5; + + if (resource < RESOURCE_MIN || resource > RESOURCE_MAX) { + return 0; + } + + int storage_available = 0; + switch(storage_type) { + case STORAGE_TYPE_ALL: + storage_available += city_resource_get_available_empty_space_warehouses(resource, respect_settings); + storage_available += city_resource_get_available_empty_space_granaries(resource, respect_settings) / RESOURCE_ONE_LOAD; + break; + case STORAGE_TYPE_GRANARIES: + storage_available += city_resource_get_available_empty_space_granaries(resource, respect_settings) / RESOURCE_ONE_LOAD; + break; + case STORAGE_TYPE_WAREHOUSES: + storage_available += city_resource_get_available_empty_space_warehouses(resource, respect_settings); + break; + default: + break; + } + + return comparison_helper_compare_values(comparison, storage_available, value); +} + +int scenario_condition_type_resource_stored_count_met(const scenario_condition_t *condition) +{ + int resource = condition->parameter1; + int comparison = condition->parameter2; + int value = condition->parameter3; + storage_types storage_type = condition->parameter4; + + if (resource < RESOURCE_MIN || resource > RESOURCE_MAX) { + return 0; + } + + int amount_stored = 0; + switch(storage_type) { + case STORAGE_TYPE_ALL: + amount_stored += city_resource_count(resource); + if (resource_is_food(resource)) { + amount_stored += city_resource_count_food_on_granaries(resource) / RESOURCE_ONE_LOAD; + } + break; + case STORAGE_TYPE_GRANARIES: + if (resource_is_food(resource)) { + amount_stored += city_resource_count_food_on_granaries(resource) / RESOURCE_ONE_LOAD; + } + break; + case STORAGE_TYPE_WAREHOUSES: + amount_stored += city_resource_count(resource); + break; + default: + break; + } + + return comparison_helper_compare_values(comparison, amount_stored, value); +} + +int scenario_condition_type_rome_wages_met(const scenario_condition_t *condition) +{ + int wages = city_labor_wages_rome(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, wages, value); +} + +int scenario_condition_type_savings_met(const scenario_condition_t *condition) +{ + int funds = city_emperor_personal_savings(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, funds, value); +} + +int scenario_condition_type_stats_city_health_met(const scenario_condition_t *condition) +{ + int stat_value = city_health(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, stat_value, value); +} + +int scenario_condition_type_stats_culture_met(const scenario_condition_t *condition) +{ + int stat_value = city_rating_culture(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, stat_value, value); +} + +int scenario_condition_type_stats_favor_met(const scenario_condition_t *condition) +{ + int stat_value = city_rating_favor(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, stat_value, value); +} + +int scenario_condition_type_stats_peace_met(const scenario_condition_t *condition) +{ + int stat_value = city_rating_peace(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, stat_value, value); +} + +int scenario_condition_type_stats_prosperity_met(const scenario_condition_t *condition) +{ + int stat_value = city_rating_prosperity(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, stat_value, value); +} + +void scenario_condition_type_time_init(scenario_condition_t *condition) +{ + int min_months = condition->parameter2; + int max_months = condition->parameter3; + + if (max_months < min_months) { + max_months = min_months; + condition->parameter3 = min_months; + } + + condition->parameter4 = random_between_from_stdlib(min_months, max_months); +} + +int scenario_condition_type_time_met(const scenario_condition_t *condition) +{ + int total_months = game_time_total_months(); + int comparison = condition->parameter1; + int target_months = condition->parameter4; + + return comparison_helper_compare_values(comparison, total_months, target_months); +} + +int scenario_condition_type_trade_route_open_met(const scenario_condition_t *condition) +{ + int route_id = condition->parameter1; + int check_for_open = condition->parameter2; + + if (!trade_route_is_valid(route_id)) { + return 0; + } + + int route_is_open = empire_city_is_trade_route_open(route_id); + return route_is_open == check_for_open; +} + +int scenario_condition_type_trade_route_price_met(const scenario_condition_t *condition) +{ + int route_id = condition->parameter1; + int comparison = condition->parameter2; + int value = condition->parameter3; + + if (!trade_route_is_valid(route_id)) { + return 0; + } + + int route_is_open = empire_city_is_trade_route_open(route_id); + int route_price = route_is_open ? 0 : empire_city_get_trade_route_cost(route_id); + return comparison_helper_compare_values(comparison, route_price, value); +} + +int scenario_condition_type_trade_sell_price_met(const scenario_condition_t *condition) +{ + int resource = condition->parameter1; + int comparison = condition->parameter2; + int value = condition->parameter3; + + if (resource < RESOURCE_MIN || resource > RESOURCE_MAX) { + return 0; + } + + int trade_sell_price = trade_price_base_sell(resource); + return comparison_helper_compare_values(comparison, trade_sell_price, value); +} + +int scenario_condition_type_tax_rate_met(const scenario_condition_t *condition) +{ + int tax_rate = city_finance_tax_percentage(); + int comparison = condition->parameter1; + int value = condition->parameter2; + + return comparison_helper_compare_values(comparison, tax_rate, value); +} diff --git a/src/scenario/event/conditions/condition_types.h b/src/scenario/event/conditions/condition_types.h new file mode 100644 index 0000000000..cf42711d75 --- /dev/null +++ b/src/scenario/event/conditions/condition_types.h @@ -0,0 +1,55 @@ +#ifndef CONDITION_TYPES_H +#define CONDITION_TYPES_H + +#include "scenario/scenario_event_data.h" + +int scenario_condition_type_building_count_active_met(const scenario_condition_t *condition); + +int scenario_condition_type_building_count_any_met(const scenario_condition_t *condition); + +int scenario_condition_type_building_count_area_met(const scenario_condition_t *condition); + +int scenario_condition_type_city_population_met(const scenario_condition_t *condition); + +int scenario_condition_type_count_own_troops_met(const scenario_condition_t *condition); + +int scenario_condition_type_custom_variable_check_met(const scenario_condition_t *condition); + +int scenario_condition_type_difficulty_met(const scenario_condition_t *condition); + +int scenario_condition_type_money_met(const scenario_condition_t *condition); + +int scenario_condition_type_population_unemployed_met(const scenario_condition_t *condition); + +int scenario_condition_type_request_is_ongoing_met(const scenario_condition_t *condition); + +int scenario_condition_type_resource_storage_available_met(const scenario_condition_t *condition); + +int scenario_condition_type_resource_stored_count_met(const scenario_condition_t *condition); + +int scenario_condition_type_rome_wages_met(const scenario_condition_t *condition); + +int scenario_condition_type_savings_met(const scenario_condition_t *condition); + +int scenario_condition_type_stats_city_health_met(const scenario_condition_t *condition); + +int scenario_condition_type_stats_culture_met(const scenario_condition_t *condition); + +int scenario_condition_type_stats_favor_met(const scenario_condition_t *condition); + +int scenario_condition_type_stats_peace_met(const scenario_condition_t *condition); + +int scenario_condition_type_stats_prosperity_met(const scenario_condition_t *condition); + +void scenario_condition_type_time_init(scenario_condition_t *condition); +int scenario_condition_type_time_met(const scenario_condition_t *condition); + +int scenario_condition_type_trade_route_open_met(const scenario_condition_t *condition); + +int scenario_condition_type_trade_route_price_met(const scenario_condition_t *condition); + +int scenario_condition_type_trade_sell_price_met(const scenario_condition_t *condition); + +int scenario_condition_type_tax_rate_met(const scenario_condition_t *condition); + +#endif // CONDITION_TYPES_H diff --git a/src/scenario/terrain_generator.c b/src/scenario/terrain_generator.c new file mode 100644 index 0000000000..9179352a42 --- /dev/null +++ b/src/scenario/terrain_generator.c @@ -0,0 +1,129 @@ +#include "terrain_generator.h" + +#include "core/random.h" +#include "map/elevation.h" +#include "map/grid.h" +#include "map/terrain.h" + +#include + +static int clamp_int(int value, int min_value, int max_value) +{ + if (value < min_value) { + return min_value; + } + if (value > max_value) { + return max_value; + } + return value; +} + +static int random_between(int min_value, int max_value) +{ + return random_between_from_stdlib(min_value, max_value); +} + +static void clear_base_terrain(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + map_terrain_set(grid_offset, TERRAIN_CLEAR); + map_elevation_set(grid_offset, 0); + } + } +} + +static void generate_flat_plains(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + int roll = random_between(0, 100); + if (roll < 6) { + map_terrain_set(grid_offset, TERRAIN_TREE); + } else if (roll < 8) { + map_terrain_set(grid_offset, TERRAIN_ROCK); + } else if (roll < 18) { + map_terrain_set(grid_offset, TERRAIN_SHRUB); + } else if (roll < 35) { + map_terrain_set(grid_offset, TERRAIN_MEADOW); + } + } + } +} + +static void generate_river_valley(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + static int river_y[GRID_SIZE]; + int river_half_width = 1; + + river_y[0] = height / 2; + for (int x = 1; x < width; x++) { + int drift = random_between(-1, 2); + river_y[x] = clamp_int(river_y[x - 1] + drift, 1, height - 2); + } + + for (int x = 0; x < width; x++) { + for (int dy = -river_half_width; dy <= river_half_width; dy++) { + int y = river_y[x] + dy; + if (y < 0 || y >= height) { + continue; + } + map_terrain_set(map_grid_offset(x, y), TERRAIN_WATER); + map_elevation_set(map_grid_offset(x, y), 0); + } + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + if (map_terrain_is(grid_offset, TERRAIN_WATER)) { + continue; + } + int dist = abs(y - river_y[x]); + int elevation = 0; + if (dist > 2) { + elevation = (dist - 2) / 6; + } + if (elevation > 0 && random_between(0, 4) == 0) { + elevation++; + } + elevation = clamp_int(elevation, 0, 4); + map_elevation_set(grid_offset, elevation); + + int roll = random_between(0, 100); + if (dist <= 2 && roll < 30) { + map_terrain_set(grid_offset, TERRAIN_MEADOW); + } else if (elevation >= 2 && roll < 12) { + map_terrain_set(grid_offset, TERRAIN_ROCK); + } else if (roll < 8) { + map_terrain_set(grid_offset, TERRAIN_TREE); + } else if (roll < 16) { + map_terrain_set(grid_offset, TERRAIN_SHRUB); + } + } + } +} + +void terrain_generator_generate(terrain_generator_algorithm algorithm) +{ + clear_base_terrain(); + + switch (algorithm) { + case TERRAIN_GENERATOR_RIVER_VALLEY: + generate_river_valley(); + break; + case TERRAIN_GENERATOR_FLAT_PLAINS: + default: + generate_flat_plains(); + break; + } +} diff --git a/src/scenario/terrain_generator.h b/src/scenario/terrain_generator.h new file mode 100644 index 0000000000..9eb3f11999 --- /dev/null +++ b/src/scenario/terrain_generator.h @@ -0,0 +1,12 @@ +#ifndef SCENARIO_TERRAIN_GENERATOR_H +#define SCENARIO_TERRAIN_GENERATOR_H + +typedef enum { + TERRAIN_GENERATOR_FLAT_PLAINS = 0, + TERRAIN_GENERATOR_RIVER_VALLEY = 1, + TERRAIN_GENERATOR_COUNT +} terrain_generator_algorithm; + +void terrain_generator_generate(terrain_generator_algorithm algorithm); + +#endif // SCENARIO_TERRAIN_GENERATOR_H diff --git a/src/translation/english.c b/src/translation/english.c index c1d03d4967..3869393bc9 100644 --- a/src/translation/english.c +++ b/src/translation/english.c @@ -1471,6 +1471,9 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TITLE, "Warning"}, {TR_CHEAT_EDITOR_WARNING_TEXT, "Running the map editor from city mode can corrupt the save or cause the game to crash.\n\nProceed at your own risk and make sure you have a backup of the current save."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "Start new campaign"}, + {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, + {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, + {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, {TR_WINDOW_SELECT_CAMPAIGN, "Select a campaign"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Author:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "No description"}, diff --git a/src/translation/french.c b/src/translation/french.c index 59b7ebf385..23b471115d 100644 --- a/src/translation/french.c +++ b/src/translation/french.c @@ -1470,6 +1470,9 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TITLE, "Avertissement"}, {TR_CHEAT_EDITOR_WARNING_TEXT, "Lancer l'éditeur depuis une cité peut corrompre la sauvegarde ou provoquer un arrêt inopiné.\n\nEffectuez une copie de la sauvegarde avant de continuer."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "Nouvelle campagne"}, + {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, + {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, + {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, {TR_WINDOW_SELECT_CAMPAIGN, "Choisir une campagne"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Auteur :"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Pas de description"}, diff --git a/src/translation/german.c b/src/translation/german.c index c8cbd1ccf1..b43fd7ee03 100644 --- a/src/translation/german.c +++ b/src/translation/german.c @@ -1241,6 +1241,9 @@ static translation_string all_strings[] = { {TR_WINDOW_RACE_GREEN_HORSE_DESCRIPTION, "Das grüne Team - Nachfahren der \"Celeres\"-Reiter aus der Zeit des römischen Königreichs. Sie behaupten immer noch, die Besten der Besten zu sein."}, /* System - Kampagnen-Menü */ {TR_MAIN_MENU_SELECT_CAMPAIGN, "Neue Kampagne starten"}, + {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, + {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, + {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, {TR_WINDOW_SELECT_CAMPAIGN, "Eine Kampagne auswählen"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Autor:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Keine Beschreibung"}, diff --git a/src/translation/greek.c b/src/translation/greek.c index 481fb55c5a..f8f1e55f1e 100644 --- a/src/translation/greek.c +++ b/src/translation/greek.c @@ -1477,6 +1477,9 @@ static translation_string all_strings[] = { { TR_CHEAT_EDITOR_WARNING_TITLE, "Προειδοποίηση" }, { TR_CHEAT_EDITOR_WARNING_TEXT, "Η εκτέλεση του προγράμματος επεξεργασίας χαρτών από τη λειτουργία πόλης μπορεί να καταστρέψει την αποθήκευση ή να προκαλέσει σφάλμα στο παιχνίδι.\n\nΠροχωρήστε με δική σας ευθύνη και βεβαιωθείτε ότι έχετε δημιουργήσει αντίγραφο ασφαλείας της τρέχουσας αποθήκευσης." }, { TR_MAIN_MENU_SELECT_CAMPAIGN, "Έναρξη νέας εκστρατείας" }, + { TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator" }, + { TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains" }, + { TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley" }, { TR_WINDOW_SELECT_CAMPAIGN, "Επιλέξτε μια εκστρατεία" }, { TR_WINDOW_CAMPAIGN_AUTHOR, "Συγγραφέας:" }, { TR_WINDOW_CAMPAIGN_NO_DESC, "Καμία περιγραφή" }, diff --git a/src/translation/italian.c b/src/translation/italian.c index 02ba106f8b..a32690c57f 100755 --- a/src/translation/italian.c +++ b/src/translation/italian.c @@ -1470,6 +1470,9 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TITLE, "Attenzione"}, {TR_CHEAT_EDITOR_WARNING_TEXT, "Usare l'editor di mappe in modalità città può corrompere il salvataggio o causare un crash del gioco.\n\nProcedere a proprio rischio e pericolo e assicurarsi di avere una copia di sicurezza del salvataggio corrente."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "Inizia nuova campagna"}, + {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, + {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, + {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, {TR_WINDOW_SELECT_CAMPAIGN, "Seleziona una campagna"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Autore:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Nessuna descrizione"}, diff --git a/src/translation/korean.c b/src/translation/korean.c index 22cb7eb8ff..7d7fde6621 100644 --- a/src/translation/korean.c +++ b/src/translation/korean.c @@ -1469,6 +1469,9 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TITLE, "경고"}, {TR_CHEAT_EDITOR_WARNING_TEXT, "도시 모드에서 지도 편집기를 실행하면 저장 파일이 손상되거나 게임이 깨질 수 있습니다.\n진행은 사용자의 책임이며, 현재 저장 파일을 반드시 백업하십시오."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "새 캠페인 시작"}, + {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, + {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, + {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, {TR_WINDOW_SELECT_CAMPAIGN, "캠페인을 선택하세요"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "원작자:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "설명 없음"}, diff --git a/src/translation/translation.h b/src/translation/translation.h index a25e6f3896..f83898a56d 100644 --- a/src/translation/translation.h +++ b/src/translation/translation.h @@ -1472,6 +1472,9 @@ typedef enum { TR_BUILDING_ARENA_DESC_UPGRADED_NO_LIONS, TR_BUILDING_ARENA_DESC_UPGRADED_NO_SHOWS, TR_MAIN_MENU_SELECT_CAMPAIGN, + TR_MAIN_MENU_TERRAIN_GENERATOR, + TR_TERRAIN_GENERATOR_FLAT_PLAINS, + TR_TERRAIN_GENERATOR_RIVER_VALLEY, TR_WINDOW_SELECT_CAMPAIGN, TR_WINDOW_CAMPAIGN_AUTHOR, TR_WINDOW_CAMPAIGN_NO_DESC, diff --git a/src/window/main_menu.c b/src/window/main_menu.c index 1c21af036b..c22b9c3df1 100644 --- a/src/window/main_menu.c +++ b/src/window/main_menu.c @@ -19,31 +19,61 @@ #include "graphics/weather.h" #include "graphics/window.h" #include "sound/music.h" +#include "translation/translation.h" #include "window/cck_selection.h" #include "window/config.h" #include "window/editor/map.h" #include "window/file_dialog.h" #include "window/plain_message_dialog.h" #include "window/popup_dialog.h" +#include "window/select_list.h" #include "window/select_campaign.h" #include "window/video.h" +#include "scenario/terrain_generator.h" -#define MAX_BUTTONS 6 +#define MAX_BUTTONS 7 +#define TERRAIN_GENERATOR_SIZE_COUNT 6 static void button_click(const generic_button *button); +static void terrain_generator_size_selected(int id); +static void terrain_generator_algorithm_selected(int id); +static void show_terrain_generator_size_picker(const generic_button *button); +static void show_terrain_generator_algorithm_picker(const generic_button *button); static struct { unsigned int focus_button_id; int logo_image_id; + int selected_map_size; } data; +static const uint8_t size_label_40[] = "40 x 40"; +static const uint8_t size_label_60[] = "60 x 60"; +static const uint8_t size_label_80[] = "80 x 80"; +static const uint8_t size_label_100[] = "100 x 100"; +static const uint8_t size_label_120[] = "120 x 120"; +static const uint8_t size_label_160[] = "160 x 160"; + +static const uint8_t *terrain_generator_size_labels[TERRAIN_GENERATOR_SIZE_COUNT] = { + size_label_40, + size_label_60, + size_label_80, + size_label_100, + size_label_120, + size_label_160 +}; + +static const uint8_t *terrain_generator_algorithm_labels[TERRAIN_GENERATOR_COUNT]; + +static const generic_button *terrain_generator_anchor_button = NULL; + static generic_button buttons[] = { {192, 130, 256, 25, button_click, 0, 1}, - {192, 170, 256, 25, button_click, 0, 2}, - {192, 210, 256, 25, button_click, 0, 3}, - {192, 250, 256, 25, button_click, 0, 4}, - {192, 290, 256, 25, button_click, 0, 5}, - {192, 330, 256, 25, button_click, 0, 6}, + {192, 164, 256, 25, button_click, 0, 2}, + {192, 198, 256, 25, button_click, 0, 3}, + {192, 232, 256, 25, button_click, 0, 4}, + {192, 266, 256, 25, button_click, 0, 5}, + {192, 300, 256, 25, button_click, 0, 6}, + {192, 334, 256, 25, button_click, 0, 7}, }; static void draw_version_string(void) @@ -89,11 +119,12 @@ static void draw_foreground(void) } lang_text_draw_centered(CUSTOM_TRANSLATION, TR_MAIN_MENU_SELECT_CAMPAIGN, 192, 137, 256, FONT_NORMAL_GREEN); - lang_text_draw_centered(30, 2, 192, 177, 256, FONT_NORMAL_GREEN); - lang_text_draw_centered(30, 3, 192, 217, 256, FONT_NORMAL_GREEN); - lang_text_draw_centered(9, 8, 192, 257, 256, FONT_NORMAL_GREEN); - lang_text_draw_centered(2, 0, 192, 297, 256, FONT_NORMAL_GREEN); - lang_text_draw_centered(30, 5, 192, 337, 256, FONT_NORMAL_GREEN); + lang_text_draw_centered(30, 2, 192, 171, 256, FONT_NORMAL_GREEN); + lang_text_draw_centered(30, 3, 192, 205, 256, FONT_NORMAL_GREEN); + lang_text_draw_centered(9, 8, 192, 239, 256, FONT_NORMAL_GREEN); + lang_text_draw_centered(CUSTOM_TRANSLATION, TR_MAIN_MENU_TERRAIN_GENERATOR, 192, 273, 256, FONT_NORMAL_GREEN); + lang_text_draw_centered(2, 0, 192, 307, 256, FONT_NORMAL_GREEN); + lang_text_draw_centered(30, 5, 192, 341, 256, FONT_NORMAL_GREEN); graphics_reset_dialog(); } @@ -119,6 +150,42 @@ static void confirm_exit(int accepted, int checked) } } +static void terrain_generator_size_selected(int id) +{ + data.selected_map_size = id; + show_terrain_generator_algorithm_picker(terrain_generator_anchor_button); +} + +static void terrain_generator_algorithm_selected(int id) +{ + if (!editor_is_present() || + !game_init_editor_generated(data.selected_map_size, id)) { + window_plain_message_dialog_show( + TR_NO_EDITOR_TITLE, TR_NO_EDITOR_MESSAGE, 1); + return; + } + + if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { + window_video_show("map_intro.smk", window_editor_map_show); + } + sound_music_play_editor(); +} + +static void show_terrain_generator_size_picker(const generic_button *button) +{ + terrain_generator_anchor_button = button; + window_select_list_show_text(0, 0, button, terrain_generator_size_labels, + TERRAIN_GENERATOR_SIZE_COUNT, terrain_generator_size_selected); +} + +static void show_terrain_generator_algorithm_picker(const generic_button *button) +{ + terrain_generator_algorithm_labels[0] = translation_for(TR_TERRAIN_GENERATOR_FLAT_PLAINS); + terrain_generator_algorithm_labels[1] = translation_for(TR_TERRAIN_GENERATOR_RIVER_VALLEY); + window_select_list_show_text(0, 0, button, terrain_generator_algorithm_labels, TERRAIN_GENERATOR_COUNT, + terrain_generator_algorithm_selected); +} + static void button_click(const generic_button *button) { int type = button->parameter1; @@ -138,8 +205,10 @@ static void button_click(const generic_button *button) sound_music_play_editor(); } } else if (type == 5) { - window_config_show(CONFIG_FIRST_PAGE, 0, 1); + show_terrain_generator_size_picker(button); } else if (type == 6) { + window_config_show(CONFIG_FIRST_PAGE, 0, 1); + } else if (type == 7) { window_popup_dialog_show(POPUP_DIALOG_QUIT, confirm_exit, 1); } } From 7f392e55a540d1b156cee00e8ac22f02ffdad832 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 14:31:04 +0100 Subject: [PATCH 02/22] Add generator window --- CMakeLists.txt | 1 + src/core/random.c | 18 ++ src/core/random.h | 4 + src/graphics/window.h | 1 + src/scenario/terrain_generator.c | 16 ++ src/scenario/terrain_generator.h | 2 + src/window/main_menu.c | 69 +----- src/window/terrain_generator.c | 374 +++++++++++++++++++++++++++++++ src/window/terrain_generator.h | 6 + 9 files changed, 425 insertions(+), 66 deletions(-) create mode 100644 src/window/terrain_generator.c create mode 100644 src/window/terrain_generator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c9c318bfaa..6c5fb63b48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -722,6 +722,7 @@ set(WINDOW_FILES ${PROJECT_SOURCE_DIR}/src/window/select_list.c ${PROJECT_SOURCE_DIR}/src/window/set_salary.c ${PROJECT_SOURCE_DIR}/src/window/text_input.c + ${PROJECT_SOURCE_DIR}/src/window/terrain_generator.c ${PROJECT_SOURCE_DIR}/src/window/trade_opened.c ${PROJECT_SOURCE_DIR}/src/window/trade_prices.c ${PROJECT_SOURCE_DIR}/src/window/user_path_setup.c diff --git a/src/core/random.c b/src/core/random.c index 9c80482b4d..4da76da850 100644 --- a/src/core/random.c +++ b/src/core/random.c @@ -19,6 +19,8 @@ static struct { time_t last_seed; } data; +static int stdlib_use_fixed_seed = 0; + void random_init(void) { memset(&data, 0, sizeof(data)); @@ -97,6 +99,9 @@ void random_save_state(buffer *buf) } int random_from_stdlib(void) { + if (stdlib_use_fixed_seed) { + return rand(); + } time_t t; t = time(&t); if (data.last_seed != t) { @@ -106,6 +111,19 @@ int random_from_stdlib(void) { return rand(); } +void random_set_stdlib_seed(unsigned int seed) +{ + stdlib_use_fixed_seed = 1; + srand(seed); + data.last_seed = 0; +} + +void random_clear_stdlib_seed(void) +{ + stdlib_use_fixed_seed = 0; + data.last_seed = 0; +} + int random_between_from_stdlib(int min, int max) { int diff = max - min; diff --git a/src/core/random.h b/src/core/random.h index 3dee1a22cf..c44acb4c05 100644 --- a/src/core/random.h +++ b/src/core/random.h @@ -70,6 +70,10 @@ void random_load_state(buffer *buf); int random_from_stdlib(void); +void random_set_stdlib_seed(unsigned int seed); + +void random_clear_stdlib_seed(void); + int random_between_from_stdlib(int min, int max); double random_fractional_from_stdlib(void); diff --git a/src/graphics/window.h b/src/graphics/window.h index 6137b05c2d..2c1300d832 100644 --- a/src/graphics/window.h +++ b/src/graphics/window.h @@ -14,6 +14,7 @@ typedef enum { WINDOW_SELECT_CAMPAIGN, WINDOW_MISSION_LIST, WINDOW_CCK_SELECTION, + WINDOW_TERRAIN_GENERATOR, WINDOW_FILE_DIALOG, WINDOW_POPUP_DIALOG, WINDOW_PLAIN_MESSAGE_DIALOG, diff --git a/src/scenario/terrain_generator.c b/src/scenario/terrain_generator.c index 9179352a42..17f18a2cc1 100644 --- a/src/scenario/terrain_generator.c +++ b/src/scenario/terrain_generator.c @@ -23,6 +23,9 @@ static int random_between(int min_value, int max_value) return random_between_from_stdlib(min_value, max_value); } +static int use_fixed_seed = 0; +static unsigned int fixed_seed = 0; + static void clear_base_terrain(void) { int width = map_grid_width(); @@ -115,6 +118,11 @@ static void generate_river_valley(void) void terrain_generator_generate(terrain_generator_algorithm algorithm) { + if (use_fixed_seed) { + random_set_stdlib_seed(fixed_seed); + } else { + random_clear_stdlib_seed(); + } clear_base_terrain(); switch (algorithm) { @@ -126,4 +134,12 @@ void terrain_generator_generate(terrain_generator_algorithm algorithm) generate_flat_plains(); break; } + + random_clear_stdlib_seed(); +} + +void terrain_generator_set_seed(int enabled, unsigned int seed) +{ + use_fixed_seed = enabled != 0; + fixed_seed = seed; } diff --git a/src/scenario/terrain_generator.h b/src/scenario/terrain_generator.h index 9eb3f11999..c5cca9048d 100644 --- a/src/scenario/terrain_generator.h +++ b/src/scenario/terrain_generator.h @@ -9,4 +9,6 @@ typedef enum { void terrain_generator_generate(terrain_generator_algorithm algorithm); +void terrain_generator_set_seed(int enabled, unsigned int seed); + #endif // SCENARIO_TERRAIN_GENERATOR_H diff --git a/src/window/main_menu.c b/src/window/main_menu.c index c22b9c3df1..0314eb14a5 100644 --- a/src/window/main_menu.c +++ b/src/window/main_menu.c @@ -26,46 +26,18 @@ #include "window/file_dialog.h" #include "window/plain_message_dialog.h" #include "window/popup_dialog.h" -#include "window/select_list.h" #include "window/select_campaign.h" +#include "window/terrain_generator.h" #include "window/video.h" -#include "scenario/terrain_generator.h" #define MAX_BUTTONS 7 -#define TERRAIN_GENERATOR_SIZE_COUNT 6 - static void button_click(const generic_button *button); -static void terrain_generator_size_selected(int id); -static void terrain_generator_algorithm_selected(int id); -static void show_terrain_generator_size_picker(const generic_button *button); -static void show_terrain_generator_algorithm_picker(const generic_button *button); static struct { unsigned int focus_button_id; int logo_image_id; - int selected_map_size; } data; -static const uint8_t size_label_40[] = "40 x 40"; -static const uint8_t size_label_60[] = "60 x 60"; -static const uint8_t size_label_80[] = "80 x 80"; -static const uint8_t size_label_100[] = "100 x 100"; -static const uint8_t size_label_120[] = "120 x 120"; -static const uint8_t size_label_160[] = "160 x 160"; - -static const uint8_t *terrain_generator_size_labels[TERRAIN_GENERATOR_SIZE_COUNT] = { - size_label_40, - size_label_60, - size_label_80, - size_label_100, - size_label_120, - size_label_160 -}; - -static const uint8_t *terrain_generator_algorithm_labels[TERRAIN_GENERATOR_COUNT]; - -static const generic_button *terrain_generator_anchor_button = NULL; - static generic_button buttons[] = { {192, 130, 256, 25, button_click, 0, 1}, {192, 164, 256, 25, button_click, 0, 2}, @@ -150,42 +122,6 @@ static void confirm_exit(int accepted, int checked) } } -static void terrain_generator_size_selected(int id) -{ - data.selected_map_size = id; - show_terrain_generator_algorithm_picker(terrain_generator_anchor_button); -} - -static void terrain_generator_algorithm_selected(int id) -{ - if (!editor_is_present() || - !game_init_editor_generated(data.selected_map_size, id)) { - window_plain_message_dialog_show( - TR_NO_EDITOR_TITLE, TR_NO_EDITOR_MESSAGE, 1); - return; - } - - if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { - window_video_show("map_intro.smk", window_editor_map_show); - } - sound_music_play_editor(); -} - -static void show_terrain_generator_size_picker(const generic_button *button) -{ - terrain_generator_anchor_button = button; - window_select_list_show_text(0, 0, button, terrain_generator_size_labels, - TERRAIN_GENERATOR_SIZE_COUNT, terrain_generator_size_selected); -} - -static void show_terrain_generator_algorithm_picker(const generic_button *button) -{ - terrain_generator_algorithm_labels[0] = translation_for(TR_TERRAIN_GENERATOR_FLAT_PLAINS); - terrain_generator_algorithm_labels[1] = translation_for(TR_TERRAIN_GENERATOR_RIVER_VALLEY); - window_select_list_show_text(0, 0, button, terrain_generator_algorithm_labels, TERRAIN_GENERATOR_COUNT, - terrain_generator_algorithm_selected); -} - static void button_click(const generic_button *button) { int type = button->parameter1; @@ -205,7 +141,8 @@ static void button_click(const generic_button *button) sound_music_play_editor(); } } else if (type == 5) { - show_terrain_generator_size_picker(button); + (void) button; + window_terrain_generator_show(); } else if (type == 6) { window_config_show(CONFIG_FIRST_PAGE, 0, 1); } else if (type == 7) { diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c new file mode 100644 index 0000000000..04df373a65 --- /dev/null +++ b/src/window/terrain_generator.c @@ -0,0 +1,374 @@ +#include "terrain_generator.h" + +#include "assets/assets.h" +#include "city/view.h" +#include "core/config.h" +#include "core/image_group.h" +#include "core/string.h" +#include "editor/editor.h" +#include "game/game.h" +#include "graphics/button.h" +#include "graphics/generic_button.h" +#include "graphics/graphics.h" +#include "graphics/image.h" +#include "graphics/panel.h" +#include "graphics/screen.h" +#include "graphics/text.h" +#include "graphics/window.h" +#include "input/input.h" +#include "map/aqueduct.h" +#include "map/building.h" +#include "map/desirability.h" +#include "map/elevation.h" +#include "map/figure.h" +#include "map/image.h" +#include "map/image_context.h" +#include "map/property.h" +#include "map/random.h" +#include "map/road_network.h" +#include "map/soldier_strength.h" +#include "map/sprite.h" +#include "map/terrain.h" +#include "map/tiles.h" +#include "scenario/editor.h" +#include "scenario/data.h" +#include "scenario/map.h" +#include "scenario/property.h" +#include "scenario/terrain_generator.h" +#include "sound/music.h" +#include "translation/translation.h" +#include "widget/input_box.h" +#include "widget/minimap.h" +#include "window/plain_message_dialog.h" +#include "window/select_list.h" +#include "window/video.h" +#include "window/editor/map.h" + +#include + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 + +#define CONTROL_LABEL_X 32 +#define CONTROL_VALUE_X 170 +#define CONTROL_BUTTON_WIDTH 180 +#define CONTROL_BUTTON_HEIGHT 24 + +#define SIZE_BUTTON_Y 90 +#define ALGORITHM_BUTTON_Y 130 +#define SEED_INPUT_Y 170 + +#define ACTION_BUTTON_WIDTH 260 +#define ACTION_BUTTON_HEIGHT 28 +#define ACTION_BUTTON_X 32 +#define REGENERATE_BUTTON_Y 230 +#define OPEN_EDITOR_BUTTON_Y 270 +#define BACK_BUTTON_Y 310 + +#define PREVIEW_X 320 +#define PREVIEW_Y 80 +#define PREVIEW_WIDTH 288 +#define PREVIEW_HEIGHT 320 + +#define SEED_TEXT_LENGTH 16 +#define TERRAIN_GENERATOR_SIZE_COUNT 6 + +static void button_select_size(const generic_button *button); +static void button_select_algorithm(const generic_button *button); +static void button_regenerate(const generic_button *button); +static void button_open_editor(const generic_button *button); +static void button_back(const generic_button *button); + +static void size_selected(int id); +static void algorithm_selected(int id); + +static const uint8_t label_size[] = "Size"; +static const uint8_t label_algorithm[] = "Algorithm"; +static const uint8_t label_seed[] = "Seed"; +static const uint8_t label_regenerate[] = "Regenerate preview"; +static const uint8_t label_open_editor[] = "Open in editor"; +static const uint8_t label_back[] = "Back"; +static const uint8_t label_seed_placeholder[] = "Random"; + +static const uint8_t size_label_40[] = "40 x 40"; +static const uint8_t size_label_60[] = "60 x 60"; +static const uint8_t size_label_80[] = "80 x 80"; +static const uint8_t size_label_100[] = "100 x 100"; +static const uint8_t size_label_120[] = "120 x 120"; +static const uint8_t size_label_160[] = "160 x 160"; + +static const uint8_t *terrain_generator_size_labels[TERRAIN_GENERATOR_SIZE_COUNT] = { + size_label_40, + size_label_60, + size_label_80, + size_label_100, + size_label_120, + size_label_160 +}; + +static const uint8_t *terrain_generator_algorithm_labels[TERRAIN_GENERATOR_COUNT]; + +static generic_button buttons[] = { + {CONTROL_VALUE_X, SIZE_BUTTON_Y, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, button_select_size, 0, 0}, + {CONTROL_VALUE_X, ALGORITHM_BUTTON_Y, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, button_select_algorithm, 0, 0}, + {ACTION_BUTTON_X, REGENERATE_BUTTON_Y, ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, button_regenerate, 0, 0}, + {ACTION_BUTTON_X, OPEN_EDITOR_BUTTON_Y, ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, button_open_editor, 0, 0}, + {ACTION_BUTTON_X, BACK_BUTTON_Y, ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, button_back, 0, 0} +}; + +static input_box seed_input = { + CONTROL_VALUE_X, + SEED_INPUT_Y, + 12, + 2, + FONT_NORMAL_WHITE, + 0, + NULL, + SEED_TEXT_LENGTH, + 0, + label_seed_placeholder, + NULL, + NULL, + INPUT_BOX_CHARS_NUMERIC +}; + +static struct { + unsigned int focus_button_id; + int size_index; + int algorithm_index; + uint8_t seed_text[SEED_TEXT_LENGTH]; +} data; + +static minimap_functions preview_minimap_functions; + +static void preview_viewport(int *x, int *y, int *width, int *height) +{ + *x = 0; + *y = 0; + *width = map_grid_width(); + *height = map_grid_height(); +} + +static void clear_map_data(void) +{ + map_image_clear(); + map_building_clear(); + map_terrain_clear(); + map_aqueduct_clear(); + map_figure_clear(); + map_property_clear(); + map_sprite_clear(); + map_random_clear(); + map_desirability_clear(); + map_elevation_clear(); + map_soldier_strength_clear(); + map_road_network_clear(); + + map_image_context_init(); + map_terrain_init_outside_map(); + map_random_init(); + map_property_init_alternate_terrain(); +} + +static int get_seed_value(unsigned int *seed_out) +{ + if (!string_length(data.seed_text)) { + return 0; + } + int seed = string_to_int(data.seed_text); + if (seed < 0) { + seed = -seed; + } + *seed_out = (unsigned int) seed; + return 1; +} + +static void generate_preview_map(void) +{ + scenario_editor_create(data.size_index); + scenario_map_init(); + clear_map_data(); + map_image_init_edges(); + + unsigned int seed = 0; + int use_seed = get_seed_value(&seed); + terrain_generator_set_seed(use_seed, seed); + terrain_generator_generate((terrain_generator_algorithm) data.algorithm_index); + map_tiles_update_all(); + + preview_minimap_functions.climate = scenario_property_climate; + preview_minimap_functions.map.width = map_grid_width; + preview_minimap_functions.map.height = map_grid_height; + preview_minimap_functions.offset.figure = 0; + preview_minimap_functions.offset.terrain = map_terrain_get; + preview_minimap_functions.offset.building_id = 0; + preview_minimap_functions.offset.is_draw_tile = map_property_is_draw_tile; + preview_minimap_functions.offset.tile_size = map_property_multi_tile_size; + preview_minimap_functions.offset.random = map_random_get; + preview_minimap_functions.building = 0; + preview_minimap_functions.viewport = preview_viewport; + + city_view_set_custom_lookup(scenario.map.grid_start, scenario.map.width, + scenario.map.height, scenario.map.grid_border_size); + widget_minimap_update(&preview_minimap_functions); + city_view_restore_lookup(); +} + +static void draw_background(void) +{ + image_draw_fullscreen_background(image_group(GROUP_INTERMEZZO_BACKGROUND) + 25); + + graphics_set_clip_rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + graphics_in_dialog(); + outer_panel_draw(0, 0, 40, 30); + text_draw_centered(translation_for(TR_MAIN_MENU_TERRAIN_GENERATOR), 32, 14, 554, FONT_LARGE_BLACK, 0); + + text_draw(label_size, CONTROL_LABEL_X, SIZE_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_algorithm, CONTROL_LABEL_X, ALGORITHM_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_seed, CONTROL_LABEL_X, SEED_INPUT_Y + 6, FONT_NORMAL_BLACK, 0); + + inner_panel_draw(PREVIEW_X - 8, PREVIEW_Y - 8, (PREVIEW_WIDTH + 16) / BLOCK_SIZE, + (PREVIEW_HEIGHT + 16) / BLOCK_SIZE); + widget_minimap_draw(PREVIEW_X, PREVIEW_Y, PREVIEW_WIDTH, PREVIEW_HEIGHT); + + graphics_reset_clip_rectangle(); + graphics_reset_dialog(); +} + +static void draw_foreground(void) +{ + graphics_in_dialog(); + + for (unsigned int i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++) { + button_border_draw(buttons[i].x, buttons[i].y, buttons[i].width, buttons[i].height, + data.focus_button_id == i + 1); + } + + text_draw_centered(terrain_generator_size_labels[data.size_index], + CONTROL_VALUE_X, SIZE_BUTTON_Y + 6, CONTROL_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + text_draw_centered(terrain_generator_algorithm_labels[data.algorithm_index], + CONTROL_VALUE_X, ALGORITHM_BUTTON_Y + 6, CONTROL_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + + text_draw_centered(label_regenerate, ACTION_BUTTON_X, REGENERATE_BUTTON_Y + 8, + ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + text_draw_centered(label_open_editor, ACTION_BUTTON_X, OPEN_EDITOR_BUTTON_Y + 8, + ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + text_draw_centered(label_back, ACTION_BUTTON_X, BACK_BUTTON_Y + 8, + ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + + input_box_draw(&seed_input); + + graphics_reset_dialog(); +} + +static void handle_input(const mouse *m, const hotkeys *h) +{ + const mouse *m_dialog = mouse_in_dialog(m); + data.focus_button_id = 0; + + if (input_box_is_accepted()) { + generate_preview_map(); + window_invalidate(); + return; + } + + if (input_box_handle_mouse(m_dialog, &seed_input) || + generic_buttons_handle_mouse(m_dialog, 0, 0, buttons, sizeof(buttons) / sizeof(buttons[0]), + &data.focus_button_id)) { + return; + } + + if (input_go_back_requested(m, h)) { + button_back(NULL); + } +} + +static void button_select_size(const generic_button *button) +{ + window_select_list_show_text(0, 0, button, terrain_generator_size_labels, + TERRAIN_GENERATOR_SIZE_COUNT, size_selected); +} + +static void button_select_algorithm(const generic_button *button) +{ + window_select_list_show_text(0, 0, button, terrain_generator_algorithm_labels, + TERRAIN_GENERATOR_COUNT, algorithm_selected); +} + +static void button_regenerate(const generic_button *button) +{ + (void) button; + generate_preview_map(); + window_invalidate(); +} + +static void button_open_editor(const generic_button *button) +{ + (void) button; + unsigned int seed = 0; + int use_seed = get_seed_value(&seed); + terrain_generator_set_seed(use_seed, seed); + + if (!editor_is_present() || + !game_init_editor_generated(data.size_index, data.algorithm_index)) { + window_plain_message_dialog_show(TR_NO_EDITOR_TITLE, TR_NO_EDITOR_MESSAGE, 1); + return; + } + + input_box_stop(&seed_input); + if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { + window_video_show("map_intro.smk", window_editor_map_show); + } + sound_music_play_editor(); +} + +static void button_back(const generic_button *button) +{ + (void) button; + input_box_stop(&seed_input); + window_go_back(); +} + +static void size_selected(int id) +{ + data.size_index = id; + generate_preview_map(); + window_invalidate(); +} + +static void algorithm_selected(int id) +{ + data.algorithm_index = id; + generate_preview_map(); + window_invalidate(); +} + +static void init(void) +{ + data.focus_button_id = 0; + data.size_index = 2; + data.algorithm_index = 0; + data.seed_text[0] = 0; + + seed_input.text = data.seed_text; + seed_input.allowed_chars = INPUT_BOX_CHARS_NUMERIC; + seed_input.placeholder = label_seed_placeholder; + + terrain_generator_algorithm_labels[0] = translation_for(TR_TERRAIN_GENERATOR_FLAT_PLAINS); + terrain_generator_algorithm_labels[1] = translation_for(TR_TERRAIN_GENERATOR_RIVER_VALLEY); + + input_box_start(&seed_input); + generate_preview_map(); +} + +void window_terrain_generator_show(void) +{ + window_type window = { + WINDOW_TERRAIN_GENERATOR, + draw_background, + draw_foreground, + handle_input + }; + init(); + window_show(&window); +} diff --git a/src/window/terrain_generator.h b/src/window/terrain_generator.h new file mode 100644 index 0000000000..cbc91c5297 --- /dev/null +++ b/src/window/terrain_generator.h @@ -0,0 +1,6 @@ +#ifndef WINDOW_TERRAIN_GENERATOR_H +#define WINDOW_TERRAIN_GENERATOR_H + +void window_terrain_generator_show(void); + +#endif // WINDOW_TERRAIN_GENERATOR_H From f4321d09c479368f07da322d33f563ec7d07cfc0 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 14:37:06 +0100 Subject: [PATCH 03/22] Update the generator algorithm --- src/scenario/terrain_generator.c | 176 ++++++++++++++++++++++++++++--- 1 file changed, 161 insertions(+), 15 deletions(-) diff --git a/src/scenario/terrain_generator.c b/src/scenario/terrain_generator.c index 17f18a2cc1..580e3da870 100644 --- a/src/scenario/terrain_generator.c +++ b/src/scenario/terrain_generator.c @@ -65,33 +65,179 @@ static void generate_river_valley(void) { int width = map_grid_width(); int height = map_grid_height(); - static int river_y[GRID_SIZE]; int river_half_width = 1; + int guard = 0; + int max_steps = width * height * 2; + int start_side = random_between(0, 4); + int end_side = random_between(0, 4); + while (end_side == start_side) { + end_side = random_between(0, 4); + } + + int start_x = 0; + int start_y = 0; + int end_x = 0; + int end_y = 0; + + switch (start_side) { + case 0: // north + start_y = 0; + start_x = random_between(1, width - 1); + break; + case 1: // south + start_y = height - 1; + start_x = random_between(1, width - 1); + break; + case 2: // west + start_x = 0; + start_y = random_between(1, height - 1); + break; + default: // east + start_x = width - 1; + start_y = random_between(1, height - 1); + break; + } + + switch (end_side) { + case 0: // north + end_y = 0; + end_x = random_between(1, width - 1); + break; + case 1: // south + end_y = height - 1; + end_x = random_between(1, width - 1); + break; + case 2: // west + end_x = 0; + end_y = random_between(1, height - 1); + break; + default: // east + end_x = width - 1; + end_y = random_between(1, height - 1); + break; + } - river_y[0] = height / 2; - for (int x = 1; x < width; x++) { - int drift = random_between(-1, 2); - river_y[x] = clamp_int(river_y[x - 1] + drift, 1, height - 2); + if (width > 2) { + start_x = clamp_int(start_x, 0, width - 1); + end_x = clamp_int(end_x, 0, width - 1); + } + if (height > 2) { + start_y = clamp_int(start_y, 0, height - 1); + end_y = clamp_int(end_y, 0, height - 1); } - for (int x = 0; x < width; x++) { + typedef struct { + int x; + int y; + } river_point; + + static river_point river_points[GRID_SIZE * GRID_SIZE]; + int river_count = 0; + + int x = start_x; + int y = start_y; + while ((x != end_x || y != end_y) && guard++ < max_steps) { + int dx = 0; + int dy = 0; + if (end_x > x) { + dx = 1; + } else if (end_x < x) { + dx = -1; + } + if (end_y > y) { + dy = 1; + } else if (end_y < y) { + dy = -1; + } + + int step_x = 0; + int step_y = 0; + int meander_roll = random_between(0, 100); + if (dx != 0 && dy != 0) { + if (meander_roll < 35) { + step_x = dx; + } else if (meander_roll < 70) { + step_y = dy; + } else if (meander_roll < 85) { + step_x = 0; + step_y = dy; + } else { + step_x = dx; + step_y = 0; + } + } else if (dx != 0) { + if (meander_roll < 20) { + step_y = random_between(0, 2) ? 1 : -1; + } else { + step_x = dx; + } + } else if (dy != 0) { + if (meander_roll < 20) { + step_x = random_between(0, 2) ? 1 : -1; + } else { + step_y = dy; + } + } + + int next_x = clamp_int(x + step_x, 0, width - 1); + int next_y = clamp_int(y + step_y, 0, height - 1); + if (next_x == x && next_y == y) { + if (dx != 0) { + next_x = clamp_int(x + dx, 0, width - 1); + } + if (dy != 0) { + next_y = clamp_int(y + dy, 0, height - 1); + } + } + + x = next_x; + y = next_y; + if (river_count < (GRID_SIZE * GRID_SIZE)) { + river_points[river_count].x = x; + river_points[river_count].y = y; + river_count++; + } + } + + if (river_count == 0) { + river_points[river_count].x = start_x; + river_points[river_count].y = start_y; + river_count++; + } + + for (int i = 0; i < river_count; i++) { + int rx = river_points[i].x; + int ry = river_points[i].y; for (int dy = -river_half_width; dy <= river_half_width; dy++) { - int y = river_y[x] + dy; - if (y < 0 || y >= height) { - continue; + for (int dx = -river_half_width; dx <= river_half_width; dx++) { + int wx = rx + dx; + int wy = ry + dy; + if (wx < 0 || wx >= width || wy < 0 || wy >= height) { + continue; + } + int grid_offset = map_grid_offset(wx, wy); + map_terrain_set(grid_offset, TERRAIN_WATER); + map_elevation_set(grid_offset, 0); } - map_terrain_set(map_grid_offset(x, y), TERRAIN_WATER); - map_elevation_set(map_grid_offset(x, y), 0); } } - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int grid_offset = map_grid_offset(x, y); + for (int yy = 0; yy < height; yy++) { + for (int xx = 0; xx < width; xx++) { + int grid_offset = map_grid_offset(xx, yy); if (map_terrain_is(grid_offset, TERRAIN_WATER)) { continue; } - int dist = abs(y - river_y[x]); + int dist = width + height; + for (int i = 0; i < river_count; i++) { + int d = abs(xx - river_points[i].x) + abs(yy - river_points[i].y); + if (d < dist) { + dist = d; + if (dist <= 1) { + break; + } + } + } int elevation = 0; if (dist > 2) { elevation = (dist - 2) / 6; From bc04bcc1e041e9b6a8e6f731b2d093988530ec09 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 16:07:15 +0100 Subject: [PATCH 04/22] Remove unused files for mow --- src/building/model.c | 362 -------- src/building/model.h | 70 -- src/empire/xml.c | 848 ------------------ src/empire/xml.h | 8 - src/game/file_editor.c | 12 + .../event/conditions/comparison_helper.h | 6 - .../conditions/condition_comparison_helper.c | 23 - .../event/conditions/condition_handler.c | 169 ---- .../event/conditions/condition_handler.h | 21 - .../event/conditions/condition_types.c | 471 ---------- .../event/conditions/condition_types.h | 55 -- 11 files changed, 12 insertions(+), 2033 deletions(-) delete mode 100644 src/building/model.c delete mode 100644 src/building/model.h delete mode 100644 src/empire/xml.c delete mode 100644 src/empire/xml.h delete mode 100644 src/scenario/event/conditions/comparison_helper.h delete mode 100644 src/scenario/event/conditions/condition_comparison_helper.c delete mode 100644 src/scenario/event/conditions/condition_handler.c delete mode 100644 src/scenario/event/conditions/condition_handler.h delete mode 100644 src/scenario/event/conditions/condition_types.c delete mode 100644 src/scenario/event/conditions/condition_types.h diff --git a/src/building/model.c b/src/building/model.c deleted file mode 100644 index 74178886b6..0000000000 --- a/src/building/model.c +++ /dev/null @@ -1,362 +0,0 @@ -#include "building/model.h" - -#include "city/resource.h" -#include "core/io.h" -#include "core/log.h" -#include "core/string.h" - -#include -#include - -#define TMP_BUFFER_SIZE 100000 - -#define NUM_BUILDINGS 130 -#define NUM_HOUSES 20 - -static const uint8_t ALL_BUILDINGS[] = { 'A', 'L', 'L', ' ', 'B', 'U', 'I', 'L', 'D', 'I', 'N', 'G', 'S', 0 }; -static const uint8_t ALL_HOUSES[] = { 'A', 'L', 'L', ' ', 'H', 'O', 'U', 'S', 'E', 'S', 0 }; - -static model_building buildings[NUM_BUILDINGS]; -static model_house houses[NUM_HOUSES]; - -static int strings_equal(const uint8_t *a, const uint8_t *b, int len) -{ - for (int i = 0; i < len; i++, a++, b++) { - if (*a != *b) { - return 0; - } - } - return 1; -} - -static int index_of_string(const uint8_t *haystack, const uint8_t *needle, int haystack_length) -{ - int needle_length = string_length(needle); - for (int i = 0; i < haystack_length; i++) { - if (haystack[i] == needle[0] && strings_equal(&haystack[i], needle, needle_length)) { - return i + 1; - } - } - return 0; -} - -static int index_of(const uint8_t *haystack, uint8_t needle, int haystack_length) -{ - for (int i = 0; i < haystack_length; i++) { - if (haystack[i] == needle) { - return i + 1; - } - } - return 0; -} - -static const uint8_t *skip_non_digits(const uint8_t *str) -{ - int safeguard = 0; - while (1) { - if (++safeguard >= 1000) { - break; - } - if ((*str >= '0' && *str <= '9') || *str == '-') { - break; - } - str++; - } - return str; -} - -static const uint8_t *get_value(const uint8_t *ptr, const uint8_t *end_ptr, int *value) -{ - ptr = skip_non_digits(ptr); - *value = string_to_int(ptr); - ptr += index_of(ptr, ',', (int) (end_ptr - ptr)); - return ptr; -} - -static void override_model_data(void) -{ - buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_value = 14; - buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_step = 2; - buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_step_size = -2; - buildings[BUILDING_LARGE_TEMPLE_CERES].desirability_range = 5; - - buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_value = 14; - buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_step = 2; - buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_step_size = -2; - buildings[BUILDING_LARGE_TEMPLE_NEPTUNE].desirability_range = 5; - - buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_value = 14; - buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_step = 2; - buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_step_size = -2; - buildings[BUILDING_LARGE_TEMPLE_MERCURY].desirability_range = 5; - - buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_value = 14; - buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_step = 2; - buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_step_size = -2; - buildings[BUILDING_LARGE_TEMPLE_MARS].desirability_range = 5; - - buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_value = 14; - buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_step = 2; - buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_step_size = -2; - buildings[BUILDING_LARGE_TEMPLE_VENUS].desirability_range = 5; - - buildings[BUILDING_WELL].laborers = 0; - buildings[BUILDING_GATEHOUSE].laborers = 0; - buildings[BUILDING_FORT_JAVELIN].laborers = 0; - buildings[BUILDING_FORT_LEGIONARIES].laborers = 0; - buildings[BUILDING_FORT_MOUNTED].laborers = 0; - buildings[BUILDING_FORT].laborers = 0; -} - -int model_load(void) -{ - uint8_t *buffer = (uint8_t *) malloc(TMP_BUFFER_SIZE); - if (!buffer) { - log_error("No memory for model", 0, 0); - return 0; - } - memset(buffer, 0, TMP_BUFFER_SIZE); - int filesize = io_read_file_into_buffer("c3_model.txt", NOT_LOCALIZED, buffer, TMP_BUFFER_SIZE); - if (filesize == 0) { - log_error("No c3_model.txt file", 0, 0); - free(buffer); - return 0; - } - - int num_lines = 0; - int guard = 200; - int brace_index; - const uint8_t *ptr = &buffer[index_of_string(buffer, ALL_BUILDINGS, filesize)]; - do { - guard--; - brace_index = index_of(ptr, '{', filesize); - if (brace_index) { - ptr += brace_index; - num_lines++; - } - } while (brace_index && guard > 0); - - if (num_lines != NUM_BUILDINGS + NUM_HOUSES) { - log_error("Model has incorrect no of lines ", 0, num_lines + 1); - free(buffer); - return 0; - } - - int dummy; - ptr = &buffer[index_of_string(buffer, ALL_BUILDINGS, filesize)]; - const uint8_t *end_ptr = &buffer[filesize]; - for (int i = 0; i < NUM_BUILDINGS; i++) { - ptr += index_of(ptr, '{', filesize); - - ptr = get_value(ptr, end_ptr, &buildings[i].cost); - ptr = get_value(ptr, end_ptr, &buildings[i].desirability_value); - ptr = get_value(ptr, end_ptr, &buildings[i].desirability_step); - ptr = get_value(ptr, end_ptr, &buildings[i].desirability_step_size); - ptr = get_value(ptr, end_ptr, &buildings[i].desirability_range); - ptr = get_value(ptr, end_ptr, &buildings[i].laborers); - ptr = get_value(ptr, end_ptr, &dummy); - ptr = get_value(ptr, end_ptr, &dummy); - } - - ptr = &buffer[index_of_string(buffer, ALL_HOUSES, filesize)]; - - for (int i = 0; i < NUM_HOUSES; i++) { - ptr += index_of(ptr, '{', filesize); - - ptr = get_value(ptr, end_ptr, &houses[i].devolve_desirability); - ptr = get_value(ptr, end_ptr, &houses[i].evolve_desirability); - ptr = get_value(ptr, end_ptr, &houses[i].entertainment); - ptr = get_value(ptr, end_ptr, &houses[i].water); - ptr = get_value(ptr, end_ptr, &houses[i].religion); - ptr = get_value(ptr, end_ptr, &houses[i].education); - ptr = get_value(ptr, end_ptr, &dummy); - ptr = get_value(ptr, end_ptr, &houses[i].barber); - ptr = get_value(ptr, end_ptr, &houses[i].bathhouse); - ptr = get_value(ptr, end_ptr, &houses[i].health); - ptr = get_value(ptr, end_ptr, &houses[i].food_types); - ptr = get_value(ptr, end_ptr, &houses[i].pottery); - ptr = get_value(ptr, end_ptr, &houses[i].oil); - ptr = get_value(ptr, end_ptr, &houses[i].furniture); - ptr = get_value(ptr, end_ptr, &houses[i].wine); - ptr = get_value(ptr, end_ptr, &dummy); - ptr = get_value(ptr, end_ptr, &dummy); - ptr = get_value(ptr, end_ptr, &houses[i].prosperity); - ptr = get_value(ptr, end_ptr, &houses[i].max_people); - ptr = get_value(ptr, end_ptr, &houses[i].tax_multiplier); - } - - override_model_data(); - - log_info("Model loaded", 0, 0); - free(buffer); - return 1; -} - -const model_building MODEL_ROADBLOCK = { 12,0,0,0,0 }; -const model_building MODEL_WORK_CAMP = { 150,-10,2,3,4,20 }; -const model_building MODEL_ARCHITECT_GUILD = { 200,-8,1,2,4,12 }; -const model_building MODEL_GRAND_TEMPLE_CERES = { 2500,20,2,-4,5,50 }; -const model_building MODEL_GRAND_TEMPLE_NEPTUNE = { 2500,20,2,-4,5,50 }; -const model_building MODEL_GRAND_TEMPLE_MERCURY = { 2500,20,2,-4,5,50 }; -const model_building MODEL_GRAND_TEMPLE_MARS = { 2500,20,2,-4,5,50 }; -const model_building MODEL_GRAND_TEMPLE_VENUS = { 2500,20,2,-4,5,50 }; -const model_building MODEL_PANTHEON = { 3500,20,2,-4,5,50 }; -const model_building MODEL_LIGHTHOUSE = { 1000,6,1,-1,4,20 }; -const model_building MODEL_MESS_HALL = { 100,-8,1,2,4,10 }; -const model_building MODEL_TAVERN = { 40,-2,1,1,6,8 }; -const model_building MODEL_GRAND_GARDEN = { 400,0,0,0,0,0 }; -const model_building MODEL_ARENA = { 500,-3,1,1,3,25 }; -const model_building MODEL_COLOSSEUM = { 1500,-3,1,1,3,100 }; -const model_building MODEL_HIPPODROME = { 3500,-3,1,1,3,150 }; -const model_building MODEL_NULL = { 0,0,0,0,0 }; -const model_building MODEL_LARARIUM = { 45, 4, 1, -1, 3, 0 }; -const model_building MODEL_NYMPHAEUM = { 400,12,2,-1,6,0 }; -const model_building MODEL_SMALL_MAUSOLEUM = { 250,-8,1,3,5,0 }; -const model_building MODEL_LARGE_MAUSOLEUM = { 500,-10,1,3,6,0 }; -const model_building MODEL_WATCHTOWER = { 100,-6,1,2,3,8, }; -const model_building MODEL_CARAVANSERAI = { 500,-10,2,3,4,20 }; -const model_building MODEL_PALISADE = { 6,0,0,0,0,0 }; -const model_building MODEL_HIGHWAY = { 100,-4,1,2,3,0 }; -const model_building MODEL_GOLD_MINE = { 100,-6,1,1,4,30 }; -const model_building MODEL_STONE_QUARRY = { 60,-6,1,1,4,10 }; -const model_building MODEL_SAND_PIT = { 40,-6,1,1,4,10 }; -const model_building MODEL_BRICKWORKS = { 80,-3,1,1,4,10 }; -const model_building MODEL_CONCRETE_MAKER = { 60,-3,1,1,4,10 }; -const model_building MODEL_CITY_MINT = { 250,-3,1,1,3,40 }; -const model_building MODEL_DEPOT = { 100,-3,1,1,2,15 }; -const model_building MODEL_ARMOURY = { 50,-5,1,1,4,6 }; - -const model_building *model_get_building(building_type type) -{ - switch (type) { - case BUILDING_ROADBLOCK: - case BUILDING_ROOFED_GARDEN_WALL_GATE: - case BUILDING_LOOPED_GARDEN_GATE: - case BUILDING_PANELLED_GARDEN_GATE: - case BUILDING_HEDGE_GATE_DARK: - case BUILDING_HEDGE_GATE_LIGHT: - return &MODEL_ROADBLOCK; - case BUILDING_WORKCAMP: - return &MODEL_WORK_CAMP; - case BUILDING_ARCHITECT_GUILD: - return &MODEL_ARCHITECT_GUILD; - case BUILDING_GRAND_TEMPLE_CERES: - return &MODEL_GRAND_TEMPLE_CERES; - case BUILDING_GRAND_TEMPLE_NEPTUNE: - return &MODEL_GRAND_TEMPLE_NEPTUNE; - case BUILDING_GRAND_TEMPLE_MERCURY: - return &MODEL_GRAND_TEMPLE_MERCURY; - case BUILDING_GRAND_TEMPLE_MARS: - return &MODEL_GRAND_TEMPLE_MARS; - case BUILDING_GRAND_TEMPLE_VENUS: - return &MODEL_GRAND_TEMPLE_VENUS; - case BUILDING_PANTHEON: - return &MODEL_PANTHEON; - case BUILDING_MESS_HALL: - return &MODEL_MESS_HALL; - case BUILDING_LIGHTHOUSE: - return &MODEL_LIGHTHOUSE; - case BUILDING_TAVERN: - return &MODEL_TAVERN; - case BUILDING_GRAND_GARDEN: - return &MODEL_GRAND_GARDEN; - case BUILDING_ARENA: - return &MODEL_ARENA; - case BUILDING_COLOSSEUM: - return &MODEL_COLOSSEUM; - case BUILDING_HIPPODROME: - return &MODEL_HIPPODROME; - case BUILDING_LARARIUM: - case BUILDING_SHRINE_CERES: - case BUILDING_SHRINE_MARS: - case BUILDING_SHRINE_MERCURY: - case BUILDING_SHRINE_NEPTUNE: - case BUILDING_SHRINE_VENUS: - return &MODEL_LARARIUM; - case BUILDING_NYMPHAEUM: - return &MODEL_NYMPHAEUM; - case BUILDING_WATCHTOWER: - return &MODEL_WATCHTOWER; - case BUILDING_SMALL_MAUSOLEUM: - return &MODEL_SMALL_MAUSOLEUM; - case BUILDING_LARGE_MAUSOLEUM: - return &MODEL_LARGE_MAUSOLEUM; - case BUILDING_CARAVANSERAI: - return &MODEL_CARAVANSERAI; - case BUILDING_PALISADE: - case BUILDING_PALISADE_GATE: - return &MODEL_PALISADE; - case BUILDING_HIGHWAY: - return &MODEL_HIGHWAY; - case BUILDING_GOLD_MINE: - return &MODEL_GOLD_MINE; - case BUILDING_STONE_QUARRY: - return &MODEL_STONE_QUARRY; - case BUILDING_SAND_PIT: - return &MODEL_SAND_PIT; - case BUILDING_BRICKWORKS: - return &MODEL_BRICKWORKS; - case BUILDING_CONCRETE_MAKER: - return &MODEL_CONCRETE_MAKER; - case BUILDING_CITY_MINT: - return &MODEL_CITY_MINT; - case BUILDING_DEPOT: - return &MODEL_DEPOT; - case BUILDING_OVERGROWN_GARDENS: - return &buildings[BUILDING_GARDENS]; - case BUILDING_ARMOURY: - return &MODEL_ARMOURY; - default: - break; - } - - if ((type >= BUILDING_PINE_TREE && type <= BUILDING_SENATOR_STATUE) || - type == BUILDING_HEDGE_DARK || type == BUILDING_HEDGE_LIGHT || - type == BUILDING_DECORATIVE_COLUMN || type == BUILDING_LOOPED_GARDEN_WALL || - type == BUILDING_COLONNADE || type == BUILDING_LOOPED_GARDEN_WALL || - type == BUILDING_ROOFED_GARDEN_WALL || type == BUILDING_GARDEN_PATH || - type == BUILDING_PANELLED_GARDEN_WALL || type == BUILDING_GLADIATOR_STATUE) { - return &buildings[BUILDING_SMALL_STATUE]; - } - - if (type == BUILDING_SMALL_POND || type == BUILDING_OBELISK || - type == BUILDING_LEGION_STATUE || type == BUILDING_DOLPHIN_FOUNTAIN) { - return &buildings[BUILDING_MEDIUM_STATUE]; - } - - if (type == BUILDING_LARGE_POND || type == BUILDING_HORSE_STATUE) { - return &buildings[BUILDING_LARGE_STATUE]; - } - - if (type == BUILDING_FORT_AUXILIA_INFANTRY || type == BUILDING_FORT_ARCHERS) { - return &buildings[BUILDING_FORT_LEGIONARIES]; - } - - if (type > 129) { - return &MODEL_NULL; - } else { - return &buildings[type]; - } -} - -const model_house *model_get_house(house_level level) -{ - return &houses[level]; -} - -int model_house_uses_inventory(house_level level, resource_type inventory) -{ - const model_house *house = model_get_house(level); - switch (inventory) { - case RESOURCE_WINE: - return house->wine; - case RESOURCE_OIL: - return house->oil; - case RESOURCE_FURNITURE: - return house->furniture; - case RESOURCE_POTTERY: - return house->pottery; - default: - return 0; - } -} diff --git a/src/building/model.h b/src/building/model.h deleted file mode 100644 index bb6ff0381f..0000000000 --- a/src/building/model.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef BUILDING_MODEL_H -#define BUILDING_MODEL_H - -#include "building/type.h" -#include "city/resource.h" - -/** - * @file - * Building models. - */ - -/** - * Building model - */ -typedef struct { - int cost; /**< Cost of structure or of one tile of a structure (for walls) */ - int desirability_value; /**< Initial desirability value */ - int desirability_step; /**< Desirability step (in tiles) */ - int desirability_step_size; /**< Desirability step size */ - int desirability_range; /**< Max desirability range */ - int laborers; /**< Number of people a building employs */ -} model_building; - -/** - * House model - */ -typedef struct { - int devolve_desirability; /**< Desirability at which the house devolves */ - int evolve_desirability; /**< Desirability at which the house evolves */ - int entertainment; /**< Entertainment points required */ - int water; /**< Water required: 1 = well, 2 = fountain */ - int religion; /**< Number of gods required */ - int education; /**< Education required: - 1 = school or library, 2 = school and library, 3 = school, library and academy */ - int barber; /**< Barber required (boolean) */ - int bathhouse; /**< Bathhouse required (boolean) */ - int health; /**< Health required: 1 = doctor or hospital, 2 = doctor and hospital */ - int food_types; /**< Number of food types required */ - int pottery; /**< Pottery required */ - int oil; /**< Oil required */ - int furniture; /**< Furniture required */ - int wine; /**< Wine types required: 1 = any wine, 2 = two types of wine */ - int prosperity; /**< Prosperity contribution */ - int max_people; /**< Maximum people per tile (medium insula and lower) or per house (large insula and up) */ - int tax_multiplier; /**< Tax rate multiplier */ -} model_house; - -/** - * Loads the model from file - * @return boolean true if model was loaded successfully, false otherwise - */ -int model_load(void); - -/** - * Gets the model for a building - * @param type Building type - * @return Read-only model - */ -const model_building *model_get_building(building_type type); - -/** - * Gets the model for a house - * @param level House level - * @return Read-only model - */ -const model_house *model_get_house(house_level level); - -int model_house_uses_inventory(house_level level, resource_type inventory); - -#endif // BUILDING_MODEL_H diff --git a/src/empire/xml.c b/src/empire/xml.c deleted file mode 100644 index 8635d0354d..0000000000 --- a/src/empire/xml.c +++ /dev/null @@ -1,848 +0,0 @@ -#include "xml.h" - -#include "assets/assets.h" -#include "core/array.h" -#include "core/buffer.h" -#include "core/calc.h" -#include "core/file.h" -#include "core/image_group.h" -#include "core/image_group_editor.h" -#include "core/log.h" -#include "core/string.h" -#include "core/xml_parser.h" -#include "core/zlib_helper.h" -#include "editor/editor.h" -#include "empire/city.h" -#include "empire/empire.h" -#include "empire/object.h" -#include "empire/trade_route.h" -#include "scenario/data.h" -#include "scenario/empire.h" - -#include -#include -#include - -#define XML_TOTAL_ELEMENTS 19 -#define BASE_BORDER_FLAG_IMAGE_ID 3323 -#define BASE_ORNAMENT_IMAGE_ID 3356 -#define BORDER_EDGE_DEFAULT_SPACING 50 -#define ORIGINAL_ORNAMENTS 20 - -typedef enum { - LIST_NONE = -1, - LIST_BUYS = 1, - LIST_SELLS = 2, - LIST_TRADE_WAYPOINTS = 3 -} city_list; - -typedef enum { - DISTANT_BATTLE_PATH_NONE, - DISTANT_BATTLE_PATH_ROMAN, - DISTANT_BATTLE_PATH_ENEMY -} distant_battle_path_type; - -enum { - BORDER_STATUS_NONE = 0, - BORDER_STATUS_CREATING, - BORDER_STATUS_DONE -}; - -typedef struct { - int x; - int y; - int num_months; -} waypoint; - -static const char *ORNAMENTS[] = { - "The Stonehenge", - "Gallic Wheat", - "The Pyrenees", - "Iberian Aqueduct", - "Triumphal Arch", - "West Desert Wheat", - "Lighthouse of Alexandria", - "West Desert Palm Trees", - "Trade Ship", - "Waterside Palm Trees", - "Colosseum|The Colosseum", - "The Alps", - "Roman Tree", - "Greek Mountain Range", - "The Parthenon", - "The Pyramids", - "The Hagia Sophia", - "East Desert Palm Trees", - "East Desert Wheat", - "Trade Camel", - "Mount Etna", - "Colossus of Rhodes" -}; - -#define TOTAL_ORNAMENTS (sizeof(ORNAMENTS) / sizeof(const char *)) - -map_point ORNAMENT_POSITIONS[TOTAL_ORNAMENTS] = { - { 247, 81 }, { 361, 356 }, { 254, 428 }, { 199, 590 }, { 275, 791 }, - { 423, 802 }, { 1465, 883 }, { 518, 764 }, { 691, 618 }, { 742, 894 }, - { 726, 468 }, { 502, 280 }, { 855, 551 }, { 1014, 443 }, { 1158, 698 }, - { 1431, 961 }, { 1300, 500 }, { 1347, 648 }, { 1707, 783 }, { 1704, 876 }, - { 829, 720 }, { 1347, 745 } -}; - -static struct { - int success; - int version; - int current_city_id; - city_list current_city_list; - int has_vulnerable_city; - int current_invasion_path_id; - array(int) invasion_path_ids; - distant_battle_path_type distant_battle_path_type; - array(waypoint) distant_battle_waypoints; - int border_status; - char added_ornaments[TOTAL_ORNAMENTS]; -} data; - -static int xml_start_empire(void); -static int xml_start_map(void); -static int xml_start_coords(void); -static int xml_start_ornament(void); -static int xml_start_border(void); -static int xml_start_border_edge(void); -static int xml_start_city(void); -static int xml_start_buys(void); -static int xml_start_sells(void); -static int xml_start_waypoints(void); -static int xml_start_resource(void); -static int xml_start_trade_point(void); -static int xml_start_invasion_path(void); -static int xml_start_battle(void); -static int xml_start_distant_battle_path(void); -static int xml_start_distant_battle_waypoint(void); - -static void xml_end_border(void); -static void xml_end_city(void); -static void xml_end_sells_buys_or_waypoints(void); -static void xml_end_invasion_path(void); -static void xml_end_distant_battle_path(void); - -static const xml_parser_element xml_elements[XML_TOTAL_ELEMENTS] = { - { "empire", xml_start_empire }, - { "map", xml_start_map, 0, "empire" }, - { "coordinates", xml_start_coords, 0, "map" }, - { "ornament", xml_start_ornament, 0, "empire|map" }, - { "border", xml_start_border, xml_end_border, "empire" }, - { "edge", xml_start_border_edge, 0, "border" }, - { "cities", 0, 0, "empire" }, - { "city", xml_start_city, xml_end_city, "cities" }, - { "buys", xml_start_buys, xml_end_sells_buys_or_waypoints, "city" }, - { "sells", xml_start_sells, xml_end_sells_buys_or_waypoints, "city" }, - { "resource", xml_start_resource, 0, "buys|sells" }, - { "trade_points", xml_start_waypoints, xml_end_sells_buys_or_waypoints, "city" }, - { "point", xml_start_trade_point, 0, "trade_points" }, - { "invasion_paths", 0, 0, "empire" }, - { "path", xml_start_invasion_path, xml_end_invasion_path, "invasion_paths"}, - { "battle", xml_start_battle, 0, "path"}, - { "distant_battle_paths", 0, 0, "empire" }, - { "path", xml_start_distant_battle_path, xml_end_distant_battle_path, "distant_battle_paths" }, - { "waypoint", xml_start_distant_battle_waypoint, 0, "path" }, -}; - -static resource_type get_resource_from_attr(const char *key) -{ - const char *value = xml_parser_get_attribute_string(key); - if (!value) { - return RESOURCE_NONE; - } - for (resource_type i = RESOURCE_MIN; i < RESOURCE_MAX; i++) { - const char *resource_name = resource_get_data(i)->xml_attr_name; - if (xml_parser_compare_multiple(resource_name, value)) { - return i; - } - } - return RESOURCE_NONE; -} - -static int xml_start_empire(void) -{ - data.version = xml_parser_get_attribute_int("version"); - if (!data.version) { - data.success = 0; - log_error("No version set", 0, 0); - return 0; - } - if (data.version == 1 && xml_parser_get_attribute_bool("show_ireland")) { - full_empire_object *obj = empire_object_get_new(); - obj->in_use = 1; - obj->obj.type = EMPIRE_OBJECT_ORNAMENT; - obj->obj.image_id = -1; - } - return 1; -} - -static int xml_start_map(void) -{ - if (data.version < 2) { - log_info("Custom maps only work on version 2 and higher", 0, 0); - return 1; - } - const char *filename = xml_parser_get_attribute_string("image"); - int x_offset = xml_parser_get_attribute_int("x_offset"); - int y_offset = xml_parser_get_attribute_int("y_offset"); - int width = xml_parser_get_attribute_int("width"); - int height = xml_parser_get_attribute_int("height"); - - empire_set_custom_map(filename, x_offset, y_offset, width, height); - - if (xml_parser_get_attribute_bool("show_ireland")) { - if (empire_get_image_id() != image_group(editor_is_active() ? GROUP_EDITOR_EMPIRE_MAP : GROUP_EMPIRE_MAP)) { - log_info("Ireland image cannot be enabled on custom maps", 0, 0); - return 1; - } - full_empire_object *obj = empire_object_get_new(); - obj->in_use = 1; - obj->obj.type = EMPIRE_OBJECT_ORNAMENT; - obj->obj.image_id = -1; - } - - return 1; -} - -static int xml_start_coords(void) -{ - int relative = xml_parser_get_attribute_bool("relative"); - int x_offset = xml_parser_get_attribute_int("x_offset"); - int y_offset = xml_parser_get_attribute_int("y_offset"); - - empire_set_coordinates(relative, x_offset, y_offset); - return 1; -} - -static void add_ornament(int ornament_id) -{ - if (data.added_ornaments[ornament_id]) { - return; - } - data.added_ornaments[ornament_id] = 1; - full_empire_object *obj = empire_object_get_new(); - if (!obj) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return; - } - obj->in_use = 1; - obj->obj.type = EMPIRE_OBJECT_ORNAMENT; - if (ornament_id < ORIGINAL_ORNAMENTS) { - obj->obj.image_id = BASE_ORNAMENT_IMAGE_ID + ornament_id; - } else { - obj->obj.image_id = ORIGINAL_ORNAMENTS - ornament_id - 2; - } - obj->obj.x = ORNAMENT_POSITIONS[ornament_id].x; - obj->obj.y = ORNAMENT_POSITIONS[ornament_id].y; -} - -static int xml_start_ornament(void) -{ - const char *parent_name = xml_parser_get_parent_element_name(); - if (data.version >= 2 && parent_name && strcmp(parent_name, "empire") == 0) { - log_info("Ornaments should go inside the map tag on version 2 and later", 0, 0); - return 1; - } - if (empire_get_image_id() != image_group(editor_is_active() ? GROUP_EDITOR_EMPIRE_MAP : GROUP_EMPIRE_MAP)) { - log_info("Ornaments are not shown on custom maps", 0, 0); - return 1; - } - if (!xml_parser_has_attribute("type")) { - log_info("No ornament type specified", 0, 0); - return 1; - } - int ornament_id = xml_parser_get_attribute_enum("type", ORNAMENTS, TOTAL_ORNAMENTS, 0); - if (ornament_id == -1) { - if (strcmp("all", xml_parser_get_attribute_string("type")) == 0) { - for (int i = 0; i < (int) TOTAL_ORNAMENTS; i++) { - add_ornament(i); - } - } else { - log_info("Invalid ornament type specified", 0, 0); - } - } else { - add_ornament(ornament_id); - } - return 1; -} - -static int xml_start_border(void) -{ - if (data.border_status != BORDER_STATUS_NONE) { - data.success = 0; - log_error("Border is being set twice", 0, 0); - return 0; - } - full_empire_object *obj = empire_object_get_new(); - if (!obj) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - obj->in_use = 1; - obj->obj.type = EMPIRE_OBJECT_BORDER; - obj->obj.width = xml_parser_get_attribute_int("density"); - if (obj->obj.width == 0) { - obj->obj.width = BORDER_EDGE_DEFAULT_SPACING; - } - data.border_status = BORDER_STATUS_CREATING; - return 1; -} - -static int xml_start_border_edge(void) -{ - if (data.border_status != BORDER_STATUS_CREATING) { - data.success = 0; - log_error("Border edge is being wrongly added", 0, 0); - return 0; - } - full_empire_object *obj = empire_object_get_new(); - if (!obj) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - obj->in_use = 1; - obj->obj.type = EMPIRE_OBJECT_BORDER_EDGE; - obj->obj.x = xml_parser_get_attribute_int("x"); - obj->obj.y = xml_parser_get_attribute_int("y"); - empire_transform_coordinates(&obj->obj.x, &obj->obj.y); - obj->obj.image_id = xml_parser_get_attribute_bool("hidden") ? 0 : BASE_BORDER_FLAG_IMAGE_ID; - - return 1; -} - -static int xml_start_city(void) -{ - full_empire_object *city_obj = empire_object_get_new(); - - if (!city_obj) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - - data.current_city_id = city_obj->obj.id; - city_obj->in_use = 1; - city_obj->obj.type = EMPIRE_OBJECT_CITY; - city_obj->city_type = EMPIRE_CITY_TRADE; - city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_TRADE); - city_obj->trade_route_cost = 500; - - static const char *city_types[6] = { "roman", "ours", "trade", "future_trade", "distant", "vulnerable" }; - static const char *trade_route_types[2] = { "land", "sea" }; - - const char *name = xml_parser_get_attribute_string("name"); - if (name) { - string_copy(string_from_ascii(name), city_obj->city_custom_name, sizeof(city_obj->city_custom_name)); - } - - int city_type = xml_parser_get_attribute_enum("type", city_types, 6, EMPIRE_CITY_DISTANT_ROMAN); - if (city_type < EMPIRE_CITY_DISTANT_ROMAN) { - city_obj->city_type = EMPIRE_CITY_TRADE; - } else { - city_obj->city_type = city_type; - } - switch (city_obj->city_type) { - case EMPIRE_CITY_OURS: - city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY); - break; - case EMPIRE_CITY_FUTURE_TRADE: - case EMPIRE_CITY_DISTANT_ROMAN: - city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_DISTANT_ROMAN); - break; - case EMPIRE_CITY_DISTANT_FOREIGN: - city_obj->obj.image_id = image_group(GROUP_EMPIRE_FOREIGN_CITY); - break; - case EMPIRE_CITY_VULNERABLE_ROMAN: - city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_DISTANT_ROMAN); - data.has_vulnerable_city = 1; - break; - default: - city_obj->obj.image_id = image_group(GROUP_EMPIRE_CITY_TRADE); - break; - } - - const image *img = image_get(city_obj->obj.image_id); - - city_obj->obj.width = img->width; - city_obj->obj.height = img->height; - - city_obj->obj.x = xml_parser_get_attribute_int("x") - city_obj->obj.width / 2; - city_obj->obj.y = xml_parser_get_attribute_int("y") - city_obj->obj.height / 2; - empire_transform_coordinates(&city_obj->obj.x, &city_obj->obj.y); - - if (city_obj->city_type == EMPIRE_CITY_TRADE || city_obj->city_type == EMPIRE_CITY_FUTURE_TRADE) { - full_empire_object *route_obj = empire_object_get_new(); - if (!route_obj) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - route_obj->in_use = 1; - route_obj->obj.type = EMPIRE_OBJECT_LAND_TRADE_ROUTE; - - route_obj->obj.type = xml_parser_get_attribute_enum("trade_route_type", - trade_route_types, 2, EMPIRE_OBJECT_LAND_TRADE_ROUTE); - if (route_obj->obj.type < EMPIRE_OBJECT_LAND_TRADE_ROUTE) { - route_obj->obj.type = EMPIRE_OBJECT_LAND_TRADE_ROUTE; - } - if (route_obj->obj.type == EMPIRE_OBJECT_SEA_TRADE_ROUTE) { - route_obj->obj.image_id = image_group(GROUP_EMPIRE_TRADE_ROUTE_TYPE); - } else { - route_obj->obj.image_id = image_group(GROUP_EMPIRE_TRADE_ROUTE_TYPE) + 1; - } - - city_obj->trade_route_cost = xml_parser_get_attribute_int("trade_route_cost"); - if (!city_obj->trade_route_cost) { - city_obj->trade_route_cost = 500; - } - } - - return 1; -} - -static int xml_start_buys(void) -{ - data.current_city_list = LIST_BUYS; - return 1; -} - -static int xml_start_sells(void) -{ - data.current_city_list = LIST_SELLS; - return 1; -} - -static int xml_start_waypoints(void) -{ - data.current_city_list = LIST_TRADE_WAYPOINTS; - return 1; -} - -static int xml_start_resource(void) -{ - if (data.current_city_id == -1) { - data.success = 0; - log_error("No active city when parsing resource", 0, 0); - return 0; - } else if (data.current_city_list != LIST_BUYS && data.current_city_list != LIST_SELLS) { - data.success = 0; - log_error("Resource not in buy or sell tag", 0, 0); - return 0; - } - - full_empire_object *city_obj = empire_object_get_full(data.current_city_id); - - if (!xml_parser_has_attribute("type")) { - data.success = 0; - log_error("Unable to find resource type attribute", 0, 0); - return 0; - } - resource_type resource = get_resource_from_attr("type"); - if (resource == RESOURCE_NONE) { - data.success = 0; - log_error("Unable to determine resource type", xml_parser_get_attribute_string("type"), 0); - return 0; - } - - int amount = xml_parser_has_attribute("amount") ? - xml_parser_get_attribute_int("amount") : 1; - - if (data.current_city_list == LIST_BUYS) { - city_obj->city_buys_resource[resource] = amount; - } else if (data.current_city_list == LIST_SELLS) { - city_obj->city_sells_resource[resource] = amount; - } - - return 1; -} - -static int xml_start_trade_point(void) -{ - if (data.current_city_id == -1) { - data.success = 0; - log_error("No active city when parsing trade point", 0, 0); - return 0; - } else if (data.current_city_list != LIST_TRADE_WAYPOINTS) { - data.success = 0; - log_error("Trade point not trade_points tag", 0, 0); - return 0; - } else if (!empire_object_get_full(data.current_city_id)->trade_route_cost) { - data.success = 0; - log_error("Attempting to parse trade point in a city that can't trade", 0, 0); - return 0; - } - - full_empire_object *obj = empire_object_get_new(); - if (!obj) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - obj->in_use = 1; - obj->obj.type = EMPIRE_OBJECT_TRADE_WAYPOINT; - obj->obj.x = xml_parser_get_attribute_int("x"); - obj->obj.y = xml_parser_get_attribute_int("y"); - empire_transform_coordinates(&obj->obj.x, &obj->obj.y); - - return 1; -} - -static int xml_start_invasion_path(void) -{ - data.current_invasion_path_id++; - return 1; -} - -static int xml_start_battle(void) -{ - if (!data.current_invasion_path_id) { - data.success = 0; - log_error("Battle not in path tag", 0, 0); - return 0; - } - int *battle_id = array_advance(data.invasion_path_ids); - if (!battle_id) { - data.success = 0; - log_error("Error creating invasion path - out of memory", 0, 0); - return 0; - } - - full_empire_object *battle_obj = empire_object_get_new(); - if (!battle_obj) { - data.invasion_path_ids.size--; - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - *battle_id = battle_obj->obj.id; - battle_obj->in_use = 1; - battle_obj->obj.type = EMPIRE_OBJECT_BATTLE_ICON; - battle_obj->obj.invasion_path_id = data.current_invasion_path_id; - battle_obj->obj.image_id = image_group(GROUP_EMPIRE_BATTLE); - battle_obj->obj.x = xml_parser_get_attribute_int("x"); - battle_obj->obj.y = xml_parser_get_attribute_int("y"); - empire_transform_coordinates(&battle_obj->obj.x, &battle_obj->obj.y); - - return 1; -} - -static int xml_start_distant_battle_path(void) -{ - if (!data.has_vulnerable_city) { - data.success = 0; - log_error("Must have a vulnerable city to set up distant battle paths", 0, 0); - return 0; - } else if (!xml_parser_has_attribute("type")) { - data.success = 0; - log_error("Unable to find type attribute on distant battle path", 0, 0); - return 0; - } else if (!xml_parser_has_attribute("start_x")) { - data.success = 0; - log_error("Unable to find start_x attribute on distant battle path", 0, 0); - return 0; - } else if (!xml_parser_has_attribute("start_y")) { - data.success = 0; - log_error("Unable to find start_y attribute on distant battle path", 0, 0); - return 0; - } - - const char *type = xml_parser_get_attribute_string("type"); - if (strcmp(type, "roman") == 0) { - data.distant_battle_path_type = DISTANT_BATTLE_PATH_ROMAN; - } else if (strcmp(type, "enemy") == 0) { - data.distant_battle_path_type = DISTANT_BATTLE_PATH_ENEMY; - } else { - data.success = 0; - log_error("Distant battle path type must be \"roman\" or \"enemy\"", type, 0); - return 0; - } - - waypoint *w = array_advance(data.distant_battle_waypoints); - if (!w) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - w->x = xml_parser_get_attribute_int("start_x"); - w->y = xml_parser_get_attribute_int("start_y"); - empire_transform_coordinates(&w->x, &w->y); - w->num_months = 0; - return 1; -} - -static int xml_start_distant_battle_waypoint(void) -{ - if (!xml_parser_has_attribute("num_months")) { - data.success = 0; - log_error("Unable to find num_months attribute on distant battle path", 0, 0); - return 0; - } else if (!xml_parser_has_attribute("x")) { - data.success = 0; - log_error("Unable to find x attribute on distant battle path", 0, 0); - return 0; - } else if (!xml_parser_has_attribute("y")) { - data.success = 0; - log_error("Unable to find y attribute on distant battle path", 0, 0); - return 0; - } - - waypoint *w = array_advance(data.distant_battle_waypoints); - if (!w) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return 0; - } - w->x = xml_parser_get_attribute_int("x"); - w->y = xml_parser_get_attribute_int("y"); - empire_transform_coordinates(&w->x, &w->y); - w->num_months = xml_parser_get_attribute_int("num_months"); - return 1; -} - -static void xml_end_border(void) -{ - if (data.border_status == BORDER_STATUS_CREATING) { - data.border_status = BORDER_STATUS_DONE; - } -} - -static void xml_end_city(void) -{ - data.current_city_id = -1; - data.current_city_list = LIST_NONE; -} - -static void xml_end_sells_buys_or_waypoints(void) -{ - data.current_city_list = LIST_NONE; -} - -static void xml_end_invasion_path(void) -{ - for (int i = data.invasion_path_ids.size - 1; i >= 0; i--) { - full_empire_object *battle = empire_object_get_full(*array_item(data.invasion_path_ids, i)); - battle->obj.invasion_years = i + 1; - } - data.invasion_path_ids.size = 0; -} - -static void xml_end_distant_battle_path(void) -{ - empire_object_type obj_type = 0; - int image_id = 0; - if (data.distant_battle_path_type == DISTANT_BATTLE_PATH_ROMAN) { - obj_type = EMPIRE_OBJECT_ROMAN_ARMY; - image_id = GROUP_EMPIRE_ROMAN_ARMY; - } else if (data.distant_battle_path_type == DISTANT_BATTLE_PATH_ENEMY) { - obj_type = EMPIRE_OBJECT_ENEMY_ARMY; - image_id = GROUP_EMPIRE_ENEMY_ARMY; - } else { - data.success = 0; - log_error("Invalid distant battle path type", 0, data.distant_battle_path_type); - return; - } - - int month = 1; - for (int i = 1; i < data.distant_battle_waypoints.size; i++) { - waypoint *last = array_item(data.distant_battle_waypoints, i - 1); - waypoint *current = array_item(data.distant_battle_waypoints, i); - int x_diff = current->x - last->x; - int y_diff = current->y - last->y; - for (int j = 0; j < current->num_months; j++) { - full_empire_object *army_obj = empire_object_get_new(); - if (!army_obj) { - data.success = 0; - log_error("Error creating new object - out of memory", 0, 0); - return; - } - army_obj->in_use = 1; - army_obj->obj.type = obj_type; - army_obj->obj.image_id = image_group(image_id); - army_obj->obj.x = (int) ((double) j / current->num_months * x_diff + last->x); - army_obj->obj.y = (int) ((double)j / current->num_months * y_diff + last->y); - army_obj->obj.distant_battle_travel_months = month; - month++; - } - } - data.distant_battle_path_type = DISTANT_BATTLE_PATH_NONE; - data.distant_battle_waypoints.size = 0; -} - -static void reset_data(void) -{ - data.success = 1; - data.current_city_id = -1; - data.current_city_list = LIST_NONE; - data.has_vulnerable_city = 0; - data.current_invasion_path_id = 0; - array_init(data.invasion_path_ids, 10, 0, 0); - data.distant_battle_path_type = DISTANT_BATTLE_PATH_NONE; - array_init(data.distant_battle_waypoints, 50, 0, 0); - data.border_status = BORDER_STATUS_NONE; - memset(data.added_ornaments, 0, sizeof(data.added_ornaments)); -} - -static void set_trade_coords(const empire_object *our_city) -{ - int *section_distances = 0; - for (int i = 0; i < empire_object_count(); i++) { - full_empire_object *trade_city = empire_object_get_full(i); - if ( - !trade_city->in_use || - trade_city->obj.type != EMPIRE_OBJECT_CITY || - trade_city->city_type == EMPIRE_CITY_OURS || - (trade_city->city_type != EMPIRE_CITY_TRADE && trade_city->city_type != EMPIRE_CITY_FUTURE_TRADE) - ) { - continue; - } - empire_object *trade_route = empire_object_get(i + 1); - - if (!section_distances) { - section_distances = malloc(sizeof(int) * (empire_object_count() - 1)); - } - int sections = 0; - int distance = 0; - int last_x = our_city->x + 25; - int last_y = our_city->y + 25; - int x_diff, y_diff; - for (int j = i + 2; j < empire_object_count(); j++) { - empire_object *obj = empire_object_get(j); - if (obj->type != EMPIRE_OBJECT_TRADE_WAYPOINT) { - break; - } - x_diff = obj->x - last_x; - y_diff = obj->y - last_y; - section_distances[sections] = (int) sqrt(x_diff * x_diff + y_diff * y_diff); - distance += section_distances[sections]; - last_x = obj->x; - last_y = obj->y; - sections++; - } - x_diff = trade_city->obj.x + 25 - last_x; - y_diff = trade_city->obj.y + 25 - last_y; - section_distances[sections] = (int) sqrt(x_diff * x_diff + y_diff * y_diff); - distance += section_distances[sections]; - sections++; - - last_x = our_city->x + 25; - last_y = our_city->y + 25; - int next_x = trade_city->obj.x + 25; - int next_y = trade_city->obj.y + 25; - - if (sections == 1) { - trade_route->x = (next_x + last_x) / 2 - 16; - trade_route->y = (next_y + last_y) / 2 - 10; - continue; - } - int crossed_distance = 0; - int current_section = 0; - int remaining_distance = 0; - while (current_section < sections) { - if (current_section == sections - 1) { - next_x = trade_city->obj.x + 25; - next_y = trade_city->obj.y + 25; - } else { - empire_object *obj = empire_object_get(current_section + i + 2); - next_x = obj->x; - next_y = obj->y; - } - if (section_distances[current_section] + crossed_distance > distance / 2) { - remaining_distance = distance / 2 - crossed_distance; - break; - } - last_x = next_x; - last_y = next_y; - crossed_distance += section_distances[current_section]; - current_section++; - } - x_diff = next_x - last_x; - y_diff = next_y - last_y; - int x_factor = calc_percentage(x_diff, section_distances[current_section]); - int y_factor = calc_percentage(y_diff, section_distances[current_section]); - trade_route->x = calc_adjust_with_percentage(remaining_distance, x_factor) + last_x - 16; - trade_route->y = calc_adjust_with_percentage(remaining_distance, y_factor) + last_y - 10; - - i += sections; // We know the following objects are waypoints so we skip them - } - free(section_distances); -} - -static int parse_xml(char *buf, int buffer_length) -{ - reset_data(); - empire_clear(); - empire_object_clear(); - if (!xml_parser_init(xml_elements, XML_TOTAL_ELEMENTS, 0)) { - return 0; - } - if (!xml_parser_parse(buf, buffer_length, 1)) { - data.success = 0; - } - xml_parser_free(); - if (!data.success) { - return 0; - } - - const empire_object *our_city = empire_object_get_our_city(); - if (!our_city) { - log_error("No home city specified", 0, 0); - return 0; - } - - set_trade_coords(our_city); - empire_object_init_cities(SCENARIO_CUSTOM_EMPIRE); - - return data.success; -} - -static char *file_to_buffer(const char *filename, int *output_length) -{ - FILE *file = file_open(filename, "r"); - if (!file) { - log_error("Error opening empire file", filename, 0); - return 0; - } - fseek(file, 0, SEEK_END); - int size = ftell(file); - rewind(file); - - char *buf = malloc(size); - if (!buf) { - log_error("Unable to allocate buffer to read XML file", filename, 0); - file_close(file); - return 0; - } - memset(buf, 0, size); - *output_length = (int) fread(buf, 1, size, file); - if (*output_length > size) { - log_error("Unable to read file into buffer", filename, 0); - free(buf); - file_close(file); - *output_length = 0; - return 0; - } - file_close(file); - return buf; -} - -int empire_xml_parse_file(const char *filename) -{ - int output_length = 0; - char *xml_contents = file_to_buffer(filename, &output_length); - if (!xml_contents) { - return 0; - } - int success = parse_xml(xml_contents, output_length); - free(xml_contents); - if (!success) { - log_error("Error parsing file", filename, 0); - } - return success; -} diff --git a/src/empire/xml.h b/src/empire/xml.h deleted file mode 100644 index 664907ad15..0000000000 --- a/src/empire/xml.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef EMPIRE_XML_H -#define EMPIRE_XML_H - -#include "core/buffer.h" - -int empire_xml_parse_file(const char *filename); - -#endif // EMPIRE_XML_H diff --git a/src/game/file_editor.c b/src/game/file_editor.c index 7a8b923e20..962eee80a7 100644 --- a/src/game/file_editor.c +++ b/src/game/file_editor.c @@ -129,6 +129,18 @@ static void prepare_map_for_editing(void) figure_create_flotsam(); map_tiles_update_all(); + map_tiles_update_all_elevation_editor(); + map_tiles_update_all_water(); + map_tiles_update_all_earthquake(); + map_tiles_update_all_rocks(); + map_tiles_update_all_empty_land(); + map_tiles_update_all_meadow(); + map_tiles_update_all_rubble(); + map_tiles_update_all_roads(); + map_tiles_update_all_highways(); + map_tiles_update_all_plazas(); + map_tiles_update_all_walls(); + map_tiles_update_all_aqueducts(0); widget_map_editor_custom_earthquake_request_refresh(); map_natives_init_editor(); map_routing_update_all(); diff --git a/src/scenario/event/conditions/comparison_helper.h b/src/scenario/event/conditions/comparison_helper.h deleted file mode 100644 index f7c81c8c4f..0000000000 --- a/src/scenario/event/conditions/comparison_helper.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef COMPARISON_HELPER_H -#define COMPARISON_HELPER_H - -int comparison_helper_compare_values(int compare_type, int value1, int value2); - -#endif // COMPARISON_HELPER_H diff --git a/src/scenario/event/conditions/condition_comparison_helper.c b/src/scenario/event/conditions/condition_comparison_helper.c deleted file mode 100644 index 3b2e2f363d..0000000000 --- a/src/scenario/event/conditions/condition_comparison_helper.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "comparison_helper.h" - -#include "scenario/scenario_event_data.h" - -int comparison_helper_compare_values(int compare_type, int value1, int value2) -{ - switch (compare_type) { - case COMPARISON_TYPE_EQUAL: - return value1 == value2; - case COMPARISON_TYPE_EQUAL_OR_LESS: - return value1 <= value2; - case COMPARISON_TYPE_EQUAL_OR_MORE: - return value1 >= value2; - case COMPARISON_TYPE_NOT_EQUAL: - return value1 != value2; - case COMPARISON_TYPE_LESS_THAN: - return value1 < value2; - case COMPARISON_TYPE_GREATER_THAN: - return value1 > value2; - default: - return 0; - } -} diff --git a/src/scenario/event/conditions/condition_handler.c b/src/scenario/event/conditions/condition_handler.c deleted file mode 100644 index 0b44dd10ec..0000000000 --- a/src/scenario/event/conditions/condition_handler.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "condition_handler.h" - -#include "core/log.h" -#include "game/resource.h" -#include "scenario/condition_types/condition_types.h" - -static int condition_in_use(const scenario_condition_t *condition) -{ - return condition->type != CONDITION_TYPE_UNDEFINED; -} - -void scenario_condition_group_new(scenario_condition_group_t *group, unsigned int id) -{ - // This group has been moved and is not new, no need to setup the array - if (group->type == FULFILLMENT_TYPE_ANY) { - return; - } - group->type = id == 0 ? FULFILLMENT_TYPE_ALL : FULFILLMENT_TYPE_ANY; - if (!array_init(group->conditions, CONDITION_GROUP_ITEMS_ARRAY_SIZE_STEP, 0, condition_in_use)) { - log_error("Unable to allocate enough memory for the scenario condition group. The game will now crash.", 0, 0); - } -} - -int scenario_condition_group_in_use(const scenario_condition_group_t *group) -{ - return group->type == FULFILLMENT_TYPE_ALL || group->conditions.size > 0; -} - -void scenario_condition_type_init(scenario_condition_t *condition) -{ - switch (condition->type) { - case CONDITION_TYPE_TIME_PASSED: - scenario_condition_type_time_init(condition); - break; - default: - break; - } -} - -int scenario_condition_type_is_met(scenario_condition_t *condition) -{ - switch (condition->type) { - case CONDITION_TYPE_BUILDING_COUNT_ACTIVE: - return scenario_condition_type_building_count_active_met(condition); - case CONDITION_TYPE_BUILDING_COUNT_ANY: - return scenario_condition_type_building_count_any_met(condition); - case CONDITION_TYPE_BUILDING_COUNT_AREA: - return scenario_condition_type_building_count_area_met(condition); - case CONDITION_TYPE_CITY_POPULATION: - return scenario_condition_type_city_population_met(condition); - case CONDITION_TYPE_COUNT_OWN_TROOPS: - return scenario_condition_type_count_own_troops_met(condition); - case CONDITION_TYPE_CUSTOM_VARIABLE_CHECK: - return scenario_condition_type_custom_variable_check_met(condition); - case CONDITION_TYPE_DIFFICULTY: - return scenario_condition_type_difficulty_met(condition); - case CONDITION_TYPE_MONEY: - return scenario_condition_type_money_met(condition); - case CONDITION_TYPE_POPS_UNEMPLOYMENT: - return scenario_condition_type_population_unemployed_met(condition); - case CONDITION_TYPE_REQUEST_IS_ONGOING: - return scenario_condition_type_request_is_ongoing_met(condition); - case CONDITION_TYPE_RESOURCE_STORAGE_AVAILABLE: - return scenario_condition_type_resource_storage_available_met(condition); - case CONDITION_TYPE_RESOURCE_STORED_COUNT: - return scenario_condition_type_resource_stored_count_met(condition); - case CONDITION_TYPE_ROME_WAGES: - return scenario_condition_type_rome_wages_met(condition); - case CONDITION_TYPE_SAVINGS: - return scenario_condition_type_savings_met(condition); - case CONDITION_TYPE_STATS_CITY_HEALTH: - return scenario_condition_type_stats_city_health_met(condition); - case CONDITION_TYPE_STATS_CULTURE: - return scenario_condition_type_stats_culture_met(condition); - case CONDITION_TYPE_STATS_FAVOR: - return scenario_condition_type_stats_favor_met(condition); - case CONDITION_TYPE_STATS_PEACE: - return scenario_condition_type_stats_peace_met(condition); - case CONDITION_TYPE_STATS_PROSPERITY: - return scenario_condition_type_stats_prosperity_met(condition); - case CONDITION_TYPE_TIME_PASSED: - return scenario_condition_type_time_met(condition); - case CONDITION_TYPE_TRADE_ROUTE_OPEN: - return scenario_condition_type_trade_route_open_met(condition); - case CONDITION_TYPE_TRADE_ROUTE_PRICE: - return scenario_condition_type_trade_route_price_met(condition); - case CONDITION_TYPE_TRADE_SELL_PRICE: - return scenario_condition_type_trade_sell_price_met(condition); - case CONDITION_TYPE_TAX_RATE: - return scenario_condition_type_tax_rate_met(condition); - default: - // If we cannot figure condition type (such as with deleted conditions) then default to passed. - return 1; - } -} - -void scenario_condition_type_delete(scenario_condition_t *condition) -{ - memset(condition, 0, sizeof(scenario_condition_t)); - condition->type = CONDITION_TYPE_UNDEFINED; -} - -static void save_conditions_in_group(buffer *buf, const scenario_condition_group_t *group) -{ - const scenario_condition_t *condition; - array_foreach(group->conditions, condition) { - buffer_write_i16(buf, condition->type); - buffer_write_i32(buf, condition->parameter1); - buffer_write_i32(buf, condition->parameter2); - buffer_write_i32(buf, condition->parameter3); - buffer_write_i32(buf, condition->parameter4); - buffer_write_i32(buf, condition->parameter5); - } -} - -void scenario_condition_group_save_state(buffer *buf, const scenario_condition_group_t *group, int link_type, - int32_t link_id) -{ - buffer_write_i16(buf, link_type); - buffer_write_u32(buf, link_id); - buffer_write_u8(buf, group->type); - buffer_write_u32(buf, group->conditions.size); - save_conditions_in_group(buf, group); -} - -void scenario_condition_load_state(buffer *buf, scenario_condition_group_t *group, scenario_condition_t *condition) -{ - condition->type = buffer_read_i16(buf); - condition->parameter1 = buffer_read_i32(buf); - condition->parameter2 = buffer_read_i32(buf); - condition->parameter3 = buffer_read_i32(buf); - condition->parameter4 = buffer_read_i32(buf); - condition->parameter5 = buffer_read_i32(buf); - - if (condition->type == CONDITION_TYPE_TRADE_SELL_PRICE) { - condition->parameter1 = resource_remap(condition->parameter1); - } else if (condition->type == CONDITION_TYPE_RESOURCE_STORED_COUNT) { - condition->parameter1 = resource_remap(condition->parameter1); - } else if (condition->type == CONDITION_TYPE_RESOURCE_STORAGE_AVAILABLE) { - condition->parameter1 = resource_remap(condition->parameter1); - } -} - -void scenario_condition_group_load_state(buffer *buf, scenario_condition_group_t *group, - int *link_type, int32_t *link_id) -{ - *link_type = buffer_read_i16(buf); - *link_id = buffer_read_u32(buf); - group->type = buffer_read_u8(buf); - unsigned int total_conditions = buffer_read_u32(buf); - if (!array_init(group->conditions, CONDITION_GROUP_ITEMS_ARRAY_SIZE_STEP, 0, condition_in_use) || - !array_expand(group->conditions, total_conditions)) { - log_error("Unable to create condition group array. The game will now crash.", 0, 0); - } - for (unsigned int i = 0; i < total_conditions; i++) { - scenario_condition_t *condition = array_next(group->conditions); - scenario_condition_load_state(buf, group, condition); - } -} - -int scenario_condition_uses_custom_variable(const scenario_condition_t *condition, int custom_variable_id) -{ - switch (condition->type) { - case CONDITION_TYPE_CUSTOM_VARIABLE_CHECK: - return condition->parameter1 == custom_variable_id; - default: - return 0; - } -} diff --git a/src/scenario/event/conditions/condition_handler.h b/src/scenario/event/conditions/condition_handler.h deleted file mode 100644 index d548a3102e..0000000000 --- a/src/scenario/event/conditions/condition_handler.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CONDITION_HANDLER_H -#define CONDITION_HANDLER_H - -#include "core/buffer.h" -#include "scenario/scenario_event_data.h" - -void scenario_condition_group_new(scenario_condition_group_t *group, unsigned int id); -int scenario_condition_group_in_use(const scenario_condition_group_t *group); - -void scenario_condition_type_init(scenario_condition_t *condition); -int scenario_condition_type_is_met(scenario_condition_t *condition); - -void scenario_condition_type_delete(scenario_condition_t *condition); -void scenario_condition_group_save_state(buffer *buf, const scenario_condition_group_t *condition_group, int link_type, - int32_t link_id); -void scenario_condition_load_state(buffer *buf, scenario_condition_group_t *group, scenario_condition_t *condition); -void scenario_condition_group_load_state(buffer *buf, scenario_condition_group_t *condition_group, - int *link_type, int32_t *link_id); -int scenario_condition_uses_custom_variable(const scenario_condition_t *condition, int custom_variable_id); - -#endif // CONDITION_HANDLER_H diff --git a/src/scenario/event/conditions/condition_types.c b/src/scenario/event/conditions/condition_types.c deleted file mode 100644 index 6fb59e518c..0000000000 --- a/src/scenario/event/conditions/condition_types.c +++ /dev/null @@ -1,471 +0,0 @@ -#include "condition_types.h" - -#include "building/count.h" -#include "building/type.h" -#include "city/data_private.h" -#include "city/emperor.h" -#include "city/finance.h" -#include "city/health.h" -#include "city/labor.h" -#include "city/military.h" -#include "city/ratings.h" -#include "core/random.h" -#include "empire/city.h" -#include "empire/trade_prices.h" -#include "empire/trade_route.h" -#include "game/settings.h" -#include "game/time.h" -#include "map/grid.h" -#include "scenario/custom_variable.h" -#include "scenario/request.h" -#include "scenario/scenario.h" -#include "scenario/condition_types/comparison_helper.h" - -int scenario_condition_type_building_count_active_met(const scenario_condition_t *condition) -{ - int comparison = condition->parameter1; - int value = condition->parameter2; - building_type type = condition->parameter3; - - int total_active_count = 0; - switch(type) { - case BUILDING_MENU_FARMS: - total_active_count = building_set_count_farms(1); - break; - case BUILDING_MENU_RAW_MATERIALS: - total_active_count = building_set_count_raw_materials(1); - break; - case BUILDING_MENU_WORKSHOPS: - total_active_count = building_set_count_workshops(1); - break; - case BUILDING_MENU_SMALL_TEMPLES: - total_active_count = building_set_count_small_temples(1); - break; - case BUILDING_MENU_LARGE_TEMPLES: - total_active_count = building_set_count_large_temples(1); - break; - case BUILDING_MENU_GRAND_TEMPLES: - total_active_count = building_count_grand_temples_active(); - break; - case BUILDING_MENU_TREES: - total_active_count = building_set_count_deco_trees(); - break; - case BUILDING_MENU_PATHS: - total_active_count = building_set_count_deco_paths(); - break; - case BUILDING_MENU_PARKS: - total_active_count = building_set_count_deco_statues(); - break; - case BUILDING_ANY: - total_active_count = building_count_any_total(1); - break; - case BUILDING_FORT_LEGIONARIES: - total_active_count += building_count_fort_type_total(FIGURE_FORT_LEGIONARY); - break; - case BUILDING_FORT_JAVELIN: - total_active_count += building_count_fort_type_total(FIGURE_FORT_JAVELIN); - break; - case BUILDING_FORT_MOUNTED: - total_active_count += building_count_fort_type_total(FIGURE_FORT_MOUNTED); - break; - default: - total_active_count = building_count_active(type); - break; - } - - return comparison_helper_compare_values(comparison, total_active_count, value); -} - -int scenario_condition_type_building_count_any_met(const scenario_condition_t *condition) -{ - int comparison = condition->parameter1; - int value = condition->parameter2; - building_type type = condition->parameter3; - - int total_active_count = 0; - switch(type) { - case BUILDING_MENU_FARMS: - total_active_count = building_set_count_farms(0); - break; - case BUILDING_MENU_RAW_MATERIALS: - total_active_count = building_set_count_raw_materials(0); - break; - case BUILDING_MENU_WORKSHOPS: - total_active_count = building_set_count_workshops(0); - break; - case BUILDING_MENU_SMALL_TEMPLES: - total_active_count = building_set_count_small_temples(0); - break; - case BUILDING_MENU_LARGE_TEMPLES: - total_active_count = building_set_count_large_temples(0); - break; - case BUILDING_MENU_GRAND_TEMPLES: - total_active_count = building_count_grand_temples(); - break; - case BUILDING_MENU_TREES: - total_active_count = building_set_count_deco_trees(); - break; - case BUILDING_MENU_PATHS: - total_active_count = building_set_count_deco_paths(); - break; - case BUILDING_MENU_PARKS: - total_active_count = building_set_count_deco_statues(); - break; - case BUILDING_ANY: - total_active_count = building_count_any_total(0); - break; - case BUILDING_FORT_LEGIONARIES: - total_active_count += building_count_fort_type_total(FIGURE_FORT_LEGIONARY); - break; - case BUILDING_FORT_JAVELIN: - total_active_count += building_count_fort_type_total(FIGURE_FORT_JAVELIN); - break; - case BUILDING_FORT_MOUNTED: - total_active_count += building_count_fort_type_total(FIGURE_FORT_MOUNTED); - break; - default: - total_active_count = building_count_total(type); - break; - } - - return comparison_helper_compare_values(comparison, total_active_count, value); -} - -int scenario_condition_type_building_count_area_met(const scenario_condition_t *condition) -{ - int grid_offset = condition->parameter1; - int block_radius = condition->parameter2; - building_type type = condition->parameter3; - int comparison = condition->parameter4; - int value = condition->parameter5; - - if (!map_grid_is_valid_offset(grid_offset)) { - return 0; - } - - int minx = map_grid_offset_to_x(grid_offset) - block_radius; - int miny = map_grid_offset_to_y(grid_offset) - block_radius; - int maxx = map_grid_offset_to_x(grid_offset) + block_radius; - int maxy = map_grid_offset_to_y(grid_offset) + block_radius; - int buildings_in_area = 0; - switch(type) { - case BUILDING_MENU_FARMS: - buildings_in_area = building_set_area_count_farms(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_RAW_MATERIALS: - buildings_in_area = building_set_area_count_raw_materials(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_WORKSHOPS: - buildings_in_area = building_set_area_count_workshops(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_SMALL_TEMPLES: - buildings_in_area = building_set_area_count_small_temples(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_LARGE_TEMPLES: - buildings_in_area = building_set_area_count_large_temples(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_GRAND_TEMPLES: - buildings_in_area = building_set_area_count_grand_temples(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_TREES: - buildings_in_area = building_set_area_count_deco_trees(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_PATHS: - buildings_in_area = building_set_area_count_deco_paths(minx, miny, maxx, maxy); - break; - case BUILDING_MENU_PARKS: - buildings_in_area = building_set_area_count_deco_statues(minx, miny, maxx, maxy); - break; - case BUILDING_FORT_LEGIONARIES: - buildings_in_area = building_count_fort_type_in_area(minx, miny, maxx, maxy, FIGURE_FORT_LEGIONARY); - break; - case BUILDING_FORT_JAVELIN: - buildings_in_area = building_count_fort_type_in_area(minx, miny, maxx, maxy, FIGURE_FORT_JAVELIN); - break; - case BUILDING_FORT_MOUNTED: - buildings_in_area = building_count_fort_type_in_area(minx, miny, maxx, maxy, FIGURE_FORT_MOUNTED); - break; - default: - buildings_in_area = building_count_in_area(type, minx, miny, maxx, maxy); - break; - } - - return comparison_helper_compare_values(comparison, buildings_in_area, value); -} - -int scenario_condition_type_city_population_met(const scenario_condition_t *condition) -{ - int comparison = condition->parameter1; - int value = condition->parameter2; - int class = condition->parameter3; - - int population_value_to_use = city_data.population.population; - if (class == POP_CLASS_PATRICIAN) { - population_value_to_use = city_data.population.people_in_villas_palaces; - } else if (class == POP_CLASS_PLEBEIAN) { - population_value_to_use = city_data.population.population - city_data.population.people_in_villas_palaces; - } else if (class == POP_CLASS_SLUMS) { - population_value_to_use = city_data.population.people_in_tents_shacks; - } - - return comparison_helper_compare_values(comparison, population_value_to_use, value); -} - -int scenario_condition_type_count_own_troops_met(const scenario_condition_t *condition) -{ - int comparison = condition->parameter1; - int value = condition->parameter2; - int in_city_only = condition->parameter3; - - int soldier_count = in_city_only ? city_military_total_soldiers_in_city() : city_military_total_soldiers(); - - return comparison_helper_compare_values(comparison, soldier_count, value); -} - -int scenario_condition_type_custom_variable_check_met(const scenario_condition_t *condition) -{ - int target_variable = scenario_custom_variable_get_value(condition->parameter1); - int comparison = condition->parameter2; - int value = condition->parameter3; - - return comparison_helper_compare_values(comparison, target_variable, value); -} - -int scenario_condition_type_difficulty_met(const scenario_condition_t *condition) -{ - int difficulty = setting_difficulty(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, difficulty, value); -} - -int scenario_condition_type_money_met(const scenario_condition_t *condition) -{ - int funds = city_finance_treasury(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, funds, value); -} - -int scenario_condition_type_population_unemployed_met(const scenario_condition_t *condition) -{ - int use_percentage = condition->parameter1; - int comparison = condition->parameter2; - int value = condition->parameter3; - - int unemployed_total = use_percentage ? city_labor_unemployment_percentage() : city_labor_workers_unemployed(); - - return comparison_helper_compare_values(comparison, unemployed_total, value); -} - -int scenario_condition_type_request_is_ongoing_met(const scenario_condition_t *condition) -{ - int request_id = condition->parameter1; - int check_for_ongoing = condition->parameter2; - int is_ongoing = scenario_request_is_ongoing(request_id); - - return check_for_ongoing ? is_ongoing : !is_ongoing; -} - -int scenario_condition_type_resource_storage_available_met(const scenario_condition_t *condition) -{ - int resource = condition->parameter1; - int comparison = condition->parameter2; - int value = condition->parameter3; - storage_types storage_type = condition->parameter4; - int respect_settings = condition->parameter5; - - if (resource < RESOURCE_MIN || resource > RESOURCE_MAX) { - return 0; - } - - int storage_available = 0; - switch(storage_type) { - case STORAGE_TYPE_ALL: - storage_available += city_resource_get_available_empty_space_warehouses(resource, respect_settings); - storage_available += city_resource_get_available_empty_space_granaries(resource, respect_settings) / RESOURCE_ONE_LOAD; - break; - case STORAGE_TYPE_GRANARIES: - storage_available += city_resource_get_available_empty_space_granaries(resource, respect_settings) / RESOURCE_ONE_LOAD; - break; - case STORAGE_TYPE_WAREHOUSES: - storage_available += city_resource_get_available_empty_space_warehouses(resource, respect_settings); - break; - default: - break; - } - - return comparison_helper_compare_values(comparison, storage_available, value); -} - -int scenario_condition_type_resource_stored_count_met(const scenario_condition_t *condition) -{ - int resource = condition->parameter1; - int comparison = condition->parameter2; - int value = condition->parameter3; - storage_types storage_type = condition->parameter4; - - if (resource < RESOURCE_MIN || resource > RESOURCE_MAX) { - return 0; - } - - int amount_stored = 0; - switch(storage_type) { - case STORAGE_TYPE_ALL: - amount_stored += city_resource_count(resource); - if (resource_is_food(resource)) { - amount_stored += city_resource_count_food_on_granaries(resource) / RESOURCE_ONE_LOAD; - } - break; - case STORAGE_TYPE_GRANARIES: - if (resource_is_food(resource)) { - amount_stored += city_resource_count_food_on_granaries(resource) / RESOURCE_ONE_LOAD; - } - break; - case STORAGE_TYPE_WAREHOUSES: - amount_stored += city_resource_count(resource); - break; - default: - break; - } - - return comparison_helper_compare_values(comparison, amount_stored, value); -} - -int scenario_condition_type_rome_wages_met(const scenario_condition_t *condition) -{ - int wages = city_labor_wages_rome(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, wages, value); -} - -int scenario_condition_type_savings_met(const scenario_condition_t *condition) -{ - int funds = city_emperor_personal_savings(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, funds, value); -} - -int scenario_condition_type_stats_city_health_met(const scenario_condition_t *condition) -{ - int stat_value = city_health(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, stat_value, value); -} - -int scenario_condition_type_stats_culture_met(const scenario_condition_t *condition) -{ - int stat_value = city_rating_culture(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, stat_value, value); -} - -int scenario_condition_type_stats_favor_met(const scenario_condition_t *condition) -{ - int stat_value = city_rating_favor(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, stat_value, value); -} - -int scenario_condition_type_stats_peace_met(const scenario_condition_t *condition) -{ - int stat_value = city_rating_peace(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, stat_value, value); -} - -int scenario_condition_type_stats_prosperity_met(const scenario_condition_t *condition) -{ - int stat_value = city_rating_prosperity(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, stat_value, value); -} - -void scenario_condition_type_time_init(scenario_condition_t *condition) -{ - int min_months = condition->parameter2; - int max_months = condition->parameter3; - - if (max_months < min_months) { - max_months = min_months; - condition->parameter3 = min_months; - } - - condition->parameter4 = random_between_from_stdlib(min_months, max_months); -} - -int scenario_condition_type_time_met(const scenario_condition_t *condition) -{ - int total_months = game_time_total_months(); - int comparison = condition->parameter1; - int target_months = condition->parameter4; - - return comparison_helper_compare_values(comparison, total_months, target_months); -} - -int scenario_condition_type_trade_route_open_met(const scenario_condition_t *condition) -{ - int route_id = condition->parameter1; - int check_for_open = condition->parameter2; - - if (!trade_route_is_valid(route_id)) { - return 0; - } - - int route_is_open = empire_city_is_trade_route_open(route_id); - return route_is_open == check_for_open; -} - -int scenario_condition_type_trade_route_price_met(const scenario_condition_t *condition) -{ - int route_id = condition->parameter1; - int comparison = condition->parameter2; - int value = condition->parameter3; - - if (!trade_route_is_valid(route_id)) { - return 0; - } - - int route_is_open = empire_city_is_trade_route_open(route_id); - int route_price = route_is_open ? 0 : empire_city_get_trade_route_cost(route_id); - return comparison_helper_compare_values(comparison, route_price, value); -} - -int scenario_condition_type_trade_sell_price_met(const scenario_condition_t *condition) -{ - int resource = condition->parameter1; - int comparison = condition->parameter2; - int value = condition->parameter3; - - if (resource < RESOURCE_MIN || resource > RESOURCE_MAX) { - return 0; - } - - int trade_sell_price = trade_price_base_sell(resource); - return comparison_helper_compare_values(comparison, trade_sell_price, value); -} - -int scenario_condition_type_tax_rate_met(const scenario_condition_t *condition) -{ - int tax_rate = city_finance_tax_percentage(); - int comparison = condition->parameter1; - int value = condition->parameter2; - - return comparison_helper_compare_values(comparison, tax_rate, value); -} diff --git a/src/scenario/event/conditions/condition_types.h b/src/scenario/event/conditions/condition_types.h deleted file mode 100644 index cf42711d75..0000000000 --- a/src/scenario/event/conditions/condition_types.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef CONDITION_TYPES_H -#define CONDITION_TYPES_H - -#include "scenario/scenario_event_data.h" - -int scenario_condition_type_building_count_active_met(const scenario_condition_t *condition); - -int scenario_condition_type_building_count_any_met(const scenario_condition_t *condition); - -int scenario_condition_type_building_count_area_met(const scenario_condition_t *condition); - -int scenario_condition_type_city_population_met(const scenario_condition_t *condition); - -int scenario_condition_type_count_own_troops_met(const scenario_condition_t *condition); - -int scenario_condition_type_custom_variable_check_met(const scenario_condition_t *condition); - -int scenario_condition_type_difficulty_met(const scenario_condition_t *condition); - -int scenario_condition_type_money_met(const scenario_condition_t *condition); - -int scenario_condition_type_population_unemployed_met(const scenario_condition_t *condition); - -int scenario_condition_type_request_is_ongoing_met(const scenario_condition_t *condition); - -int scenario_condition_type_resource_storage_available_met(const scenario_condition_t *condition); - -int scenario_condition_type_resource_stored_count_met(const scenario_condition_t *condition); - -int scenario_condition_type_rome_wages_met(const scenario_condition_t *condition); - -int scenario_condition_type_savings_met(const scenario_condition_t *condition); - -int scenario_condition_type_stats_city_health_met(const scenario_condition_t *condition); - -int scenario_condition_type_stats_culture_met(const scenario_condition_t *condition); - -int scenario_condition_type_stats_favor_met(const scenario_condition_t *condition); - -int scenario_condition_type_stats_peace_met(const scenario_condition_t *condition); - -int scenario_condition_type_stats_prosperity_met(const scenario_condition_t *condition); - -void scenario_condition_type_time_init(scenario_condition_t *condition); -int scenario_condition_type_time_met(const scenario_condition_t *condition); - -int scenario_condition_type_trade_route_open_met(const scenario_condition_t *condition); - -int scenario_condition_type_trade_route_price_met(const scenario_condition_t *condition); - -int scenario_condition_type_trade_sell_price_met(const scenario_condition_t *condition); - -int scenario_condition_type_tax_rate_met(const scenario_condition_t *condition); - -#endif // CONDITION_TYPES_H From 1bfca3bfae84e6290923ab3c305af2ec80893018 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 16:12:42 +0100 Subject: [PATCH 05/22] Keep original implementation and add helper function --- src/game/file_editor.c | 2 +- src/map/tiles.c | 6 ++++++ src/map/tiles.h | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/game/file_editor.c b/src/game/file_editor.c index 962eee80a7..4b06f2dc73 100644 --- a/src/game/file_editor.c +++ b/src/game/file_editor.c @@ -128,11 +128,11 @@ static void prepare_map_for_editing(void) figure_create_editor_flags(); figure_create_flotsam(); - map_tiles_update_all(); map_tiles_update_all_elevation_editor(); map_tiles_update_all_water(); map_tiles_update_all_earthquake(); map_tiles_update_all_rocks(); + map_tiles_update_all_trees_shrubs(); map_tiles_update_all_empty_land(); map_tiles_update_all_meadow(); map_tiles_update_all_rubble(); diff --git a/src/map/tiles.c b/src/map/tiles.c index d25f3fdc2d..e1f7075b9f 100644 --- a/src/map/tiles.c +++ b/src/map/tiles.c @@ -216,6 +216,12 @@ void map_tiles_update_region_shrub(int x_min, int y_min, int x_max, int y_max) foreach_region_tile(x_min, y_min, x_max, y_max, set_shrub_image); } +void map_tiles_update_all_trees_shrubs(void) +{ + foreach_map_tile(set_tree_image); + foreach_map_tile(set_shrub_image); +} + static void clear_garden_image(int x, int y, int grid_offset) { if (map_terrain_is(grid_offset, TERRAIN_GARDEN) && diff --git a/src/map/tiles.h b/src/map/tiles.h index 1d91d7f96f..7e196107ba 100644 --- a/src/map/tiles.h +++ b/src/map/tiles.h @@ -5,6 +5,7 @@ void map_tiles_update_all_rocks(void); void map_tiles_update_region_trees(int x_min, int y_min, int x_max, int y_max); void map_tiles_update_region_shrub(int x_min, int y_min, int x_max, int y_max); +void map_tiles_update_all_trees_shrubs(void); void map_tiles_update_all_gardens(void); From f882953b6a882000d5ae665a9ef3a2f6380d7849 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 16:25:08 +0100 Subject: [PATCH 06/22] Small IU fixes in generator window --- src/window/terrain_generator.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c index 04df373a65..d56baa3685 100644 --- a/src/window/terrain_generator.c +++ b/src/window/terrain_generator.c @@ -12,7 +12,6 @@ #include "graphics/graphics.h" #include "graphics/image.h" #include "graphics/panel.h" -#include "graphics/screen.h" #include "graphics/text.h" #include "graphics/window.h" #include "input/input.h" @@ -50,7 +49,7 @@ #define WINDOW_HEIGHT 480 #define CONTROL_LABEL_X 32 -#define CONTROL_VALUE_X 170 +#define CONTROL_VALUE_X 120 #define CONTROL_BUTTON_WIDTH 180 #define CONTROL_BUTTON_HEIGHT 24 @@ -119,7 +118,7 @@ static generic_button buttons[] = { static input_box seed_input = { CONTROL_VALUE_X, SEED_INPUT_Y, - 12, + 11, 2, FONT_NORMAL_WHITE, 0, From 6ddb9f5d32ca0321f61f7ab841228a1ae7e57d90 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 16:42:38 +0100 Subject: [PATCH 07/22] Split terrain generator into seperate files --- src/game/file_editor.c | 2 +- src/scenario/terrain_generator/flat_plains.c | 26 +++ .../river_valley.c} | 137 +++--------- .../terrain_generator/terrain_generator.c | 207 ++++++++++++++++++ .../terrain_generator.h | 0 .../terrain_generator_algorithms.h | 10 + src/window/terrain_generator.c | 27 ++- 7 files changed, 297 insertions(+), 112 deletions(-) create mode 100644 src/scenario/terrain_generator/flat_plains.c rename src/scenario/{terrain_generator.c => terrain_generator/river_valley.c} (57%) create mode 100644 src/scenario/terrain_generator/terrain_generator.c rename src/scenario/{ => terrain_generator}/terrain_generator.h (100%) create mode 100644 src/scenario/terrain_generator/terrain_generator_algorithms.h diff --git a/src/game/file_editor.c b/src/game/file_editor.c index 4b06f2dc73..1b0990e911 100644 --- a/src/game/file_editor.c +++ b/src/game/file_editor.c @@ -50,7 +50,7 @@ #include "scenario/invasion.h" #include "scenario/map.h" #include "scenario/property.h" -#include "scenario/terrain_generator.h" +#include "scenario/terrain_generator/terrain_generator.h" #include "sound/city.h" #include "sound/music.h" #include "widget/map_editor.h" diff --git a/src/scenario/terrain_generator/flat_plains.c b/src/scenario/terrain_generator/flat_plains.c new file mode 100644 index 0000000000..a068d89250 --- /dev/null +++ b/src/scenario/terrain_generator/flat_plains.c @@ -0,0 +1,26 @@ +#include "terrain_generator_algorithms.h" + +#include "map/grid.h" +#include "map/terrain.h" + +void terrain_generator_generate_flat_plains(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + int roll = terrain_generator_random_between(0, 100); + if (roll < 6) { + map_terrain_set(grid_offset, TERRAIN_TREE); + } else if (roll < 8) { + map_terrain_set(grid_offset, TERRAIN_ROCK); + } else if (roll < 18) { + map_terrain_set(grid_offset, TERRAIN_SHRUB); + } else if (roll < 35) { + map_terrain_set(grid_offset, TERRAIN_MEADOW); + } + } + } +} diff --git a/src/scenario/terrain_generator.c b/src/scenario/terrain_generator/river_valley.c similarity index 57% rename from src/scenario/terrain_generator.c rename to src/scenario/terrain_generator/river_valley.c index 580e3da870..51a111f059 100644 --- a/src/scenario/terrain_generator.c +++ b/src/scenario/terrain_generator/river_valley.c @@ -1,77 +1,22 @@ -#include "terrain_generator.h" +#include "terrain_generator_algorithms.h" -#include "core/random.h" #include "map/elevation.h" #include "map/grid.h" #include "map/terrain.h" #include -static int clamp_int(int value, int min_value, int max_value) -{ - if (value < min_value) { - return min_value; - } - if (value > max_value) { - return max_value; - } - return value; -} - -static int random_between(int min_value, int max_value) -{ - return random_between_from_stdlib(min_value, max_value); -} - -static int use_fixed_seed = 0; -static unsigned int fixed_seed = 0; - -static void clear_base_terrain(void) -{ - int width = map_grid_width(); - int height = map_grid_height(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int grid_offset = map_grid_offset(x, y); - map_terrain_set(grid_offset, TERRAIN_CLEAR); - map_elevation_set(grid_offset, 0); - } - } -} - -static void generate_flat_plains(void) -{ - int width = map_grid_width(); - int height = map_grid_height(); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int grid_offset = map_grid_offset(x, y); - int roll = random_between(0, 100); - if (roll < 6) { - map_terrain_set(grid_offset, TERRAIN_TREE); - } else if (roll < 8) { - map_terrain_set(grid_offset, TERRAIN_ROCK); - } else if (roll < 18) { - map_terrain_set(grid_offset, TERRAIN_SHRUB); - } else if (roll < 35) { - map_terrain_set(grid_offset, TERRAIN_MEADOW); - } - } - } -} - -static void generate_river_valley(void) +void terrain_generator_generate_river_valley(void) { int width = map_grid_width(); int height = map_grid_height(); int river_half_width = 1; int guard = 0; int max_steps = width * height * 2; - int start_side = random_between(0, 4); - int end_side = random_between(0, 4); + int start_side = terrain_generator_random_between(0, 4); + int end_side = terrain_generator_random_between(0, 4); while (end_side == start_side) { - end_side = random_between(0, 4); + end_side = terrain_generator_random_between(0, 4); } int start_x = 0; @@ -82,48 +27,48 @@ static void generate_river_valley(void) switch (start_side) { case 0: // north start_y = 0; - start_x = random_between(1, width - 1); + start_x = terrain_generator_random_between(1, width - 1); break; case 1: // south start_y = height - 1; - start_x = random_between(1, width - 1); + start_x = terrain_generator_random_between(1, width - 1); break; case 2: // west start_x = 0; - start_y = random_between(1, height - 1); + start_y = terrain_generator_random_between(1, height - 1); break; default: // east start_x = width - 1; - start_y = random_between(1, height - 1); + start_y = terrain_generator_random_between(1, height - 1); break; } switch (end_side) { case 0: // north end_y = 0; - end_x = random_between(1, width - 1); + end_x = terrain_generator_random_between(1, width - 1); break; case 1: // south end_y = height - 1; - end_x = random_between(1, width - 1); + end_x = terrain_generator_random_between(1, width - 1); break; case 2: // west end_x = 0; - end_y = random_between(1, height - 1); + end_y = terrain_generator_random_between(1, height - 1); break; default: // east end_x = width - 1; - end_y = random_between(1, height - 1); + end_y = terrain_generator_random_between(1, height - 1); break; } if (width > 2) { - start_x = clamp_int(start_x, 0, width - 1); - end_x = clamp_int(end_x, 0, width - 1); + start_x = terrain_generator_clamp_int(start_x, 0, width - 1); + end_x = terrain_generator_clamp_int(end_x, 0, width - 1); } if (height > 2) { - start_y = clamp_int(start_y, 0, height - 1); - end_y = clamp_int(end_y, 0, height - 1); + start_y = terrain_generator_clamp_int(start_y, 0, height - 1); + end_y = terrain_generator_clamp_int(end_y, 0, height - 1); } typedef struct { @@ -152,7 +97,7 @@ static void generate_river_valley(void) int step_x = 0; int step_y = 0; - int meander_roll = random_between(0, 100); + int meander_roll = terrain_generator_random_between(0, 100); if (dx != 0 && dy != 0) { if (meander_roll < 35) { step_x = dx; @@ -167,26 +112,26 @@ static void generate_river_valley(void) } } else if (dx != 0) { if (meander_roll < 20) { - step_y = random_between(0, 2) ? 1 : -1; + step_y = terrain_generator_random_between(0, 2) ? 1 : -1; } else { step_x = dx; } } else if (dy != 0) { if (meander_roll < 20) { - step_x = random_between(0, 2) ? 1 : -1; + step_x = terrain_generator_random_between(0, 2) ? 1 : -1; } else { step_y = dy; } } - int next_x = clamp_int(x + step_x, 0, width - 1); - int next_y = clamp_int(y + step_y, 0, height - 1); + int next_x = terrain_generator_clamp_int(x + step_x, 0, width - 1); + int next_y = terrain_generator_clamp_int(y + step_y, 0, height - 1); if (next_x == x && next_y == y) { if (dx != 0) { - next_x = clamp_int(x + dx, 0, width - 1); + next_x = terrain_generator_clamp_int(x + dx, 0, width - 1); } if (dy != 0) { - next_y = clamp_int(y + dy, 0, height - 1); + next_y = terrain_generator_clamp_int(y + dy, 0, height - 1); } } @@ -242,13 +187,13 @@ static void generate_river_valley(void) if (dist > 2) { elevation = (dist - 2) / 6; } - if (elevation > 0 && random_between(0, 4) == 0) { + if (elevation > 0 && terrain_generator_random_between(0, 4) == 0) { elevation++; } - elevation = clamp_int(elevation, 0, 4); + elevation = terrain_generator_clamp_int(elevation, 0, 4); map_elevation_set(grid_offset, elevation); - int roll = random_between(0, 100); + int roll = terrain_generator_random_between(0, 100); if (dist <= 2 && roll < 30) { map_terrain_set(grid_offset, TERRAIN_MEADOW); } else if (elevation >= 2 && roll < 12) { @@ -261,31 +206,3 @@ static void generate_river_valley(void) } } } - -void terrain_generator_generate(terrain_generator_algorithm algorithm) -{ - if (use_fixed_seed) { - random_set_stdlib_seed(fixed_seed); - } else { - random_clear_stdlib_seed(); - } - clear_base_terrain(); - - switch (algorithm) { - case TERRAIN_GENERATOR_RIVER_VALLEY: - generate_river_valley(); - break; - case TERRAIN_GENERATOR_FLAT_PLAINS: - default: - generate_flat_plains(); - break; - } - - random_clear_stdlib_seed(); -} - -void terrain_generator_set_seed(int enabled, unsigned int seed) -{ - use_fixed_seed = enabled != 0; - fixed_seed = seed; -} diff --git a/src/scenario/terrain_generator/terrain_generator.c b/src/scenario/terrain_generator/terrain_generator.c new file mode 100644 index 0000000000..eb56f1ff3c --- /dev/null +++ b/src/scenario/terrain_generator/terrain_generator.c @@ -0,0 +1,207 @@ +#include "terrain_generator.h" + +#include "terrain_generator_algorithms.h" + +#include "core/random.h" +#include "map/elevation.h" +#include "map/grid.h" +#include "map/property.h" +#include "map/terrain.h" +#include "map/tiles.h" +#include "scenario/editor_map.h" +#include "scenario/map.h" + +#include + +int terrain_generator_clamp_int(int value, int min_value, int max_value) +{ + if (value < min_value) { + return min_value; + } + if (value > max_value) { + return max_value; + } + return value; +} + +int terrain_generator_random_between(int min_value, int max_value) +{ + return random_between_from_stdlib(min_value, max_value); +} + +static int use_fixed_seed = 0; +static unsigned int fixed_seed = 0; + +static void clear_base_terrain(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + map_terrain_set(grid_offset, TERRAIN_CLEAR); + map_elevation_set(grid_offset, 0); + } + } +} + +static void choose_edge_point(int side, int width, int height, int *x, int *y) +{ + switch (side) { + case 0: // north + *y = 0; + *x = terrain_generator_random_between(1, width - 1); + break; + case 1: // south + *y = height - 1; + *x = terrain_generator_random_between(1, width - 1); + break; + case 2: // west + *x = 0; + *y = terrain_generator_random_between(1, height - 1); + break; + default: // east + *x = width - 1; + *y = terrain_generator_random_between(1, height - 1); + break; + } + + if (width > 2) { + *x = terrain_generator_clamp_int(*x, 0, width - 1); + } + if (height > 2) { + *y = terrain_generator_clamp_int(*y, 0, height - 1); + } +} + +static void adjust_point_to_land(int *x, int *y, int width, int height) +{ + int grid_offset = map_grid_offset(*x, *y); + if (!map_terrain_is(grid_offset, TERRAIN_WATER)) { + return; + } + + for (int radius = 1; radius <= 10; radius++) { + for (int dy = -radius; dy <= radius; dy++) { + for (int dx = -radius; dx <= radius; dx++) { + int nx = *x + dx; + int ny = *y + dy; + if (!map_grid_is_inside(nx, ny, 1)) { + continue; + } + int offset = map_grid_offset(nx, ny); + if (!map_terrain_is(offset, TERRAIN_WATER)) { + *x = nx; + *y = ny; + return; + } + } + } + } +} + +static void set_road_tile(int x, int y) +{ + int grid_offset = map_grid_offset(x, y); + map_terrain_remove(grid_offset, + TERRAIN_WATER | TERRAIN_TREE | TERRAIN_SHRUB | TERRAIN_ROCK | TERRAIN_MEADOW | TERRAIN_ELEVATION | TERRAIN_ACCESS_RAMP); + map_terrain_add(grid_offset, TERRAIN_ROAD); + map_property_clear_constructing(grid_offset); + map_property_set_multi_tile_size(grid_offset, 1); + map_property_mark_draw_tile(grid_offset); +} + +static void add_road_between_points(int start_x, int start_y, int end_x, int end_y) +{ + int x = start_x; + int y = start_y; + int guard = 0; + int max_steps = map_grid_width() * map_grid_height() * 2; + + set_road_tile(x, y); + while ((x != end_x || y != end_y) && guard++ < max_steps) { + int dx = end_x - x; + int dy = end_y - y; + int step_x = (dx > 0) - (dx < 0); + int step_y = (dy > 0) - (dy < 0); + int abs_dx = abs(dx); + int abs_dy = abs(dy); + int roll = terrain_generator_random_between(0, 100); + + if (abs_dx >= abs_dy) { + if (step_x && roll < 70) { + x += step_x; + } else if (step_y) { + y += step_y; + } + } else { + if (step_y && roll < 70) { + y += step_y; + } else if (step_x) { + x += step_x; + } + } + + x = terrain_generator_clamp_int(x, 0, map_grid_width() - 1); + y = terrain_generator_clamp_int(y, 0, map_grid_height() - 1); + set_road_tile(x, y); + } +} + +static void set_entry_exit_points(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + int entry_side = terrain_generator_random_between(0, 4); + int exit_side = terrain_generator_random_between(0, 4); + while (exit_side == entry_side) { + exit_side = terrain_generator_random_between(0, 4); + } + + int entry_x = 0; + int entry_y = 0; + int exit_x = 0; + int exit_y = 0; + + choose_edge_point(entry_side, width, height, &entry_x, &entry_y); + choose_edge_point(exit_side, width, height, &exit_x, &exit_y); + + adjust_point_to_land(&entry_x, &entry_y, width, height); + adjust_point_to_land(&exit_x, &exit_y, width, height); + + scenario_editor_set_entry_point(entry_x, entry_y); + scenario_editor_set_exit_point(exit_x, exit_y); + + add_road_between_points(entry_x, entry_y, exit_x, exit_y); +} + +void terrain_generator_generate(terrain_generator_algorithm algorithm) +{ + if (use_fixed_seed) { + random_set_stdlib_seed(fixed_seed); + } else { + random_clear_stdlib_seed(); + } + + clear_base_terrain(); + + switch (algorithm) { + case TERRAIN_GENERATOR_RIVER_VALLEY: + terrain_generator_generate_river_valley(); + break; + case TERRAIN_GENERATOR_FLAT_PLAINS: + default: + terrain_generator_generate_flat_plains(); + break; + } + + set_entry_exit_points(); + + random_clear_stdlib_seed(); +} + +void terrain_generator_set_seed(int enabled, unsigned int seed) +{ + use_fixed_seed = enabled != 0; + fixed_seed = seed; +} diff --git a/src/scenario/terrain_generator.h b/src/scenario/terrain_generator/terrain_generator.h similarity index 100% rename from src/scenario/terrain_generator.h rename to src/scenario/terrain_generator/terrain_generator.h diff --git a/src/scenario/terrain_generator/terrain_generator_algorithms.h b/src/scenario/terrain_generator/terrain_generator_algorithms.h new file mode 100644 index 0000000000..38dddf5a16 --- /dev/null +++ b/src/scenario/terrain_generator/terrain_generator_algorithms.h @@ -0,0 +1,10 @@ +#ifndef SCENARIO_TERRAIN_GENERATOR_ALGORITHMS_H +#define SCENARIO_TERRAIN_GENERATOR_ALGORITHMS_H + +int terrain_generator_random_between(int min_value, int max_value); +int terrain_generator_clamp_int(int value, int min_value, int max_value); + +void terrain_generator_generate_flat_plains(void); +void terrain_generator_generate_river_valley(void); + +#endif // SCENARIO_TERRAIN_GENERATOR_ALGORITHMS_H diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c index d56baa3685..a81b5c8aee 100644 --- a/src/window/terrain_generator.c +++ b/src/window/terrain_generator.c @@ -20,6 +20,7 @@ #include "map/desirability.h" #include "map/elevation.h" #include "map/figure.h" +#include "map/grid.h" #include "map/image.h" #include "map/image_context.h" #include "map/property.h" @@ -33,7 +34,7 @@ #include "scenario/data.h" #include "scenario/map.h" #include "scenario/property.h" -#include "scenario/terrain_generator.h" +#include "scenario/terrain_generator/terrain_generator.h" #include "sound/music.h" #include "translation/translation.h" #include "widget/input_box.h" @@ -140,6 +141,8 @@ static struct { static minimap_functions preview_minimap_functions; +static uint8_t preview_road_flags[GRID_SIZE * GRID_SIZE]; + static void preview_viewport(int *x, int *y, int *width, int *height) { *x = 0; @@ -193,8 +196,30 @@ static void generate_preview_map(void) int use_seed = get_seed_value(&seed); terrain_generator_set_seed(use_seed, seed); terrain_generator_generate((terrain_generator_algorithm) data.algorithm_index); + int width = map_grid_width(); + int height = map_grid_height(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + preview_road_flags[grid_offset] = map_terrain_is(grid_offset, TERRAIN_ROAD) ? 1 : 0; + if (preview_road_flags[grid_offset]) { + map_terrain_remove(grid_offset, TERRAIN_ROAD); + } + } + } + map_tiles_update_all(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + if (preview_road_flags[grid_offset]) { + map_terrain_add(grid_offset, TERRAIN_ROAD); + } + } + } + map_tiles_update_all_roads(); + preview_minimap_functions.climate = scenario_property_climate; preview_minimap_functions.map.width = map_grid_width; preview_minimap_functions.map.height = map_grid_height; From 422bc0e2e5e38afe854fe116966292ecde3638b5 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 20 Mar 2026 16:51:20 +0100 Subject: [PATCH 08/22] Add missing files to cmake --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c5fb63b48..b0c0843e7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -535,7 +535,9 @@ set(SCENARIO_FILES ${PROJECT_SOURCE_DIR}/src/scenario/editor.c ${PROJECT_SOURCE_DIR}/src/scenario/editor_events.c ${PROJECT_SOURCE_DIR}/src/scenario/editor_map.c - ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/terrain_generator.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/flat_plains.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river_valley.c ${PROJECT_SOURCE_DIR}/src/scenario/emperor_change.c ${PROJECT_SOURCE_DIR}/src/scenario/empire.c ${PROJECT_SOURCE_DIR}/src/scenario/gladiator_revolt.c From 122315ff500a7b464117396a655c461bac84eef2 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 16:58:37 +0200 Subject: [PATCH 09/22] Remove unused translation terxts --- src/translation/english.c | 1 + src/translation/french.c | 1 + src/translation/german.c | 1 + src/translation/greek.c | 1 + src/translation/italian.c | 2 -- src/translation/korean.c | 1 + src/translation/translation.h | 2 -- 7 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/translation/english.c b/src/translation/english.c index 3869393bc9..3e5db2cbc1 100644 --- a/src/translation/english.c +++ b/src/translation/english.c @@ -1474,6 +1474,7 @@ static translation_string all_strings[] = { {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, + {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "Select a campaign"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Author:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "No description"}, diff --git a/src/translation/french.c b/src/translation/french.c index 23b471115d..08a0c51ecc 100644 --- a/src/translation/french.c +++ b/src/translation/french.c @@ -1473,6 +1473,7 @@ static translation_string all_strings[] = { {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, + {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "Choisir une campagne"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Auteur :"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Pas de description"}, diff --git a/src/translation/german.c b/src/translation/german.c index b43fd7ee03..a8d584c303 100644 --- a/src/translation/german.c +++ b/src/translation/german.c @@ -1244,6 +1244,7 @@ static translation_string all_strings[] = { {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, + {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "Eine Kampagne auswählen"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Autor:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Keine Beschreibung"}, diff --git a/src/translation/greek.c b/src/translation/greek.c index f8f1e55f1e..b797cf69cd 100644 --- a/src/translation/greek.c +++ b/src/translation/greek.c @@ -1480,6 +1480,7 @@ static translation_string all_strings[] = { { TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator" }, { TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains" }, { TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley" }, + { TR_TERRAIN_GENERATOR_PERLIN, "Perlin" }, { TR_WINDOW_SELECT_CAMPAIGN, "Επιλέξτε μια εκστρατεία" }, { TR_WINDOW_CAMPAIGN_AUTHOR, "Συγγραφέας:" }, { TR_WINDOW_CAMPAIGN_NO_DESC, "Καμία περιγραφή" }, diff --git a/src/translation/italian.c b/src/translation/italian.c index a32690c57f..8a52150a42 100755 --- a/src/translation/italian.c +++ b/src/translation/italian.c @@ -1471,8 +1471,6 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TEXT, "Usare l'editor di mappe in modalità città può corrompere il salvataggio o causare un crash del gioco.\n\nProcedere a proprio rischio e pericolo e assicurarsi di avere una copia di sicurezza del salvataggio corrente."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "Inizia nuova campagna"}, {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, - {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, - {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, {TR_WINDOW_SELECT_CAMPAIGN, "Seleziona una campagna"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Autore:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Nessuna descrizione"}, diff --git a/src/translation/korean.c b/src/translation/korean.c index 7d7fde6621..a59fc16edf 100644 --- a/src/translation/korean.c +++ b/src/translation/korean.c @@ -1472,6 +1472,7 @@ static translation_string all_strings[] = { {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, + {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "캠페인을 선택하세요"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "원작자:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "설명 없음"}, diff --git a/src/translation/translation.h b/src/translation/translation.h index f83898a56d..b876812315 100644 --- a/src/translation/translation.h +++ b/src/translation/translation.h @@ -1473,8 +1473,6 @@ typedef enum { TR_BUILDING_ARENA_DESC_UPGRADED_NO_SHOWS, TR_MAIN_MENU_SELECT_CAMPAIGN, TR_MAIN_MENU_TERRAIN_GENERATOR, - TR_TERRAIN_GENERATOR_FLAT_PLAINS, - TR_TERRAIN_GENERATOR_RIVER_VALLEY, TR_WINDOW_SELECT_CAMPAIGN, TR_WINDOW_CAMPAIGN_AUTHOR, TR_WINDOW_CAMPAIGN_NO_DESC, From 4ca0a4784dd2e819e1480dd7ffa084a15d21618a Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 16:58:57 +0200 Subject: [PATCH 10/22] Add random functions --- src/map/random.c | 27 +++++++++++++++++++++------ src/map/random.h | 2 ++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/map/random.c b/src/map/random.c index fb31d4a911..2c135256d8 100644 --- a/src/map/random.c +++ b/src/map/random.c @@ -1,13 +1,17 @@ #include "random.h" #include "core/random.h" + +#include +#include + #include "map/grid.h" -static grid_u8 random; +static grid_u8 random_value; void map_random_clear(void) { - map_grid_clear_u8(random.items); + map_grid_clear_u8(random_value.items); } void map_random_init(void) @@ -16,14 +20,14 @@ void map_random_init(void) for (int y = 0; y < GRID_SIZE; y++) { for (int x = 0; x < GRID_SIZE; x++, grid_offset++) { random_generate_next(); - random.items[grid_offset] = (uint8_t) random_short(); + random_value.items[grid_offset] = (uint8_t) random_short(); } } } int map_random_get(int grid_offset) { - return random.items[grid_offset]; + return random_value.items[grid_offset]; } int map_random_get_from_buffer(buffer *buf, int grid_offset) @@ -34,10 +38,21 @@ int map_random_get_from_buffer(buffer *buf, int grid_offset) void map_random_save_state(buffer *buf) { - map_grid_save_state_u8(random.items, buf); + map_grid_save_state_u8(random_value.items, buf); } void map_random_load_state(buffer *buf) { - map_grid_load_state_u8(random.items, buf); + map_grid_load_state_u8(random_value.items, buf); +} + +unsigned int generate_seed_value(void) +{ + // Generate a random value between 1 and UINT_MAX. Return the unsingned value directly, as it can be used as a seed for the terrain generator. + // Do not use time value directly as it is just increasing and might not provide enough randomness if the terrain generator is opened multiple times within the same second. Instead, use a combination of time and a random value from the C library's rand() function to increase randomness. + unsigned int time_seed = (unsigned int) time(NULL); + unsigned int rand_seed = (unsigned int) rand(); + unsigned int seed = time_seed ^ rand_seed; // Combine time and rand values using XOR + seed = seed == 0 ? 1 : seed; // Ensure seed is not zero, as some terrain generators might treat zero as a special case. + return seed; } diff --git a/src/map/random.h b/src/map/random.h index a6ac3a3b70..718e92e6ee 100644 --- a/src/map/random.h +++ b/src/map/random.h @@ -15,4 +15,6 @@ void map_random_save_state(buffer *buf); void map_random_load_state(buffer *buf); +unsigned int generate_seed_value(void); + #endif // MAP_RANDOM_H From b87c109c97c474ccb804e309e27389c8511fd83b Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 16:59:49 +0200 Subject: [PATCH 11/22] Simplify the terrain generator window --- CMakeLists.txt | 1 + src/window/terrain_generator.c | 193 ++++++++++++++++++++++++++++++--- 2 files changed, 177 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0c0843e7b..af6b29652c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -538,6 +538,7 @@ set(SCENARIO_FILES ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/terrain_generator.c ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/flat_plains.c ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river_valley.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/perlin.c ${PROJECT_SOURCE_DIR}/src/scenario/emperor_change.c ${PROJECT_SOURCE_DIR}/src/scenario/empire.c ${PROJECT_SOURCE_DIR}/src/scenario/gladiator_revolt.c diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c index a81b5c8aee..f2c21e87e9 100644 --- a/src/window/terrain_generator.c +++ b/src/window/terrain_generator.c @@ -3,14 +3,18 @@ #include "assets/assets.h" #include "city/view.h" #include "core/config.h" +#include "core/dir.h" #include "core/image_group.h" #include "core/string.h" #include "editor/editor.h" +#include "game/file.h" +#include "game/file_editor.h" #include "game/game.h" #include "graphics/button.h" #include "graphics/generic_button.h" #include "graphics/graphics.h" #include "graphics/image.h" +#include "graphics/lang_text.h" #include "graphics/panel.h" #include "graphics/text.h" #include "graphics/window.h" @@ -30,6 +34,8 @@ #include "map/sprite.h" #include "map/terrain.h" #include "map/tiles.h" +#include "building/type.h" +#include "scenario/allowed_building.h" #include "scenario/editor.h" #include "scenario/data.h" #include "scenario/map.h" @@ -42,6 +48,7 @@ #include "window/plain_message_dialog.h" #include "window/select_list.h" #include "window/video.h" +#include "window/city.h" #include "window/editor/map.h" #include @@ -50,43 +57,72 @@ #define WINDOW_HEIGHT 480 #define CONTROL_LABEL_X 32 -#define CONTROL_VALUE_X 120 -#define CONTROL_BUTTON_WIDTH 180 -#define CONTROL_BUTTON_HEIGHT 24 +#define CONTROL_VALUE_X 160 +#define CONTROL_BUTTON_WIDTH 120 +#define CONTROL_BUTTON_HEIGHT 20 -#define SIZE_BUTTON_Y 90 -#define ALGORITHM_BUTTON_Y 130 -#define SEED_INPUT_Y 170 +#define SIZE_BUTTON_Y 60 +#define ALGORITHM_BUTTON_Y 84 +#define SEED_INPUT_Y 108 +#define RANDOMIZE_BUTTON_Y 144 +#define CLIMATE_BUTTON_Y 168 + + +#define SETTINGS_LABEL_Y 135 +#define SETTINGS_ROW_Y 155 +#define SETTINGS_ROW_HEIGHT 22 + +#define VICTORY_LABEL_Y (SETTINGS_ROW_Y + SETTINGS_ROW_HEIGHT * 4 + 8) +#define VICTORY_ROW_Y (VICTORY_LABEL_Y + 20) + +#define SETTING_BUTTON_WIDTH 120 +#define TOGGLE_BUTTON_WIDTH 60 +#define VALUE_BUTTON_WIDTH 90 +#define CRITERIA_TOGGLE_X CONTROL_VALUE_X +#define CRITERIA_VALUE_X (CONTROL_VALUE_X + TOGGLE_BUTTON_WIDTH + 8) + +#define ACTION_BUTTON_WIDTH 288 +#define ACTION_BUTTON_HEIGHT 24 +#define ACTION_BUTTON_X 320 + +#define REGENERATE_BUTTON_Y 356 +#define OPEN_EDITOR_BUTTON_Y 384 +#define START_GAME_BUTTON_Y 412 +#define BACK_BUTTON_Y 440 -#define ACTION_BUTTON_WIDTH 260 -#define ACTION_BUTTON_HEIGHT 28 -#define ACTION_BUTTON_X 32 -#define REGENERATE_BUTTON_Y 230 -#define OPEN_EDITOR_BUTTON_Y 270 -#define BACK_BUTTON_Y 310 #define PREVIEW_X 320 -#define PREVIEW_Y 80 +#define PREVIEW_Y 60 #define PREVIEW_WIDTH 288 -#define PREVIEW_HEIGHT 320 +// #define PREVIEW_HEIGHT 240 +#define PREVIEW_HEIGHT 288 #define SEED_TEXT_LENGTH 16 #define TERRAIN_GENERATOR_SIZE_COUNT 6 +#define CLIMATE_COUNT 3 + static void button_select_size(const generic_button *button); static void button_select_algorithm(const generic_button *button); +static void button_select_climate(const generic_button *button); +static void button_randomize(const generic_button *button); static void button_regenerate(const generic_button *button); static void button_open_editor(const generic_button *button); +static void button_start_game(const generic_button *button); static void button_back(const generic_button *button); static void size_selected(int id); static void algorithm_selected(int id); +static void climate_selected(int id); static const uint8_t label_size[] = "Size"; static const uint8_t label_algorithm[] = "Algorithm"; static const uint8_t label_seed[] = "Seed"; +static const uint8_t label_randomize[] = "Randomize"; +static const uint8_t label_climate[] = "Climate"; static const uint8_t label_regenerate[] = "Regenerate preview"; static const uint8_t label_open_editor[] = "Open in editor"; +static const uint8_t label_start_game[] = "Start game"; static const uint8_t label_back[] = "Back"; static const uint8_t label_seed_placeholder[] = "Random"; @@ -97,6 +133,17 @@ static const uint8_t size_label_100[] = "100 x 100"; static const uint8_t size_label_120[] = "120 x 120"; static const uint8_t size_label_160[] = "160 x 160"; +static const uint8_t label_climate_central[] = "Central"; +static const uint8_t label_climate_northern[] = "Northern"; +static const uint8_t label_climate_desert[] = "Desert"; + + +static const uint8_t *climate_labels[CLIMATE_COUNT] = { + label_climate_central, + label_climate_northern, + label_climate_desert +}; + static const uint8_t *terrain_generator_size_labels[TERRAIN_GENERATOR_SIZE_COUNT] = { size_label_40, size_label_60, @@ -108,11 +155,15 @@ static const uint8_t *terrain_generator_size_labels[TERRAIN_GENERATOR_SIZE_COUNT static const uint8_t *terrain_generator_algorithm_labels[TERRAIN_GENERATOR_COUNT]; + static generic_button buttons[] = { {CONTROL_VALUE_X, SIZE_BUTTON_Y, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, button_select_size, 0, 0}, {CONTROL_VALUE_X, ALGORITHM_BUTTON_Y, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, button_select_algorithm, 0, 0}, + {CONTROL_VALUE_X, CLIMATE_BUTTON_Y, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, button_select_climate, 0, 0}, + {CONTROL_VALUE_X, RANDOMIZE_BUTTON_Y, CONTROL_BUTTON_WIDTH, CONTROL_BUTTON_HEIGHT, button_randomize, 0, 0}, {ACTION_BUTTON_X, REGENERATE_BUTTON_Y, ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, button_regenerate, 0, 0}, {ACTION_BUTTON_X, OPEN_EDITOR_BUTTON_Y, ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, button_open_editor, 0, 0}, + {ACTION_BUTTON_X, START_GAME_BUTTON_Y, ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, button_start_game, 0, 0}, {ACTION_BUTTON_X, BACK_BUTTON_Y, ACTION_BUTTON_WIDTH, ACTION_BUTTON_HEIGHT, button_back, 0, 0} }; @@ -137,12 +188,31 @@ static struct { int size_index; int algorithm_index; uint8_t seed_text[SEED_TEXT_LENGTH]; + int settings_initialized; + scenario_climate climate_index; } data; static minimap_functions preview_minimap_functions; static uint8_t preview_road_flags[GRID_SIZE * GRID_SIZE]; +static int get_group_allowed(const building_type *types, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) { + if (!scenario_allowed_building(types[i])) { + return 0; + } + } + return 1; +} + +static void set_group_allowed(const building_type *types, unsigned int count, int allowed) +{ + for (unsigned int i = 0; i < count; i++) { + scenario_allowed_building_set(types[i], allowed); + } +} + static void preview_viewport(int *x, int *y, int *width, int *height) { *x = 0; @@ -189,11 +259,18 @@ static void generate_preview_map(void) { scenario_editor_create(data.size_index); scenario_map_init(); + if (data.settings_initialized) { + // apply_scenario_settings(); + } else { + // sync_settings_from_scenario(); + data.settings_initialized = 1; + } clear_map_data(); map_image_init_edges(); unsigned int seed = 0; int use_seed = get_seed_value(&seed); + terrain_generator_set_seed(use_seed, seed); terrain_generator_generate((terrain_generator_algorithm) data.algorithm_index); int width = map_grid_width(); @@ -250,6 +327,8 @@ static void draw_background(void) text_draw(label_size, CONTROL_LABEL_X, SIZE_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); text_draw(label_algorithm, CONTROL_LABEL_X, ALGORITHM_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); text_draw(label_seed, CONTROL_LABEL_X, SEED_INPUT_Y + 6, FONT_NORMAL_BLACK, 0); + // text_draw(label_randomize, CONTROL_LABEL_X, RANDOMIZE_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_climate, CONTROL_LABEL_X, CLIMATE_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); inner_panel_draw(PREVIEW_X - 8, PREVIEW_Y - 8, (PREVIEW_WIDTH + 16) / BLOCK_SIZE, (PREVIEW_HEIGHT + 16) / BLOCK_SIZE); @@ -272,11 +351,18 @@ static void draw_foreground(void) CONTROL_VALUE_X, SIZE_BUTTON_Y + 6, CONTROL_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); text_draw_centered(terrain_generator_algorithm_labels[data.algorithm_index], CONTROL_VALUE_X, ALGORITHM_BUTTON_Y + 6, CONTROL_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + text_draw_centered(label_randomize, + CONTROL_VALUE_X, RANDOMIZE_BUTTON_Y + 6, CONTROL_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + text_draw_centered(climate_labels[data.climate_index], + CONTROL_VALUE_X, CLIMATE_BUTTON_Y + 6, CONTROL_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + text_draw_centered(label_regenerate, ACTION_BUTTON_X, REGENERATE_BUTTON_Y + 8, ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); text_draw_centered(label_open_editor, ACTION_BUTTON_X, OPEN_EDITOR_BUTTON_Y + 8, ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); + text_draw_centered(label_start_game, ACTION_BUTTON_X, START_GAME_BUTTON_Y + 8, + ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); text_draw_centered(label_back, ACTION_BUTTON_X, BACK_BUTTON_Y + 8, ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); @@ -319,6 +405,22 @@ static void button_select_algorithm(const generic_button *button) TERRAIN_GENERATOR_COUNT, algorithm_selected); } +static void button_select_climate(const generic_button *button) +{ + window_select_list_show_text(0, 0, button, climate_labels, CLIMATE_COUNT, climate_selected); +} + +static void button_randomize(const generic_button *button) +{ + (void) button; + unsigned int random_seed = generate_seed_value(); + sprintf(data.seed_text, "%d", random_seed); + seed_input.placeholder = data.seed_text; + // input_box_set_text(&seed_input, data.seed_text); + generate_preview_map(); + window_invalidate(); +} + static void button_regenerate(const generic_button *button) { (void) button; @@ -339,6 +441,8 @@ static void button_open_editor(const generic_button *button) return; } + // apply_scenario_settings(); + input_box_stop(&seed_input); if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { window_video_show("map_intro.smk", window_editor_map_show); @@ -346,6 +450,48 @@ static void button_open_editor(const generic_button *button) sound_music_play_editor(); } +static void button_start_game(const generic_button *button) +{ + (void) button; + const uint8_t scenario_name_text[] = "generated_terrain"; + const char scenario_filename[] = "generated_terrain.map"; + + const char *scenario_path = dir_append_location(scenario_filename, PATH_LOCATION_SCENARIO); + + input_box_stop(&seed_input); + + if (!scenario_path) { + window_plain_message_dialog_show(TR_SAVEGAME_NOT_ABLE_TO_SAVE_TITLE, + TR_SAVEGAME_NOT_ABLE_TO_SAVE_MESSAGE, 1); + return; + } + + unsigned int seed = 0; + int use_seed = get_seed_value(&seed); + terrain_generator_set_seed(use_seed, seed); + + scenario_editor_create(data.size_index); + scenario_map_init(); + clear_map_data(); + map_image_init_edges(); + // apply_scenario_settings(); + terrain_generator_generate((terrain_generator_algorithm) data.algorithm_index); + scenario_set_name(scenario_name_text); + scenario_set_custom(2); + + if (!game_file_editor_write_scenario(scenario_path)) { + window_plain_message_dialog_show(TR_SAVEGAME_NOT_ABLE_TO_SAVE_TITLE, + TR_SAVEGAME_NOT_ABLE_TO_SAVE_MESSAGE, 1); + return; + } + + window_city_show(); + if (!game_file_start_scenario_by_name(scenario_name())) { + window_plain_message_dialog_show_with_extra(TR_REPLAY_MAP_NOT_FOUND_TITLE, + TR_REPLAY_MAP_NOT_FOUND_MESSAGE, 0, scenario_name()); + } +} + static void button_back(const generic_button *button) { (void) button; @@ -367,19 +513,32 @@ static void algorithm_selected(int id) window_invalidate(); } +static void climate_selected(int id) +{ + data.climate_index = id; + generate_preview_map(); + window_invalidate(); +} + static void init(void) { data.focus_button_id = 0; data.size_index = 2; data.algorithm_index = 0; data.seed_text[0] = 0; + data.settings_initialized = 0; + data.climate_index = CLIMATE_CENTRAL; seed_input.text = data.seed_text; seed_input.allowed_chars = INPUT_BOX_CHARS_NUMERIC; - seed_input.placeholder = label_seed_placeholder; - terrain_generator_algorithm_labels[0] = translation_for(TR_TERRAIN_GENERATOR_FLAT_PLAINS); - terrain_generator_algorithm_labels[1] = translation_for(TR_TERRAIN_GENERATOR_RIVER_VALLEY); + unsigned int random_seed = generate_seed_value(); + sprintf(data.seed_text, "%d", random_seed); + seed_input.placeholder = data.seed_text; + + terrain_generator_algorithm_labels[2] = "Flat Plains"; + terrain_generator_algorithm_labels[1] = "River Valley"; + terrain_generator_algorithm_labels[0] = "Perlin"; input_box_start(&seed_input); generate_preview_map(); From 041a9c813bf6a8a3f45e22514181dc4faac12ae6 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 17:01:32 +0200 Subject: [PATCH 12/22] Update perlin generator --- src/scenario/terrain_generator/perlin.c | 194 ++++++++++++++++++ .../terrain_generator/terrain_generator.c | 15 +- .../terrain_generator/terrain_generator.h | 1 + .../terrain_generator_algorithms.h | 1 + 4 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 src/scenario/terrain_generator/perlin.c diff --git a/src/scenario/terrain_generator/perlin.c b/src/scenario/terrain_generator/perlin.c new file mode 100644 index 0000000000..7bfc0e7be1 --- /dev/null +++ b/src/scenario/terrain_generator/perlin.c @@ -0,0 +1,194 @@ +#include "terrain_generator_algorithms.h" + +#include "map/elevation.h" +#include "map/grid.h" +#include "map/terrain.h" + +#include +#include + +static unsigned int perlin_seed = 1; +static unsigned int mountain_seed = 1; +static unsigned int meadow_seed = 1; + +static uint32_t hash_2d(int x, int y, unsigned int seed) +{ + uint32_t h = (uint32_t) x; + h ^= (uint32_t) y * 374761393u; + h ^= seed * 668265263u; + h = (h ^ (h >> 13)) * 1274126177u; + return h ^ (h >> 16); +} + +static float hash_value(int x, int y, unsigned int seed) +{ + return (hash_2d(x, y, seed) & 0xffffu) / 65535.0f; +} + +static float fade(float t) +{ + return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); +} + +static float lerp(float a, float b, float t) +{ + return a + (b - a) * t; +} + +static float value_noise(float x, float y, unsigned int seed) +{ + int x0 = (int) floorf(x); + int y0 = (int) floorf(y); + int x1 = x0 + 1; + int y1 = y0 + 1; + + float sx = fade(x - (float) x0); + float sy = fade(y - (float) y0); + + float n00 = hash_value(x0, y0, seed); + float n10 = hash_value(x1, y0, seed); + float n01 = hash_value(x0, y1, seed); + float n11 = hash_value(x1, y1, seed); + + float ix0 = lerp(n00, n10, sx); + float ix1 = lerp(n01, n11, sx); + return lerp(ix0, ix1, sy); +} + +static float fbm(float x, float y, unsigned int seed) +{ + float sum = 0.0f; + float amp = 0.5f; + float freq = 0.02f; + float norm = 0.0f; + + for (int i = 0; i < 4; i++) { + sum += amp * value_noise(x * freq, y * freq, seed + (unsigned int) i * 101u); + norm += amp; + amp *= 0.5f; + freq *= 2.0f; + } + + if (norm > 0.0f) { + sum /= norm; + } + return sum; +} + +static void generate_grassland(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + map_terrain_set(grid_offset, TERRAIN_CLEAR); + map_elevation_set(grid_offset, 0); + } + } +} + +static void add_forests(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float n = fbm((float) x, (float) y, perlin_seed); + if (n > 0.58f) { + int terrain = map_terrain_get(map_grid_offset(x, y)); + // if (!(terrain & TERRAIN_TREE)) { + // terrain |= TERRAIN_TREE; + // } + // int terrain = TERRAIN_SHRUB; + // map_terrain_set(map_grid_offset(x, y), terrain); + map_terrain_set(map_grid_offset(x, y), TERRAIN_ROCK); + } + } + } +} + +static void add_mountains(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float n = fbm((float) x + 1000.0f, (float) y + 1000.0f, mountain_seed); + if (n > 0.68f) { + // int elevation = 2 + (int) ((n - 0.68f) * 10.0f); + // elevation = terrain_generator_clamp_int(elevation, 2, 4); + // map_elevation_set(map_grid_offset(x, y), elevation); + map_terrain_set(map_grid_offset(x, y), TERRAIN_ROCK); + } + } + } +} + +static void add_meadows(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + if (map_elevation_at(grid_offset) > 0) { + continue; + } + if (map_terrain_is(grid_offset, TERRAIN_TREE | TERRAIN_ROCK | TERRAIN_WATER)) { + continue; + } + float n = fbm((float) x + 2500.0f, (float) y + 2500.0f, meadow_seed); + if (n > 0.52f && n < 0.65f) { + map_terrain_set(grid_offset, TERRAIN_MEADOW); + } + } + } +} + +static void add_sea_edge(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + int side = terrain_generator_random_between(0, 4); + int thickness = terrain_generator_random_between(3, 6); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int is_sea = 0; + if (side == 0 && y < thickness) { + is_sea = 1; + } else if (side == 1 && y >= height - thickness) { + is_sea = 1; + } else if (side == 2 && x < thickness) { + is_sea = 1; + } else if (side == 3 && x >= width - thickness) { + is_sea = 1; + } + if (is_sea) { + int grid_offset = map_grid_offset(x, y); + map_terrain_set(grid_offset, TERRAIN_WATER); + map_elevation_set(grid_offset, 0); + } + } + } +} + +void terrain_generator_generate_perlin(unsigned int seed) +{ + + + perlin_seed = seed; + mountain_seed = seed; + meadow_seed = seed; + + generate_grassland(); + add_forests(); + // add_mountains(); + // add_meadows(); + // add_sea_edge(); +} diff --git a/src/scenario/terrain_generator/terrain_generator.c b/src/scenario/terrain_generator/terrain_generator.c index eb56f1ff3c..3d52f15471 100644 --- a/src/scenario/terrain_generator/terrain_generator.c +++ b/src/scenario/terrain_generator/terrain_generator.c @@ -13,6 +13,9 @@ #include +static int use_fixed_seed = 0; +static unsigned int fixed_seed = 0; + int terrain_generator_clamp_int(int value, int min_value, int max_value) { if (value < min_value) { @@ -29,9 +32,6 @@ int terrain_generator_random_between(int min_value, int max_value) return random_between_from_stdlib(min_value, max_value); } -static int use_fixed_seed = 0; -static unsigned int fixed_seed = 0; - static void clear_base_terrain(void) { int width = map_grid_width(); @@ -180,15 +180,22 @@ void terrain_generator_generate(terrain_generator_algorithm algorithm) if (use_fixed_seed) { random_set_stdlib_seed(fixed_seed); } else { - random_clear_stdlib_seed(); + fixed_seed = terrain_generator_random_between(1, 0x7fffffff); + use_fixed_seed = 1; + random_set_stdlib_seed(fixed_seed); } + + clear_base_terrain(); switch (algorithm) { case TERRAIN_GENERATOR_RIVER_VALLEY: terrain_generator_generate_river_valley(); break; + case TERRAIN_GENERATOR_PERLIN: + terrain_generator_generate_perlin(fixed_seed); + break; case TERRAIN_GENERATOR_FLAT_PLAINS: default: terrain_generator_generate_flat_plains(); diff --git a/src/scenario/terrain_generator/terrain_generator.h b/src/scenario/terrain_generator/terrain_generator.h index c5cca9048d..3b4ef1da17 100644 --- a/src/scenario/terrain_generator/terrain_generator.h +++ b/src/scenario/terrain_generator/terrain_generator.h @@ -4,6 +4,7 @@ typedef enum { TERRAIN_GENERATOR_FLAT_PLAINS = 0, TERRAIN_GENERATOR_RIVER_VALLEY = 1, + TERRAIN_GENERATOR_PERLIN = 2, TERRAIN_GENERATOR_COUNT } terrain_generator_algorithm; diff --git a/src/scenario/terrain_generator/terrain_generator_algorithms.h b/src/scenario/terrain_generator/terrain_generator_algorithms.h index 38dddf5a16..a35389493a 100644 --- a/src/scenario/terrain_generator/terrain_generator_algorithms.h +++ b/src/scenario/terrain_generator/terrain_generator_algorithms.h @@ -6,5 +6,6 @@ int terrain_generator_clamp_int(int value, int min_value, int max_value); void terrain_generator_generate_flat_plains(void); void terrain_generator_generate_river_valley(void); +void terrain_generator_generate_perlin(unsigned int); #endif // SCENARIO_TERRAIN_GENERATOR_ALGORITHMS_H From eb77a6506bfddf645127644b03c2b0f095d74b06 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 19:05:42 +0200 Subject: [PATCH 13/22] Finally fix the tree placement --- src/map/terrain.c | 17 +++++++++++++++++ src/map/terrain.h | 1 + src/scenario/terrain_generator/flat_plains.c | 10 ++++++---- src/scenario/terrain_generator/perlin.c | 10 +++++----- src/scenario/terrain_generator/river_valley.c | 10 +++++----- .../terrain_generator/terrain_generator.c | 2 ++ src/translation/english.c | 3 --- src/translation/french.c | 3 --- src/translation/german.c | 3 --- src/translation/greek.c | 3 --- src/translation/korean.c | 3 --- 11 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/map/terrain.c b/src/map/terrain.c index 212260e1ac..ce6ad209c9 100644 --- a/src/map/terrain.c +++ b/src/map/terrain.c @@ -10,6 +10,7 @@ #include "map/ring.h" #include "map/routing.h" #include "map/sprite.h" +#include "map/tiles.h" #include @@ -94,6 +95,22 @@ void map_terrain_set(int grid_offset, int terrain) terrain_grid.items[grid_offset] = terrain; } +void map_terrain_set_with_tile_update(int grid_offset, int terrain) +{ + map_terrain_set(grid_offset, terrain); + + if (terrain & TERRAIN_TREE) { + int x = map_grid_offset_to_x(grid_offset); + int y = map_grid_offset_to_y(grid_offset); + map_tiles_update_region_trees(x, y, x, y); + } + if (terrain & TERRAIN_SHRUB) { + int x = map_grid_offset_to_x(grid_offset); + int y = map_grid_offset_to_y(grid_offset); + map_tiles_update_region_shrub(x, y, x, y); + } +} + void map_terrain_add(int grid_offset, int terrain) { terrain_grid.items[grid_offset] |= terrain; diff --git a/src/map/terrain.h b/src/map/terrain.h index 48ed98bc7c..23a1076eb3 100644 --- a/src/map/terrain.h +++ b/src/map/terrain.h @@ -89,6 +89,7 @@ int map_terrain_get_from_buffer_16(buffer *buf, int grid_offset); int map_terrain_get_from_buffer_32(buffer *buf, int grid_offset); void map_terrain_set(int grid_offset, int terrain); +void map_terrain_set_with_tile_update(int grid_offset, int terrain); void map_terrain_add(int grid_offset, int terrain); diff --git a/src/scenario/terrain_generator/flat_plains.c b/src/scenario/terrain_generator/flat_plains.c index a068d89250..1e67c6223b 100644 --- a/src/scenario/terrain_generator/flat_plains.c +++ b/src/scenario/terrain_generator/flat_plains.c @@ -13,13 +13,15 @@ void terrain_generator_generate_flat_plains(void) int grid_offset = map_grid_offset(x, y); int roll = terrain_generator_random_between(0, 100); if (roll < 6) { - map_terrain_set(grid_offset, TERRAIN_TREE); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_TREE); } else if (roll < 8) { - map_terrain_set(grid_offset, TERRAIN_ROCK); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_ROCK); } else if (roll < 18) { - map_terrain_set(grid_offset, TERRAIN_SHRUB); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_SHRUB); } else if (roll < 35) { - map_terrain_set(grid_offset, TERRAIN_MEADOW); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_MEADOW); + } else if (roll < 60) { + map_terrain_set_with_tile_update(grid_offset, TERRAIN_WATER); } } } diff --git a/src/scenario/terrain_generator/perlin.c b/src/scenario/terrain_generator/perlin.c index 7bfc0e7be1..7abc9ef0a3 100644 --- a/src/scenario/terrain_generator/perlin.c +++ b/src/scenario/terrain_generator/perlin.c @@ -83,7 +83,7 @@ static void generate_grassland(void) for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int grid_offset = map_grid_offset(x, y); - map_terrain_set(grid_offset, TERRAIN_CLEAR); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_CLEAR); map_elevation_set(grid_offset, 0); } } @@ -104,7 +104,7 @@ static void add_forests(void) // } // int terrain = TERRAIN_SHRUB; // map_terrain_set(map_grid_offset(x, y), terrain); - map_terrain_set(map_grid_offset(x, y), TERRAIN_ROCK); + map_terrain_set_with_tile_update(map_grid_offset(x, y), TERRAIN_ROCK); } } } @@ -122,7 +122,7 @@ static void add_mountains(void) // int elevation = 2 + (int) ((n - 0.68f) * 10.0f); // elevation = terrain_generator_clamp_int(elevation, 2, 4); // map_elevation_set(map_grid_offset(x, y), elevation); - map_terrain_set(map_grid_offset(x, y), TERRAIN_ROCK); + map_terrain_set_with_tile_update(map_grid_offset(x, y), TERRAIN_ROCK); } } } @@ -144,7 +144,7 @@ static void add_meadows(void) } float n = fbm((float) x + 2500.0f, (float) y + 2500.0f, meadow_seed); if (n > 0.52f && n < 0.65f) { - map_terrain_set(grid_offset, TERRAIN_MEADOW); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_MEADOW); } } } @@ -171,7 +171,7 @@ static void add_sea_edge(void) } if (is_sea) { int grid_offset = map_grid_offset(x, y); - map_terrain_set(grid_offset, TERRAIN_WATER); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_WATER); map_elevation_set(grid_offset, 0); } } diff --git a/src/scenario/terrain_generator/river_valley.c b/src/scenario/terrain_generator/river_valley.c index 51a111f059..ae79b83470 100644 --- a/src/scenario/terrain_generator/river_valley.c +++ b/src/scenario/terrain_generator/river_valley.c @@ -161,7 +161,7 @@ void terrain_generator_generate_river_valley(void) continue; } int grid_offset = map_grid_offset(wx, wy); - map_terrain_set(grid_offset, TERRAIN_WATER); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_WATER); map_elevation_set(grid_offset, 0); } } @@ -195,13 +195,13 @@ void terrain_generator_generate_river_valley(void) int roll = terrain_generator_random_between(0, 100); if (dist <= 2 && roll < 30) { - map_terrain_set(grid_offset, TERRAIN_MEADOW); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_MEADOW); } else if (elevation >= 2 && roll < 12) { - map_terrain_set(grid_offset, TERRAIN_ROCK); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_ROCK); } else if (roll < 8) { - map_terrain_set(grid_offset, TERRAIN_TREE); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_TREE); } else if (roll < 16) { - map_terrain_set(grid_offset, TERRAIN_SHRUB); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_SHRUB); } } } diff --git a/src/scenario/terrain_generator/terrain_generator.c b/src/scenario/terrain_generator/terrain_generator.c index 3d52f15471..e3ed881d07 100644 --- a/src/scenario/terrain_generator/terrain_generator.c +++ b/src/scenario/terrain_generator/terrain_generator.c @@ -109,6 +109,8 @@ static void set_road_tile(int x, int y) map_property_clear_constructing(grid_offset); map_property_set_multi_tile_size(grid_offset, 1); map_property_mark_draw_tile(grid_offset); + map_tiles_update_area_roads(x, y, 3); + map_tiles_update_region_empty_land(x - 1, y - 1, x + 1, y + 1); } static void add_road_between_points(int start_x, int start_y, int end_x, int end_y) diff --git a/src/translation/english.c b/src/translation/english.c index 3e5db2cbc1..630216df09 100644 --- a/src/translation/english.c +++ b/src/translation/english.c @@ -1472,9 +1472,6 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TEXT, "Running the map editor from city mode can corrupt the save or cause the game to crash.\n\nProceed at your own risk and make sure you have a backup of the current save."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "Start new campaign"}, {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, - {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, - {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, - {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "Select a campaign"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Author:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "No description"}, diff --git a/src/translation/french.c b/src/translation/french.c index 08a0c51ecc..d0a22c70d9 100644 --- a/src/translation/french.c +++ b/src/translation/french.c @@ -1471,9 +1471,6 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TEXT, "Lancer l'éditeur depuis une cité peut corrompre la sauvegarde ou provoquer un arrêt inopiné.\n\nEffectuez une copie de la sauvegarde avant de continuer."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "Nouvelle campagne"}, {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, - {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, - {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, - {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "Choisir une campagne"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Auteur :"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Pas de description"}, diff --git a/src/translation/german.c b/src/translation/german.c index a8d584c303..c6d7e83f8e 100644 --- a/src/translation/german.c +++ b/src/translation/german.c @@ -1242,9 +1242,6 @@ static translation_string all_strings[] = { /* System - Kampagnen-Menü */ {TR_MAIN_MENU_SELECT_CAMPAIGN, "Neue Kampagne starten"}, {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, - {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, - {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, - {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "Eine Kampagne auswählen"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "Autor:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "Keine Beschreibung"}, diff --git a/src/translation/greek.c b/src/translation/greek.c index b797cf69cd..e06a3110c2 100644 --- a/src/translation/greek.c +++ b/src/translation/greek.c @@ -1478,9 +1478,6 @@ static translation_string all_strings[] = { { TR_CHEAT_EDITOR_WARNING_TEXT, "Η εκτέλεση του προγράμματος επεξεργασίας χαρτών από τη λειτουργία πόλης μπορεί να καταστρέψει την αποθήκευση ή να προκαλέσει σφάλμα στο παιχνίδι.\n\nΠροχωρήστε με δική σας ευθύνη και βεβαιωθείτε ότι έχετε δημιουργήσει αντίγραφο ασφαλείας της τρέχουσας αποθήκευσης." }, { TR_MAIN_MENU_SELECT_CAMPAIGN, "Έναρξη νέας εκστρατείας" }, { TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator" }, - { TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains" }, - { TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley" }, - { TR_TERRAIN_GENERATOR_PERLIN, "Perlin" }, { TR_WINDOW_SELECT_CAMPAIGN, "Επιλέξτε μια εκστρατεία" }, { TR_WINDOW_CAMPAIGN_AUTHOR, "Συγγραφέας:" }, { TR_WINDOW_CAMPAIGN_NO_DESC, "Καμία περιγραφή" }, diff --git a/src/translation/korean.c b/src/translation/korean.c index a59fc16edf..df09f49354 100644 --- a/src/translation/korean.c +++ b/src/translation/korean.c @@ -1470,9 +1470,6 @@ static translation_string all_strings[] = { {TR_CHEAT_EDITOR_WARNING_TEXT, "도시 모드에서 지도 편집기를 실행하면 저장 파일이 손상되거나 게임이 깨질 수 있습니다.\n진행은 사용자의 책임이며, 현재 저장 파일을 반드시 백업하십시오."}, {TR_MAIN_MENU_SELECT_CAMPAIGN, "새 캠페인 시작"}, {TR_MAIN_MENU_TERRAIN_GENERATOR, "Terrain generator"}, - {TR_TERRAIN_GENERATOR_FLAT_PLAINS, "Flat plains"}, - {TR_TERRAIN_GENERATOR_RIVER_VALLEY, "River valley"}, - {TR_TERRAIN_GENERATOR_PERLIN, "Perlin"}, {TR_WINDOW_SELECT_CAMPAIGN, "캠페인을 선택하세요"}, {TR_WINDOW_CAMPAIGN_AUTHOR, "원작자:"}, {TR_WINDOW_CAMPAIGN_NO_DESC, "설명 없음"}, From 65fbf9f90f2c0bb84b19f0c176f4deb2d41bc4c1 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 19:13:25 +0200 Subject: [PATCH 14/22] Add lakes and fix algorithm selection in window --- src/scenario/terrain_generator/perlin.c | 46 ++++++++++++++++++- .../terrain_generator/terrain_generator.h | 6 +-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/scenario/terrain_generator/perlin.c b/src/scenario/terrain_generator/perlin.c index 7abc9ef0a3..e1ad541929 100644 --- a/src/scenario/terrain_generator/perlin.c +++ b/src/scenario/terrain_generator/perlin.c @@ -150,6 +150,47 @@ static void add_meadows(void) } } + +static void add_lakes(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + int area = width * height; + + int lake_count = 1 + area / 8000; + if (lake_count < 1) { + lake_count = 1; + } else if (lake_count > 6) { + lake_count = 6; + } + + for (int i = 0; i < lake_count; i++) { + int radius = terrain_generator_random_between(2, 6); + int center_x = terrain_generator_random_between(radius + 2, width - radius - 3); + int center_y = terrain_generator_random_between(radius + 2, height - radius - 3); + unsigned int shape_seed = perlin_seed + 3001u + (unsigned int) i * 97u; + + for (int dy = -radius - 2; dy <= radius + 2; dy++) { + for (int dx = -radius - 2; dx <= radius + 2; dx++) { + int x = center_x + dx; + int y = center_y + dy; + if (!map_grid_is_inside(x, y, 1)) { + continue; + } + + float dist = sqrtf((float) (dx * dx + dy * dy)); + float n = fbm((float) x * 0.35f, (float) y * 0.35f, shape_seed); + float threshold = (float) radius * (0.75f + 0.55f * n); + if (dist <= threshold) { + int grid_offset = map_grid_offset(x, y); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_WATER); + map_elevation_set(grid_offset, 0); + } + } + } + } +} + static void add_sea_edge(void) { int width = map_grid_width(); @@ -188,7 +229,8 @@ void terrain_generator_generate_perlin(unsigned int seed) generate_grassland(); add_forests(); - // add_mountains(); - // add_meadows(); + add_mountains(); + add_meadows(); + add_lakes(); // add_sea_edge(); } diff --git a/src/scenario/terrain_generator/terrain_generator.h b/src/scenario/terrain_generator/terrain_generator.h index 3b4ef1da17..11005cd6b8 100644 --- a/src/scenario/terrain_generator/terrain_generator.h +++ b/src/scenario/terrain_generator/terrain_generator.h @@ -2,10 +2,10 @@ #define SCENARIO_TERRAIN_GENERATOR_H typedef enum { - TERRAIN_GENERATOR_FLAT_PLAINS = 0, + TERRAIN_GENERATOR_FLAT_PLAINS = 2, TERRAIN_GENERATOR_RIVER_VALLEY = 1, - TERRAIN_GENERATOR_PERLIN = 2, - TERRAIN_GENERATOR_COUNT + TERRAIN_GENERATOR_PERLIN = 0, + TERRAIN_GENERATOR_COUNT = 3 } terrain_generator_algorithm; void terrain_generator_generate(terrain_generator_algorithm algorithm); From e2a34b98db87f70d9f2d822844190c54a7a82957 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 21:11:47 +0200 Subject: [PATCH 15/22] Add river algorithm --- CMakeLists.txt | 1 + src/scenario/terrain_generator/perlin.c | 10 +- src/scenario/terrain_generator/river.c | 323 ++++++++++++++++++ .../terrain_generator_algorithms.h | 1 + 4 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 src/scenario/terrain_generator/river.c diff --git a/CMakeLists.txt b/CMakeLists.txt index af6b29652c..b9f6820825 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -539,6 +539,7 @@ set(SCENARIO_FILES ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/flat_plains.c ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river_valley.c ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/perlin.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river.c ${PROJECT_SOURCE_DIR}/src/scenario/emperor_change.c ${PROJECT_SOURCE_DIR}/src/scenario/empire.c ${PROJECT_SOURCE_DIR}/src/scenario/gladiator_revolt.c diff --git a/src/scenario/terrain_generator/perlin.c b/src/scenario/terrain_generator/perlin.c index e1ad541929..a06e0cb777 100644 --- a/src/scenario/terrain_generator/perlin.c +++ b/src/scenario/terrain_generator/perlin.c @@ -228,9 +228,11 @@ void terrain_generator_generate_perlin(unsigned int seed) meadow_seed = seed; generate_grassland(); - add_forests(); - add_mountains(); - add_meadows(); - add_lakes(); + + terrain_generator_generate_river(); + // add_forests(); + // add_mountains(); + // add_meadows(); + // add_lakes(); // add_sea_edge(); } diff --git a/src/scenario/terrain_generator/river.c b/src/scenario/terrain_generator/river.c new file mode 100644 index 0000000000..1e9806b4a2 --- /dev/null +++ b/src/scenario/terrain_generator/river.c @@ -0,0 +1,323 @@ +#include "terrain_generator_algorithms.h" + +#include "map/elevation.h" +#include "map/grid.h" +#include "map/terrain.h" + +enum { + SIDE_NORTH = 0, + SIDE_SOUTH = 1, + SIDE_WEST = 2, + SIDE_EAST = 3, + DIR_COUNT = 8, +}; + +static const int DIR_X[DIR_COUNT] = { 0, 1, 1, 1, 0, -1, -1, -1 }; +static const int DIR_Y[DIR_COUNT] = { -1, -1, 0, 1, 1, 1, 0, -1 }; + +static int abs_int(int value) +{ + return (value < 0) ? -value : value; +} + +static int sign_int(int value) +{ + return (value > 0) - (value < 0); +} + +static int tile_is_on_side(int x, int y, int width, int height, int side) +{ + switch (side) { + case SIDE_NORTH: + return y == 0; + case SIDE_SOUTH: + return y == height - 1; + case SIDE_WEST: + return x == 0; + default: + return x == width - 1; + } +} + +static void choose_edge_point(int side, int width, int height, int *x, int *y) +{ + switch (side) { + case SIDE_NORTH: + *x = terrain_generator_random_between(0, width); + *y = 0; + break; + case SIDE_SOUTH: + *x = terrain_generator_random_between(0, width); + *y = height - 1; + break; + case SIDE_WEST: + *x = 0; + *y = terrain_generator_random_between(0, height); + break; + default: + *x = width - 1; + *y = terrain_generator_random_between(0, height); + break; + } +} + +static int direction_moves_away_from_side(int dir, int side) +{ + switch (side) { + case SIDE_NORTH: + return DIR_Y[dir] > 0; + case SIDE_SOUTH: + return DIR_Y[dir] < 0; + case SIDE_WEST: + return DIR_X[dir] > 0; + default: + return DIR_X[dir] < 0; + } +} + +static int direction_moves_toward_side(int dir, int side) +{ + switch (side) { + case SIDE_NORTH: + return DIR_Y[dir] < 0; + case SIDE_SOUTH: + return DIR_Y[dir] > 0; + case SIDE_WEST: + return DIR_X[dir] < 0; + default: + return DIR_X[dir] > 0; + } +} + +static void carve_river_tile(int x, int y) +{ + int grid_offset = map_grid_offset(x, y); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_WATER); + map_elevation_set(grid_offset, 0); +} + +typedef struct { + int x; + int y; +} river_tile; + +static river_tile generated_river_tiles[GRID_SIZE * GRID_SIZE]; +static int generated_river_tile_count = 0; + +static void record_river_tile(int x, int y) +{ + if (generated_river_tile_count <= 0) { + generated_river_tiles[generated_river_tile_count].x = x; + generated_river_tiles[generated_river_tile_count].y = y; + generated_river_tile_count++; + return; + } + + river_tile *last = &generated_river_tiles[generated_river_tile_count - 1]; + if (last->x == x && last->y == y) { + return; + } + + if (generated_river_tile_count >= GRID_SIZE * GRID_SIZE) { + return; + } + + generated_river_tiles[generated_river_tile_count].x = x; + generated_river_tiles[generated_river_tile_count].y = y; + generated_river_tile_count++; +} + +static void replay_river_tiles(void) +{ + for (int i = 0; i < generated_river_tile_count; i++) { + carve_river_tile(generated_river_tiles[i].x, generated_river_tiles[i].y); + } +} + +static int choose_next_direction( + int x, + int y, + int target_x, + int target_y, + int previous_dir, + int phase, + int start_side, + int end_side, + int width, + int height, + int step, + int min_exit_steps) +{ + int total_weight = 0; + int weights[DIR_COUNT] = { 0 }; + + int target_step_x = sign_int(target_x - x); + int target_step_y = sign_int(target_y - y); + + for (int dir = 0; dir < DIR_COUNT; dir++) { + int nx = x + DIR_X[dir]; + int ny = y + DIR_Y[dir]; + if (!map_grid_is_inside(nx, ny, 1)) { + continue; + } + + int weight = 1; + + if (previous_dir >= 0) { + int delta = dir - previous_dir; + if (delta < 0) { + delta = -delta; + } + if (delta > DIR_COUNT / 2) { + delta = DIR_COUNT - delta; + } + + if (delta == 4) { + continue; + } else if (delta == 0) { + weight += 8; + } else if (delta == 1) { + weight += 3; + } else if (delta == 2) { + weight += 1; + } + } + + int alignment = DIR_X[dir] * target_step_x + DIR_Y[dir] * target_step_y; + if (alignment >= 2) { + weight += (phase == 0) ? 5 : 4; + } else if (alignment == 1) { + weight += 2; + } + + if (phase == 0 && direction_moves_away_from_side(dir, start_side)) { + weight += 3; + } + if (phase == 1 && direction_moves_toward_side(dir, end_side)) { + weight += 2; + } + + if (step < min_exit_steps && tile_is_on_side(nx, ny, width, height, start_side)) { + weight -= 4; + } + + weight += terrain_generator_random_between(0, 3); + + if (weight <= 0) { + continue; + } + + weights[dir] = weight; + total_weight += weight; + } + + if (total_weight <= 0) { + return -1; + } + + int pick = terrain_generator_random_between(0, total_weight); + for (int dir = 0; dir < DIR_COUNT; dir++) { + if (weights[dir] <= 0) { + continue; + } + if (pick < weights[dir]) { + return dir; + } + pick -= weights[dir]; + } + + return -1; +} + +void terrain_generator_generate_river(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + if (width <= 1 || height <= 1) { + return; + } + + generated_river_tile_count = 0; + + int start_side = terrain_generator_random_between(0, 4); + int end_side = terrain_generator_random_between(0, 4); + while (end_side == start_side) { + end_side = terrain_generator_random_between(0, 4); + } + + int x = 0; + int y = 0; + int end_x = 0; + int end_y = 0; + choose_edge_point(start_side, width, height, &x, &y); + choose_edge_point(end_side, width, height, &end_x, &end_y); + + int center_x = width / 2; + int center_y = height / 2; + int center_radius = terrain_generator_clamp_int((width + height) / 16, 4, 12); + int min_exit_steps = terrain_generator_clamp_int((width + height) / 6, 8, 80); + + int previous_dir = -1; + int phase = 0; + int max_steps = width * height; + + for (int step = 0; step < max_steps; step++) { + record_river_tile(x, y); + + if (phase == 0) { + int distance_to_center = abs_int(x - center_x) + abs_int(y - center_y); + if (distance_to_center <= center_radius) { + phase = 1; + } + } + + if (phase == 1 && step >= min_exit_steps && tile_is_on_side(x, y, width, height, end_side)) { + break; + } + + int target_x = (phase == 0) ? center_x : end_x; + int target_y = (phase == 0) ? center_y : end_y; + int dir = choose_next_direction( + x, + y, + target_x, + target_y, + previous_dir, + phase, + start_side, + end_side, + width, + height, + step, + min_exit_steps); + if (dir < 0) { + break; + } + + x += DIR_X[dir]; + y += DIR_Y[dir]; + previous_dir = dir; + } + + for (int guard = 0; guard < width + height; guard++) { + if (tile_is_on_side(x, y, width, height, end_side)) { + break; + } + + record_river_tile(x, y); + + int step_x = sign_int(end_x - x); + int step_y = sign_int(end_y - y); + if (abs_int(end_x - x) >= abs_int(end_y - y)) { + x += step_x; + } else { + y += step_y; + } + x = terrain_generator_clamp_int(x, 0, width - 1); + y = terrain_generator_clamp_int(y, 0, height - 1); + } + + record_river_tile(x, y); + + replay_river_tiles(); +} diff --git a/src/scenario/terrain_generator/terrain_generator_algorithms.h b/src/scenario/terrain_generator/terrain_generator_algorithms.h index a35389493a..51a4394e95 100644 --- a/src/scenario/terrain_generator/terrain_generator_algorithms.h +++ b/src/scenario/terrain_generator/terrain_generator_algorithms.h @@ -5,6 +5,7 @@ int terrain_generator_random_between(int min_value, int max_value); int terrain_generator_clamp_int(int value, int min_value, int max_value); void terrain_generator_generate_flat_plains(void); +void terrain_generator_generate_river(void); void terrain_generator_generate_river_valley(void); void terrain_generator_generate_perlin(unsigned int); From 90109bff8f56b448e2dea3a336c8e30f3121502b Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 21:45:08 +0200 Subject: [PATCH 16/22] Only generate terrain on clear land --- src/scenario/terrain_generator/perlin.c | 27 +++--- src/scenario/terrain_generator/river.c | 89 ++++++++++++++++++- .../terrain_generator_algorithms.h | 1 + 3 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/scenario/terrain_generator/perlin.c b/src/scenario/terrain_generator/perlin.c index a06e0cb777..ce2c83d429 100644 --- a/src/scenario/terrain_generator/perlin.c +++ b/src/scenario/terrain_generator/perlin.c @@ -96,15 +96,14 @@ static void add_forests(void) for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + if (map_terrain_get(grid_offset) != TERRAIN_CLEAR) { + continue; + } + float n = fbm((float) x, (float) y, perlin_seed); if (n > 0.58f) { - int terrain = map_terrain_get(map_grid_offset(x, y)); - // if (!(terrain & TERRAIN_TREE)) { - // terrain |= TERRAIN_TREE; - // } - // int terrain = TERRAIN_SHRUB; - // map_terrain_set(map_grid_offset(x, y), terrain); - map_terrain_set_with_tile_update(map_grid_offset(x, y), TERRAIN_ROCK); + map_terrain_set_with_tile_update(grid_offset, TERRAIN_TREE); } } } @@ -117,6 +116,10 @@ static void add_mountains(void) for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { + int grid_offset = map_grid_offset(x, y); + if (map_terrain_get(grid_offset) != TERRAIN_CLEAR) { + continue; + } float n = fbm((float) x + 1000.0f, (float) y + 1000.0f, mountain_seed); if (n > 0.68f) { // int elevation = 2 + (int) ((n - 0.68f) * 10.0f); @@ -136,6 +139,9 @@ static void add_meadows(void) for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int grid_offset = map_grid_offset(x, y); + if (map_terrain_get(grid_offset) != TERRAIN_CLEAR) { + continue; + } if (map_elevation_at(grid_offset) > 0) { continue; } @@ -230,9 +236,10 @@ void terrain_generator_generate_perlin(unsigned int seed) generate_grassland(); terrain_generator_generate_river(); - // add_forests(); - // add_mountains(); - // add_meadows(); + // terrain_generator_straight_river(); + add_forests(); + add_mountains(); + add_meadows(); // add_lakes(); // add_sea_edge(); } diff --git a/src/scenario/terrain_generator/river.c b/src/scenario/terrain_generator/river.c index 1e9806b4a2..719d241015 100644 --- a/src/scenario/terrain_generator/river.c +++ b/src/scenario/terrain_generator/river.c @@ -96,6 +96,30 @@ static void carve_river_tile(int x, int y) map_elevation_set(grid_offset, 0); } +static void carve_river_tile_brush(int x, int y, int r) +{ + if (r <= 0) { + carve_river_tile(x, y); + return; + } + + for (int dy = -r; dy <= r; dy++) { + for (int dx = -r; dx <= r; dx++) { + if ((dx * dx) + (dy * dy) > (r * r)) { + continue; + } + + int nx = x + dx; + int ny = y + dy; + if (!map_grid_is_inside(nx, ny, 1)) { + continue; + } + + carve_river_tile(nx, ny); + } + } +} + typedef struct { int x; int y; @@ -127,10 +151,24 @@ static void record_river_tile(int x, int y) generated_river_tile_count++; } + + static void replay_river_tiles(void) { + const int river_min_radius = 1; + const int river_max_radius = 10; + int current_river_radius = river_min_radius; + for (int i = 0; i < generated_river_tile_count; i++) { - carve_river_tile(generated_river_tiles[i].x, generated_river_tiles[i].y); + // Adjust the river_radius by randomly increasing or decreasing it by 1 step + current_river_radius += terrain_generator_random_between(-1, 2); + if (current_river_radius < river_min_radius) { + current_river_radius = river_min_radius; + } else if (current_river_radius > river_max_radius) { + current_river_radius = river_max_radius; + } + + carve_river_tile_brush(generated_river_tiles[i].x, generated_river_tiles[i].y, current_river_radius); } } @@ -229,6 +267,55 @@ static int choose_next_direction( return -1; } + + +void terrain_generator_straight_river(void) +{ + int width = map_grid_width(); + int height = map_grid_height(); + if (width <= 1 || height <= 1) { + return; + } + + generated_river_tile_count = 0; + + int start_side = terrain_generator_random_between(0, 4); + int x = 0; + int y = 0; + choose_edge_point(start_side, width, height, &x, &y); + + int end_x = x; + int end_y = y; + switch (start_side) { + case SIDE_NORTH: + end_y = height - 1; + break; + case SIDE_SOUTH: + end_y = 0; + break; + case SIDE_WEST: + end_x = width - 1; + break; + default: + end_x = 0; + break; + } + + int step_x = sign_int(end_x - x); + int step_y = sign_int(end_y - y); + int guard = 0; + int max_steps = width + height + 4; + + while ((x != end_x || y != end_y) && guard++ < max_steps) { + record_river_tile(x, y); + x += step_x; + y += step_y; + } + + record_river_tile(end_x, end_y); + replay_river_tiles(); +} + void terrain_generator_generate_river(void) { int width = map_grid_width(); diff --git a/src/scenario/terrain_generator/terrain_generator_algorithms.h b/src/scenario/terrain_generator/terrain_generator_algorithms.h index 51a4394e95..218b691317 100644 --- a/src/scenario/terrain_generator/terrain_generator_algorithms.h +++ b/src/scenario/terrain_generator/terrain_generator_algorithms.h @@ -6,6 +6,7 @@ int terrain_generator_clamp_int(int value, int min_value, int max_value); void terrain_generator_generate_flat_plains(void); void terrain_generator_generate_river(void); +void terrain_generator_straight_river(void); void terrain_generator_generate_river_valley(void); void terrain_generator_generate_perlin(unsigned int); From 43a1834887ab985f4a27fcb563e9ad331f835baf Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 22:03:11 +0200 Subject: [PATCH 17/22] Add random scenario conditions --- .../terrain_generator/terrain_generator.c | 2 + src/window/terrain_generator.c | 68 +++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/scenario/terrain_generator/terrain_generator.c b/src/scenario/terrain_generator/terrain_generator.c index e3ed881d07..6124603b7b 100644 --- a/src/scenario/terrain_generator/terrain_generator.c +++ b/src/scenario/terrain_generator/terrain_generator.c @@ -13,6 +13,8 @@ #include +#include "map/routing.h" + static int use_fixed_seed = 0; static unsigned int fixed_seed = 0; diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c index f2c21e87e9..e4edbbc26c 100644 --- a/src/window/terrain_generator.c +++ b/src/window/terrain_generator.c @@ -114,6 +114,9 @@ static void button_back(const generic_button *button); static void size_selected(int id); static void algorithm_selected(int id); static void climate_selected(int id); +static void sync_settings_from_scenario(void); +static void apply_scenario_settings(void); +static void randomize_scenario_settings(void); static const uint8_t label_size[] = "Size"; static const uint8_t label_algorithm[] = "Algorithm"; @@ -190,6 +193,8 @@ static struct { uint8_t seed_text[SEED_TEXT_LENGTH]; int settings_initialized; scenario_climate climate_index; + int initial_funds; + scenario_win_criteria win_criteria; } data; static minimap_functions preview_minimap_functions; @@ -260,10 +265,11 @@ static void generate_preview_map(void) scenario_editor_create(data.size_index); scenario_map_init(); if (data.settings_initialized) { - // apply_scenario_settings(); + apply_scenario_settings(); } else { - // sync_settings_from_scenario(); + sync_settings_from_scenario(); data.settings_initialized = 1; + apply_scenario_settings(); } clear_map_data(); map_image_init_edges(); @@ -416,6 +422,7 @@ static void button_randomize(const generic_button *button) unsigned int random_seed = generate_seed_value(); sprintf(data.seed_text, "%d", random_seed); seed_input.placeholder = data.seed_text; + randomize_scenario_settings(); // input_box_set_text(&seed_input, data.seed_text); generate_preview_map(); window_invalidate(); @@ -441,7 +448,7 @@ static void button_open_editor(const generic_button *button) return; } - // apply_scenario_settings(); + apply_scenario_settings(); input_box_stop(&seed_input); if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { @@ -474,7 +481,7 @@ static void button_start_game(const generic_button *button) scenario_map_init(); clear_map_data(); map_image_init_edges(); - // apply_scenario_settings(); + apply_scenario_settings(); terrain_generator_generate((terrain_generator_algorithm) data.algorithm_index); scenario_set_name(scenario_name_text); scenario_set_custom(2); @@ -520,6 +527,57 @@ static void climate_selected(int id) window_invalidate(); } +static void sync_settings_from_scenario(void) +{ + data.climate_index = scenario_property_climate(); + data.initial_funds = scenario.initial_funds; + data.win_criteria = scenario.win_criteria; +} + +static void apply_scenario_settings(void) +{ + scenario_change_climate(data.climate_index); + scenario.initial_funds = data.initial_funds; + scenario.win_criteria = data.win_criteria; + + // Stub: future scenario settings (resources/victory/etc.) should be applied here. +} + +static void randomize_scenario_settings(void) +{ + data.climate_index = (scenario_climate) (generate_seed_value() % CLIMATE_COUNT); + + data.initial_funds = 500 + (int) (generate_seed_value() % 60) * 250; + + data.win_criteria.culture.enabled = (int) (generate_seed_value() % 2); + data.win_criteria.prosperity.enabled = (int) (generate_seed_value() % 2); + data.win_criteria.peace.enabled = (int) (generate_seed_value() % 2); + data.win_criteria.favor.enabled = (int) (generate_seed_value() % 2); + data.win_criteria.population.enabled = (int) (generate_seed_value() % 3 == 0); + + data.win_criteria.culture.goal = 10 + (int) (generate_seed_value() % 91); + data.win_criteria.prosperity.goal = 10 + (int) (generate_seed_value() % 91); + data.win_criteria.peace.goal = 10 + (int) (generate_seed_value() % 91); + data.win_criteria.favor.goal = 10 + (int) (generate_seed_value() % 91); + data.win_criteria.population.goal = 500 + (int) (generate_seed_value() % 56) * 250; + + data.win_criteria.time_limit.enabled = (int) (generate_seed_value() % 4 == 0); + data.win_criteria.time_limit.years = 5 + (int) (generate_seed_value() % 36); + + data.win_criteria.survival_time.enabled = 0; + if (!data.win_criteria.time_limit.enabled && generate_seed_value() % 5 == 0) { + data.win_criteria.survival_time.enabled = 1; + } + data.win_criteria.survival_time.years = 5 + (int) (generate_seed_value() % 36); + + if (!data.win_criteria.culture.enabled && !data.win_criteria.prosperity.enabled + && !data.win_criteria.peace.enabled && !data.win_criteria.favor.enabled + && !data.win_criteria.population.enabled && !data.win_criteria.time_limit.enabled + && !data.win_criteria.survival_time.enabled) { + data.win_criteria.culture.enabled = 1; + } +} + static void init(void) { data.focus_button_id = 0; @@ -528,6 +586,8 @@ static void init(void) data.seed_text[0] = 0; data.settings_initialized = 0; data.climate_index = CLIMATE_CENTRAL; + data.initial_funds = 1000; + memset(&data.win_criteria, 0, sizeof(data.win_criteria)); seed_input.text = data.seed_text; seed_input.allowed_chars = INPUT_BOX_CHARS_NUMERIC; From e9e8ea25f93c8804e758f7440b49389aec2accbc Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Fri, 3 Apr 2026 22:31:30 +0200 Subject: [PATCH 18/22] Make win criteria editable --- src/window/terrain_generator.c | 343 +++++++++++++++++++++++++++++++-- 1 file changed, 323 insertions(+), 20 deletions(-) diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c index e4edbbc26c..032f04c07e 100644 --- a/src/window/terrain_generator.c +++ b/src/window/terrain_generator.c @@ -51,6 +51,7 @@ #include "window/city.h" #include "window/editor/map.h" +#include #include #define WINDOW_WIDTH 640 @@ -102,6 +103,20 @@ #define CLIMATE_COUNT 3 +#define SETTINGS_INPUT_WIDTH_BLOCKS 8 +#define SETTINGS_INPUT_HEIGHT_BLOCKS 2 +#define SETTINGS_INPUT_X CONTROL_VALUE_X +#define SETTINGS_START_Y 196 +#define SETTINGS_ROW_SPACING 24 +#define SETTINGS_TEXT_LENGTH 12 + +#define FUNDS_MIN 0 +#define FUNDS_MAX 99999 +#define CRITERIA_PERCENT_MIN 0 +#define CRITERIA_PERCENT_MAX 100 +#define POPULATION_GOAL_MIN 0 +#define POPULATION_GOAL_MAX 50000 + static void button_select_size(const generic_button *button); static void button_select_algorithm(const generic_button *button); static void button_select_climate(const generic_button *button); @@ -117,6 +132,15 @@ static void climate_selected(int id); static void sync_settings_from_scenario(void); static void apply_scenario_settings(void); static void randomize_scenario_settings(void); +static void sync_settings_to_inputs(void); +static void sync_inputs_to_settings(void); +static void set_input_box_value(input_box *box, int value); +static void initial_funds_on_change(int is_addition_at_end); +static void culture_goal_on_change(int is_addition_at_end); +static void prosperity_goal_on_change(int is_addition_at_end); +static void peace_goal_on_change(int is_addition_at_end); +static void favor_goal_on_change(int is_addition_at_end); +static void population_goal_on_change(int is_addition_at_end); static const uint8_t label_size[] = "Size"; static const uint8_t label_algorithm[] = "Algorithm"; @@ -128,6 +152,12 @@ static const uint8_t label_open_editor[] = "Open in editor"; static const uint8_t label_start_game[] = "Start game"; static const uint8_t label_back[] = "Back"; static const uint8_t label_seed_placeholder[] = "Random"; +static const uint8_t label_initial_funds[] = "Initial funds"; +static const uint8_t label_culture_goal[] = "Culture goal"; +static const uint8_t label_prosperity_goal[] = "Prosperity goal"; +static const uint8_t label_peace_goal[] = "Peace goal"; +static const uint8_t label_favor_goal[] = "Favor goal"; +static const uint8_t label_population_goal[] = "Population goal"; static const uint8_t size_label_40[] = "40 x 40"; static const uint8_t size_label_60[] = "60 x 60"; @@ -186,6 +216,120 @@ static input_box seed_input = { INPUT_BOX_CHARS_NUMERIC }; +static uint8_t initial_funds_text[SETTINGS_TEXT_LENGTH] = { 0 }; +static uint8_t culture_goal_text[SETTINGS_TEXT_LENGTH] = { 0 }; +static uint8_t prosperity_goal_text[SETTINGS_TEXT_LENGTH] = { 0 }; +static uint8_t peace_goal_text[SETTINGS_TEXT_LENGTH] = { 0 }; +static uint8_t favor_goal_text[SETTINGS_TEXT_LENGTH] = { 0 }; +static uint8_t population_goal_text[SETTINGS_TEXT_LENGTH] = { 0 }; + +static input_box initial_funds_input = { + SETTINGS_INPUT_X, + SETTINGS_START_Y, + SETTINGS_INPUT_WIDTH_BLOCKS, + SETTINGS_INPUT_HEIGHT_BLOCKS, + FONT_NORMAL_WHITE, + 0, + initial_funds_text, + SETTINGS_TEXT_LENGTH, + 0, + NULL, + initial_funds_on_change, + NULL, + INPUT_BOX_CHARS_NUMERIC +}; + +static input_box culture_goal_input = { + SETTINGS_INPUT_X, + SETTINGS_START_Y + SETTINGS_ROW_SPACING, + SETTINGS_INPUT_WIDTH_BLOCKS, + SETTINGS_INPUT_HEIGHT_BLOCKS, + FONT_NORMAL_WHITE, + 0, + culture_goal_text, + SETTINGS_TEXT_LENGTH, + 0, + NULL, + culture_goal_on_change, + NULL, + INPUT_BOX_CHARS_NUMERIC +}; + +static input_box prosperity_goal_input = { + SETTINGS_INPUT_X, + SETTINGS_START_Y + SETTINGS_ROW_SPACING * 2, + SETTINGS_INPUT_WIDTH_BLOCKS, + SETTINGS_INPUT_HEIGHT_BLOCKS, + FONT_NORMAL_WHITE, + 0, + prosperity_goal_text, + SETTINGS_TEXT_LENGTH, + 0, + NULL, + prosperity_goal_on_change, + NULL, + INPUT_BOX_CHARS_NUMERIC +}; + +static input_box peace_goal_input = { + SETTINGS_INPUT_X, + SETTINGS_START_Y + SETTINGS_ROW_SPACING * 3, + SETTINGS_INPUT_WIDTH_BLOCKS, + SETTINGS_INPUT_HEIGHT_BLOCKS, + FONT_NORMAL_WHITE, + 0, + peace_goal_text, + SETTINGS_TEXT_LENGTH, + 0, + NULL, + peace_goal_on_change, + NULL, + INPUT_BOX_CHARS_NUMERIC +}; + +static input_box favor_goal_input = { + SETTINGS_INPUT_X, + SETTINGS_START_Y + SETTINGS_ROW_SPACING * 4, + SETTINGS_INPUT_WIDTH_BLOCKS, + SETTINGS_INPUT_HEIGHT_BLOCKS, + FONT_NORMAL_WHITE, + 0, + favor_goal_text, + SETTINGS_TEXT_LENGTH, + 0, + NULL, + favor_goal_on_change, + NULL, + INPUT_BOX_CHARS_NUMERIC +}; + +static input_box population_goal_input = { + SETTINGS_INPUT_X, + SETTINGS_START_Y + SETTINGS_ROW_SPACING * 5, + SETTINGS_INPUT_WIDTH_BLOCKS, + SETTINGS_INPUT_HEIGHT_BLOCKS, + FONT_NORMAL_WHITE, + 0, + population_goal_text, + SETTINGS_TEXT_LENGTH, + 0, + NULL, + population_goal_on_change, + NULL, + INPUT_BOX_CHARS_NUMERIC +}; + +static input_box *settings_inputs[] = { + &initial_funds_input, + &culture_goal_input, + &prosperity_goal_input, + &peace_goal_input, + &favor_goal_input, + &population_goal_input +}; + +static input_box *active_input = &seed_input; + static struct { unsigned int focus_button_id; int size_index; @@ -260,6 +404,131 @@ static int get_seed_value(unsigned int *seed_out) return 1; } +static int clamp_int(int value, int min_value, int max_value) +{ + if (value < min_value) { + return min_value; + } + if (value > max_value) { + return max_value; + } + return value; +} + +static int parse_input_box_value(const input_box *box) +{ + if (!string_length(box->text)) { + return 0; + } + return string_to_int(box->text); +} + +static int sanitize_input_box_value(input_box *box, int min_value, int max_value) +{ + int value = clamp_int(parse_input_box_value(box), min_value, max_value); + set_input_box_value(box, value); + return value; +} + +static void set_input_box_value(input_box *box, int value) +{ + snprintf((char *) box->text, box->text_length, "%d", value); +} + +static void initial_funds_on_change(int is_addition_at_end) +{ + (void) is_addition_at_end; + sanitize_input_box_value(&initial_funds_input, FUNDS_MIN, FUNDS_MAX); +} + +static void culture_goal_on_change(int is_addition_at_end) +{ + (void) is_addition_at_end; + sanitize_input_box_value(&culture_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); +} + +static void prosperity_goal_on_change(int is_addition_at_end) +{ + (void) is_addition_at_end; + sanitize_input_box_value(&prosperity_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); +} + +static void peace_goal_on_change(int is_addition_at_end) +{ + (void) is_addition_at_end; + sanitize_input_box_value(&peace_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); +} + +static void favor_goal_on_change(int is_addition_at_end) +{ + (void) is_addition_at_end; + sanitize_input_box_value(&favor_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); +} + +static void population_goal_on_change(int is_addition_at_end) +{ + (void) is_addition_at_end; + sanitize_input_box_value(&population_goal_input, POPULATION_GOAL_MIN, POPULATION_GOAL_MAX); +} + +static void draw_static_input_box(const input_box *box) +{ + inner_panel_draw(box->x, box->y, box->width_blocks, box->height_blocks); + text_draw(box->text, box->x + 16, box->y + 10, box->font, 0); +} + +static int is_mouse_inside_box(const mouse *m, const input_box *box) +{ + return m->x >= box->x && m->x < box->x + box->width_blocks * BLOCK_SIZE + && m->y >= box->y && m->y < box->y + box->height_blocks * BLOCK_SIZE; +} + +static void focus_input_box(input_box *box) +{ + if (active_input == box) { + return; + } + + if (active_input) { + input_box_stop(active_input); + } + active_input = box; + input_box_start(active_input); +} + +static void sync_settings_to_inputs(void) +{ + set_input_box_value(&initial_funds_input, data.initial_funds); + set_input_box_value(&culture_goal_input, data.win_criteria.culture.goal); + set_input_box_value(&prosperity_goal_input, data.win_criteria.prosperity.goal); + set_input_box_value(&peace_goal_input, data.win_criteria.peace.goal); + set_input_box_value(&favor_goal_input, data.win_criteria.favor.goal); + set_input_box_value(&population_goal_input, data.win_criteria.population.goal); + + if (active_input) { + input_box_refresh_text(active_input); + } +} + +static void sync_inputs_to_settings(void) +{ + data.initial_funds = sanitize_input_box_value(&initial_funds_input, FUNDS_MIN, FUNDS_MAX); + + data.win_criteria.culture.goal = sanitize_input_box_value(&culture_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); + data.win_criteria.prosperity.goal = sanitize_input_box_value(&prosperity_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); + data.win_criteria.peace.goal = sanitize_input_box_value(&peace_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); + data.win_criteria.favor.goal = sanitize_input_box_value(&favor_goal_input, CRITERIA_PERCENT_MIN, CRITERIA_PERCENT_MAX); + data.win_criteria.population.goal = sanitize_input_box_value(&population_goal_input, POPULATION_GOAL_MIN, POPULATION_GOAL_MAX); + + data.win_criteria.culture.enabled = data.win_criteria.culture.goal > 0; + data.win_criteria.prosperity.enabled = data.win_criteria.prosperity.goal > 0; + data.win_criteria.peace.enabled = data.win_criteria.peace.goal > 0; + data.win_criteria.favor.enabled = data.win_criteria.favor.goal > 0; + data.win_criteria.population.enabled = data.win_criteria.population.goal > 0; + + sync_settings_to_inputs(); +} + static void generate_preview_map(void) { scenario_editor_create(data.size_index); @@ -336,6 +605,13 @@ static void draw_background(void) // text_draw(label_randomize, CONTROL_LABEL_X, RANDOMIZE_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); text_draw(label_climate, CONTROL_LABEL_X, CLIMATE_BUTTON_Y + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_initial_funds, CONTROL_LABEL_X, SETTINGS_START_Y + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_culture_goal, CONTROL_LABEL_X, SETTINGS_START_Y + SETTINGS_ROW_SPACING + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_prosperity_goal, CONTROL_LABEL_X, SETTINGS_START_Y + SETTINGS_ROW_SPACING * 2 + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_peace_goal, CONTROL_LABEL_X, SETTINGS_START_Y + SETTINGS_ROW_SPACING * 3 + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_favor_goal, CONTROL_LABEL_X, SETTINGS_START_Y + SETTINGS_ROW_SPACING * 4 + 6, FONT_NORMAL_BLACK, 0); + text_draw(label_population_goal, CONTROL_LABEL_X, SETTINGS_START_Y + SETTINGS_ROW_SPACING * 5 + 6, FONT_NORMAL_BLACK, 0); + inner_panel_draw(PREVIEW_X - 8, PREVIEW_Y - 8, (PREVIEW_WIDTH + 16) / BLOCK_SIZE, (PREVIEW_HEIGHT + 16) / BLOCK_SIZE); widget_minimap_draw(PREVIEW_X, PREVIEW_Y, PREVIEW_WIDTH, PREVIEW_HEIGHT); @@ -372,7 +648,18 @@ static void draw_foreground(void) text_draw_centered(label_back, ACTION_BUTTON_X, BACK_BUTTON_Y + 8, ACTION_BUTTON_WIDTH, FONT_NORMAL_BLACK, 0); - input_box_draw(&seed_input); + if (active_input == &seed_input) { + input_box_draw(&seed_input); + } else { + draw_static_input_box(&seed_input); + } + for (unsigned int i = 0; i < sizeof(settings_inputs) / sizeof(settings_inputs[0]); i++) { + if (settings_inputs[i] == active_input) { + input_box_draw(settings_inputs[i]); + } else { + draw_static_input_box(settings_inputs[i]); + } + } graphics_reset_dialog(); } @@ -383,13 +670,30 @@ static void handle_input(const mouse *m, const hotkeys *h) data.focus_button_id = 0; if (input_box_is_accepted()) { + sync_inputs_to_settings(); generate_preview_map(); window_invalidate(); return; } - if (input_box_handle_mouse(m_dialog, &seed_input) || - generic_buttons_handle_mouse(m_dialog, 0, 0, buttons, sizeof(buttons) / sizeof(buttons[0]), + if (active_input && input_box_handle_mouse(m_dialog, active_input)) { + return; + } + + if (m_dialog->left.went_up) { + if (is_mouse_inside_box(m_dialog, &seed_input)) { + focus_input_box(&seed_input); + return; + } + for (unsigned int i = 0; i < sizeof(settings_inputs) / sizeof(settings_inputs[0]); i++) { + if (is_mouse_inside_box(m_dialog, settings_inputs[i])) { + focus_input_box(settings_inputs[i]); + return; + } + } + } + + if (generic_buttons_handle_mouse(m_dialog, 0, 0, buttons, sizeof(buttons) / sizeof(buttons[0]), &data.focus_button_id)) { return; } @@ -431,6 +735,7 @@ static void button_randomize(const generic_button *button) static void button_regenerate(const generic_button *button) { (void) button; + sync_inputs_to_settings(); generate_preview_map(); window_invalidate(); } @@ -450,7 +755,9 @@ static void button_open_editor(const generic_button *button) apply_scenario_settings(); - input_box_stop(&seed_input); + if (active_input) { + input_box_stop(active_input); + } if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { window_video_show("map_intro.smk", window_editor_map_show); } @@ -465,7 +772,9 @@ static void button_start_game(const generic_button *button) const char *scenario_path = dir_append_location(scenario_filename, PATH_LOCATION_SCENARIO); - input_box_stop(&seed_input); + if (active_input) { + input_box_stop(active_input); + } if (!scenario_path) { window_plain_message_dialog_show(TR_SAVEGAME_NOT_ABLE_TO_SAVE_TITLE, @@ -502,7 +811,9 @@ static void button_start_game(const generic_button *button) static void button_back(const generic_button *button) { (void) button; - input_box_stop(&seed_input); + if (active_input) { + input_box_stop(active_input); + } window_go_back(); } @@ -532,15 +843,15 @@ static void sync_settings_from_scenario(void) data.climate_index = scenario_property_climate(); data.initial_funds = scenario.initial_funds; data.win_criteria = scenario.win_criteria; + sync_settings_to_inputs(); } static void apply_scenario_settings(void) { + sync_inputs_to_settings(); scenario_change_climate(data.climate_index); scenario.initial_funds = data.initial_funds; scenario.win_criteria = data.win_criteria; - - // Stub: future scenario settings (resources/victory/etc.) should be applied here. } static void randomize_scenario_settings(void) @@ -561,21 +872,12 @@ static void randomize_scenario_settings(void) data.win_criteria.favor.goal = 10 + (int) (generate_seed_value() % 91); data.win_criteria.population.goal = 500 + (int) (generate_seed_value() % 56) * 250; - data.win_criteria.time_limit.enabled = (int) (generate_seed_value() % 4 == 0); - data.win_criteria.time_limit.years = 5 + (int) (generate_seed_value() % 36); - - data.win_criteria.survival_time.enabled = 0; - if (!data.win_criteria.time_limit.enabled && generate_seed_value() % 5 == 0) { - data.win_criteria.survival_time.enabled = 1; - } - data.win_criteria.survival_time.years = 5 + (int) (generate_seed_value() % 36); - if (!data.win_criteria.culture.enabled && !data.win_criteria.prosperity.enabled && !data.win_criteria.peace.enabled && !data.win_criteria.favor.enabled - && !data.win_criteria.population.enabled && !data.win_criteria.time_limit.enabled - && !data.win_criteria.survival_time.enabled) { + && !data.win_criteria.population.enabled) { data.win_criteria.culture.enabled = 1; } + sync_settings_to_inputs(); } static void init(void) @@ -600,7 +902,8 @@ static void init(void) terrain_generator_algorithm_labels[1] = "River Valley"; terrain_generator_algorithm_labels[0] = "Perlin"; - input_box_start(&seed_input); + active_input = &seed_input; + input_box_start(active_input); generate_preview_map(); } From c6d139dd06950ecac34776445ade74d1ae5c174c Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Sat, 4 Apr 2026 09:43:59 +0200 Subject: [PATCH 19/22] Remove not needed include --- src/window/main_menu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/window/main_menu.c b/src/window/main_menu.c index 0314eb14a5..0ac5775089 100644 --- a/src/window/main_menu.c +++ b/src/window/main_menu.c @@ -19,7 +19,6 @@ #include "graphics/weather.h" #include "graphics/window.h" #include "sound/music.h" -#include "translation/translation.h" #include "window/cck_selection.h" #include "window/config.h" #include "window/editor/map.h" From 09e0af7b8a400f025d341ab5b1f3e621de0fa4b0 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Sat, 4 Apr 2026 10:50:31 +0200 Subject: [PATCH 20/22] Cleaning up some code and renaming files --- src/core/random.c | 8 + src/core/random.h | 2 + src/game/game.c | 28 +-- src/map/random.c | 13 -- src/map/random.h | 1 - ...at_plains.c => random_terrain_generator.c} | 2 +- .../{perlin.c => river_generator.c} | 20 +- .../{river.c => river_tools.c} | 0 src/scenario/terrain_generator/river_valley.c | 208 ------------------ .../terrain_generator/terrain_generator.c | 14 +- .../terrain_generator/terrain_generator.h | 7 +- .../terrain_generator_algorithms.h | 8 +- src/window/terrain_generator.c | 23 +- 13 files changed, 52 insertions(+), 282 deletions(-) rename src/scenario/terrain_generator/{flat_plains.c => random_terrain_generator.c} (95%) rename src/scenario/terrain_generator/{perlin.c => river_generator.c} (87%) rename src/scenario/terrain_generator/{river.c => river_tools.c} (100%) delete mode 100644 src/scenario/terrain_generator/river_valley.c diff --git a/src/core/random.c b/src/core/random.c index 4da76da850..54240eb3b9 100644 --- a/src/core/random.c +++ b/src/core/random.c @@ -134,6 +134,14 @@ int random_between_from_stdlib(int min, int max) return min + rnd; } +unsigned int generate_seed_value(void) +{ + unsigned int high = (unsigned int) random_from_stdlib(); + unsigned int low = (unsigned int) random_from_stdlib(); + unsigned int seed = (high << 16) ^ low ^ (unsigned int) time(NULL); + return seed ? seed : 1; +} + double random_fractional_from_stdlib(void) { return (double) random_from_stdlib() / (double) RAND_MAX; diff --git a/src/core/random.h b/src/core/random.h index c44acb4c05..047673cd80 100644 --- a/src/core/random.h +++ b/src/core/random.h @@ -76,6 +76,8 @@ void random_clear_stdlib_seed(void); int random_between_from_stdlib(int min, int max); +unsigned int generate_seed_value(void); + double random_fractional_from_stdlib(void); #endif // CORE_RANDOM_H diff --git a/src/game/game.c b/src/game/game.c index 89531c732a..ba73962f3d 100644 --- a/src/game/game.c +++ b/src/game/game.c @@ -165,14 +165,18 @@ static int reload_language(int is_editor, int reload_images) return 1; } -int game_init_editor(void) +static int game_init_editor_internal(int generated, int size, int generator_algorithm) { if (!reload_language(1, 0)) { return 0; } game_file_editor_clear_data(); - game_file_editor_create_scenario(2); + if (generated) { + game_file_editor_create_scenario_generated(size, generator_algorithm); + } else { + game_file_editor_create_scenario(2); + } if (city_view_is_sidebar_collapsed()) { city_view_toggle_sidebar(); @@ -183,22 +187,14 @@ int game_init_editor(void) return 1; } -int game_init_editor_generated(int size, int generator_algorithm) +int game_init_editor(void) { - if (!reload_language(1, 0)) { - return 0; - } - - game_file_editor_clear_data(); - game_file_editor_create_scenario_generated(size, generator_algorithm); - - if (city_view_is_sidebar_collapsed()) { - city_view_toggle_sidebar(); - } + return game_init_editor_internal(0, 0, 0); +} - editor_set_active(1); - window_editor_map_show(); - return 1; +int game_init_editor_generated(int size, int generator_algorithm) +{ + return game_init_editor_internal(1, size, generator_algorithm); } void game_exit_editor(void) diff --git a/src/map/random.c b/src/map/random.c index 2c135256d8..18cb44162e 100644 --- a/src/map/random.c +++ b/src/map/random.c @@ -2,9 +2,6 @@ #include "core/random.h" -#include -#include - #include "map/grid.h" static grid_u8 random_value; @@ -46,13 +43,3 @@ void map_random_load_state(buffer *buf) map_grid_load_state_u8(random_value.items, buf); } -unsigned int generate_seed_value(void) -{ - // Generate a random value between 1 and UINT_MAX. Return the unsingned value directly, as it can be used as a seed for the terrain generator. - // Do not use time value directly as it is just increasing and might not provide enough randomness if the terrain generator is opened multiple times within the same second. Instead, use a combination of time and a random value from the C library's rand() function to increase randomness. - unsigned int time_seed = (unsigned int) time(NULL); - unsigned int rand_seed = (unsigned int) rand(); - unsigned int seed = time_seed ^ rand_seed; // Combine time and rand values using XOR - seed = seed == 0 ? 1 : seed; // Ensure seed is not zero, as some terrain generators might treat zero as a special case. - return seed; -} diff --git a/src/map/random.h b/src/map/random.h index 718e92e6ee..ee68443128 100644 --- a/src/map/random.h +++ b/src/map/random.h @@ -15,6 +15,5 @@ void map_random_save_state(buffer *buf); void map_random_load_state(buffer *buf); -unsigned int generate_seed_value(void); #endif // MAP_RANDOM_H diff --git a/src/scenario/terrain_generator/flat_plains.c b/src/scenario/terrain_generator/random_terrain_generator.c similarity index 95% rename from src/scenario/terrain_generator/flat_plains.c rename to src/scenario/terrain_generator/random_terrain_generator.c index 1e67c6223b..3f6c53b06f 100644 --- a/src/scenario/terrain_generator/flat_plains.c +++ b/src/scenario/terrain_generator/random_terrain_generator.c @@ -3,7 +3,7 @@ #include "map/grid.h" #include "map/terrain.h" -void terrain_generator_generate_flat_plains(void) +void terrain_generator_random_terrain(void) { int width = map_grid_width(); int height = map_grid_height(); diff --git a/src/scenario/terrain_generator/perlin.c b/src/scenario/terrain_generator/river_generator.c similarity index 87% rename from src/scenario/terrain_generator/perlin.c rename to src/scenario/terrain_generator/river_generator.c index ce2c83d429..2d3bd5f7e9 100644 --- a/src/scenario/terrain_generator/perlin.c +++ b/src/scenario/terrain_generator/river_generator.c @@ -11,6 +11,7 @@ static unsigned int perlin_seed = 1; static unsigned int mountain_seed = 1; static unsigned int meadow_seed = 1; +// Mixes integer coordinates and seed into a stable pseudo-random 32-bit value. static uint32_t hash_2d(int x, int y, unsigned int seed) { uint32_t h = (uint32_t) x; @@ -20,21 +21,25 @@ static uint32_t hash_2d(int x, int y, unsigned int seed) return h ^ (h >> 16); } +// Maps the 2D hash to a normalized [0, 1] float. static float hash_value(int x, int y, unsigned int seed) { return (hash_2d(x, y, seed) & 0xffffu) / 65535.0f; } +// Quintic smoothing curve used for interpolation weights. static float fade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); } +// Linear interpolation between two values. static float lerp(float a, float b, float t) { return a + (b - a) * t; } +// Bilinearly interpolated value noise sampled from four lattice corners. static float value_noise(float x, float y, unsigned int seed) { int x0 = (int) floorf(x); @@ -55,6 +60,7 @@ static float value_noise(float x, float y, unsigned int seed) return lerp(ix0, ix1, sy); } +// Fractal Brownian motion: sums multiple noise octaves for richer structure. static float fbm(float x, float y, unsigned int seed) { float sum = 0.0f; @@ -75,6 +81,7 @@ static float fbm(float x, float y, unsigned int seed) return sum; } +// Resets the map to flat grassland as the base layer for procedural passes. static void generate_grassland(void) { int width = map_grid_width(); @@ -89,6 +96,7 @@ static void generate_grassland(void) } } +// Places forests using an fBM threshold, but only on untouched clear tiles. static void add_forests(void) { int width = map_grid_width(); @@ -109,6 +117,7 @@ static void add_forests(void) } } +// Places mountain rock patches in high-noise regions on remaining clear tiles. static void add_mountains(void) { int width = map_grid_width(); @@ -131,6 +140,7 @@ static void add_mountains(void) } } +// Adds meadow patches from a mid-range noise band on low, clear terrain. static void add_meadows(void) { int width = map_grid_width(); @@ -157,6 +167,8 @@ static void add_meadows(void) } +// Carves irregular circular lakes with noise-distorted shorelines. +// Not used at the moment. static void add_lakes(void) { int width = map_grid_width(); @@ -197,6 +209,8 @@ static void add_lakes(void) } } +// Floods one random border to form a simple sea edge. +// Not used at the moment static void add_sea_edge(void) { int width = map_grid_width(); @@ -225,16 +239,14 @@ static void add_sea_edge(void) } } -void terrain_generator_generate_perlin(unsigned int seed) +// Builds a complete terrain pass from base layer through river and biome overlays. +void terrain_generator_river_map(unsigned int seed) { - - perlin_seed = seed; mountain_seed = seed; meadow_seed = seed; generate_grassland(); - terrain_generator_generate_river(); // terrain_generator_straight_river(); add_forests(); diff --git a/src/scenario/terrain_generator/river.c b/src/scenario/terrain_generator/river_tools.c similarity index 100% rename from src/scenario/terrain_generator/river.c rename to src/scenario/terrain_generator/river_tools.c diff --git a/src/scenario/terrain_generator/river_valley.c b/src/scenario/terrain_generator/river_valley.c deleted file mode 100644 index ae79b83470..0000000000 --- a/src/scenario/terrain_generator/river_valley.c +++ /dev/null @@ -1,208 +0,0 @@ -#include "terrain_generator_algorithms.h" - -#include "map/elevation.h" -#include "map/grid.h" -#include "map/terrain.h" - -#include - -void terrain_generator_generate_river_valley(void) -{ - int width = map_grid_width(); - int height = map_grid_height(); - int river_half_width = 1; - int guard = 0; - int max_steps = width * height * 2; - int start_side = terrain_generator_random_between(0, 4); - int end_side = terrain_generator_random_between(0, 4); - while (end_side == start_side) { - end_side = terrain_generator_random_between(0, 4); - } - - int start_x = 0; - int start_y = 0; - int end_x = 0; - int end_y = 0; - - switch (start_side) { - case 0: // north - start_y = 0; - start_x = terrain_generator_random_between(1, width - 1); - break; - case 1: // south - start_y = height - 1; - start_x = terrain_generator_random_between(1, width - 1); - break; - case 2: // west - start_x = 0; - start_y = terrain_generator_random_between(1, height - 1); - break; - default: // east - start_x = width - 1; - start_y = terrain_generator_random_between(1, height - 1); - break; - } - - switch (end_side) { - case 0: // north - end_y = 0; - end_x = terrain_generator_random_between(1, width - 1); - break; - case 1: // south - end_y = height - 1; - end_x = terrain_generator_random_between(1, width - 1); - break; - case 2: // west - end_x = 0; - end_y = terrain_generator_random_between(1, height - 1); - break; - default: // east - end_x = width - 1; - end_y = terrain_generator_random_between(1, height - 1); - break; - } - - if (width > 2) { - start_x = terrain_generator_clamp_int(start_x, 0, width - 1); - end_x = terrain_generator_clamp_int(end_x, 0, width - 1); - } - if (height > 2) { - start_y = terrain_generator_clamp_int(start_y, 0, height - 1); - end_y = terrain_generator_clamp_int(end_y, 0, height - 1); - } - - typedef struct { - int x; - int y; - } river_point; - - static river_point river_points[GRID_SIZE * GRID_SIZE]; - int river_count = 0; - - int x = start_x; - int y = start_y; - while ((x != end_x || y != end_y) && guard++ < max_steps) { - int dx = 0; - int dy = 0; - if (end_x > x) { - dx = 1; - } else if (end_x < x) { - dx = -1; - } - if (end_y > y) { - dy = 1; - } else if (end_y < y) { - dy = -1; - } - - int step_x = 0; - int step_y = 0; - int meander_roll = terrain_generator_random_between(0, 100); - if (dx != 0 && dy != 0) { - if (meander_roll < 35) { - step_x = dx; - } else if (meander_roll < 70) { - step_y = dy; - } else if (meander_roll < 85) { - step_x = 0; - step_y = dy; - } else { - step_x = dx; - step_y = 0; - } - } else if (dx != 0) { - if (meander_roll < 20) { - step_y = terrain_generator_random_between(0, 2) ? 1 : -1; - } else { - step_x = dx; - } - } else if (dy != 0) { - if (meander_roll < 20) { - step_x = terrain_generator_random_between(0, 2) ? 1 : -1; - } else { - step_y = dy; - } - } - - int next_x = terrain_generator_clamp_int(x + step_x, 0, width - 1); - int next_y = terrain_generator_clamp_int(y + step_y, 0, height - 1); - if (next_x == x && next_y == y) { - if (dx != 0) { - next_x = terrain_generator_clamp_int(x + dx, 0, width - 1); - } - if (dy != 0) { - next_y = terrain_generator_clamp_int(y + dy, 0, height - 1); - } - } - - x = next_x; - y = next_y; - if (river_count < (GRID_SIZE * GRID_SIZE)) { - river_points[river_count].x = x; - river_points[river_count].y = y; - river_count++; - } - } - - if (river_count == 0) { - river_points[river_count].x = start_x; - river_points[river_count].y = start_y; - river_count++; - } - - for (int i = 0; i < river_count; i++) { - int rx = river_points[i].x; - int ry = river_points[i].y; - for (int dy = -river_half_width; dy <= river_half_width; dy++) { - for (int dx = -river_half_width; dx <= river_half_width; dx++) { - int wx = rx + dx; - int wy = ry + dy; - if (wx < 0 || wx >= width || wy < 0 || wy >= height) { - continue; - } - int grid_offset = map_grid_offset(wx, wy); - map_terrain_set_with_tile_update(grid_offset, TERRAIN_WATER); - map_elevation_set(grid_offset, 0); - } - } - } - - for (int yy = 0; yy < height; yy++) { - for (int xx = 0; xx < width; xx++) { - int grid_offset = map_grid_offset(xx, yy); - if (map_terrain_is(grid_offset, TERRAIN_WATER)) { - continue; - } - int dist = width + height; - for (int i = 0; i < river_count; i++) { - int d = abs(xx - river_points[i].x) + abs(yy - river_points[i].y); - if (d < dist) { - dist = d; - if (dist <= 1) { - break; - } - } - } - int elevation = 0; - if (dist > 2) { - elevation = (dist - 2) / 6; - } - if (elevation > 0 && terrain_generator_random_between(0, 4) == 0) { - elevation++; - } - elevation = terrain_generator_clamp_int(elevation, 0, 4); - map_elevation_set(grid_offset, elevation); - - int roll = terrain_generator_random_between(0, 100); - if (dist <= 2 && roll < 30) { - map_terrain_set_with_tile_update(grid_offset, TERRAIN_MEADOW); - } else if (elevation >= 2 && roll < 12) { - map_terrain_set_with_tile_update(grid_offset, TERRAIN_ROCK); - } else if (roll < 8) { - map_terrain_set_with_tile_update(grid_offset, TERRAIN_TREE); - } else if (roll < 16) { - map_terrain_set_with_tile_update(grid_offset, TERRAIN_SHRUB); - } - } - } -} diff --git a/src/scenario/terrain_generator/terrain_generator.c b/src/scenario/terrain_generator/terrain_generator.c index 6124603b7b..d475fcfeac 100644 --- a/src/scenario/terrain_generator/terrain_generator.c +++ b/src/scenario/terrain_generator/terrain_generator.c @@ -9,12 +9,9 @@ #include "map/terrain.h" #include "map/tiles.h" #include "scenario/editor_map.h" -#include "scenario/map.h" #include -#include "map/routing.h" - static int use_fixed_seed = 0; static unsigned int fixed_seed = 0; @@ -194,15 +191,12 @@ void terrain_generator_generate(terrain_generator_algorithm algorithm) clear_base_terrain(); switch (algorithm) { - case TERRAIN_GENERATOR_RIVER_VALLEY: - terrain_generator_generate_river_valley(); - break; - case TERRAIN_GENERATOR_PERLIN: - terrain_generator_generate_perlin(fixed_seed); + case TERRAIN_GENERATOR_RIVER: + terrain_generator_river_map(fixed_seed); break; - case TERRAIN_GENERATOR_FLAT_PLAINS: + case TERRAIN_GENERATOR_RANDOM: default: - terrain_generator_generate_flat_plains(); + terrain_generator_random_terrain(); break; } diff --git a/src/scenario/terrain_generator/terrain_generator.h b/src/scenario/terrain_generator/terrain_generator.h index 11005cd6b8..33fa3fb541 100644 --- a/src/scenario/terrain_generator/terrain_generator.h +++ b/src/scenario/terrain_generator/terrain_generator.h @@ -2,10 +2,9 @@ #define SCENARIO_TERRAIN_GENERATOR_H typedef enum { - TERRAIN_GENERATOR_FLAT_PLAINS = 2, - TERRAIN_GENERATOR_RIVER_VALLEY = 1, - TERRAIN_GENERATOR_PERLIN = 0, - TERRAIN_GENERATOR_COUNT = 3 + TERRAIN_GENERATOR_RANDOM = 1, + TERRAIN_GENERATOR_RIVER = 0, + TERRAIN_GENERATOR_COUNT = 2 } terrain_generator_algorithm; void terrain_generator_generate(terrain_generator_algorithm algorithm); diff --git a/src/scenario/terrain_generator/terrain_generator_algorithms.h b/src/scenario/terrain_generator/terrain_generator_algorithms.h index 218b691317..052888e8a8 100644 --- a/src/scenario/terrain_generator/terrain_generator_algorithms.h +++ b/src/scenario/terrain_generator/terrain_generator_algorithms.h @@ -4,10 +4,8 @@ int terrain_generator_random_between(int min_value, int max_value); int terrain_generator_clamp_int(int value, int min_value, int max_value); -void terrain_generator_generate_flat_plains(void); -void terrain_generator_generate_river(void); +void terrain_generator_random_terrain(void); void terrain_generator_straight_river(void); -void terrain_generator_generate_river_valley(void); -void terrain_generator_generate_perlin(unsigned int); - +void terrain_generator_generate_river(void); +void terrain_generator_river_map(unsigned int); #endif // SCENARIO_TERRAIN_GENERATOR_ALGORITHMS_H diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c index 032f04c07e..e9fd5e5799 100644 --- a/src/window/terrain_generator.c +++ b/src/window/terrain_generator.c @@ -5,6 +5,7 @@ #include "core/config.h" #include "core/dir.h" #include "core/image_group.h" +#include "core/random.h" #include "core/string.h" #include "editor/editor.h" #include "game/file.h" @@ -34,8 +35,6 @@ #include "map/sprite.h" #include "map/terrain.h" #include "map/tiles.h" -#include "building/type.h" -#include "scenario/allowed_building.h" #include "scenario/editor.h" #include "scenario/data.h" #include "scenario/map.h" @@ -345,23 +344,6 @@ static minimap_functions preview_minimap_functions; static uint8_t preview_road_flags[GRID_SIZE * GRID_SIZE]; -static int get_group_allowed(const building_type *types, unsigned int count) -{ - for (unsigned int i = 0; i < count; i++) { - if (!scenario_allowed_building(types[i])) { - return 0; - } - } - return 1; -} - -static void set_group_allowed(const building_type *types, unsigned int count, int allowed) -{ - for (unsigned int i = 0; i < count; i++) { - scenario_allowed_building_set(types[i], allowed); - } -} - static void preview_viewport(int *x, int *y, int *width, int *height) { *x = 0; @@ -747,13 +729,14 @@ static void button_open_editor(const generic_button *button) int use_seed = get_seed_value(&seed); terrain_generator_set_seed(use_seed, seed); + apply_scenario_settings(); + if (!editor_is_present() || !game_init_editor_generated(data.size_index, data.algorithm_index)) { window_plain_message_dialog_show(TR_NO_EDITOR_TITLE, TR_NO_EDITOR_MESSAGE, 1); return; } - apply_scenario_settings(); if (active_input) { input_box_stop(active_input); From dec97ebbb0e5a520d146e93d6c64122590a66b95 Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Sat, 4 Apr 2026 10:50:55 +0200 Subject: [PATCH 21/22] Update cmakelist to include changed filenames --- CMakeLists.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9f6820825..94a1f1a326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -536,10 +536,9 @@ set(SCENARIO_FILES ${PROJECT_SOURCE_DIR}/src/scenario/editor_events.c ${PROJECT_SOURCE_DIR}/src/scenario/editor_map.c ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/terrain_generator.c - ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/flat_plains.c - ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river_valley.c - ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/perlin.c - ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/random_terrain_generator.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river_generator.c + ${PROJECT_SOURCE_DIR}/src/scenario/terrain_generator/river_tools.c ${PROJECT_SOURCE_DIR}/src/scenario/emperor_change.c ${PROJECT_SOURCE_DIR}/src/scenario/empire.c ${PROJECT_SOURCE_DIR}/src/scenario/gladiator_revolt.c From a6c38db4550b4613de4194cb1150a3d013162dcd Mon Sep 17 00:00:00 2001 From: Bart Cox Date: Sat, 4 Apr 2026 11:06:05 +0200 Subject: [PATCH 22/22] Make sure the seed is always positive --- src/core/random.c | 3 +++ .../terrain_generator/random_terrain_generator.c | 10 +++++----- src/window/terrain_generator.c | 5 ++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/core/random.c b/src/core/random.c index 54240eb3b9..c3816a508c 100644 --- a/src/core/random.c +++ b/src/core/random.c @@ -139,6 +139,9 @@ unsigned int generate_seed_value(void) unsigned int high = (unsigned int) random_from_stdlib(); unsigned int low = (unsigned int) random_from_stdlib(); unsigned int seed = (high << 16) ^ low ^ (unsigned int) time(NULL); + + // Keep bit 31 clear so signed interpretation can never appear negative. + seed &= 0x7fffffffu; return seed ? seed : 1; } diff --git a/src/scenario/terrain_generator/random_terrain_generator.c b/src/scenario/terrain_generator/random_terrain_generator.c index 3f6c53b06f..cb7056a377 100644 --- a/src/scenario/terrain_generator/random_terrain_generator.c +++ b/src/scenario/terrain_generator/random_terrain_generator.c @@ -14,13 +14,13 @@ void terrain_generator_random_terrain(void) int roll = terrain_generator_random_between(0, 100); if (roll < 6) { map_terrain_set_with_tile_update(grid_offset, TERRAIN_TREE); - } else if (roll < 8) { - map_terrain_set_with_tile_update(grid_offset, TERRAIN_ROCK); - } else if (roll < 18) { + } else if (roll < 12) { map_terrain_set_with_tile_update(grid_offset, TERRAIN_SHRUB); - } else if (roll < 35) { + } else if (roll < 18) { map_terrain_set_with_tile_update(grid_offset, TERRAIN_MEADOW); - } else if (roll < 60) { + } else if (roll < 19) { + map_terrain_set_with_tile_update(grid_offset, TERRAIN_ROCK); + } else if (roll < 20) { map_terrain_set_with_tile_update(grid_offset, TERRAIN_WATER); } } diff --git a/src/window/terrain_generator.c b/src/window/terrain_generator.c index e9fd5e5799..d925bc1f8c 100644 --- a/src/window/terrain_generator.c +++ b/src/window/terrain_generator.c @@ -881,9 +881,8 @@ static void init(void) sprintf(data.seed_text, "%d", random_seed); seed_input.placeholder = data.seed_text; - terrain_generator_algorithm_labels[2] = "Flat Plains"; - terrain_generator_algorithm_labels[1] = "River Valley"; - terrain_generator_algorithm_labels[0] = "Perlin"; + terrain_generator_algorithm_labels[1] = "Random"; + terrain_generator_algorithm_labels[0] = "River"; active_input = &seed_input; input_box_start(active_input);