diff --git Makefile Makefile index 69f2b8acb..0189882d9 100644 --- Makefile +++ Makefile @@ -608,6 +608,7 @@ SOURCES += vehicle/simroadtraffic.cc SOURCES += vehicle/vehicle.cc SOURCES += vehicle/vehicle_base.cc SOURCES += vehicle/water_vehicle.cc +SOURCES += world/terraformer.cc ifeq ($(BACKEND),gdi) diff --git Simutrans-Main.vcxitems Simutrans-Main.vcxitems index b8de9871a..5d0f7e93f 100644 --- Simutrans-Main.vcxitems +++ Simutrans-Main.vcxitems @@ -356,6 +356,7 @@ + @@ -738,6 +739,7 @@ + diff --git bauer/tunnelbauer.cc bauer/tunnelbauer.cc index 878f96d44..d8ed2b3c2 100644 --- bauer/tunnelbauer.cc +++ bauer/tunnelbauer.cc @@ -33,6 +33,7 @@ #include "wegbauer.h" #include "../tpl/stringhashtable_tpl.h" #include "../tpl/vector_tpl.h" +#include "../world/terraformer.h" karte_ptr_t tunnel_builder_t::welt; @@ -173,9 +174,11 @@ koord3d tunnel_builder_t::find_end_pos(player_t *player, koord3d pos, koord zv, sint8 hse = pos.z + corner_se(new_slope); sint8 hne = pos.z + corner_ne(new_slope); sint8 hnw = pos.z + corner_nw(new_slope); - karte_t::terraformer_t raise(welt); - raise.add_raise_node(pos.x, pos.y, hsw, hse, hne, hnw); - raise.iterate(true); + + terraformer_t raise(welt, terraformer_t::raise); + raise.add_node(pos.x, pos.y, hsw, hse, hne, hnw); + raise.generate_affected_tile_list(); + if (raise.can_raise_all(player)) { // returned true therefore error reported return koord3d::invalid; @@ -183,6 +186,7 @@ koord3d tunnel_builder_t::find_end_pos(player_t *player, koord3d pos, koord zv, // if we can adjust height here we can build an entrance so don't need checks below return pos; } + if( gr->get_hoehe() < pos.z ){ return koord3d::invalid; } @@ -244,15 +248,21 @@ koord3d tunnel_builder_t::find_end_pos(player_t *player, koord3d pos, koord zv, sint8 hse = pos.z + corner_se(new_slope); sint8 hne = pos.z + corner_ne(new_slope); sint8 hnw = pos.z + corner_nw(new_slope); - karte_t::terraformer_t raise(welt), lower(welt); - raise.add_raise_node(pos.x, pos.y, hsw, hse, hne, hnw); - raise.iterate(false); - lower.add_lower_node(pos.x, pos.y, hsw, hse, hne, hnw); - lower.iterate(false); - if (raise.can_lower_all(player) || lower.can_lower_all(player)) { - // returned true therefore error reported + + terraformer_t raise(welt, terraformer_t::raise); + terraformer_t lower(welt, terraformer_t::lower); + + raise.add_node(pos.x, pos.y, hsw, hse, hne, hnw); + lower.add_node(pos.x, pos.y, hsw, hse, hne, hnw); + + raise.generate_affected_tile_list(); + lower.generate_affected_tile_list(); + + if (raise.can_raise_all(player)!= NULL || lower.can_lower_all(player)!=NULL) { + // returned non-null therefore error reported return koord3d::invalid; } + // if we can adjust height here we can build an entrance so don't need checks below return pos; } @@ -406,18 +416,22 @@ const char *tunnel_builder_t::build( player_t *player, koord pos, const tunnel_d int n = 0; - karte_t::terraformer_t raise(welt),lower(welt); - raise.add_raise_node(end.x, end.y, hsw, hse, hne, hnw); - lower.add_lower_node(end.x, end.y, hsw, hse, hne, hnw); - raise.iterate(true); - lower.iterate(false); + terraformer_t raise(welt, terraformer_t::raise); + terraformer_t lower(welt, terraformer_t::lower); + + raise.add_node(end.x, end.y, hsw, hse, hne, hnw); + lower.add_node(end.x, end.y, hsw, hse, hne, hnw); + + raise.generate_affected_tile_list(); + lower.generate_affected_tile_list(); + err = raise.can_raise_all(player); if (!err) err = lower.can_lower_all(player); if (err) return 0; // TODO: this is rather hackish as 4 seems to come from nowhere but works most of the time // feel free to change if you have a better idea! - n = (raise.raise_all()+lower.lower_all())/4; + n = (raise.apply() + lower.apply()) / 4; player_t::book_construction_costs(player, welt->get_settings().cst_alter_land * n, end.get_2d(), desc->get_waytype()); } diff --git cmake/SimutransSourceList.cmake cmake/SimutransSourceList.cmake index ba8982b02..ec535d071 100644 --- cmake/SimutransSourceList.cmake +++ cmake/SimutransSourceList.cmake @@ -340,4 +340,5 @@ target_sources(simutrans PRIVATE vehicle/vehicle.cc vehicle/vehicle_base.cc vehicle/water_vehicle.cc + world/terraformer.cc ) diff --git simworld.cc simworld.cc index fa4df6445..83d7e8e21 100644 --- simworld.cc +++ simworld.cc @@ -105,8 +105,10 @@ #include "player/ai_goods.h" #include "player/ai_scripted.h" +#include "world/terraformer.h" #include "io/rdwr/adler32_stream.h" + #include "pathes.h" @@ -2188,80 +2190,6 @@ karte_t::~karte_t() } } -const char* karte_t::can_lower_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const -{ - const planquadrat_t *plan = access(x,y); - - if( plan==NULL ) { - return ""; - } - - if( h < groundwater - 3 ) { - return ""; - } - - const sint8 hmax = plan->get_kartenboden()->get_hoehe(); - if( (hmax == h || hmax == h - 1) && (plan->get_kartenboden()->get_grund_hang() == 0 || is_plan_height_changeable( x, y )) ) { - return NULL; - } - - if( !is_plan_height_changeable(x, y) ) { - return ""; - } - - // tunnel slope below? - grund_t *gr = plan->get_boden_in_hoehe( h - 1 ); - if( !gr ) { - gr = plan->get_boden_in_hoehe( h - 2 ); - } - if( !gr && settings.get_way_height_clearance()==2 ) { - gr = plan->get_boden_in_hoehe( h - 3 ); - } - if( gr && h < gr->get_pos().z + slope_t::max_diff( gr->get_weg_hang() ) + settings.get_way_height_clearance() ) { - return ""; - } - - // tunnel below? - while(h < hmax) { - if(plan->get_boden_in_hoehe(h)) { - return ""; - } - h ++; - } - - // check allowance by scenario - if (get_scenario()->is_scripted()) { - return get_scenario()->is_work_allowed_here(player, TOOL_LOWER_LAND|GENERAL_TOOL, ignore_wt, plan->get_kartenboden()->get_pos()); - } - - return NULL; -} - - -const char* karte_t::can_raise_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const -{ - const planquadrat_t *plan = access(x,y); - if( plan == 0 || !is_plan_height_changeable(x, y) ) { - return ""; - } - - // irgendwo eine Bruecke im Weg? - int hmin = plan->get_kartenboden()->get_hoehe(); - while(h > hmin) { - if(plan->get_boden_in_hoehe(h)) { - return ""; - } - h --; - } - - // check allowance by scenario - if (get_scenario()->is_scripted()) { - return get_scenario()->is_work_allowed_here(player, TOOL_RAISE_LAND|GENERAL_TOOL, ignore_wt, plan->get_kartenboden()->get_pos()); - } - - return NULL; -} - bool karte_t::is_plan_height_changeable(sint16 x, sint16 y) const { @@ -2290,284 +2218,6 @@ bool karte_t::is_plan_height_changeable(sint16 x, sint16 y) const } -bool karte_t::terraformer_t::node_t::comp(const karte_t::terraformer_t::node_t& a, const karte_t::terraformer_t::node_t& b) -{ - int diff = a.x- b.x; - if (diff == 0) { - diff = a.y - b.y; - } - return diff<0; -} - - -void karte_t::terraformer_t::add_node(bool raise, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) -{ - if (!welt->is_within_limits(x,y)) { - return; - } - node_t test(x, y, hsw, hse, hne, hnw, actual_flag^3); - node_t *other = list.insert_unique_ordered(test, node_t::comp); - - sint8 factor = raise ? +1 : -1; - - if (other) { - for(int i=0; i<4; i++) { - if (factor*other->h[i] < factor*test.h[i]) { - other->h[i] = test.h[i]; - other->changed |= actual_flag^3; - ready = false; - } - } - } - else { - ready = false; - } -} - -void karte_t::terraformer_t::add_raise_node(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) -{ - add_node(true, x, y, hsw, hse, hne, hnw); -} - -void karte_t::terraformer_t::add_lower_node(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) -{ - add_node(false, x, y, hsw, hse, hne, hnw); -} - -void karte_t::terraformer_t::iterate(bool raise) -{ - while( !ready) { - actual_flag ^= 3; // flip bits - // clear new_flag bit - FOR(vector_tpl, &i, list) { - i.changed &= actual_flag; - } - // process nodes with actual_flag set - ready = true; - for(uint32 j=0; j < list.get_count(); j++) { - node_t& i = list[j]; - if (i.changed & actual_flag) { - i.changed &= ~ actual_flag; - if (raise) { - welt->prepare_raise(*this, i.x, i.y, i.h[0], i.h[1], i.h[2], i.h[3]); - } - else { - welt->prepare_lower(*this, i.x, i.y, i.h[0], i.h[1], i.h[2], i.h[3]); - } - } - } - } -} - - -const char* karte_t::terraformer_t::can_raise_all(const player_t *player, bool keep_water) const -{ - const char* err = NULL; - FOR(vector_tpl, const &i, list) { - err = welt->can_raise_to(player, i.x, i.y, keep_water, i.h[0], i.h[1], i.h[2], i.h[3]); - if (err) return err; - } - return NULL; -} - -const char* karte_t::terraformer_t::can_lower_all(const player_t *player) const -{ - const char* err = NULL; - FOR(vector_tpl, const &i, list) { - err = welt->can_lower_to(player, i.x, i.y, i.h[0], i.h[1], i.h[2], i.h[3]); - if (err) { - return err; - } - } - return NULL; -} - -int karte_t::terraformer_t::raise_all() -{ - int n=0; - FOR(vector_tpl, &i, list) { - n += welt->raise_to(i.x, i.y, i.h[0], i.h[1], i.h[2], i.h[3]); - } - return n; -} - -int karte_t::terraformer_t::lower_all() -{ - int n=0; - FOR(vector_tpl, &i, list) { - n += welt->lower_to(i.x, i.y, i.h[0], i.h[1], i.h[2], i.h[3]); - } - return n; -} - - -const char* karte_t::can_raise_to(const player_t *player, sint16 x, sint16 y, bool keep_water, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) const -{ - assert(is_within_limits(x,y)); - grund_t *gr = lookup_kartenboden_nocheck(x,y); - const sint8 water_hgt = get_water_hgt_nocheck(x,y); - - const sint8 max_hgt = max(max(hsw,hse),max(hne,hnw)); - - if( gr->is_water() && keep_water && max_hgt > water_hgt ) { - return ""; - } - - const char* err = can_raise_plan_to(player, x, y, max_hgt); - - return err; -} - - -void karte_t::prepare_raise(terraformer_t& digger, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) -{ - assert(is_within_limits(x,y)); - grund_t *gr = lookup_kartenboden_nocheck(x,y); - const sint8 water_hgt = get_water_hgt_nocheck(x,y); - const sint8 h0 = gr->get_hoehe(); - // old height - const sint8 h0_sw = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x,y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); - const sint8 h0_se = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x+1,y+1) ) : h0 + corner_se( gr->get_grund_hang() ); - const sint8 h0_ne = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x+1,y) ) : h0 + corner_ne( gr->get_grund_hang() ); - const sint8 h0_nw = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x,y) ) : h0 + corner_nw( gr->get_grund_hang() ); - - // new height - const sint8 hn_sw = max(hsw, h0_sw); - const sint8 hn_se = max(hse, h0_se); - const sint8 hn_ne = max(hne, h0_ne); - const sint8 hn_nw = max(hnw, h0_nw); - - // nothing to do? - if( !gr->is_water() && h0_sw >= hsw && h0_se >= hse && h0_ne >= hne && h0_nw >= hnw ) { - return; - } - - // calc new height and slope - const sint8 hneu = min( min( hn_sw, hn_se ), min( hn_ne, hn_nw ) ); - const sint8 hmaxneu = max( max( hn_sw, hn_se ), max( hn_ne, hn_nw ) ); - - const uint8 max_hdiff = ground_desc_t::double_grounds ? 2 : 1; - - bool ok = (hmaxneu - hneu <= max_hdiff); // may fail on water tiles since lookup_hgt might be modified from previous raise_to calls - if( !ok && !gr->is_water() ) { - assert(false); - } - - // sw - if (h0_sw < hsw) { - digger.add_raise_node( x - 1, y + 1, hsw - max_hdiff, hsw - max_hdiff, hsw, hsw - max_hdiff ); - } - // s - if (h0_sw < hsw || h0_se < hse) { - const sint8 hs = max( hse, hsw ) - max_hdiff; - digger.add_raise_node( x, y + 1, hs, hs, hse, hsw ); - } - // se - if (h0_se < hse) { - digger.add_raise_node( x + 1, y + 1, hse - max_hdiff, hse - max_hdiff, hse - max_hdiff, hse ); - } - // e - if (h0_se < hse || h0_ne < hne) { - const sint8 he = max( hse, hne ) - max_hdiff; - digger.add_raise_node( x + 1, y, hse, he, he, hne ); - } - // ne - if (h0_ne < hne) { - digger.add_raise_node( x + 1,y - 1, hne, hne - max_hdiff, hne - max_hdiff, hne - max_hdiff ); - } - // n - if (h0_nw < hnw || h0_ne < hne) { - const sint8 hn = max( hnw, hne ) - max_hdiff; - digger.add_raise_node( x, y - 1, hnw, hne, hn, hn ); - } - // nw - if (h0_nw < hnw) { - digger.add_raise_node( x - 1, y - 1, hnw - max_hdiff, hnw, hnw - max_hdiff, hnw - max_hdiff ); - } - // w - if (h0_sw < hsw || h0_nw < hnw) { - const sint8 hw = max( hnw, hsw ) - max_hdiff; - digger.add_raise_node( x - 1, y, hw, hsw, hnw, hw ); - } -} - - -int karte_t::raise_to(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) -{ - int n=0; - assert(is_within_limits(x,y)); - grund_t *gr = lookup_kartenboden_nocheck(x,y); - const sint8 water_hgt = get_water_hgt_nocheck(x,y); - const sint8 h0 = gr->get_hoehe(); - - // old height - const sint8 h0_sw = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x,y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); - const sint8 h0_se = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x+1,y+1) ) : h0 + corner_se( gr->get_grund_hang() ); - const sint8 h0_ne = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x+1,y) ) : h0 + corner_ne( gr->get_grund_hang() ); - const sint8 h0_nw = gr->is_water() ? min(water_hgt, lookup_hgt_nocheck(x,y) ) : h0 + corner_nw( gr->get_grund_hang() ); - - // new height - const sint8 hn_sw = max(hsw, h0_sw); - const sint8 hn_se = max(hse, h0_se); - const sint8 hn_ne = max(hne, h0_ne); - const sint8 hn_nw = max(hnw, h0_nw); - // nothing to do? - if( !gr->is_water() && h0_sw >= hsw && h0_se >= hse && h0_ne >= hne && h0_nw >= hnw ) { - return 0; - } - - // calc new height and slope - const sint8 hneu = min( min( hn_sw, hn_se ), min( hn_ne, hn_nw ) ); - const sint8 hmaxneu = max( max( hn_sw, hn_se ), max( hn_ne, hn_nw ) ); - - const uint8 max_hdiff = ground_desc_t::double_grounds ? 2 : 1; - const sint8 disp_hneu = max( hneu, water_hgt ); - const sint8 disp_hn_sw = max( hn_sw, water_hgt ); - const sint8 disp_hn_se = max( hn_se, water_hgt ); - const sint8 disp_hn_ne = max( hn_ne, water_hgt ); - const sint8 disp_hn_nw = max( hn_nw, water_hgt ); - const slope_t::type sneu = encode_corners(disp_hn_sw - disp_hneu, disp_hn_se - disp_hneu, disp_hn_ne - disp_hneu, disp_hn_nw - disp_hneu); - - bool ok = (hmaxneu - hneu <= max_hdiff); // may fail on water tiles since lookup_hgt might be modified from previous raise_to calls - if (!ok && !gr->is_water()) { - assert(false); - } - // change height and slope, for water tiles only if they will become land - if( !gr->is_water() || (hmaxneu > water_hgt || (hneu == water_hgt && hmaxneu == water_hgt) ) ) { - gr->set_pos( koord3d( x, y, disp_hneu ) ); - gr->set_grund_hang( sneu ); - access_nocheck(x,y)->angehoben(); - set_water_hgt(x, y, groundwater-4); - } - - // update north point in grid - set_grid_hgt(x, y, hn_nw); - calc_climate(koord(x,y), true); - if ( x == cached_size.x ) { - // update eastern grid coordinates too if we are in the edge. - set_grid_hgt(x+1, y, hn_ne); - set_grid_hgt(x+1, y+1, hn_se); - } - if ( y == cached_size.y ) { - // update southern grid coordinates too if we are in the edge. - set_grid_hgt(x, y+1, hn_sw); - set_grid_hgt(x+1, y+1, hn_se); - } - - n += hn_sw - h0_sw + hn_se - h0_se + hn_ne - h0_ne + hn_nw - h0_nw; - - lookup_kartenboden_nocheck(x,y)->calc_image(); - if ( (x+1) < cached_size.x ) { - lookup_kartenboden_nocheck(x+1,y)->calc_image(); - } - if ( (y+1) < cached_size.y ) { - lookup_kartenboden_nocheck(x,y+1)->calc_image(); - } - - return n; -} - - // raise height in the hgt-array void karte_t::raise_grid_to(sint16 x, sint16 y, sint8 h) { @@ -2620,15 +2270,15 @@ int karte_t::grid_raise(const player_t *player, koord k, const char*&err) hsw = hse = hne = hnw = hgt; } - terraformer_t digger(this); - digger.add_raise_node(x, y, hsw, hse, hne, hnw); - digger.iterate(true); + terraformer_t digger(this, terraformer_t::raise); + digger.add_node(x, y, hsw, hse, hne, hnw); + digger.generate_affected_tile_list(); err = digger.can_raise_all(player); if (err) { return 0; } - n = digger.raise_all(); + n = digger.apply(); // force world full redraw, or background could be dirty. set_dirty(); @@ -2641,253 +2291,6 @@ int karte_t::grid_raise(const player_t *player, koord k, const char*&err) } -void karte_t::prepare_lower(terraformer_t& digger, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) -{ - assert(is_within_limits(x,y)); - grund_t *gr = lookup_kartenboden_nocheck(x,y); - const sint8 water_hgt = get_water_hgt_nocheck(x,y); - const sint8 h0 = gr->get_hoehe(); - // which corners have to be raised? - const sint8 h0_sw = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x,y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); - const sint8 h0_se = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x+1,y+1) ) : h0 + corner_se( gr->get_grund_hang() ); - const sint8 h0_ne = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x,y+1) ) : h0 + corner_ne( gr->get_grund_hang() ); - const sint8 h0_nw = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x,y) ) : h0 + corner_nw( gr->get_grund_hang() ); - - const uint8 max_hdiff = ground_desc_t::double_grounds ? 2 : 1; - - // sw - if (h0_sw > hsw) { - digger.add_lower_node(x - 1, y + 1, hsw + max_hdiff, hsw + max_hdiff, hsw, hsw + max_hdiff); - } - // s - if (h0_se > hse || h0_sw > hsw) { - const sint8 hs = min( hse, hsw ) + max_hdiff; - digger.add_lower_node( x, y + 1, hs, hs, hse, hsw); - } - // se - if (h0_se > hse) { - digger.add_lower_node( x + 1, y + 1, hse + max_hdiff, hse + max_hdiff, hse + max_hdiff, hse); - } - // e - if (h0_se > hse || h0_ne > hne) { - const sint8 he = max( hse, hne ) + max_hdiff; - digger.add_lower_node( x + 1,y, hse, he, he, hne); - } - // ne - if (h0_ne > hne) { - digger.add_lower_node( x + 1, y - 1, hne, hne + max_hdiff, hne + max_hdiff, hne + max_hdiff); - } - // n - if (h0_nw > hnw || h0_ne > hne) { - const sint8 hn = min( hnw, hne ) + max_hdiff; - digger.add_lower_node( x, y - 1, hnw, hne, hn, hn); - } - // nw - if (h0_nw > hnw) { - digger.add_lower_node( x - 1, y - 1, hnw + max_hdiff, hnw, hnw + max_hdiff, hnw + max_hdiff); - } - // w - if (h0_nw > hnw || h0_sw > hsw) { - const sint8 hw = min( hnw, hsw ) + max_hdiff; - digger.add_lower_node( x - 1, y, hw, hsw, hnw, hw); - } -} - -// lower plan -// new heights for each corner given -// only test corners in ctest to avoid infinite loops -const char* karte_t::can_lower_to(const player_t* player, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) const -{ - assert(is_within_limits(x,y)); - - const sint8 hneu = min( min( hsw, hse ), min( hne, hnw ) ); - - if( hneu < get_minimumheight() ) { - return "Maximum tile height difference reached."; - } - - // water heights - // check if need to lower water height for higher neighbouring tiles - for( sint16 i = 0 ; i < 8 ; i++ ) { - const koord neighbour = koord( x, y ) + koord::neighbours[i]; - if( is_within_limits(neighbour) && get_water_hgt_nocheck(neighbour) > hneu ) { - if (!is_plan_height_changeable( neighbour.x, neighbour.y )) { - return ""; - } - } - } - - return can_lower_plan_to(player, x, y, hneu ); -} - - -int karte_t::lower_to(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) -{ - int n=0; - assert(is_within_limits(x,y)); - grund_t *gr = lookup_kartenboden_nocheck(x,y); - sint8 water_hgt = get_water_hgt_nocheck(x,y); - const sint8 h0 = gr->get_hoehe(); - // old height - const sint8 h0_sw = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x,y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); - const sint8 h0_se = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x+1,y+1) ) : h0 + corner_se( gr->get_grund_hang() ); - const sint8 h0_ne = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x+1,y) ) : h0 + corner_ne( gr->get_grund_hang() ); - const sint8 h0_nw = gr->is_water() ? min( water_hgt, lookup_hgt_nocheck(x,y) ) : h0 + corner_nw( gr->get_grund_hang() ); - // new height - const sint8 hn_sw = min(hsw, h0_sw); - const sint8 hn_se = min(hse, h0_se); - const sint8 hn_ne = min(hne, h0_ne); - const sint8 hn_nw = min(hnw, h0_nw); - // nothing to do? - if( gr->is_water() ) { - if( h0_nw <= hnw ) { - return 0; - } - } - else { - if( h0_sw <= hsw && h0_se <= hse && h0_ne <= hne && h0_nw <= hnw ) { - return 0; - } - } - - // calc new height and slope - const sint8 hneu = min( min( hn_sw, hn_se ), min( hn_ne, hn_nw ) ); - const sint8 hmaxneu = max( max( hn_sw, hn_se ), max( hn_ne, hn_nw ) ); - - if( hneu >= water_hgt ) { - // calculate water table from surrounding tiles - start off with height on this tile - sint8 water_table = water_hgt >= h0 ? water_hgt : groundwater - 4; - - /* we test each corner in turn to see whether it is at the base height of the tile - if it is we then mark the 3 surrounding tiles for that corner for checking - surrounding tiles are indicated by bits going anti-clockwise from - (binary) 00000001 for north-west through to (binary) 10000000 for north - as this is the order of directions used by koord::neighbours[] */ - - uint8 neighbour_flags = 0; - - if( hn_nw == hneu ) { - neighbour_flags |= 0x83; - } - if( hn_ne == hneu ) { - neighbour_flags |= 0xe0; - } - if( hn_se == hneu ) { - neighbour_flags |= 0x38; - } - if( hn_sw == hneu ) { - neighbour_flags |= 0x0e; - } - - for( sint16 i = 0; i < 8 ; i++ ) { - const koord neighbour = koord( x, y ) + koord::neighbours[i]; - - // here we look at the bit in neighbour_flags for this direction - // we shift it i bits to the right and test the least significant bit - - if( is_within_limits( neighbour ) && ((neighbour_flags >> i) & 1) ) { - grund_t *gr2 = lookup_kartenboden_nocheck( neighbour ); - const sint8 water_hgt_neighbour = get_water_hgt_nocheck( neighbour ); - if( gr2 && (water_hgt_neighbour >= gr2->get_hoehe()) && water_hgt_neighbour <= hneu ) { - water_table = max( water_table, water_hgt_neighbour ); - } - } - } - - for( sint16 i = 0; i < 8 ; i++ ) { - const koord neighbour = koord( x, y ) + koord::neighbours[i]; - if( is_within_limits( neighbour ) ) { - grund_t *gr2 = lookup_kartenboden_nocheck( neighbour ); - if( gr2 && gr2->get_hoehe() < water_table ) { - i = 8; - water_table = groundwater - 4; - } - } - } - - // only allow water table to be lowered (except for case of sea level) - // this prevents severe (errors! - if( water_table < get_water_hgt_nocheck(x,y) ) { - water_hgt = water_table; - set_water_hgt(x, y, water_table ); - } - } - - // calc new height and slope - const sint8 disp_hneu = max( hneu, water_hgt ); - const sint8 disp_hn_sw = max( hn_sw, water_hgt ); - const sint8 disp_hn_se = max( hn_se, water_hgt ); - const sint8 disp_hn_ne = max( hn_ne, water_hgt ); - const sint8 disp_hn_nw = max( hn_nw, water_hgt ); - const slope_t::type sneu = encode_corners(disp_hn_sw - disp_hneu, disp_hn_se - disp_hneu, disp_hn_ne - disp_hneu, disp_hn_nw - disp_hneu); - - // change height and slope for land tiles only - if( !gr->is_water() || (hmaxneu > water_hgt) ) { - gr->set_pos( koord3d( x, y, disp_hneu ) ); - gr->set_grund_hang( (slope_t::type)sneu ); - access_nocheck(x,y)->abgesenkt(); - } - // update north point in grid - set_grid_hgt(x, y, hn_nw); - if ( x == cached_size.x ) { - // update eastern grid coordinates too if we are in the edge. - set_grid_hgt(x+1, y, hn_ne); - set_grid_hgt(x+1, y+1, hn_se); - } - if ( y == cached_size.y ) { - // update southern grid coordinates too if we are in the edge. - set_grid_hgt(x, y+1, hn_sw); - set_grid_hgt(x+1, y+1, hn_se); - } - - // water heights - // lower water height for higher neighbouring tiles - // find out how high water is - for( sint16 i = 0; i < 8; i++ ) { - const koord neighbour = koord( x, y ) + koord::neighbours[i]; - if( is_within_limits( neighbour ) ) { - const sint8 water_hgt_neighbour = get_water_hgt_nocheck( neighbour ); - if(water_hgt_neighbour > hneu ) { - if( min_hgt_nocheck( neighbour ) < water_hgt_neighbour ) { - // convert to flat ground before lowering water level - raise_grid_to( neighbour.x, neighbour.y, water_hgt_neighbour ); - raise_grid_to( neighbour.x + 1, neighbour.y, water_hgt_neighbour ); - raise_grid_to( neighbour.x, neighbour.y + 1, water_hgt_neighbour ); - raise_grid_to( neighbour.x + 1, neighbour.y + 1, water_hgt_neighbour ); - } - set_water_hgt( neighbour, hneu ); - access_nocheck(neighbour)->correct_water(); - } - } - } - - calc_climate( koord( x, y ), false ); - for( sint16 i = 0; i < 8; i++ ) { - const koord neighbour = koord( x, y ) + koord::neighbours[i]; - calc_climate( neighbour, false ); - } - - // recalc landscape images - need to extend 2 in each direction - for( sint16 j = y - 2; j <= y + 2; j++ ) { - for( sint16 i = x - 2; i <= x + 2; i++ ) { - if( is_within_limits( i, j ) /*&& (i != x || j != y)*/ ) { - recalc_transitions( koord (i, j ) ); - } - } - } - - n += h0_sw-hn_sw + h0_se-hn_se + h0_ne-hn_ne + h0_nw-hn_nw; - - lookup_kartenboden_nocheck(x,y)->calc_image(); - if( (x+1) < cached_size.x ) { - lookup_kartenboden_nocheck(x+1,y)->calc_image(); - } - if( (y+1) < cached_size.y ) { - lookup_kartenboden_nocheck(x,y+1)->calc_image(); - } - return n; -} - void karte_t::lower_grid_to(sint16 x, sint16 y, sint8 h) { @@ -2931,16 +2334,16 @@ int karte_t::grid_lower(const player_t *player, koord k, const char*&err) const sint8 hne = hgt + o - scorner_ne( corner_to_lower ) * f; const sint8 hnw = hgt + o - scorner_nw( corner_to_lower ) * f; - terraformer_t digger(this); - digger.add_lower_node(x, y, hsw, hse, hne, hnw); - digger.iterate(false); + terraformer_t digger(this, terraformer_t::lower); + digger.add_node(x, y, hsw, hse, hne, hnw); + digger.generate_affected_tile_list(); err = digger.can_lower_all(player); if (err) { return 0; } - n = digger.lower_all(); + n = digger.apply(); err = NULL; // force world full redraw, or background could be dirty. @@ -2971,31 +2374,32 @@ bool karte_t::flatten_tile(player_t *player, koord k, sint8 hgt, bool keep_water const sint8 max_hgt = old_hgt + slope_t::max_diff(slope); if( max_hgt > hgt ) { - terraformer_t digger(this); - digger.add_lower_node(k.x, k.y, hgt, hgt, hgt, hgt); - digger.iterate(false); + terraformer_t digger(this, terraformer_t::lower); + digger.add_node(k.x, k.y, hgt, hgt, hgt, hgt); + digger.generate_affected_tile_list(); ok = digger.can_lower_all(player) == NULL; if (ok && !justcheck) { - n += digger.lower_all(); + n += digger.apply(); } } - if( ok && old_hgt < hgt ) { - terraformer_t digger(this); - digger.add_raise_node(k.x, k.y, hgt, hgt, hgt, hgt); - digger.iterate(true); + if( ok && old_hgt < hgt ) { + terraformer_t digger(this, terraformer_t::raise); + digger.add_node(k.x, k.y, hgt, hgt, hgt, hgt); + digger.generate_affected_tile_list(); ok = digger.can_raise_all(player, keep_water) == NULL; if (ok && !justcheck) { - n += digger.raise_all(); + n += digger.apply(); } } + // was changed => pay for it if(n>0) { - n = (n+3) >> 2; + n = (n+3) / 4; player_t::book_construction_costs(player, n * settings.cst_alter_land, k, ignore_wt); } return ok; diff --git simworld.h simworld.h index a73ce853a..610e8ba0f 100644 --- simworld.h +++ simworld.h @@ -50,6 +50,7 @@ class memory_rw_t; class viewport_t; class records_t; class loadingscreen_t; +class terraformer_t; /** @@ -318,55 +319,7 @@ private: */ interaction_t *eventmanager; - /** - * Checks whether the heights of the corners of the tile at (@p x, @p y) can be raised. - * If the desired height of a corner is lower than its current height, this corner is ignored. - * @param player player who wants to lower - * @param x coordinate - * @param y coordinate - * @param keep_water returns false if water tiles would be raised above water - * @param hsw desired height of sw-corner - * @param hse desired height of se-corner - * @param hne desired height of ne-corner - * @param hnw desired height of nw-corner - * @returns NULL if raise_to operation can be performed, an error message otherwise - */ - const char* can_raise_to(const player_t* player, sint16 x, sint16 y, bool keep_water, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) const; - - /** - * Raises heights of the corners of the tile at (@p x, @p y). - * New heights for each corner given. - * @pre can_raise_to should be called before this method. - * @see can_raise_to - * @returns count of full raise operations (4 corners raised one level) - * @note Clear tile, reset water/land type, calc minimap pixel. - */ - int raise_to(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); - - /** - * Checks whether the heights of the corners of the tile at (@p x, @p y) can be lowered. - * If the desired height of a corner is higher than its current height, this corner is ignored. - * @param player player who wants to lower - * @param x coordinate - * @param y coordinate - * @param hsw desired height of sw-corner - * @param hse desired height of se-corner - * @param hne desired height of ne-corner - * @param hnw desired height of nw-corner - * @returns NULL if lower_to operation can be performed, an error message otherwise - */ - const char* can_lower_to(const player_t* player, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) const; - - /** - * Lowers heights of the corners of the tile at (@p x, @p y). - * New heights for each corner given. - * @pre can_lower_to should be called before this method. - * @see can_lower_to - * @returns count of full lower operations (4 corners lowered one level) - * @note Clear tile, reset water/land type, calc minimap pixel. - */ - int lower_to(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); - +public: /** * Raise grid point (@p x,@p y). Changes grid_hgts only, used during map creation/enlargement. * @see clean_up @@ -379,6 +332,7 @@ private: */ void lower_grid_to(sint16 x, sint16 y, sint8 h); +private: /** * The fractal generation of the map is not perfect. * cleanup_karte() eliminates errors. @@ -1174,14 +1128,17 @@ public: const char* call_work(tool_t *t, player_t *pl, koord3d pos, bool &suspended); /** - * Returns the (x,y) map size. - * @brief Map size. + * Returns the size in tiles of the map. * @note Valid coords are (0..x-1,0..y-1) * @note These values are exactly one less then get_grid_size ones. * @see get_grid_size() */ inline koord const &get_size() const { return cached_grid_size; } + /// Returns the maximum possible index when accessing tiles. + /// Valid tiles are in the range (0..x, 0..y) + inline koord get_max_tile_index() const { return cached_size; } + /** * Maximum size for waiting bars etc. */ @@ -1327,7 +1284,7 @@ public: } -private: +public: /** * @return grund at the bottom (where house will be build) * @note Inline because called very frequently! - nocheck for more speed @@ -1382,18 +1339,6 @@ public: */ uint32 load_version; - /** - * Checks if the planquadrat (tile) at coordinate (x,y) - * can be lowered at the specified height. - */ - const char* can_lower_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const; - - /** - * Checks if the planquadrat (tile) at coordinate (x,y) - * can be raised at the specified height. - */ - const char* can_raise_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const; - /** * Checks if the whole planquadrat (tile) at coordinates (x,y) height can * be changed ( for example, water height can't be changed ). @@ -1416,72 +1361,6 @@ public: bool can_flatten_tile(player_t *player, koord k, sint8 hgt, bool keep_water=false, bool make_underwater_hill=false); bool flatten_tile(player_t *player, koord k, sint8 hgt, bool keep_water=false, bool make_underwater_hill=false, bool justcheck=false); - /** - * Class to manage terraform operations. - * Can be used for raise only or lower only operations, but not mixed. - */ - class terraformer_t { - /// Structure to save terraforming operations - struct node_t { - sint16 x; ///< x-coordinate - sint16 y; ///< y-coordinate - sint8 h[4]; ///< height of corners, order: sw se ne nw - uint8 changed; - - node_t(sint16 x_, sint16 y_, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw, uint8 c) - : x(x_), y(y_), changed(c) { h[0]=hsw; h[1]=hse; h[2]=hne; h[3]=hnw; } - - node_t() : x(-1), y(-1), changed(0) {} - - /// compares position - bool operator== (const node_t& a) const { return (a.x==x) && (a.y==y); } - - /// compares position - static bool comp(const node_t& a, const node_t& b); - }; - - vector_tpl list; ///< list of affected tiles - uint8 actual_flag; ///< internal flag to iterate through list - bool ready; ///< internal flag to signal iteration ready - karte_t* welt; - - void add_node(bool raise, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); - public: - terraformer_t(karte_t* w) { init(); welt = w; } - - void init() { list.clear(); actual_flag = 1; ready = false; } - - /** - * Add tile to be raised. - */ - void add_raise_node(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); - - /** - * Add tile to be lowered. - */ - void add_lower_node(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); - - /** - * Generate list of all tiles that will be affected. - */ - void iterate(bool raise); - - /// Check whether raise operation would succeed - const char* can_raise_all(const player_t *player, bool keep_water=false) const; - /// Check whether lower operation would succeed - const char* can_lower_all(const player_t *player) const; - - /// Do the raise operations - int raise_all(); - /// Do the lower operations - int lower_all(); - }; - -private: - /// Internal functions to be used with terraformer_t to propagate terrain changes to neighbouring tiles - void prepare_raise(terraformer_t& digger, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); - void prepare_lower(terraformer_t& digger, sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); - public: // the convois are also handled each step => thus we keep track of them too @@ -1566,7 +1445,7 @@ public: */ void step(); -private: +public: inline planquadrat_t *access_nocheck(int i, int j) const { return &plan[i + j*cached_grid_size.x]; } @@ -1580,7 +1459,7 @@ public: inline planquadrat_t *access(koord k) const { return access(k.x, k.y); } -private: +public: /** * @return Height at the grid point i, j - versions without checks for speed */ @@ -1608,9 +1487,10 @@ public: inline void set_grid_hgt(koord k, sint8 hgt) { set_grid_hgt(k.x, k.y, hgt); } +public: void get_height_slope_from_grid(koord k, sint8 &hgt, slope_t::type &slope); - -private: + +public: /** * @return water height - versions without checks for speed */ @@ -1688,7 +1568,7 @@ public: */ void cleanup_grounds_loop(sint16, sint16, sint16, sint16); -private: +public: /** * @return Minimum height of the planquadrats (tile) at i, j. - for speed no checks performed that coordinates are valid */ diff --git world/terraformer.cc world/terraformer.cc new file mode 100644 index 000000000..4d04c89f9 --- /dev/null +++ world/terraformer.cc @@ -0,0 +1,685 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#include "terraformer.h" + +#include "../dataobj/scenario.h" +#include "../descriptor/ground_desc.h" +#include "../simmenu.h" +#include "../simworld.h" + + +terraformer_t::node_t::node_t() : + x(-1), + y(-1), + changed(0) +{ +} + + +terraformer_t::node_t::node_t(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw, uint8 c) : + x(x), + y(y), + hsw(hsw), + hse(hse), + hne(hne), + hnw(hnw), + changed(c) +{ +} + + +bool terraformer_t::node_t::operator==(const terraformer_t::node_t &a) const +{ + return (a.x==x) && (a.y==y); +} + + +terraformer_t::terraformer_t(karte_t *w, operation_t op) : + actual_flag(1), + ready(false), + op(op), + welt(w) +{ +} + + +bool terraformer_t::node_t::comp(const terraformer_t::node_t &a, const terraformer_t::node_t &b) +{ + int diff = a.x - b.x; + if (diff == 0) { + diff = a.y - b.y; + } + return diff<0; +} + + +void terraformer_t::add_node(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw) +{ + if (!welt->is_within_limits(x,y)) { + return; + } + + const node_t test(x, y, hsw, hse, hne, hnw, actual_flag^3); + node_t *other = list.insert_unique_ordered(test, node_t::comp); + + const sint8 factor = (op == terraformer_t::raise) ? +1 : -1; + + if (other) { + if (factor*other->hsw < factor*test.hsw) { + other->hsw = test.hsw; + other->changed |= actual_flag ^ 3; + ready = false; + } + + if (factor*other->hse < factor*test.hse) { + other->hse = test.hse; + other->changed |= actual_flag ^ 3; + ready = false; + } + + if (factor*other->hne < factor*test.hne) { + other->hne = test.hne; + other->changed |= actual_flag ^ 3; + ready = false; + } + + if (factor*other->hnw < factor*test.hnw) { + other->hnw = test.hnw; + other->changed |= actual_flag ^ 3; + ready = false; + } + } + else { + ready = false; + } +} + + +void terraformer_t::generate_affected_tile_list() +{ + while( !ready) { + actual_flag ^= 3; // flip bits + // clear new_flag bit + FOR(vector_tpl, &i, list) { + i.changed &= actual_flag; + } + + // process nodes with actual_flag set + ready = true; + for(uint32 j=0; j < list.get_count(); j++) { + node_t& i = list[j]; + if (i.changed & actual_flag) { + i.changed &= ~actual_flag; + if (op == terraformer_t::raise) { + prepare_raise(i); + } + else { + prepare_lower(i); + } + } + } + } +} + + +const char *terraformer_t::can_raise_all(const player_t *player, bool keep_water) const +{ + assert(op == terraformer_t::raise); + assert(ready); + + FOR(vector_tpl, const &i, list) { + if (const char *err = can_raise_tile_to(i, player, keep_water)) { + return err; + } + } + return NULL; +} + + +const char *terraformer_t::can_lower_all(const player_t *player) const +{ + assert(op == terraformer_t::lower); + assert(ready); + + FOR(vector_tpl, const &i, list) { + if (const char *err = can_lower_tile_to(i, player)) { + return err; + } + } + + return NULL; +} + + +int terraformer_t::apply() +{ + assert(ready); + int n = 0; + + if (op == terraformer_t::raise) { + FOR(vector_tpl, const &i, list) { + n += raise_to(i); + } + } + else { + FOR(vector_tpl, const &i, list) { + n += lower_to(i); + } + } + + return n; +} + + +void terraformer_t::prepare_raise(const node_t node) +{ + assert(welt->is_within_limits(node.x,node.y)); + + const grund_t *gr = welt->lookup_kartenboden_nocheck(node.x,node.y); + const sint8 water_hgt = welt->get_water_hgt_nocheck(node.x,node.y); + const sint8 h0 = gr->get_hoehe(); + + // old height + const sint8 h0_sw = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x, node.y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); + const sint8 h0_se = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x+1,node.y+1) ) : h0 + corner_se( gr->get_grund_hang() ); + const sint8 h0_ne = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x+1,node.y ) ) : h0 + corner_ne( gr->get_grund_hang() ); + const sint8 h0_nw = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x, node.y ) ) : h0 + corner_nw( gr->get_grund_hang() ); + + // new height + const sint8 hn_sw = max(node.hsw, h0_sw); + const sint8 hn_se = max(node.hse, h0_se); + const sint8 hn_ne = max(node.hne, h0_ne); + const sint8 hn_nw = max(node.hnw, h0_nw); + + // nothing to do? + if( !gr->is_water() && h0_sw >= node.hsw && h0_se >= node.hse && h0_ne >= node.hne && h0_nw >= node.hnw ) { + return; + } + + // calc new height and slope + const sint8 hneu = min( min( hn_sw, hn_se ), min( hn_ne, hn_nw ) ); + const sint8 hmaxneu = max( max( hn_sw, hn_se ), max( hn_ne, hn_nw ) ); + + const uint8 max_hdiff = ground_desc_t::double_grounds ? 2 : 1; + + const bool ok = (hmaxneu - hneu <= max_hdiff); // may fail on water tiles since lookup_hgt might be modified from previous raise_to calls + if( !ok && !gr->is_water() ) { + assert(false); + } + + // sw + if (h0_sw < node.hsw) { + add_node( node.x - 1, node.y + 1, node.hsw - max_hdiff, node.hsw - max_hdiff, node.hsw, node.hsw - max_hdiff ); + } + // s + if (h0_sw < node.hsw || h0_se < node.hse) { + const sint8 hs = max( node.hse, node.hsw ) - max_hdiff; + add_node( node.x, node.y + 1, hs, hs, node.hse, node.hsw ); + } + // se + if (h0_se < node.hse) { + add_node( node.x + 1, node.y + 1, node.hse - max_hdiff, node.hse - max_hdiff, node.hse - max_hdiff, node.hse ); + } + // e + if (h0_se < node.hse || h0_ne < node.hne) { + const sint8 he = max( node.hse, node.hne ) - max_hdiff; + add_node( node.x + 1, node.y, node.hse, he, he, node.hne ); + } + // ne + if (h0_ne < node.hne) { + add_node( node.x + 1,node.y - 1, node.hne, node.hne - max_hdiff, node.hne - max_hdiff, node.hne - max_hdiff ); + } + // n + if (h0_nw < node.hnw || h0_ne < node.hne) { + const sint8 hn = max( node.hnw, node.hne ) - max_hdiff; + add_node( node.x, node.y - 1, node.hnw, node.hne, hn, hn ); + } + // nw + if (h0_nw < node.hnw) { + add_node( node.x - 1, node.y - 1, node.hnw - max_hdiff, node.hnw, node.hnw - max_hdiff, node.hnw - max_hdiff ); + } + // w + if (h0_sw < node.hsw || h0_nw < node.hnw) { + const sint8 hw = max( node.hnw, node.hsw ) - max_hdiff; + add_node( node.x - 1, node.y, hw, node.hsw, node.hnw, hw ); + } +} + + +void terraformer_t::prepare_lower(const node_t node) +{ + assert(welt->is_within_limits(node.x,node.y)); + const grund_t *gr = welt->lookup_kartenboden_nocheck(node.x,node.y); + const sint8 water_hgt = welt->get_water_hgt_nocheck(node.x,node.y); + + const sint8 h0 = gr->get_hoehe(); + + // which corners have to be raised? + const sint8 h0_sw = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x, node.y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); + const sint8 h0_se = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x+1,node.y+1) ) : h0 + corner_se( gr->get_grund_hang() ); + const sint8 h0_ne = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x, node.y+1) ) : h0 + corner_ne( gr->get_grund_hang() ); + const sint8 h0_nw = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x, node.y ) ) : h0 + corner_nw( gr->get_grund_hang() ); + + const uint8 max_hdiff = ground_desc_t::double_grounds ? 2 : 1; + + // sw + if (h0_sw > node.hsw) { + add_node(node.x - 1, node.y + 1, node.hsw + max_hdiff, node.hsw + max_hdiff, node.hsw, node.hsw + max_hdiff); + } + // s + if (h0_se > node.hse || h0_sw > node.hsw) { + const sint8 hs = min( node.hse, node.hsw ) + max_hdiff; + add_node( node.x, node.y + 1, hs, hs, node.hse, node.hsw); + } + // se + if (h0_se > node.hse) { + add_node( node.x + 1, node.y + 1, node.hse + max_hdiff, node.hse + max_hdiff, node.hse + max_hdiff, node.hse); + } + // e + if (h0_se > node.hse || h0_ne > node.hne) { + const sint8 he = max( node.hse, node.hne ) + max_hdiff; + add_node( node.x + 1, node.y, node.hse, he, he, node.hne); + } + // ne + if (h0_ne > node.hne) { + add_node( node.x + 1, node.y - 1, node.hne, node.hne + max_hdiff, node.hne + max_hdiff, node.hne + max_hdiff); + } + // n + if (h0_nw > node.hnw || h0_ne > node.hne) { + const sint8 hn = min( node.hnw, node.hne ) + max_hdiff; + add_node( node.x, node.y - 1, node.hnw, node.hne, hn, hn); + } + // nw + if (h0_nw > node.hnw) { + add_node( node.x - 1, node.y - 1, node.hnw + max_hdiff, node.hnw, node.hnw + max_hdiff, node.hnw + max_hdiff); + } + // w + if (h0_nw > node.hnw || h0_sw > node.hsw) { + const sint8 hw = min( node.hnw, node.hsw ) + max_hdiff; + add_node( node.x - 1, node.y, hw, node.hsw, node.hnw, hw); + } +} + + +const char *terraformer_t::can_raise_tile_to(const node_t &node, const player_t *player, bool keep_water) const +{ + assert(welt->is_within_limits(node.x,node.y)); + + const grund_t *gr = welt->lookup_kartenboden_nocheck(node.x,node.y); + const sint8 water_hgt = welt->get_water_hgt_nocheck(node.x,node.y); + + const sint8 max_hgt = max(max(node.hsw,node.hse),max(node.hne,node.hnw)); + + if( gr->is_water() && keep_water && max_hgt > water_hgt ) { + return ""; + } + + return can_raise_plan_to(player, node.x, node.y, max_hgt); +} + + +// lower plan +// new heights for each corner given +const char* terraformer_t::can_lower_tile_to(const node_t &node, const player_t *player) const +{ + assert(welt->is_within_limits(node.x,node.y)); + + const sint8 hneu = min( min( node.hsw, node.hse ), min( node.hne, node.hnw ) ); + + if( hneu < welt->get_minimumheight() ) { + return "Maximum tile height difference reached."; + } + + // water heights + // check if need to lower water height for higher neighbouring tiles + for( sint16 i = 0 ; i < 8 ; i++ ) { + const koord neighbour = koord( node.x, node.y ) + koord::neighbours[i]; + if( welt->is_within_limits(neighbour) && welt->get_water_hgt_nocheck(neighbour) > hneu ) { + if (!welt->is_plan_height_changeable( neighbour.x, neighbour.y )) { + return ""; + } + } + } + + return can_lower_plan_to(player, node.x, node.y, hneu ); +} + + +int terraformer_t::raise_to(const node_t &node) +{ + assert(welt->is_within_limits(node.x,node.y)); + + int n = 0; + grund_t *gr = welt->lookup_kartenboden_nocheck(node.x,node.y); + const sint8 water_hgt = welt->get_water_hgt_nocheck(node.x,node.y); + const sint8 h0 = gr->get_hoehe(); + + // old height + const sint8 h0_sw = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x, node.y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); + const sint8 h0_se = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x+1, node.y+1) ) : h0 + corner_se( gr->get_grund_hang() ); + const sint8 h0_ne = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x+1, node.y ) ) : h0 + corner_ne( gr->get_grund_hang() ); + const sint8 h0_nw = gr->is_water() ? min(water_hgt, welt->lookup_hgt_nocheck(node.x, node.y ) ) : h0 + corner_nw( gr->get_grund_hang() ); + + // new height + const sint8 hn_sw = max(node.hsw, h0_sw); + const sint8 hn_se = max(node.hse, h0_se); + const sint8 hn_ne = max(node.hne, h0_ne); + const sint8 hn_nw = max(node.hnw, h0_nw); + + // nothing to do? + if( !gr->is_water() && h0_sw >= node.hsw && h0_se >= node.hse && h0_ne >= node.hne && h0_nw >= node.hnw ) { + return 0; + } + + // calc new height and slope + const sint8 hneu = min( min( hn_sw, hn_se ), min( hn_ne, hn_nw ) ); + const sint8 hmaxneu = max( max( hn_sw, hn_se ), max( hn_ne, hn_nw ) ); + + const uint8 max_hdiff = ground_desc_t::double_grounds ? 2 : 1; + const sint8 disp_hneu = max( hneu, water_hgt ); + const sint8 disp_hn_sw = max( hn_sw, water_hgt ); + const sint8 disp_hn_se = max( hn_se, water_hgt ); + const sint8 disp_hn_ne = max( hn_ne, water_hgt ); + const sint8 disp_hn_nw = max( hn_nw, water_hgt ); + const slope_t::type sneu = encode_corners(disp_hn_sw - disp_hneu, disp_hn_se - disp_hneu, disp_hn_ne - disp_hneu, disp_hn_nw - disp_hneu); + + const bool ok = (hmaxneu - hneu <= max_hdiff); // may fail on water tiles since lookup_hgt might be modified from previous raise_to calls + if (!ok && !gr->is_water()) { + assert(false); + } + + // change height and slope, for water tiles only if they will become land + if( !gr->is_water() || (hmaxneu > water_hgt || (hneu == water_hgt && hmaxneu == water_hgt) ) ) { + gr->set_pos( koord3d( node.x, node.y, disp_hneu ) ); + gr->set_grund_hang( sneu ); + welt->access_nocheck(node.x,node.y)->angehoben(); + welt->set_water_hgt(node.x, node.y, welt->get_groundwater()-4); + } + + // update north point in grid + welt->set_grid_hgt(node.x, node.y, hn_nw); + welt->calc_climate(koord(node.x,node.y), true); + + if ( node.x == welt->get_max_tile_index().x ) { + // update eastern grid coordinates too if we are in the edge. + welt->set_grid_hgt(node.x+1, node.y, hn_ne); + welt->set_grid_hgt(node.x+1, node.y+1, hn_se); + } + + if ( node.y == welt->get_max_tile_index().y ) { + // update southern grid coordinates too if we are in the edge. + welt->set_grid_hgt(node.x, node.y+1, hn_sw); + welt->set_grid_hgt(node.x+1, node.y+1, hn_se); + } + + n += hn_sw - h0_sw + hn_se - h0_se + hn_ne - h0_ne + hn_nw - h0_nw; + + welt->lookup_kartenboden_nocheck(node.x,node.y)->calc_image(); + + if ( (node.x+1) < welt->get_max_tile_index().x ) { + welt->lookup_kartenboden_nocheck(node.x+1,node.y)->calc_image(); + } + + if ( (node.y+1) < welt->get_max_tile_index().y ) { + welt->lookup_kartenboden_nocheck(node.x,node.y+1)->calc_image(); + } + + return n; +} + + +int terraformer_t::lower_to(const node_t &node) +{ + assert(welt->is_within_limits(node.x,node.y)); + + int n = 0; + grund_t *gr = welt->lookup_kartenboden_nocheck(node.x,node.y); + sint8 water_hgt = welt->get_water_hgt_nocheck(node.x,node.y); + const sint8 h0 = gr->get_hoehe(); + + // old height + const sint8 h0_sw = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x, node.y+1) ) : h0 + corner_sw( gr->get_grund_hang() ); + const sint8 h0_se = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x+1, node.y+1) ) : h0 + corner_se( gr->get_grund_hang() ); + const sint8 h0_ne = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x+1, node.y ) ) : h0 + corner_ne( gr->get_grund_hang() ); + const sint8 h0_nw = gr->is_water() ? min( water_hgt, welt->lookup_hgt_nocheck(node.x, node.y ) ) : h0 + corner_nw( gr->get_grund_hang() ); + + // new height + const sint8 hn_sw = min(node.hsw, h0_sw); + const sint8 hn_se = min(node.hse, h0_se); + const sint8 hn_ne = min(node.hne, h0_ne); + const sint8 hn_nw = min(node.hnw, h0_nw); + + // nothing to do? + if( gr->is_water() ) { + if( h0_nw <= node.hnw ) { + return 0; + } + } + else if( h0_sw <= node.hsw && h0_se <= node.hse && h0_ne <= node.hne && h0_nw <= node.hnw ) { + return 0; + } + + // calc new height and slope + const sint8 hneu = min( min( hn_sw, hn_se ), min( hn_ne, hn_nw ) ); + const sint8 hmaxneu = max( max( hn_sw, hn_se ), max( hn_ne, hn_nw ) ); + + if( hneu >= water_hgt ) { + // calculate water table from surrounding tiles - start off with height on this tile + sint8 water_table = water_hgt >= h0 ? water_hgt : welt->get_groundwater() - 4; + + /* + * we test each corner in turn to see whether it is at the base height of the tile. + * If it is we then mark the 3 surrounding tiles for that corner for checking. + * Surrounding tiles are indicated by bits going anti-clockwise from + * (binary) 00000001 for north-west through to (binary) 10000000 for north + * as this is the order of directions used by koord::neighbours[] + */ + + uint8 neighbour_flags = 0; + + if( hn_nw == hneu ) { + neighbour_flags |= 0x83; + } + if( hn_ne == hneu ) { + neighbour_flags |= 0xe0; + } + if( hn_se == hneu ) { + neighbour_flags |= 0x38; + } + if( hn_sw == hneu ) { + neighbour_flags |= 0x0e; + } + + for( sint16 i = 0; i < 8 ; i++ ) { + const koord neighbour = koord( node.x, node.y ) + koord::neighbours[i]; + + // here we look at the bit in neighbour_flags for this direction + // we shift it i bits to the right and test the least significant bit + + if( welt->is_within_limits( neighbour ) && ((neighbour_flags >> i) & 1) ) { + grund_t *gr2 = welt->lookup_kartenboden_nocheck( neighbour ); + const sint8 water_hgt_neighbour = welt->get_water_hgt_nocheck( neighbour ); + if( gr2 && (water_hgt_neighbour >= gr2->get_hoehe()) && water_hgt_neighbour <= hneu ) { + water_table = max( water_table, water_hgt_neighbour ); + } + } + } + + for( sint16 i = 0; i < 8 ; i++ ) { + const koord neighbour = koord( node.x, node.y ) + koord::neighbours[i]; + if( welt->is_within_limits( neighbour ) ) { + grund_t *gr2 = welt->lookup_kartenboden_nocheck( neighbour ); + if( gr2 && gr2->get_hoehe() < water_table ) { + i = 8; + water_table = welt->get_groundwater() - 4; + } + } + } + + // only allow water table to be lowered (except for case of sea level) + // this prevents severe (errors! + if( water_table < welt->get_water_hgt_nocheck(node.x,node.y) ) { + water_hgt = water_table; + welt->set_water_hgt(node.x, node.y, water_table ); + } + } + + // calc new height and slope + const sint8 disp_hneu = max( hneu, water_hgt ); + const sint8 disp_hn_sw = max( hn_sw, water_hgt ); + const sint8 disp_hn_se = max( hn_se, water_hgt ); + const sint8 disp_hn_ne = max( hn_ne, water_hgt ); + const sint8 disp_hn_nw = max( hn_nw, water_hgt ); + const slope_t::type sneu = encode_corners(disp_hn_sw - disp_hneu, disp_hn_se - disp_hneu, disp_hn_ne - disp_hneu, disp_hn_nw - disp_hneu); + + // change height and slope for land tiles only + if( !gr->is_water() || (hmaxneu > water_hgt) ) { + gr->set_pos( koord3d( node.x, node.y, disp_hneu ) ); + gr->set_grund_hang( (slope_t::type)sneu ); + welt->access_nocheck(node.x,node.y)->abgesenkt(); + } + + // update north point in grid + welt->set_grid_hgt(node.x, node.y, hn_nw); + if ( node.x == welt->get_max_tile_index().x ) { + // update eastern grid coordinates too if we are in the edge. + welt->set_grid_hgt(node.x+1, node.y, hn_ne); + welt->set_grid_hgt(node.x+1, node.y+1, hn_se); + } + + if ( node.y == welt->get_max_tile_index().y ) { + // update southern grid coordinates too if we are in the edge. + welt->set_grid_hgt(node.x, node.y+1, hn_sw); + welt->set_grid_hgt(node.x+1, node.y+1, hn_se); + } + + // water heights + // lower water height for higher neighbouring tiles + // find out how high water is + for( sint16 i = 0; i < 8; i++ ) { + const koord neighbour = koord( node.x, node.y ) + koord::neighbours[i]; + if( welt->is_within_limits( neighbour ) ) { + const sint8 water_hgt_neighbour = welt->get_water_hgt_nocheck( neighbour ); + if(water_hgt_neighbour > hneu ) { + if( welt->min_hgt_nocheck( neighbour ) < water_hgt_neighbour ) { + // convert to flat ground before lowering water level + welt->raise_grid_to( neighbour.x, neighbour.y, water_hgt_neighbour ); + welt->raise_grid_to( neighbour.x + 1, neighbour.y, water_hgt_neighbour ); + welt->raise_grid_to( neighbour.x, neighbour.y + 1, water_hgt_neighbour ); + welt->raise_grid_to( neighbour.x + 1, neighbour.y + 1, water_hgt_neighbour ); + } + + welt->set_water_hgt( neighbour, hneu ); + welt->access_nocheck(neighbour)->correct_water(); + } + } + } + + welt->calc_climate( koord( node.x, node.y ), false ); + for( sint16 i = 0; i < 8; i++ ) { + const koord neighbour = koord( node.x, node.y ) + koord::neighbours[i]; + welt->calc_climate( neighbour, false ); + } + + // recalc landscape images - need to extend 2 in each direction + for( sint16 j = node.y - 2; j <= node.y + 2; j++ ) { + for( sint16 i = node.x - 2; i <= node.x + 2; i++ ) { + if( welt->is_within_limits( i, j ) /*&& (i != x || j != y)*/ ) { + welt->recalc_transitions( koord (i, j ) ); + } + } + } + + n += h0_sw-hn_sw + h0_se-hn_se + h0_ne-hn_ne + h0_nw-hn_nw; + + welt->lookup_kartenboden_nocheck(node.x,node.y)->calc_image(); + if( (node.x+1) < welt->get_max_tile_index().x ) { + welt->lookup_kartenboden_nocheck(node.x+1,node.y)->calc_image(); + } + + if( (node.y+1) < welt->get_max_tile_index().y ) { + welt->lookup_kartenboden_nocheck(node.x,node.y+1)->calc_image(); + } + + return n; +} + + +const char *terraformer_t::can_lower_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const +{ + const planquadrat_t *plan = welt->access(x,y); + + if( plan==NULL ) { + return ""; + } + + if( h < welt->get_groundwater() - 3 ) { + return ""; + } + + const sint8 hmax = plan->get_kartenboden()->get_hoehe(); + if( (hmax == h || hmax == h - 1) && (plan->get_kartenboden()->get_grund_hang() == 0 || welt->is_plan_height_changeable( x, y )) ) { + return NULL; + } + + if( !welt->is_plan_height_changeable(x, y) ) { + return ""; + } + + // tunnel slope below? + const grund_t *gr = plan->get_boden_in_hoehe( h - 1 ); + if( !gr ) { + gr = plan->get_boden_in_hoehe( h - 2 ); + } + + if( !gr && welt->get_settings().get_way_height_clearance()==2 ) { + gr = plan->get_boden_in_hoehe( h - 3 ); + } + + if( gr && h < gr->get_pos().z + slope_t::max_diff( gr->get_weg_hang() ) + welt->get_settings().get_way_height_clearance() ) { + return ""; + } + + // tunnel below? + while(h < hmax) { + if(plan->get_boden_in_hoehe(h)) { + return ""; + } + h ++; + } + + // check allowance by scenario + if (welt->get_scenario()->is_scripted()) { + return welt->get_scenario()->is_work_allowed_here(player, TOOL_LOWER_LAND|GENERAL_TOOL, ignore_wt, plan->get_kartenboden()->get_pos()); + } + + return NULL; +} + + +const char *terraformer_t::can_raise_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const +{ + const planquadrat_t *plan = welt->access(x,y); + if( plan == NULL || !welt->is_plan_height_changeable(x, y) ) { + return ""; + } + + // irgendwo eine Bruecke im Weg? + const sint8 hmin = plan->get_kartenboden()->get_hoehe(); + while(h > hmin) { + if(plan->get_boden_in_hoehe(h)) { + return ""; + } + h --; + } + + // check allowance by scenario + if (welt->get_scenario()->is_scripted()) { + return welt->get_scenario()->is_work_allowed_here(player, TOOL_RAISE_LAND|GENERAL_TOOL, ignore_wt, plan->get_kartenboden()->get_pos()); + } + + return NULL; +} diff --git world/terraformer.h world/terraformer.h new file mode 100644 index 000000000..885a5ca5c --- /dev/null +++ world/terraformer.h @@ -0,0 +1,143 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#ifndef WORLD_TERRAFORMER_H +#define WORLD_TERRAFORMER_H + + +#include "../simtypes.h" +#include "../tpl/vector_tpl.h" + + +class karte_t; +class player_t; + + +/** + * Class to manage terraform operations. + * Can be used for raise only or lower only operations, but not mixed. + * + * Usual order of calls: + * 1. add_node() (can be repeated) + * 2. generate_affected_tile_list() + * 3. can_raise_all() / can_lower_all() + * 4. apply() (to actually apply the changes to the terrain; optional) + */ +class terraformer_t +{ +public: + enum operation_t + { + lower = 0, + raise = 1 + }; + +private: + /// Structure to save terraforming operations + struct node_t + { + public: + node_t(); + node_t(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw, uint8 c); + + public: + /// compares position + bool operator==(const node_t &a) const; + + /// compares position + static bool comp(const node_t &a, const node_t &b); + + public: + sint16 x; ///< x-coordinate + sint16 y; ///< y-coordinate + + sint8 hsw, hse, hne, hnw; + + uint8 changed; + }; + +public: + terraformer_t(karte_t *world, terraformer_t::operation_t op); + +public: + /// Add tile to be raised/lowered. + void add_node(sint16 x, sint16 y, sint8 hsw, sint8 hse, sint8 hne, sint8 hnw); + + /// Generate list of all tiles that will be affected. + void generate_affected_tile_list(); + + /// Check whether raise operation would succeed + const char *can_raise_all(const player_t *player, bool keep_water = false) const; + + /// Check whether lower operation would succeed + const char *can_lower_all(const player_t *player) const; + + /// Do the raise/lower operations + int apply(); + +private: + /// Internal functions to be used with terraformer_t to propagate terrain changes to neighbouring tiles + void prepare_raise(const node_t node); + void prepare_lower(const node_t node); + + /** + * Checks whether the heights of the corners of the tile at (@p x, @p y) can be raised. + * If the desired height of a corner is lower than its current height, this corner is ignored. + * @param player player who wants to lower + * @param keep_water returns false if water tiles would be raised above water + * @returns NULL if raise_to operation can be performed, an error message otherwise + */ + const char *can_raise_tile_to(const node_t &node, const player_t *player, bool keep_water) const; + + /** + * Checks whether the heights of the corners of the tile at (@p x, @p y) can be lowered. + * If the desired height of a corner is higher than its current height, this corner is ignored. + * @param player player who wants to lower + * @returns NULL if lower_to operation can be performed, an error message otherwise + */ + const char *can_lower_tile_to(const node_t &node, const player_t *player) const; + + /** + * Raises heights of the corners of the tile at (@p x, @p y). + * New heights for each corner given. + * @pre can_raise_to should be called before this method. + * @see can_raise_to + * @returns count of full raise operations (4 corners raised one level) + * @note Clear tile, reset water/land type, calc minimap pixel. + */ + int raise_to(const node_t &node); + + /** + * Lowers heights of the corners of the tile at (@p x, @p y). + * New heights for each corner given. + * @pre can_lower_to should be called before this method. + * @see can_lower_to + * @returns count of full lower operations (4 corners lowered one level) + * @note Clear tile, reset water/land type, calc minimap pixel. + */ + int lower_to(const node_t &node); + + /** + * Checks if the planquadrat (tile) at coordinate (x,y) + * can be lowered at the specified height. + */ + const char *can_lower_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const; + + /** + * Checks if the planquadrat (tile) at coordinate (x,y) + * can be raised at the specified height. + */ + const char *can_raise_plan_to(const player_t *player, sint16 x, sint16 y, sint8 h) const; + +private: + vector_tpl list; ///< list of affected tiles + uint8 actual_flag; ///< internal flag to iterate through list + bool ready; ///< internal flag to signal that the affected tile list is updated + operation_t op; ///< Raise or lower? + karte_t *welt; +}; + + +#endif