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