From e70bbdceb049e460b4db2b74fe2d625e92affa44 Mon Sep 17 00:00:00 2001 From: crudelios Date: Fri, 20 Mar 2026 17:15:34 +0000 Subject: [PATCH 1/7] Implement shared building creation/demolition and add undo support Missing: - repair support - destruction support (collapse, enemy, fire, etc) - better savegame integration (for possible new shared buildings) --- src/building/building.c | 30 +++- src/building/building.h | 3 + src/building/construction.c | 8 +- src/building/construction_clear.c | 224 +++++++++++++++++------------- src/building/construction_clear.h | 31 ++++- src/building/properties.c | 1 + src/building/properties.h | 1 + src/game/undo.c | 67 +++++---- src/map/building.c | 5 +- src/map/building_tiles.c | 9 +- src/map/terrain.c | 28 ++-- src/widget/city_without_overlay.c | 52 ++++--- 12 files changed, 294 insertions(+), 165 deletions(-) diff --git a/src/building/building.c b/src/building/building.c index f3d90278b5..646a0bf8d7 100644 --- a/src/building/building.c +++ b/src/building/building.c @@ -211,14 +211,23 @@ static void remove_adjacent_types(building *b) building *building_create(building_type type, int x, int y) { building *b; + + const building_properties *props = building_properties_for_type(type); + + if (props->shared) { + b = building_first_of_type(type); + if (b) { + return b; + } + } + array_new_item_after_index(data.buildings, 1, b); + if (!b) { city_warning_show(WARNING_DATA_LIMIT_REACHED, NEW_WARNING_SLOT); return array_first(data.buildings); } - const building_properties *props = building_properties_for_type(type); - b->state = BUILDING_STATE_CREATED; b->faction_id = 1; b->type = type; @@ -280,9 +289,12 @@ building *building_create(building_type type, int x, int y) building_distribution_accept_all_goods(b); } - b->x = x; - b->y = y; - b->grid_offset = map_grid_offset(x, y); + if (!props->shared) { + b->x = x; + b->y = y; + b->grid_offset = map_grid_offset(x, y); + } + b->house_figure_generation_delay = map_random_get(b->grid_offset) & 0x7f; b->figure_roam_direction = b->house_figure_generation_delay & 6; b->fire_proof = props->fire_proof; @@ -301,8 +313,14 @@ void building_change_type(building *b, building_type type) fill_adjacent_types(b); } -static void building_delete(building *b) +void building_delete(building *b) { + if (building_properties_for_type(b->type)->shared && b == building_first_of_type(b->type)) { + if (b->subtype.instances > 0) { + b->subtype.instances--; + } + return; // shared building types should never be deleted, unless the building type is repeated + } building_clear_related_data(b); remove_adjacent_types(b); int id = b->id; diff --git a/src/building/building.h b/src/building/building.h index d43d68ddd1..31ce02a54c 100644 --- a/src/building/building.h +++ b/src/building/building.h @@ -49,6 +49,7 @@ typedef struct building { short fort_figure_type; short native_meeting_center_id; short barracks_priority; + unsigned short instances; } subtype; unsigned char road_network_id; unsigned short created_sequence; @@ -250,6 +251,8 @@ int building_repair_cost(building *b); void building_clear_related_data(building *b); +void building_delete(building *b); + building *building_restore_from_undo(building *to_restore); void building_trim(void); diff --git a/src/building/construction.c b/src/building/construction.c index 6723888fab..a1893dc5f9 100644 --- a/src/building/construction.c +++ b/src/building/construction.c @@ -359,6 +359,8 @@ static int place_wall(int x_start, int y_start, int x_end, int y_end, int measur map_terrain_add(grid_offset, TERRAIN_BUILDING); map_terrain_add(grid_offset, TERRAIN_WALL); map_property_clear_multi_tile_xy(grid_offset); + game_undo_add_building(wall); + wall->subtype.instances++; } } } @@ -818,7 +820,7 @@ void building_construction_update(int x, int y, int grid_offset) int current_cost = model_get_building(type)->cost; int repaired_buildings = 0; if (type == BUILDING_CLEAR_LAND) { - int items_placed = last_items_cleared = building_construction_clear_land(1, data.start.x, data.start.y, x, y); + int items_placed = last_items_cleared = building_construction_clear_select(data.start.x, data.start.y, x, y); if (items_placed >= 0) { current_cost *= items_placed; } @@ -1074,8 +1076,8 @@ void building_construction_place(void) // the previous cost is deducted from treasury and if user chooses 'no', they still pay for removal. // If we don't do it this way, the user doesn't pay for the removal at all since we don't come back // here when the user says yes. - int items_placed = building_construction_clear_land(0, x_start, y_start, x_end, y_end); - if (items_placed < 0) { + int items_placed = building_construction_clear_land(x_start, y_start, x_end, y_end); + if (items_placed == BUILDING_CONSTRUCTION_CLEAR_LAND_INTERRUPTED) { items_placed = last_items_cleared; } placement_cost *= items_placed; diff --git a/src/building/construction_clear.c b/src/building/construction_clear.c index bb78bc7f57..96907ec8cf 100644 --- a/src/building/construction_clear.c +++ b/src/building/construction_clear.c @@ -4,6 +4,7 @@ #include "building/connectable.h" #include "building/construction.h" #include "building/monument.h" +#include "building/properties.h" #include "city/warning.h" #include "core/config.h" #include "core/string.h" @@ -38,7 +39,7 @@ static struct { int repair_cost; int repairable_buildings[1000]; } confirm; -static int repair_land_confirmed(int measure_only, int x_start, int y_start, int x_end, int y_end, int *buildings_count); + static building *get_deletable_building(int grid_offset) { int building_id = map_building_at(grid_offset); @@ -49,7 +50,8 @@ static building *get_deletable_building(int grid_offset) if (b->type == BUILDING_BURNING_RUIN || b->type == BUILDING_NATIVE_CROPS || b->type == BUILDING_NATIVE_HUT || b->type == BUILDING_NATIVE_HUT_ALT || b->type == BUILDING_NATIVE_MEETING || b->type == BUILDING_NATIVE_MONUMENT || - b->type == BUILDING_NATIVE_DECORATION || b->type == BUILDING_NATIVE_WATCHTOWER) { + b->type == BUILDING_NATIVE_DECORATION || b->type == BUILDING_NATIVE_WATCHTOWER || + building_properties_for_type(b->type)->shared) { return 0; } if (b->state == BUILDING_STATE_DELETED_BY_PLAYER || b->is_deleted) { @@ -58,71 +60,106 @@ static building *get_deletable_building(int grid_offset) return b; } -static int clear_land_confirmed(int measure_only, int x_start, int y_start, int x_end, int y_end) +unsigned int building_construction_clear_select(int x_start, int y_start, int x_end, int y_end) { - int items_placed = 0; + unsigned int items_placed = 0; + + int x_min, x_max, y_min, y_max; + map_grid_start_end_to_area(x_start, y_start, x_end, y_end, &x_min, &y_min, &x_max, &y_max); + + for (int y = y_min; y <= y_max; y++) { + for (int x = x_min; x <= x_max; x++) { + + int grid_offset = map_grid_offset(x, y); + building *b = get_deletable_building(grid_offset); + + if (map_property_is_deleted(grid_offset) || (b && map_property_is_deleted(b->grid_offset))) { + continue; + } + + map_building_tiles_mark_deleting(grid_offset); + + if (map_terrain_is(grid_offset, TERRAIN_BUILDING) && b) { + items_placed++; + } else if (map_terrain_is(grid_offset, TERRAIN_ROCK | TERRAIN_ELEVATION | TERRAIN_ACCESS_RAMP)) { + continue; + } else if (map_terrain_is(grid_offset, TERRAIN_WATER)) { // keep the "bridge is free" bug from C3 + continue; + } else if (map_terrain_is(grid_offset, TERRAIN_AQUEDUCT)) { + items_placed++; + } else if (map_terrain_is(grid_offset, TERRAIN_HIGHWAY)) { + items_placed += map_tiles_clear_highway(grid_offset, 1); + } else if (map_terrain_is(grid_offset, TERRAIN_NOT_CLEAR)) { + items_placed++; + } + } + } + return items_placed; +} + +static int is_shared_building_at(int grid_offset) +{ + int building_id = map_building_at(grid_offset); + if (!building_id) { + return 0; + } + building *b = building_main(building_get(building_id)); + return building_properties_for_type(b->type)->shared; +} + +static unsigned int clear_land_confirmed(int x_start, int y_start, int x_end, int y_end) +{ + unsigned int items_placed = 0; game_undo_restore_building_state(); game_undo_restore_map(0); int x_min, x_max, y_min, y_max; map_grid_start_end_to_area(x_start, y_start, x_end, y_end, &x_min, &y_min, &x_max, &y_max); - int visual_feedback_on_delete = 1; int highways_removed = 0; int radius = 0; for (int y = y_min; y <= y_max; y++) { for (int x = x_min; x <= x_max; x++) { + int grid_offset = map_grid_offset(x, y); - if (measure_only && visual_feedback_on_delete) { - building *b = get_deletable_building(grid_offset); - if (map_property_is_deleted(grid_offset) || (b && map_property_is_deleted(b->grid_offset))) { - continue; - } - map_building_tiles_mark_deleting(grid_offset); - if (map_terrain_is(grid_offset, TERRAIN_BUILDING)) { - if (b) { - items_placed++; - } - } else if (map_terrain_is(grid_offset, TERRAIN_ROCK | TERRAIN_ELEVATION | TERRAIN_ACCESS_RAMP)) { - continue; - } else if (map_terrain_is(grid_offset, TERRAIN_WATER)) { // keep the "bridge is free" bug from C3 - continue; - } else if (map_terrain_is(grid_offset, TERRAIN_AQUEDUCT)) { - items_placed++; - } else if (map_terrain_is(grid_offset, TERRAIN_HIGHWAY)) { - int next_highways_removed = map_tiles_clear_highway(grid_offset, measure_only); - highways_removed += next_highways_removed; - items_placed += next_highways_removed; - } else if (map_terrain_is(grid_offset, TERRAIN_NOT_CLEAR)) { - items_placed++; - } - continue; - } if (map_terrain_is(grid_offset, TERRAIN_ROCK | TERRAIN_ELEVATION | TERRAIN_ACCESS_RAMP)) { continue; } - if (map_terrain_is(grid_offset, TERRAIN_BUILDING) && !map_is_bridge(grid_offset)) { + + if (map_terrain_is(grid_offset, TERRAIN_BUILDING) && is_shared_building_at(grid_offset)) { + + building *b = building_get(map_building_at(grid_offset)); + game_undo_add_building(b); + + if (b->subtype.instances > 0) { + b->subtype.instances--; + } + } + + if (map_terrain_is(grid_offset, TERRAIN_BUILDING) && !map_is_bridge(grid_offset) && + !is_shared_building_at(grid_offset)) { + building *b = get_deletable_building(grid_offset); if (!b) { continue; } if (b->type == BUILDING_FORT_GROUND || building_is_fort(b->type)) { - if (!measure_only && confirm.fort_confirmed != 1) { + if (confirm.fort_confirmed != 1) { continue; } - if (!measure_only && confirm.fort_confirmed == 1) { + if (confirm.fort_confirmed == 1) { game_undo_disable(); } } if (building_monument_is_monument(b)) { - if (!measure_only && confirm.monument_confirmed != 1) { + if (confirm.monument_confirmed != 1) { continue; } - if (!measure_only && confirm.monument_confirmed == 1) { + if (confirm.monument_confirmed == 1) { game_undo_disable(); } } - if (b->house_size && b->house_population && !measure_only) { + if (b->house_size && b->house_population) { figure *homeless = figure_create_homeless(b, b->house_population); b->house_population = 0; b->figure_id = homeless->id; @@ -160,29 +197,32 @@ static int clear_land_confirmed(int measure_only, int x_start, int y_start, int items_placed++; map_aqueduct_remove(grid_offset); } else if (map_terrain_is(grid_offset, TERRAIN_WATER)) { //only bridges fall here - if (!measure_only && (map_bridge_has_figures(grid_offset) && !config_get(CONFIG_GP_CH_ALWAYS_DESTROY_BRIDGES))) { + if (map_bridge_has_figures(grid_offset) && !config_get(CONFIG_GP_CH_ALWAYS_DESTROY_BRIDGES)) { city_warning_show(WARNING_PEOPLE_ON_BRIDGE, NEW_WARNING_SLOT); } else if (confirm.bridge_confirmed == 1) { - map_bridge_remove(grid_offset, measure_only); + map_bridge_remove(grid_offset, 0); items_placed++; } } else if (map_terrain_is(grid_offset, TERRAIN_HIGHWAY)) { - int next_highways_removed = map_tiles_clear_highway(grid_offset, measure_only); + int next_highways_removed = map_tiles_clear_highway(grid_offset, 0); highways_removed += next_highways_removed; items_placed += next_highways_removed; } else if (map_terrain_is(grid_offset, TERRAIN_NOT_CLEAR)) { if (map_terrain_is(grid_offset, TERRAIN_ROAD | TERRAIN_GARDEN)) { map_property_clear_plaza_earthquake_or_overgrown_garden(grid_offset); } - if (map_terrain_is(grid_offset, TERRAIN_RUBBLE) && !measure_only) { + if (map_terrain_is(grid_offset, TERRAIN_RUBBLE)) { //rubble state handling: if (map_building_rubble_building_id(grid_offset)) { int rubble_id = map_building_rubble_building_id(grid_offset); - building *rubble_building = building_get(map_building_rubble_building_id(grid_offset)); if (rubble_id) { + map_building_set_rubble_grid_building_id(grid_offset, 0, 1); // remove rubble marker + + building *rubble_building = building_get(rubble_id); + if (rubble_building->state == BUILDING_STATE_RUBBLE || - rubble_building->type == BUILDING_BURNING_RUIN) { + rubble_building->type == BUILDING_BURNING_RUIN) { int ruins_left = map_building_ruins_left(rubble_id); if (!ruins_left) { //dont remove buildings until their last rubble is gone rubble_building->state = BUILDING_STATE_DELETED_BY_GAME; @@ -194,7 +234,6 @@ static int clear_land_confirmed(int measure_only, int x_start, int y_start, int rubble_building->state = BUILDING_STATE_DELETED_BY_GAME; } } - map_building_set_rubble_grid_building_id(grid_offset, 0, 1); // remove rubble marker } } map_terrain_remove(grid_offset, TERRAIN_CLEARABLE); @@ -202,37 +241,35 @@ static int clear_land_confirmed(int measure_only, int x_start, int y_start, int } } } - if (!measure_only || !visual_feedback_on_delete) { - if (x_max - x_min <= y_max - y_min) { - radius = y_max - y_min + 3; - } else { - radius = x_max - x_min + 3; - } - if (highways_removed) { - x_min -= 1; - y_min -= 1; - x_max += 1; - y_max += 1; - } - map_tiles_update_region_empty_land(x_min, y_min, x_max, y_max); - map_tiles_update_region_meadow(x_min, y_min, x_max, y_max); - map_tiles_update_region_rubble(x_min, y_min, x_max, y_max); - map_tiles_update_all_gardens(); - map_tiles_update_area_roads(x_min, y_min, radius); - map_tiles_update_area_highways(x_min - 1, y_min - 1, radius); - map_tiles_update_all_plazas(); - map_tiles_update_region_aqueducts(x_min - 3, y_min - 3, x_max + 3, y_max + 3); + + if (x_max - x_min <= y_max - y_min) { + radius = y_max - y_min + 3; + } else { + radius = x_max - x_min + 3; } - if (!measure_only) { - map_routing_update_land(); - map_routing_update_walls(); - map_routing_update_water(); - building_update_state(); // the update of b state is needed to determine the right images for walls/palisades - map_tiles_update_area_walls(x_min, y_min, radius + 1); - building_connectable_update_connections(); - figure_roamer_preview_reset(BUILDING_CLEAR_LAND); - window_invalidate(); + if (highways_removed) { + x_min -= 1; + y_min -= 1; + x_max += 1; + y_max += 1; } + map_tiles_update_region_empty_land(x_min, y_min, x_max, y_max); + map_tiles_update_region_meadow(x_min, y_min, x_max, y_max); + map_tiles_update_region_rubble(x_min, y_min, x_max, y_max); + map_tiles_update_all_gardens(); + map_tiles_update_area_roads(x_min, y_min, radius); + map_tiles_update_area_highways(x_min - 1, y_min - 1, radius); + map_tiles_update_all_plazas(); + map_tiles_update_region_aqueducts(x_min - 3, y_min - 3, x_max + 3, y_max + 3); + map_routing_update_land(); + map_routing_update_walls(); + map_routing_update_water(); + building_update_state(); // the update of b state is needed to determine the right images for walls/palisades + map_tiles_update_area_walls(x_min, y_min, radius + 1); + building_connectable_update_connections(); + figure_roamer_preview_reset(BUILDING_CLEAR_LAND); + window_invalidate(); + return items_placed; } @@ -243,7 +280,7 @@ static void confirm_delete_fort(int accepted, int checked) } else { confirm.fort_confirmed = -1; } - clear_land_confirmed(0, confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end); + clear_land_confirmed(confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end); } static void confirm_delete_bridge(int accepted, int checked) @@ -253,7 +290,7 @@ static void confirm_delete_bridge(int accepted, int checked) } else { confirm.bridge_confirmed = -1; } - clear_land_confirmed(0, confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end); + clear_land_confirmed(confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end); } static void confirm_delete_monument(int accepted, int checked) @@ -263,30 +300,15 @@ static void confirm_delete_monument(int accepted, int checked) } else { confirm.monument_confirmed = -1; } - clear_land_confirmed(0, confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end); -} - -static void confirm_repair_buildings(int accepted, int checked) -{ - if (accepted == 1) { - confirm.repair_confirmed = 1; - } else { - confirm.repair_confirmed = -1; - } - if (accepted == 1) { - repair_land_confirmed(0, confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end, 0); - } + clear_land_confirmed(confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end); } -int building_construction_clear_land(int measure_only, int x_start, int y_start, int x_end, int y_end) +unsigned int building_construction_clear_land(int x_start, int y_start, int x_end, int y_end) { confirm.fort_confirmed = 0; confirm.bridge_confirmed = 0; confirm.monument_confirmed = 0; confirm.repair_confirmed = 0; - if (measure_only) { - return clear_land_confirmed(measure_only, x_start, y_start, x_end, y_end); - } int x_min, x_max, y_min, y_max; map_grid_start_end_to_area(x_start, y_start, x_end, y_end, &x_min, &y_min, &x_max, &y_max); @@ -323,15 +345,15 @@ int building_construction_clear_land(int measure_only, int x_start, int y_start, confirm.y_end = y_end; if (ask_confirm_fort) { window_popup_dialog_show(POPUP_DIALOG_DELETE_FORT, confirm_delete_fort, 2); - return -1; + return BUILDING_CONSTRUCTION_CLEAR_LAND_INTERRUPTED; } else if (ask_confirm_monument) { window_popup_dialog_show_confirmation(translation_for(TR_CONFIRM_DELETE_MONUMENT), 0, 0, confirm_delete_monument); - return -1; + return BUILDING_CONSTRUCTION_CLEAR_LAND_INTERRUPTED; } else if (ask_confirm_bridge) { window_popup_dialog_show(POPUP_DIALOG_DELETE_BRIDGE, confirm_delete_bridge, 2); - return -1; + return BUILDING_CONSTRUCTION_CLEAR_LAND_INTERRUPTED; } else { - return clear_land_confirmed(measure_only, x_start, y_start, x_end, y_end); + return clear_land_confirmed(x_start, y_start, x_end, y_end); } } @@ -399,6 +421,18 @@ static int repair_land_confirmed(int measure_only, int x_start, int y_start, int return repair_cost; } +static void confirm_repair_buildings(int accepted, int checked) +{ + if (accepted == 1) { + confirm.repair_confirmed = 1; + } else { + confirm.repair_confirmed = -1; + } + if (accepted == 1) { + repair_land_confirmed(0, confirm.x_start, confirm.y_start, confirm.x_end, confirm.y_end, 0); + } +} + int building_construction_repair_land(int measure_only, int x_start, int y_start, int x_end, int y_end, int *buildings_count) { confirm.repair_confirmed = 0; diff --git a/src/building/construction_clear.h b/src/building/construction_clear.h index 3efa37d0e0..a943083011 100644 --- a/src/building/construction_clear.h +++ b/src/building/construction_clear.h @@ -1,20 +1,47 @@ #ifndef BUILDING_CONSTRUCTION_CLEAR_H #define BUILDING_CONSTRUCTION_CLEAR_H +#define BUILDING_CONSTRUCTION_CLEAR_LAND_INTERRUPTED ((unsigned int) -1) + #include "graphics/color.h" + +/** + * Selects how many tiles would be cleared by clear land + * @param x_start Start X + * @param y_start Start Y + * @param x_end End X + * @param y_end End Y + * @return Number of tiles cleared + */ +unsigned int building_construction_clear_select(int x_start, int y_start, int x_end, int y_end); + /** * Clears land - * @param measure_only Whether to measure only * @param x_start Start X * @param y_start Start Y * @param x_end End X * @param y_end End Y * @return Number of tiles cleared */ -int building_construction_clear_land(int measure_only, int x_start, int y_start, int x_end, int y_end); +unsigned int building_construction_clear_land(int x_start, int y_start, int x_end, int y_end); +/** + * Gets the color used for highlighting clearing or repairing land + * @return Color + */ color_t building_construction_clear_color(void); +/** + *@brief Repairs land and buildings on that land. + * + * @param measure_only if `1`, the function only calculates the cost and number of buildings that would be repaired without actually performing the repairs. + * @param x_start Start X + * @param y_start Start Y + * @param x_end End X + * @param y_end End Y + * @param buildings_count Pointer to store the number of buildings that would be repaired + * @return The total cost of repairs + */ int building_construction_repair_land(int measure_only, int x_start, int y_start, int x_end, int y_end, int *buildings_count); #endif // BUILDING_CONSTRUCTION_CLEAR_H diff --git a/src/building/properties.c b/src/building/properties.c index 1fc339e0b1..690a11cae4 100644 --- a/src/building/properties.c +++ b/src/building/properties.c @@ -56,6 +56,7 @@ static building_properties properties[BUILDING_TYPE_MAX] = { .fire_proof = 1, .draw_desirability_range = 1, .event_data.attr = "wall", + .shared = 1, .building_model_data = {.cost = 12, .desirability_value = 0, .desirability_step = 0, .desirability_step_size = 0, .desirability_range = 0, .laborers = 0} }, diff --git a/src/building/properties.h b/src/building/properties.h index be744b5435..affcf49c2f 100644 --- a/src/building/properties.h +++ b/src/building/properties.h @@ -93,6 +93,7 @@ typedef struct { int sound_id; int draw_desirability_range; int venus_gt_bonus; // indicator of whether building is part of the 'garden/statue/temple' group + int shared; // Whether all buildings of this type share the same building instance data struct { const char *group; const char *id; diff --git a/src/game/undo.c b/src/game/undo.c index f3075fd0ba..03115e9053 100644 --- a/src/game/undo.c +++ b/src/game/undo.c @@ -255,6 +255,11 @@ void game_undo_perform(void) if (data.type == BUILDING_CLEAR_LAND) { for (int i = 0; i < data.num_buildings; i++) { if (data.buildings[i].id) { + if (building_properties_for_type(data.buildings[i].type)->shared) { + building *original = building_get(data.buildings[i].id); + original->subtype.instances = data.buildings[i].subtype.instances; + continue; + } building *b = building_restore_from_undo(&data.buildings[i]); switch (b->type) { default: @@ -287,36 +292,48 @@ void game_undo_perform(void) map_property_restore(); map_building_restore(); map_property_clear_constructing_and_deleted(); - } else if (data.type == BUILDING_AQUEDUCT || data.type == BUILDING_ROAD || - data.type == BUILDING_WALL || data.type == BUILDING_HIGHWAY) { - map_terrain_restore(); - map_aqueduct_restore(); - restore_map_images(); - game_undo_restore_building_types(); - building_connectable_update_connections(); - - } else if (data.type == BUILDING_LOW_BRIDGE || data.type == BUILDING_SHIP_BRIDGE) { - map_terrain_restore(); - map_sprite_restore(); - restore_map_images(); - } else if (data.type == BUILDING_PLAZA || data.type == BUILDING_GARDENS || - data.type == BUILDING_OVERGROWN_GARDENS) { - map_terrain_restore(); - map_aqueduct_restore(); - map_property_restore(); - restore_map_images(); - } else if (data.num_buildings) { - if (data.type == BUILDING_DRAGGABLE_RESERVOIR) { + } else { + int restore_map_buildings = 0; + for (int i = 0; i < data.num_buildings; i++) { + if (data.buildings[i].id && building_properties_for_type(data.buildings[i].type)->shared) { + building *original = building_get(data.buildings[i].id); + original->subtype.instances = data.buildings[i].subtype.instances; + restore_map_buildings = 1; + } + } + if (restore_map_buildings) { + map_building_restore(); + } + if (data.type == BUILDING_AQUEDUCT || data.type == BUILDING_ROAD || + data.type == BUILDING_WALL || data.type == BUILDING_HIGHWAY) { map_terrain_restore(); map_aqueduct_restore(); restore_map_images(); - } - for (int i = 0; i < data.num_buildings; i++) { - if (data.buildings[i].id) { - building_get(data.buildings[i].id)->state = BUILDING_STATE_UNDO; + game_undo_restore_building_types(); + building_connectable_update_connections(); + } else if (data.type == BUILDING_LOW_BRIDGE || data.type == BUILDING_SHIP_BRIDGE) { + map_terrain_restore(); + map_sprite_restore(); + restore_map_images(); + } else if (data.type == BUILDING_PLAZA || data.type == BUILDING_GARDENS || + data.type == BUILDING_OVERGROWN_GARDENS) { + map_terrain_restore(); + map_aqueduct_restore(); + map_property_restore(); + restore_map_images(); + } else if (data.num_buildings) { + if (data.type == BUILDING_DRAGGABLE_RESERVOIR) { + map_terrain_restore(); + map_aqueduct_restore(); + restore_map_images(); + } + for (int i = 0; i < data.num_buildings; i++) { + if (data.buildings[i].id && !building_properties_for_type(data.buildings[i].type)->shared) { + building_get(data.buildings[i].id)->state = BUILDING_STATE_UNDO; + } } + building_update_state(); } - building_update_state(); } map_routing_update_land(); map_routing_update_walls(); diff --git a/src/map/building.c b/src/map/building.c index c6db3a1fda..9e5bb69c6a 100644 --- a/src/map/building.c +++ b/src/map/building.c @@ -4,6 +4,7 @@ #include "core/config.h" #include "game/save_version.h" #include "map/grid.h" +#include "map/terrain.h" static grid_u32 buildings_grid; static grid_u8 damage_grid; @@ -62,7 +63,9 @@ void map_building_set_rubble_grid_building_id(int grid_offset, unsigned int buil for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { int offset = map_grid_offset(x + i, y + j); - rubble_info_grid.items[offset] = building_id; + if (!building_id || !map_terrain_is(offset, TERRAIN_WATER)) { + rubble_info_grid.items[offset] = building_id; + } } } } diff --git a/src/map/building_tiles.c b/src/map/building_tiles.c index 70d39ce1c8..e4d20da683 100644 --- a/src/map/building_tiles.c +++ b/src/map/building_tiles.c @@ -2,6 +2,7 @@ #include "building/building.h" #include "building/industry.h" +#include "building/properties.h" #include "city/view.h" #include "core/direction.h" #include "core/image.h" @@ -277,13 +278,19 @@ int map_building_tiles_mark_construction(int x, int y, int size, int terrain, in return 1; } +static int is_shared_building(int building_id) +{ + building *b = building_main(building_get(building_id)); + return b && building_properties_for_type(b->type)->shared; +} + void map_building_tiles_mark_deleting(int grid_offset) { int building_id = map_building_at(grid_offset); if (map_is_bridge(grid_offset)) { // previous version triggered map_bridge_remove with an early exit condition for regular terrain. map_bridge_remove(grid_offset, 1); - } else if (building_id) { + } else if (building_id && !is_shared_building(building_id)) { grid_offset = building_main(building_get(building_id))->grid_offset; } map_property_mark_deleted(grid_offset); diff --git a/src/map/terrain.c b/src/map/terrain.c index 212260e1ac..7594589982 100644 --- a/src/map/terrain.c +++ b/src/map/terrain.c @@ -625,23 +625,25 @@ void map_terrain_migrate_old_walls(void) continue; } if (map_terrain_is(grid_offset, TERRAIN_WALL)) { - if (!map_terrain_is(grid_offset, TERRAIN_BUILDING)) { - // Create wall building for each wall tile - building *wall = building_create(BUILDING_WALL, x, y); - map_building_set(grid_offset, wall->id); - map_terrain_add(grid_offset, TERRAIN_BUILDING); - } else { - building *wall = building_get(map_building_at(grid_offset)); - // Recreate the wall if pointing to a wrong building - if (!wall || wall->type != BUILDING_WALL) { - wall = building_create(BUILDING_WALL, x, y); - map_building_set(grid_offset, wall->id); - } - } + building *wall = building_create(BUILDING_WALL, 0, 0); + wall->subtype.instances++; + map_building_set(grid_offset, wall->id); + map_terrain_add(grid_offset, TERRAIN_BUILDING); map_property_clear_multi_tile_xy(grid_offset); } } } + + building *wall = building_first_of_type(BUILDING_WALL); + + // No walls found, nothing to migrate + if (!wall) { + return; + } + + while (wall->next_of_type) { + building_delete(wall->next_of_type); + } } void map_terrain_load_state(buffer *buf, int expanded_terrain_data, buffer *images, int legacy_image_buffer) diff --git a/src/widget/city_without_overlay.c b/src/widget/city_without_overlay.c index a01f7c9a06..ed279a12e7 100644 --- a/src/widget/city_without_overlay.c +++ b/src/widget/city_without_overlay.c @@ -115,10 +115,17 @@ static void init_draw_context(int selected_figure_id, pixel_coordinate *figure_c } } -static int draw_building_as_deleted(building *b) +static int draw_building_as_deleted(building *b, int grid_offset) { b = building_main(b); - return (b->id && (b->is_deleted || map_property_is_deleted(b->grid_offset))); + if (!b || !b->id) { + return 0; + } + if (building_properties_for_type(b->type)->shared) { + return map_property_is_deleted(grid_offset); + } else { + return b->is_deleted || map_property_is_deleted(b->grid_offset); + } } static int is_multi_tile_terrain(int grid_offset) @@ -132,8 +139,9 @@ static int has_adjacent_deletion(int grid_offset) int total_adjacent_offsets = size * 2 + 1; const int *adjacent_offset = ADJACENT_OFFSETS[size - 2][city_view_orientation() / 2]; for (int i = 0; i < total_adjacent_offsets; ++i) { - if (map_property_is_deleted(grid_offset + adjacent_offset[i]) || - draw_building_as_deleted(building_get(map_building_at(grid_offset + adjacent_offset[i])))) { + int offset = grid_offset + adjacent_offset[i]; + if (map_property_is_deleted(offset) || + draw_building_as_deleted(building_get(map_building_at(offset)), offset)) { return 1; } } @@ -189,21 +197,23 @@ static int is_building_selected(building *b) } const building *main_building = building_main(b); unsigned int main_part_id = main_building->id; - if (b->id == draw_context.selected_building_id || main_part_id == draw_context.selected_building_id) { - return 1; - } else { + if (building_properties_for_type(main_building->type)->shared) { return 0; + } else { + return b->id == draw_context.selected_building_id || main_part_id == draw_context.selected_building_id; } - } -static int is_building_hovered(building *b) +static int is_building_hovered(building *b, int grid_offset) { if (!draw_context.hovered_building_id) { return 0; } building *main_building = building_main(b); unsigned int main_part_id = main_building->id; + if (building_properties_for_type(main_building->type)->shared) { + return draw_context.cursor_tile && draw_context.cursor_tile->grid_offset == grid_offset; + } return (b->id == draw_context.hovered_building_id || main_part_id == draw_context.hovered_building_id); } @@ -221,11 +231,11 @@ static void draw_footprint(int x, int y, int grid_offset) if (building_id) { building *b = building_get(building_id); - if (draw_building_as_deleted(b)) { + if (draw_building_as_deleted(b, grid_offset)) { color_mask = building_construction_clear_color(); } else if (is_building_selected(b)) { color_mask = get_building_color_mask(b); - } else if (is_building_hovered(b)) { + } else if (is_building_hovered(b, grid_offset)) { // Hover effect - only if not deleted or selected color_mask = COLOR_MASK_HOVER; } @@ -522,11 +532,12 @@ static void draw_top(int x, int y, int grid_offset) building *b = building_get(map_building_at(grid_offset)); int image_id = map_image_at(grid_offset); color_t color_mask = 0; - if (draw_building_as_deleted(b) || (map_property_is_deleted(grid_offset) && !is_multi_tile_terrain(grid_offset))) { + if (draw_building_as_deleted(b, grid_offset) || + (map_property_is_deleted(grid_offset) && !is_multi_tile_terrain(grid_offset))) { color_mask = building_construction_clear_color(); } else if (is_building_selected(b)) { color_mask = get_building_color_mask(b); - } else if (is_building_hovered(b)) { + } else if (is_building_hovered(b, grid_offset)) { // Hover effect for tops - only if not deleted or selected color_mask = COLOR_MASK_HOVER; } @@ -811,11 +822,11 @@ static void draw_animation(int x, int y, int grid_offset) int building_id = map_building_at(grid_offset); building *b = building_get(building_id); color_t color_mask = 0; - if (draw_building_as_deleted(b) || map_property_is_deleted(grid_offset)) { + if (draw_building_as_deleted(b, grid_offset) || map_property_is_deleted(grid_offset)) { color_mask = building_construction_clear_color(); } else if (is_building_selected(b)) { color_mask = get_building_color_mask(b); - } else if (is_building_hovered(b)) { + } else if (is_building_hovered(b, grid_offset)) { // Hover effect for animations - only if not deleted or selected color_mask = COLOR_MASK_HOVER; } @@ -903,7 +914,8 @@ static void draw_animation(int x, int y, int grid_offset) } } image_draw(image_id, x + 81, y + 5, - draw_building_as_deleted(fort) ? building_construction_clear_color() : COLOR_MASK_NONE, draw_context.scale); + draw_building_as_deleted(fort, grid_offset) ? + building_construction_clear_color() : COLOR_MASK_NONE, draw_context.scale); } } else if (b->type == BUILDING_GATEHOUSE) { int xy = map_property_multi_tile_xy(grid_offset); @@ -914,7 +926,7 @@ static void draw_animation(int x, int y, int grid_offset) (orientation == DIR_6_LEFT && xy == EDGE_X1Y0)) { building *gate = building_get(map_building_at(grid_offset)); image_id = image_group(GROUP_BUILDING_GATEHOUSE); - color_mask = draw_building_as_deleted(gate) ? building_construction_clear_color() : 0; + color_mask = draw_building_as_deleted(gate, grid_offset) ? building_construction_clear_color() : 0; if (gate->subtype.orientation == 1) { if (orientation == DIR_0_TOP || orientation == DIR_4_BOTTOM) { image_draw(image_id, x - 22, y - 80, color_mask, draw_context.scale); @@ -967,7 +979,8 @@ static void draw_hippodrome_ornaments(int x, int y, int grid_offset) image_draw(image_id + 1, x + img->animation->sprite_offset_x, y + img->animation->sprite_offset_y - top_height + FOOTPRINT_HALF_HEIGHT, - draw_building_as_deleted(b) ? building_construction_clear_color() : COLOR_MASK_NONE, draw_context.scale + draw_building_as_deleted(b, grid_offset) ? + building_construction_clear_color() : COLOR_MASK_NONE, draw_context.scale ); } } @@ -986,7 +999,8 @@ static void deletion_draw_terrain_top(int x, int y, int grid_offset) static void deletion_draw_figures_animations(int x, int y, int grid_offset) { - if (map_property_is_deleted(grid_offset) || draw_building_as_deleted(building_get(map_building_at(grid_offset)))) { + if (map_property_is_deleted(grid_offset) || + draw_building_as_deleted(building_get(map_building_at(grid_offset)), grid_offset)) { color_t color = building_construction_clear_color(); if (color == COLOR_MASK_RED || color == COLOR_MASK_GREEN) { image_blend_footprint_color(x, y, color, draw_context.scale); From bacba95b1d2ab84a457cfe0b5f5d6ce2657b0fa8 Mon Sep 17 00:00:00 2001 From: crudelios Date: Mon, 23 Mar 2026 23:07:48 +0000 Subject: [PATCH 2/7] More small changes --- src/building/destruction.c | 10 +++++++--- src/map/building.c | 6 ------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/building/destruction.c b/src/building/destruction.c index 3856208a5c..34e0eb279a 100644 --- a/src/building/destruction.c +++ b/src/building/destruction.c @@ -2,6 +2,7 @@ #include "building/data_transfer.h" #include "building/image.h" +#include "building/properties.h" #include "city/message.h" #include "city/population.h" #include "city/ratings.h" @@ -308,14 +309,17 @@ void building_destroy_by_enemy(int x, int y, int grid_offset) int building_id = map_building_at(grid_offset); if (building_id > 0) { building *b = building_get(building_id); + if (building_properties_for_type(b->type)->shared) { + if (map_terrain_is(grid_offset, TERRAIN_WALL)) { + figure_kill_tower_sentries_at(x, y); + } + building_destroy_by_collapse(b); + } if (b->state == BUILDING_STATE_IN_USE || b->state == BUILDING_STATE_MOTHBALLED) { city_ratings_peace_building_destroyed(b->type); building_destroy_by_collapse(b); } } else { - if (map_terrain_is(grid_offset, TERRAIN_WALL)) { - figure_kill_tower_sentries_at(x, y); - } if (map_terrain_is(grid_offset, TERRAIN_GARDEN)) { map_terrain_remove(grid_offset, TERRAIN_CLEARABLE); map_tiles_update_region_empty_land(x, y, x, y); diff --git a/src/map/building.c b/src/map/building.c index 51b4264f4e..18fe5624c3 100644 --- a/src/map/building.c +++ b/src/map/building.c @@ -54,12 +54,6 @@ unsigned int map_building_rubble_building_id(int grid_offset) void map_building_set_rubble_grid_building_id(int grid_offset, unsigned int building_id, int size) { - if (size == 1) { - if (!building_id || !map_terrain_is(grid_offset, TERRAIN_WATER)) { - rubble_info_grid.items[grid_offset] = building_id; - } - return; - } int x = map_grid_offset_to_x(grid_offset); int y = map_grid_offset_to_y(grid_offset); for (int i = 0; i < size; i++) { From fca75f75a3a866ca54c4f40daf58b83b5015b36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Cadete?= Date: Tue, 24 Mar 2026 17:13:56 +0000 Subject: [PATCH 3/7] Finish initial implementation of shared buildings Now bugtesting is needed --- src/building/building.c | 63 +++++++++++++++++++++---------- src/building/building.h | 4 +- src/building/construction_clear.c | 8 ++-- src/building/destruction.c | 34 +++++++++++++---- src/scenario/earthquake.c | 18 ++++++--- src/window/building/common.h | 1 + src/window/building/utility.c | 7 ++-- src/window/building_info.c | 1 + 8 files changed, 93 insertions(+), 43 deletions(-) diff --git a/src/building/building.c b/src/building/building.c index 646a0bf8d7..b752d924b0 100644 --- a/src/building/building.c +++ b/src/building/building.c @@ -79,6 +79,7 @@ int building_can_repair_type(building_type type) return 1; } } + int building_dist(int x, int y, int w, int h, building *b) { int size = building_properties_for_type(b->type)->size; @@ -410,7 +411,7 @@ int building_can_repair(building *b) return 1; } } else { - if (b->state != BUILDING_STATE_RUBBLE) { + if (b->state != BUILDING_STATE_RUBBLE && !building_properties_for_type(b->type)->shared) { return 0; } else { return building_can_repair_type(b->type); @@ -418,8 +419,9 @@ int building_can_repair(building *b) } } -int building_repair_cost(building *b) +int building_repair_cost_at(int grid_offset) { + building *b = building_get(map_building_at(grid_offset)); int og_grid_offset = 0, og_size = 0, og_type = 0; if (!b || !building_can_repair(b)) { return 0; @@ -427,7 +429,12 @@ int building_repair_cost(building *b) int is_ruin = b->type == BUILDING_BURNING_RUIN || // ruins and collapsed warehouse parts all use rubble data b->type == BUILDING_WAREHOUSE_SPACE || b->type == BUILDING_WAREHOUSE; - og_grid_offset = is_ruin ? b->data.rubble.og_grid_offset : b->grid_offset; + if (building_properties_for_type(b->type)->shared) { + og_grid_offset = grid_offset; + } else { + og_grid_offset = is_ruin ? b->data.rubble.og_grid_offset : b->grid_offset; + } + og_size = is_ruin ? b->data.rubble.og_size : b->size; og_type = is_ruin ? b->data.rubble.og_type : b->type; @@ -454,18 +461,25 @@ static int get_rubble_data(building *b, int *og_size, int *og_grid_offset, int * return 0; } - int has = b->data.rubble.og_size || b->data.rubble.og_grid_offset || - b->data.rubble.og_orientation || b->data.rubble.og_type; - - if (!has) { + if (!b->data.rubble.og_size && !b->data.rubble.og_grid_offset && + !b->data.rubble.og_orientation && !b->data.rubble.og_type) { return 0; - } else { - if (og_size) { *og_size = b->data.rubble.og_size; } - if (og_grid_offset) { *og_grid_offset = b->data.rubble.og_grid_offset; } - if (og_orientation) { *og_orientation = b->data.rubble.og_orientation; } - if (og_type) { *og_type = b->data.rubble.og_type; } - return 1; + } + + if (og_size) { + *og_size = b->data.rubble.og_size; + } + if (og_grid_offset) { + *og_grid_offset = b->data.rubble.og_grid_offset; + } + if (og_orientation) { + *og_orientation = b->data.rubble.og_orientation; } + if (og_type) { + *og_type = b->data.rubble.og_type; + } + + return 1; } static int is_warehouse_ruin(building *b) @@ -537,8 +551,10 @@ static int warehouse_repair(building *b) return full_cost; } -int building_repair(building *b) +int building_repair_at(int grid_offset) { + building *b = building_get(map_building_at(grid_offset)); + if (!b) { return 0; } @@ -566,8 +582,10 @@ int building_repair(building *b) // Backup building data building_data_transfer_backup(); building_data_transfer_copy(b, 1); - // Resolve placement data - int grid_offset = og_grid_offset ? og_grid_offset : b->grid_offset; + // Resolve placement data + if (!building_properties_for_type(b->type)->shared) { + grid_offset = og_grid_offset ? og_grid_offset : b->grid_offset; + } int x = map_grid_offset_to_x(grid_offset); int y = map_grid_offset_to_y(grid_offset); int size = og_size ? og_size : b->size; @@ -630,16 +648,21 @@ int building_repair(building *b) int full_cost = (placement_cost + placement_cost / 20);// +5% city_finance_process_construction(full_cost); - new_building->subtype.orientation = og_orientation; map_building_set_rubble_grid_building_id(grid_offset, 0, size); // remove rubble marker building_data_transfer_paste(new_building, 1); - new_building->state = BUILDING_STATE_CREATED; + if (!building_properties_for_type(type_to_place)->shared) { + new_building->subtype.orientation = og_orientation; + new_building->state = BUILDING_STATE_CREATED; + b->state = BUILDING_STATE_DELETED_BY_GAME; // mark old building as deleted + figure_create_explosion_cloud(new_building->x, new_building->y, og_size, 1); + } else { + figure_create_explosion_cloud(x, y, og_size, 1); + } building_data_transfer_restore_and_clear_backup(); - figure_create_explosion_cloud(new_building->x, new_building->y, og_size, 1); + if (wall) { map_tiles_update_all_walls(); // towers affect wall connections } - b->state = BUILDING_STATE_DELETED_BY_GAME; // mark old building as deleted game_undo_disable(); // not accounting for undoing repairs return full_cost; } diff --git a/src/building/building.h b/src/building/building.h index 31ce02a54c..02ff8de040 100644 --- a/src/building/building.h +++ b/src/building/building.h @@ -241,13 +241,13 @@ int building_is_storage(building_type b_type); * Keeping a building in the array is helpful because it holds the building's ID, and allows keeping the storage structure. */ -int building_repair(building *b); +int building_repair_at(int grid_offset); int building_is_still_burning(building *b); int building_can_repair(building *b); -int building_repair_cost(building *b); +int building_repair_cost_at(int grid_offset); void building_clear_related_data(building *b); diff --git a/src/building/construction_clear.c b/src/building/construction_clear.c index b8f92082f7..10e6dc75b3 100644 --- a/src/building/construction_clear.c +++ b/src/building/construction_clear.c @@ -397,11 +397,13 @@ static int repair_land_confirmed(int measure_only, int x_start, int y_start, int } if (!was_building_counted(b->id, repairable_buildings)) { if (measure_only) { - repair_cost += building_repair_cost(b); + repair_cost += building_repair_cost_at(slice->grid_offsets[i]); } else { - repair_cost += building_repair(b);// Actually perform the repair + repair_cost += building_repair_at(slice->grid_offsets[i]); // Actually perform the repair + } + if (!building_properties_for_type(b->type)->shared) { + confirm.repairable_buildings[repairable_buildings] = b->id; } - confirm.repairable_buildings[repairable_buildings] = b->id; repairable_buildings++; } } else { diff --git a/src/building/destruction.c b/src/building/destruction.c index 34e0eb279a..639972a00a 100644 --- a/src/building/destruction.c +++ b/src/building/destruction.c @@ -201,25 +201,30 @@ static void destroy_linked_parts(building *b, int destruction_method, int plague void building_destroy_by_collapse(building *b) { + game_undo_disable(); b->state = BUILDING_STATE_RUBBLE; if (b->type == BUILDING_TOWER) { figure_kill_tower_sentries_in_building(b); } set_rubble_grid_info_for_all_parts(b); - map_building_tiles_set_rubble(b->id, b->x, b->y, b->size); - figure_create_explosion_cloud(b->x, b->y, b->size, 0); destroy_linked_parts(b, DESTROY_COLLAPSE, 0); - } void building_destroy_by_fire(building *b) { + if (building_properties_for_type(b->type)->shared) { + return; + } destroy_on_fire(b, 0); destroy_linked_parts(b, DESTROY_FIRE, 0); } void building_destroy_by_earthquake(building *b) { + if (building_properties_for_type(b->type)->shared) { + return; + } + game_undo_disable(); int grid_offset = b->grid_offset; // save before destroying building int size = b->size; b->state = BUILDING_STATE_DELETED_BY_GAME; @@ -230,6 +235,9 @@ void building_destroy_by_earthquake(building *b) void building_destroy_by_plague(building *b) { + if (building_properties_for_type(b->type)->shared) { + return; + } destroy_on_fire(b, 1); destroy_linked_parts(b, DESTROY_FIRE, 1); } @@ -242,18 +250,23 @@ void building_destroy_without_rubble(building *b) void building_destroy_by_rioter(building *b) { + if (building_properties_for_type(b->type)->shared) { + return; + } destroy_on_fire(b, 0); destroy_linked_parts(b, DESTROY_FIRE, 0); } int building_destroy_first_of_type(building_type type) { + if (building_properties_for_type(type)->shared) { + return 0; + } for (building *b = building_first_of_type(type); b; b = b->next_of_type) { if (b->state != BUILDING_STATE_IN_USE && b->state != BUILDING_STATE_MOTHBALLED) { continue; } int grid_offset = b->grid_offset; - game_undo_disable(); building_destroy_by_collapse(b); map_routing_update_land(); return grid_offset; @@ -267,7 +280,8 @@ void building_destroy_last_placed(void) building *last_building = 0; for (int i = 1; i < building_count(); i++) { building *b = building_get(i); - if (b->state == BUILDING_STATE_CREATED || b->state == BUILDING_STATE_IN_USE) { + if ((b->state == BUILDING_STATE_CREATED || b->state == BUILDING_STATE_IN_USE) && + !building_properties_for_type(b->type)->shared) { if (b->created_sequence > highest_sequence) { highest_sequence = b->created_sequence; last_building = b; @@ -306,6 +320,7 @@ static void set_rubble_grid_info_for_all_parts(building *b) void building_destroy_by_enemy(int x, int y, int grid_offset) { + game_undo_disable(); int building_id = map_building_at(grid_offset); if (building_id > 0) { building *b = building_get(building_id); @@ -313,9 +328,12 @@ void building_destroy_by_enemy(int x, int y, int grid_offset) if (map_terrain_is(grid_offset, TERRAIN_WALL)) { figure_kill_tower_sentries_at(x, y); } - building_destroy_by_collapse(b); - } - if (b->state == BUILDING_STATE_IN_USE || b->state == BUILDING_STATE_MOTHBALLED) { + if (b->subtype.instances > 0) { + b->subtype.instances--; + } + map_building_tiles_set_rubble(b->id, x, y, b->size); + figure_create_explosion_cloud(x, y, b->size, 0); + } else if (b->state == BUILDING_STATE_IN_USE || b->state == BUILDING_STATE_MOTHBALLED) { city_ratings_peace_building_destroyed(b->type); building_destroy_by_collapse(b); } diff --git a/src/scenario/earthquake.c b/src/scenario/earthquake.c index 0bf85e2b3c..a4a5683579 100644 --- a/src/scenario/earthquake.c +++ b/src/scenario/earthquake.c @@ -4,12 +4,14 @@ #include "building/building.h" #include "building/destruction.h" +#include "building/properties.h" #include "city/message.h" #include "core/calc.h" #include "core/random.h" #include "figuretype/missile.h" #include "game/time.h" #include "map/building.h" +#include "map/building_tiles.h" #include "map/data.h" #include "map/grid.h" #include "map/property.h" @@ -84,18 +86,22 @@ static void advance_earthquake_to_tile(int x, int y) if (building_id) { building *b = building_get(building_id); - if (!b) { - return; - } - - if (b->type != BUILDING_BURNING_RUIN) { + if (building_properties_for_type(b->type)->shared) { + if (b->subtype.instances > 0) { + b->subtype.instances--; + } + map_building_tiles_set_rubble(b->id, x, y, b->size); + map_building_set_rubble_grid_building_id(grid_offset, 0, b->size); + } else if (b->type != BUILDING_BURNING_RUIN) { // (fort, hippodrome, ect.) if (b->prev_part_building_id > 0 || b->next_part_building_id > 0) { // find first part building *part = b; while (part->prev_part_building_id > 0) { building *prev = building_get(part->prev_part_building_id); - if (!prev) break; + if (!prev) { + break; + } part = prev; } // destroy all part diff --git a/src/window/building/common.h b/src/window/building/common.h index 57b89a1dc1..f9406b0377 100644 --- a/src/window/building/common.h +++ b/src/window/building/common.h @@ -58,6 +58,7 @@ typedef struct { building_info_type type; terrain_info_type terrain_type; int advisor_button; + int grid_offset; int rubble_building_id; int show_special_orders; struct { diff --git a/src/window/building/utility.c b/src/window/building/utility.c index 6ee8e8b7b2..be688734de 100644 --- a/src/window/building/utility.c +++ b/src/window/building/utility.c @@ -273,8 +273,7 @@ void window_building_draw_burning_ruin(building_info_context *c) static void trigger_building_repair(const complex_button *button) { - building *b = building_get(button->parameters[0]); - building_repair(b); + building_repair_at(button->parameters[0]); window_invalidate(); window_go_back(); } @@ -287,7 +286,7 @@ static void init_repair_building_button(building_info_context *c) repair_building_button->y = c->y_offset + BLOCK_SIZE * c->height_blocks - 30; repair_building_button->width = button_width; repair_building_button->height = 20; - repair_building_button->parameters[0] = c->rubble_building_id; + repair_building_button->parameters[0] = c->grid_offset; building *b = building_get(c->rubble_building_id); if (b->type == BUILDING_WAREHOUSE_SPACE) { // swap the b pointer for the main warehouse building b = building_get(map_building_rubble_building_id(b->data.rubble.og_grid_offset)); @@ -320,7 +319,7 @@ void window_building_draw_rubble(building_info_context *c) building_type type = og_type == BUILDING_NONE ? b->type : og_type; int is_burning_ruins = (b->type == BUILDING_BURNING_RUIN); - if (building_can_repair_type(type) || building_can_repair_type(type)) { + if (building_can_repair_type(type) || building_can_repair_type(og_type)) { init_repair_building_button(c); complex_button_draw(repair_building_button); } else if (building_clone_type_from_building_type(type) != BUILDING_NONE) { diff --git a/src/window/building_info.c b/src/window/building_info.c index 7f605f0ec9..ad0f97feb3 100644 --- a/src/window/building_info.c +++ b/src/window/building_info.c @@ -336,6 +336,7 @@ static void init(int grid_offset) context.show_special_orders = 0; context.depot_selection = 0; context.advisor_button = ADVISOR_NONE; + context.grid_offset = grid_offset; context.building_id = map_building_at(grid_offset); context.rubble_building_id = map_building_rubble_building_id(grid_offset); context.has_reservoir_pipes = map_terrain_is(grid_offset, TERRAIN_RESERVOIR_RANGE); From 5060fec04d19203ee09cad715ae6703fbaae8b38 Mon Sep 17 00:00:00 2001 From: crudelios Date: Wed, 25 Mar 2026 14:45:15 +0000 Subject: [PATCH 4/7] FIx collapse --- src/building/destruction.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/building/destruction.c b/src/building/destruction.c index 639972a00a..1618d511dc 100644 --- a/src/building/destruction.c +++ b/src/building/destruction.c @@ -201,12 +201,17 @@ static void destroy_linked_parts(building *b, int destruction_method, int plague void building_destroy_by_collapse(building *b) { + if (building_properties_for_type(b->type)->shared) { + return; + } game_undo_disable(); b->state = BUILDING_STATE_RUBBLE; if (b->type == BUILDING_TOWER) { figure_kill_tower_sentries_in_building(b); } set_rubble_grid_info_for_all_parts(b); + map_building_tiles_set_rubble(b->id, b->x, b->y, b->size); + figure_create_explosion_cloud(b->x, b->y, b->size, 0); destroy_linked_parts(b, DESTROY_COLLAPSE, 0); } From 6779a1d4fa8e3c8374c259adb8567f9397a1fa09 Mon Sep 17 00:00:00 2001 From: crudelios Date: Wed, 25 Mar 2026 21:51:48 +0000 Subject: [PATCH 5/7] Fix repairing --- src/building/building.c | 4 ++-- src/building/clone.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/building/building.c b/src/building/building.c index b752d924b0..0e29041b08 100644 --- a/src/building/building.c +++ b/src/building/building.c @@ -421,7 +421,7 @@ int building_can_repair(building *b) int building_repair_cost_at(int grid_offset) { - building *b = building_get(map_building_at(grid_offset)); + building *b = building_get(map_building_rubble_building_id(grid_offset)); int og_grid_offset = 0, og_size = 0, og_type = 0; if (!b || !building_can_repair(b)) { return 0; @@ -553,7 +553,7 @@ static int warehouse_repair(building *b) int building_repair_at(int grid_offset) { - building *b = building_get(map_building_at(grid_offset)); + building *b = building_get(map_building_rubble_building_id(grid_offset)); if (!b) { return 0; diff --git a/src/building/clone.c b/src/building/clone.c index 88aee55832..a1d590d8a8 100644 --- a/src/building/clone.c +++ b/src/building/clone.c @@ -82,7 +82,7 @@ static building_type get_clone_type_from_building(building *b, building_type clo building_type building_clone_type_from_building_type(building_type type) { - return get_clone_type_from_building(NULL, type); + return get_clone_type_from_building(0, type); } int building_clone_rotation_from_grid_offset(int grid_offset) From dfc1a4f2c07856ce9b1e0d439775cb59115fbb16 Mon Sep 17 00:00:00 2001 From: Jose Cadete Date: Sun, 29 Mar 2026 23:18:58 +0100 Subject: [PATCH 6/7] Bump savegame version Start adding support for aqueducts as shared buildings --- src/building/properties.c | 1 + src/game/file_io.c | 4 +++- src/game/save_version.h | 5 +++-- src/map/terrain.c | 14 ++++++++++---- src/map/terrain.h | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/building/properties.c b/src/building/properties.c index 690a11cae4..bf8ac29093 100644 --- a/src/building/properties.c +++ b/src/building/properties.c @@ -72,6 +72,7 @@ static building_properties properties[BUILDING_TYPE_MAX] = { .image_group = 19, .image_offset = 2, .fire_proof = 1, + .shared = 1, .event_data.attr = "aqueduct", .building_model_data = {.cost = 8, .desirability_value = -2, .desirability_step = 1, .desirability_step_size = 1, .desirability_range = 2, .laborers = 0} diff --git a/src/game/file_io.c b/src/game/file_io.c index 1364502feb..814567642a 100644 --- a/src/game/file_io.c +++ b/src/game/file_io.c @@ -974,7 +974,9 @@ static void savegame_load_from_state(savegame_state *state, savegame_version_t v map_terrain_migrate_old_bridges(); } - map_terrain_migrate_old_walls(); + if (version <= SAVE_GAME_LAST_NO_SHARED_BUILDINGS) { + map_terrain_migrate_shared_buildings(); + } if (version <= SAVE_GAME_LAST_NO_FORMULAS_AND_MODEL_DATA) { scenario_events_migrate_to_formulas(); diff --git a/src/game/save_version.h b/src/game/save_version.h index 5337c1258e..dd6bd90970 100644 --- a/src/game/save_version.h +++ b/src/game/save_version.h @@ -44,7 +44,7 @@ If you are unsure about anything regarding the savegame versioning, please ask o typedef enum { - SAVE_GAME_CURRENT_VERSION = 0xac, + SAVE_GAME_CURRENT_VERSION = 0xad, SAVE_GAME_LAST_ORIGINAL_LIMITS_VERSION = 0x66, SAVE_GAME_LAST_SMALLER_IMAGE_ID_VERSION = 0x76, @@ -96,7 +96,8 @@ typedef enum { SAVE_GAME_LAST_U16_GRIDS = 0xa8, SAVE_GAME_LAST_NO_FORMULAS_AND_MODEL_DATA = 0xa9, SAVE_GAME_LAST_STATIC_PATHS_AND_ROUTES = 0xaa, - SAVE_GAME_LAST_NO_EMPIRE_EDITOR = 0xab + SAVE_GAME_LAST_NO_EMPIRE_EDITOR = 0xab, + SAVE_GAME_LAST_NO_SHARED_BUILDINGS = 0xac } savegame_version_t; typedef enum { diff --git a/src/map/terrain.c b/src/map/terrain.c index 7594589982..0d3254d7e2 100644 --- a/src/map/terrain.c +++ b/src/map/terrain.c @@ -616,7 +616,7 @@ void map_terrain_migrate_old_bridges(void) } } -void map_terrain_migrate_old_walls(void) +void map_terrain_migrate_shared_buildings(void) { for (int y = 0; y < GRID_SIZE; y++) { for (int x = 0; x < GRID_SIZE; x++) { @@ -624,10 +624,16 @@ void map_terrain_migrate_old_walls(void) if (!map_grid_is_valid_offset(grid_offset)) { continue; } + + building *shared_building = 0; if (map_terrain_is(grid_offset, TERRAIN_WALL)) { - building *wall = building_create(BUILDING_WALL, 0, 0); - wall->subtype.instances++; - map_building_set(grid_offset, wall->id); + shared_building = building_create(BUILDING_WALL, 0, 0); + } else if (map_terrain_is(grid_offset, TERRAIN_AQUEDUCT)) { + shared_building = building_create(BUILDING_AQUEDUCT, 0, 0); + } + if (shared_building) { + shared_building->subtype.instances++; + map_building_set(grid_offset, shared_building->id); map_terrain_add(grid_offset, TERRAIN_BUILDING); map_property_clear_multi_tile_xy(grid_offset); } diff --git a/src/map/terrain.h b/src/map/terrain.h index 48ed98bc7c..82e0e7c203 100644 --- a/src/map/terrain.h +++ b/src/map/terrain.h @@ -162,7 +162,7 @@ void map_terrain_save_state_legacy(buffer *buf); void map_terrain_migrate_old_bridges(void); -void map_terrain_migrate_old_walls(void); +void map_terrain_migrate_shared_buildings(void); void map_terrain_load_state(buffer *buf, int expanded_terrain_data, buffer *images, int legacy_image_buffer); From d35a58c08173e9441e2937f38db0acc780c44ef9 Mon Sep 17 00:00:00 2001 From: crudelios Date: Tue, 31 Mar 2026 15:55:07 +0100 Subject: [PATCH 7/7] Finish support for aqueducts as shared buildings Additionally, aqueducts can now be repaired --- src/building/building.c | 8 +++----- src/building/construction.c | 8 ++++---- src/building/construction_clear.c | 2 -- src/building/construction_routed.c | 16 ++++++++++++---- src/building/construction_routed.h | 4 ++-- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/building/building.c b/src/building/building.c index 0e29041b08..f5fc7fb90b 100644 --- a/src/building/building.c +++ b/src/building/building.c @@ -68,9 +68,9 @@ building *building_get(unsigned int id) int building_can_repair_type(building_type type) { - if (building_monument_is_limited(type) || type == BUILDING_AQUEDUCT || building_is_fort(type)) { - return 0; // limited monuments and aqueducts cannot be repaired at the moment, aqueducts require a rework, - } //and limited monuments are too complex to easily repair, and arent a common occurrence + if (building_monument_is_limited(type) || building_is_fort(type)) { + return 0; // limited monuments cannot be repaired at the moment, + } // as they are too complex to easily repair, and arent a common occurrence // forts have the complexity of holding formations, so are also currently excluded building_type repair_type = building_clone_type_from_building_type(type); if (repair_type == BUILDING_NONE) { @@ -565,8 +565,6 @@ int building_repair_at(int grid_offset) if (!building_can_repair_type(b->type) && !building_can_repair_type(b->data.rubble.og_type)) { if (building_monument_is_limited(b->type) || building_monument_is_limited(b->data.rubble.og_type)) { city_warning_show(WARNING_REPAIR_MONUMENT, NEW_WARNING_SLOT); - } else if (b->type == BUILDING_AQUEDUCT || b->data.rubble.og_type == BUILDING_AQUEDUCT) { - city_warning_show(WARNING_REPAIR_AQUEDUCT, NEW_WARNING_SLOT); } else { city_warning_show(WARNING_REPAIR_IMPOSSIBLE, NEW_WARNING_SLOT); } diff --git a/src/building/construction.c b/src/building/construction.c index a1893dc5f9..74b0613276 100644 --- a/src/building/construction.c +++ b/src/building/construction.c @@ -527,7 +527,7 @@ static int place_reservoir_and_aqueducts(int measure_only, int x_start, int y_st int dx_end = aqueduct_offsets_x[dir_end]; int dy_end = aqueduct_offsets_y[dir_end]; int dist; - if (building_construction_place_aqueduct_for_reservoir(1, + if (building_construction_place_aqueduct_for_reservoir(1, 0, x_start + dx_start, y_start + dy_start, x_end + dx_end, y_end + dy_end, &dist)) { if (dist && dist < min_dist) { min_dist = dist; @@ -545,7 +545,7 @@ static int place_reservoir_and_aqueducts(int measure_only, int x_start, int y_st int x_aq_end = aqueduct_offsets_x[min_dir_end]; int y_aq_end = aqueduct_offsets_y[min_dir_end]; int aq_items; - building_construction_place_aqueduct_for_reservoir(0, x_start + x_aq_start, y_start + y_aq_start, + building_construction_place_aqueduct_for_reservoir(measure_only, 1, x_start + x_aq_start, y_start + y_aq_start, x_end + x_aq_end, y_end + y_aq_end, &aq_items); if (info->place_reservoir_at_start == PLACE_RESERVOIR_YES) { info->cost += model_get_building(BUILDING_RESERVOIR)->cost; @@ -921,7 +921,7 @@ void building_construction_update(int x, int y, int grid_offset) current_cost *= length; } } else if (type == BUILDING_AQUEDUCT) { - building_construction_place_aqueduct(data.start.x, data.start.y, x, y, ¤t_cost); + building_construction_place_aqueduct(1, data.start.x, data.start.y, x, y, ¤t_cost); map_tiles_update_all_aqueducts(0); } else if (type == BUILDING_DRAGGABLE_RESERVOIR) { struct reservoir_info info; @@ -1117,7 +1117,7 @@ void building_construction_place(void) placement_cost *= length; } else if (type == BUILDING_AQUEDUCT) { int cost; - if (!building_construction_place_aqueduct(x_start, y_start, x_end, y_end, &cost)) { + if (!building_construction_place_aqueduct(0, x_start, y_start, x_end, y_end, &cost)) { city_warning_show(WARNING_CLEAR_LAND_NEEDED, NEW_WARNING_SLOT); return; } diff --git a/src/building/construction_clear.c b/src/building/construction_clear.c index 10e6dc75b3..be01836c7a 100644 --- a/src/building/construction_clear.c +++ b/src/building/construction_clear.c @@ -409,8 +409,6 @@ static int repair_land_confirmed(int measure_only, int x_start, int y_start, int } else { if (building_monument_is_limited(b->type)) { city_warning_show(WARNING_REPAIR_MONUMENT, NEW_WARNING_SLOT); - } else if (b->type == BUILDING_AQUEDUCT) { - city_warning_show(WARNING_REPAIR_AQUEDUCT, NEW_WARNING_SLOT); } else { city_warning_show(WARNING_REPAIR_IMPOSSIBLE, NEW_WARNING_SLOT); } diff --git a/src/building/construction_routed.c b/src/building/construction_routed.c index 9bf82043b5..07a623edc2 100644 --- a/src/building/construction_routed.c +++ b/src/building/construction_routed.c @@ -66,6 +66,14 @@ static int place_routed_building(int x_start, int y_start, int x_end, int y_end, break; case ROUTED_BUILDING_AQUEDUCT: *items += map_building_tiles_add_aqueduct(x_end, y_end); + if (!measure_only) { + building *aqueduct = building_create(BUILDING_AQUEDUCT, x_end, y_end); + map_building_set(grid_offset, aqueduct->id); + map_terrain_add(grid_offset, TERRAIN_BUILDING); + map_property_clear_multi_tile_xy(grid_offset); + game_undo_add_building(aqueduct); + aqueduct->subtype.instances++; + } break; case ROUTED_BUILDING_AQUEDUCT_WITHOUT_GRAPHIC: *items += 1; @@ -160,7 +168,7 @@ int building_construction_place_highway(int measure_only, int x_start, int y_sta return items_placed; } -int building_construction_place_aqueduct(int x_start, int y_start, int x_end, int y_end, int *cost) +int building_construction_place_aqueduct(int measure_only, int x_start, int y_start, int x_end, int y_end, int *cost) { game_undo_restore_map(0); @@ -197,14 +205,14 @@ int building_construction_place_aqueduct(int x_start, int y_start, int x_end, in return 0; } int num_items; - place_routed_building(x_start, y_start, x_end, y_end, ROUTED_BUILDING_AQUEDUCT, &num_items, 0); + place_routed_building(x_start, y_start, x_end, y_end, ROUTED_BUILDING_AQUEDUCT, &num_items, measure_only); *cost = item_cost * num_items; return 1; } int building_construction_place_aqueduct_for_reservoir( - int measure_only, int x_start, int y_start, int x_end, int y_end, int *items) + int measure_only, int should_draw, int x_start, int y_start, int x_end, int y_end, int *items) { - routed_building_type type = measure_only ? ROUTED_BUILDING_AQUEDUCT_WITHOUT_GRAPHIC : ROUTED_BUILDING_AQUEDUCT; + routed_building_type type = should_draw ? ROUTED_BUILDING_AQUEDUCT : ROUTED_BUILDING_AQUEDUCT_WITHOUT_GRAPHIC; return place_routed_building(x_start, y_start, x_end, y_end, type, items, measure_only); } diff --git a/src/building/construction_routed.h b/src/building/construction_routed.h index fcf584a777..10953adcdf 100644 --- a/src/building/construction_routed.h +++ b/src/building/construction_routed.h @@ -7,9 +7,9 @@ int building_construction_place_road(int measure_only, int x_start, int y_start, int building_construction_place_highway(int measure_only, int x_start, int y_start, int x_end, int y_end); -int building_construction_place_aqueduct(int x_start, int y_start, int x_end, int y_end, int *cost); +int building_construction_place_aqueduct(int measure_only, int x_start, int y_start, int x_end, int y_end, int *cost); int building_construction_place_aqueduct_for_reservoir( - int measure_only, int x_start, int y_start, int x_end, int y_end, int *items); + int measure_only, int should_draw, int x_start, int y_start, int x_end, int y_end, int *items); #endif // BUILDING_CONSTRUCTION_ROUTED_H