Index: src/clear_cmd.cpp =================================================================== --- src/clear_cmd.cpp (revision 9602) +++ src/clear_cmd.cpp (working copy) @@ -23,6 +23,8 @@ #include "genworld.h" #include "industry.h" +#include "debug.h" + struct TerraformerHeightMod { TileIndex tile; byte height; @@ -349,67 +351,128 @@ return ts.cost; } +/** +* This is the inside of the for-loop through all tiles that CmdLevelLand() does. +* This first tests if there is enough money to level the selected tile, then levels it. +* Repeated until the desired height is reached. +* @bug money = GetAvailableMoneyForCommand(); returns old money value not updated value +*/ +int32 LevelLand(TileIndex tile, uint32 flags, uint h) +{ + int32 ret, money, cost; + cost = 0; + uint curh = TileHeight(tile); + money = GetAvailableMoneyForCommand(); + while (curh != h) { + ret = DoCommand(tile, 8, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND); + if (CmdFailed(ret))break; + cost += ret; + if (flags & DC_EXEC) { + DEBUG(misc,0,"money %d",money); + DEBUG(misc,0,"ret %d",ret); + if ((money -= ret) < 0) { + _additional_cash_required = ret; + return -cost + ret; + } + DoCommand(tile, 8, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND); + } + curh += (curh > h) ? -1 : 1; + } + return cost; +} -/** Levels a selected (rectangle) area of land +/** Levels a selected area of land * @param tile end tile of area-drag * @param flags for this command type * @param p1 start tile of area drag - * @param p2 unused + * @param p2 0 if leveling a normal rectangle, 1 if leveling a 45° rotated (diagonal) rectangle * @return error or cost of terraforming */ + int32 CmdLevelLand(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) { - int size_x, size_y; int ex; int ey; - int sx, sy; - uint h, curh; - int32 ret, cost, money; + int sx, sy; /* x,y coordinates of startpoint */ + int x,y; /* x,y coordinates of tile currently being cleared */ + uint h; + int32 cost, ret, money; if (p1 >= MapSize()) return CMD_ERROR; SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); - /* remember level height */ - h = TileHeight(p1); - - /* make sure sx,sy are smaller than ex,ey */ + cost = 0; + + /* make sure sx,sy are smaller than ex,ey */ ex = TileX(tile); ey = TileY(tile); sx = TileX(p1); sy = TileY(p1); - if (ex < sx) Swap(ex, sx); - if (ey < sy) Swap(ey, sy); - tile = TileXY(sx, sy); - size_x = ex-sx+1; - size_y = ey-sy+1; - + h = TileHeight(p1); money = GetAvailableMoneyForCommand(); - cost = 0; + if (!p2) { /* If (!p2) we are leveling in a simple rectangle */ + int size_x, size_y; + + /* make sure sx,sy are smaller than ex,ey */ + if (ex < sx) Swap(ex, sx); + if (ey < sy) Swap(ey, sy); - BEGIN_TILE_LOOP(tile2, size_x, size_y, tile) { - curh = TileHeight(tile2); - while (curh != h) { - ret = DoCommand(tile2, 8, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND); - if (CmdFailed(ret)) break; - cost += ret; + size_x = ex-sx+1; + size_y = ey-sy+1; + tile = TileXY(sx, sy); - if (flags & DC_EXEC) { - if ((money -= ret) < 0) { - _additional_cash_required = ret; - return cost - ret; + BEGIN_TILE_LOOP(tile2, size_x, size_y, tile) { + // DO_LEVEL_AREA + ret = LevelLand(tile2, flags, h); + if (ret >= 0) cost += ret; + else {return cost;} + + if(money < cost) + { + DEBUG(misc,0,"error %d",cost); } - DoCommand(tile2, 8, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND); + }END_TILE_LOOP(tile2, size_x, size_y, tile) + }else { /* If (p2) we are leveling in a 45° rotated rectangle. */ + int ix,iy; /* counters in for-loops */ + int dx = ex - sx; /* distance between begin and endpoints */ + int dy = ey - sy; + int fx = (dx + dy) / 2; /* x and y coordinate in rotated coordinate system (for diagonal clearing) */ + int fy = (dx - dy) / 2; + int x2,y2; + /* fx,fy form a diagonal coordinate system. + * fx counts the number of diagonals in 1 direction, fy in another. + * However with this method you only count half the squares. Compare with a checkboard with + * alternating black and white squares. You only count one color. + * That is why we clear two squares in every loop. The principal one and one square next to that + * To avoid leveling squares outside our drag-area we need some complex if's. + */ + for (ix = 0; ix <= abs(fx); ix += 1) { + for (iy = 0; iy <= abs(fy); iy += 1) { + x = sx + sign(fx) * ix + sign(fy) * iy; + y = sy + sign(fx) * ix - sign(fy) * iy; + x2 = x; y2 = y; + ret = LevelLand(TileXY(x,y), flags, h); + if (ret >= 0) cost += ret; + else return cost; + + if (abs(dx) > abs(dy) && ((ix != abs(fx) && iy != abs(fy)) || (dx + dy) % 2!=0)) + {if (ex >= sx) x += 1; else x -= 1;} + if (abs(dx) < abs(dy) && ((ix != abs(fx) && iy != abs(fy)) || (dx + dy) % 2!=0)) + {if (ey >= sy) y += 1; else y -= 1;} + + if (x!=x2 || y!=y2) {/* Do only when there's a change (to avoid screwing cost estimate) */ + ret = LevelLand(TileXY(x,y), flags, h); + if (ret >= 0) cost += ret; + else return cost; + } } - - curh += (curh > h) ? -1 : 1; } - } END_TILE_LOOP(tile2, size_x, size_y, tile) - - return (cost == 0) ? CMD_ERROR : cost; + } + DEBUG(misc,0,"cost %d",cost); + return (cost == 0) ? CMD_ERROR: cost; } - /** Purchase a land area. Actually you only purchase one tile, so * the name is a bit confusing ;p * @param tile the tile the player is purchasing Index: src/landscape.cpp =================================================================== --- src/landscape.cpp (revision 9602) +++ src/landscape.cpp (working copy) @@ -24,6 +24,8 @@ #include "genworld.h" #include "heightmap.h" +#include "debug.h" + extern const TileTypeProcs _tile_type_clear_procs, _tile_type_rail_procs, @@ -374,11 +376,36 @@ return _tile_type_procs[GetTileType(tile)]->clear_tile_proc(tile, flags); } +/* This #define statement is just here to save space in the function CmdClearArea() below. +* This is basicly the inside of the for-loop through all tiles that CmdClearArea() does. +* This first tests if there is enough money to clear the selected tile, then clears it. +*/ + #define DO_CLEAR_AREA { \ + ret = DoCommand(TileXY(x,y), 0, 0, flags &~DC_EXEC, CMD_LANDSCAPE_CLEAR); \ + if (CmdFailed(ret)) continue; \ + cost += ret; \ + success = true; \ + if (flags & DC_EXEC) { \ + DEBUG(misc,0,"%d",money); \ + if (ret > 0 && (money -= ret) < 0) { \ + _additional_cash_required = ret; \ + return cost - ret; \ + } \ + DoCommand(TileXY(x,y), 0, 0, flags, CMD_LANDSCAPE_CLEAR); \ + /* draw explosion animation...*/ \ + if ((x == sx || x == ex) && (y == sy || y == ey)) { \ + /* big explosion in each corner, or small explosion for single tiles*/ \ + CreateEffectVehicleAbove(x * TILE_SIZE + TILE_SIZE / 2 , y * TILE_SIZE + TILE_SIZE / 2, 2, \ + sy == ey && sx == ex ? EV_EXPLOSION_SMALL : EV_EXPLOSION_LARGE ); \ + } \ + }}; \ + /** Clear a big piece of landscape * @param tile end tile of area dragging + * @param flags for this command type * @param p1 start tile of area dragging - * @param flags of operation to conduct - * @param p2 unused + * @param p2 0 if leveling a normal rectangle, 1 if leveling a 45° rotated (diagonal) rectangle + * @return error or cost of terraforming */ int32 CmdClearArea(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) { @@ -398,37 +425,48 @@ ey = TileY(tile); sx = TileX(p1); sy = TileY(p1); - if (ex < sx) Swap(ex, sx); - if (ey < sy) Swap(ey, sy); - + cost = 0; money = GetAvailableMoneyForCommand(); - cost = 0; - for (x = sx; x <= ex; ++x) { - for (y = sy; y <= ey; ++y) { - ret = DoCommand(TileXY(x, y), 0, 0, flags & ~DC_EXEC, CMD_LANDSCAPE_CLEAR); - if (CmdFailed(ret)) continue; - cost += ret; - success = true; + if (!p2) { + if (ex < sx) Swap(ex, sx); + if (ey < sy) Swap(ey, sy); - if (flags & DC_EXEC) { - if (ret > 0 && (money -= ret) < 0) { - _additional_cash_required = ret; - return cost - ret; + for (x = sx; x <= ex; ++x) { + for (y = sy; y <= ey; ++y) { + DO_CLEAR_AREA + } + } + } + else{ + int ix,iy; /* counters in for-loops */ + int dx = ex - sx; /* distance between begin and endpoints */ + int dy = ey - sy; + int fx = (dx + dy) / 2; /* x and y coordinate in rotated coordinate system (for diagonal clearing) */ + int fy = (dx - dy) / 2; + int x2,y2; + /* fx,fy form a diagonal coordinate system. + * fx counts the number of diagonals in 1 direction, fy in another. + * However with this method you only count half the squares. Compare with a checkboard with + * alternating black and white squares. You only count one color. + * That is why we clear two squares in every loop. The principal one and one square next to that + * To avoid clearing squares outside our drag-area we need some complex if's. + */ + for (ix = 0; ix <= abs(fx); ix += 1) { + for (iy = 0; iy <= abs(fy); iy += 1) { + x = sx + sign(fx) * ix + sign(fy) * iy; + y = sy + sign(fx) * ix - sign(fy) * iy; + x2 = x; y2 = y; + DO_CLEAR_AREA + if (abs(dx) > abs(dy) && ((ix != abs(fx) && iy != abs(fy)) || (dx + dy) % 2!=0)) + {if (ex >= sx) x += 1; else x -= 1;} + if (abs(dx) < abs(dy) && ((ix != abs(fx) && iy != abs(fy)) || (dx + dy) % 2!=0)) + {if (ey >= sy) y += 1; else y -= 1;} + if (x!=x2 || y!=y2) { DO_CLEAR_AREA /*Do only when there's a change (to avoid screwing cost estimate) */ } - DoCommand(TileXY(x, y), 0, 0, flags, CMD_LANDSCAPE_CLEAR); - - /* draw explosion animation... */ - if ((x == sx || x == ex) && (y == sy || y == ey)) { - /* big explosion in each corner, or small explosion for single tiles */ - CreateEffectVehicleAbove(x * TILE_SIZE + TILE_SIZE / 2, y * TILE_SIZE + TILE_SIZE / 2, 2, - sy == ey && sx == ex ? EV_EXPLOSION_SMALL : EV_EXPLOSION_LARGE - ); - } } } } - return (success) ? cost : CMD_ERROR; } Index: src/macros.h =================================================================== --- src/macros.h (revision 9602) +++ src/macros.h (working copy) @@ -30,6 +30,7 @@ static inline uint minu(uint a, uint b) { if (a <= b) return a; return b; } +static inline int sign(int x) {if (x>0) return 1; else if (x<0) return -1; else return 0;} static inline int clamp(int a, int min, int max) { Index: src/terraform_gui.cpp =================================================================== --- src/terraform_gui.cpp (revision 9602) +++ src/terraform_gui.cpp (working copy) @@ -106,10 +106,17 @@ switch (e->we.place.userdata >> 4) { case GUI_PlaceProc_DemolishArea >> 4: - DoCommandP(end_tile, start_tile, 0, CcPlaySound10, CMD_CLEAR_AREA | CMD_MSG(STR_00B5_CAN_T_CLEAR_THIS_AREA)); + if(_ctrl_pressed){ + DoCommandP(end_tile, start_tile, 1, CcPlaySound10, CMD_CLEAR_AREA | CMD_MSG(STR_00B5_CAN_T_CLEAR_THIS_AREA)); + } + else + DoCommandP(end_tile, start_tile, 0, CcPlaySound10, CMD_CLEAR_AREA | CMD_MSG(STR_00B5_CAN_T_CLEAR_THIS_AREA)); break; case GUI_PlaceProc_LevelArea >> 4: - DoCommandP(end_tile, start_tile, 0, CcPlaySound10, CMD_LEVEL_LAND | CMD_AUTO); + if(_ctrl_pressed) + DoCommandP(end_tile, start_tile, 1, CcPlaySound10, CMD_LEVEL_LAND | CMD_AUTO); + else + DoCommandP(end_tile, start_tile, 0, CcPlaySound10, CMD_LEVEL_LAND | CMD_AUTO); break; case GUI_PlaceProc_RockyArea >> 4: GenerateRockyArea(end_tile, start_tile); Index: src/viewport.cpp =================================================================== --- src/viewport.cpp (revision 9602) +++ src/viewport.cpp (working copy) @@ -22,6 +22,7 @@ #include "waypoint.h" #include "variables.h" #include "train.h" +#include "gui.h" #define VIEWPORT_DRAW_MEM (65536 * 2) @@ -30,6 +31,11 @@ static uint32 _active_viewports; ///< bitmasked variable where each bit signifies if a viewport is in use or not assert_compile(lengthof(_viewports) < sizeof(_active_viewports) * 8); +/* DIA_DRAG is true if we're dragging 45° rotated (diagonal) rectangles +* This is the case if we're holding ctrl while using the clear or level land tools. +*/ +#define DIA_DRAG (_ctrl_pressed && ((_thd.userdata == (VPM_X_AND_Y | GUI_PlaceProc_DemolishArea)) || (_thd.userdata == (VPM_X_AND_Y | GUI_PlaceProc_LevelArea)))) + static bool _added_tile_sprite; static bool _offset_ground_sprites; @@ -641,6 +647,24 @@ #include "table/autorail.h" +/* Check to see if x,y lies within the selected 45° rotated rectangle. +* Called by DrawTileSelection() immidiately below. +*/ +bool IS_INSIDE_RR(int x, int y) +{ + int fx = (_thd.size.x + _thd.size.y); /* Rotated coordinate system for selected rectangle */ + int fy = (_thd.size.x - _thd.size.y); /* We don't have to divide by 2. It's all relative! */ + int xx = ((x - _thd.pos.x) + (y - _thd.pos.y)); /* Rotated coordinate system for the point under scrutiny */ + int yy = ((x - _thd.pos.x) - (y - _thd.pos.y)); + if (((fx >= 0 && xx <= fx && xx >= 0) || (fx < 0 && xx >= fx && xx <= 0)) && + ((fy >= 0 && yy <= fy && yy >= 0) || (fy < 0 && yy >= fy && yy <= 0))) + return true; else return false; +} + +/** + * Checks if the specified tile is selected and if so draws selection using correct selectionstyle. + * @param *ti Tile that is being drawn + */ static void DrawTileSelection(const TileInfo *ti) { SpriteID image; @@ -655,6 +679,26 @@ /* no selection active? */ if (_thd.drawstyle == 0) return; + if (_thd.diagonal) { /* We're drawing a 45° rotated (diagonal) rectangle*/ + if (IS_INSIDE_RR((int)ti->x,(int)ti->y)) { + if (_thd.drawstyle & HT_RECT) { /* highlighting a square (clear land) */ + image = SPR_SELECT_TILE + _tileh_to_sprite[ti->tileh]; + DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE, ti); + }else { /* highlighting a dot (level land)*/ + /* Figure out the Z coordinate for the single dot. */ + byte z = ti->z; + if (ti->tileh & 8) { + z += 8; + if (!(ti->tileh & 2) && (ti->tileh & 0x10)) { + z += 8; + } + } + DrawGroundSpriteAt(_cur_dpi->zoom != 2 ? SPR_DOT : SPR_DOT_SMALL, PAL_NONE, ti->x, ti->y, z); + } + } + return; + } + /* Inside the inner area? */ if (IS_INSIDE_1D(ti->x, _thd.pos.x, _thd.size.x) && IS_INSIDE_1D(ti->y, _thd.pos.y, _thd.size.y)) { @@ -1445,33 +1489,49 @@ static void SetSelectionTilesDirty() { - int y_size, x_size; + int x_size = _thd.size.x; + int y_size = _thd.size.y; int x = _thd.pos.x; int y = _thd.pos.y; - x_size = _thd.size.x; - y_size = _thd.size.y; + if (!_thd.diagonal) { /* Selecting in a straigth rectangle (or a single square)*/ + if (_thd.outersize.x) { + x_size += _thd.outersize.x; + x += _thd.offs.x; + y_size += _thd.outersize.y; + y += _thd.offs.y; + } + assert(x_size > 0); + assert(y_size > 0); - if (_thd.outersize.x) { - x_size += _thd.outersize.x; - x += _thd.offs.x; - y_size += _thd.outersize.y; - y += _thd.offs.y; + x_size += x; + y_size += y; + + do { + int y_bk = y; + do { + MarkTileDirty(x, y); + } while ( (y+=TILE_SIZE) != y_size); + y = y_bk; + } while ( (x+=TILE_SIZE) != x_size); + }else { /* Selecting in a 45° rotated (diagonal) rectangle.*/ + int ix,iy; + int fx = (x_size + y_size) / 2; /* fx,fy describe rotated coordinate axis*/ + int fy = (x_size - y_size) / 2; + for (ix =- TILE_SIZE; ix <= abs(fx); ix += TILE_SIZE) { + for (iy =- TILE_SIZE; iy <= abs(fy); iy += TILE_SIZE) { + x = _thd.pos.x + sign(fx) * ix + sign(fy) * iy; + y = _thd.pos.y + sign(fx) * ix - sign(fy) * iy; + MarkTileDirty(x, y); + /* Need to Mark odd and even tiles (compare with checkerboard: black and white squares)*/ + if (abs(x_size) > abs(y_size)) if ((ix != abs(fx) && iy != abs(fy)) || (x_size + y_size) % 2 != 0) + {if (x_size >= 0) x += TILE_SIZE; else x -= TILE_SIZE;} + if (abs(x_size) < abs(y_size)) if ((ix != abs(fx) && iy != abs(fy)) || (x_size - y_size) % 2 != 0) + {if (y_size >= 0) y += TILE_SIZE; else y -= TILE_SIZE;} + MarkTileDirty(x, y); + } + } } - - assert(x_size > 0); - assert(y_size > 0); - - x_size += x; - y_size += y; - - do { - int y_bk = y; - do { - MarkTileDirty(x, y); - } while ( (y += TILE_SIZE) != y_size); - y = y_bk; - } while ( (x += TILE_SIZE) != x_size); } @@ -1864,12 +1924,21 @@ } /** called regular to update tile highlighting in all cases */ -void UpdateTileSelection() +/** + * Updates tile highlighting for all cases. + * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size + * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice, + * Once for the old and once for the new selection. + * _thd is Tile Highlighting Data, found in viewport.h + * Called by MouseLoop() in windows.cpp + */ +void UpdateTileSelection(void) { int x1; int y1; _thd.new_drawstyle = 0; + _thd.new_diagonal = false; if (_thd.place_mode == VHM_SPECIAL) { x1 = _thd.selend.x; @@ -1880,12 +1949,22 @@ x1 &= ~0xF; y1 &= ~0xF; - if (x1 >= x2) Swap(x1, x2); - if (y1 >= y2) Swap(y1, y2); + if(DIA_DRAG) { /* Dia_drag means that we're dragging a 45° rotated rectangle*/ + _thd.new_diagonal = true; + }else { + if (x1 >= x2) Swap(x1,x2); + if (y1 >= y2) Swap(y1,y2); + } _thd.new_pos.x = x1; _thd.new_pos.y = y1; _thd.new_size.x = x2 - x1 + TILE_SIZE; _thd.new_size.y = y2 - y1 + TILE_SIZE; + _thd.new_size.x = x2 - x1; + _thd.new_size.y = y2 - y1; + if(!_thd.new_diagonal) { + _thd.new_size.x += TILE_SIZE; + _thd.new_size.y += TILE_SIZE; + } _thd.new_drawstyle = _thd.next_drawstyle; } } else if (_thd.place_mode != VHM_NONE) { @@ -1903,7 +1982,7 @@ y1 += 8; break; case VHM_RAIL: - _thd.new_drawstyle = GetAutorailHT(pt.x, pt.y); // draw one highlighted tile + _thd.new_drawstyle = GetAutorailHT(pt.x, pt.y); /* draw one highlighted tile*/ } _thd.new_pos.x = x1 & ~0xF; _thd.new_pos.y = y1 & ~0xF; @@ -1915,7 +1994,9 @@ _thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y || _thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y || _thd.outersize.x != _thd.new_outersize.x || - _thd.outersize.y != _thd.new_outersize.y) { + _thd.outersize.y != _thd.new_outersize.y || + _thd.diagonal != _thd.new_diagonal) { + /* clear the old selection? */ if (_thd.drawstyle) SetSelectionTilesDirty(); @@ -1923,6 +2004,7 @@ _thd.pos = _thd.new_pos; _thd.size = _thd.new_size; _thd.outersize = _thd.new_outersize; + _thd.diagonal = _thd.new_diagonal; _thd.dirty = 0xff; /* draw the new selection? */ Index: src/viewport.h =================================================================== --- src/viewport.h (revision 9602) +++ src/viewport.h (working copy) @@ -111,14 +111,14 @@ HT_DIR_MASK = 0x7 ///< masks the drag-direction }; -struct TileHighlightData { +typedef struct TileHighlightData{ Point size; Point outersize; Point pos; Point offs; - Point new_pos; - Point new_size; + Point new_pos; // All new_* variables are only used in UpdateTileSelection() as a buffer to compare + Point new_size; // if there was a change between old and new queued, but not yet drawn selection/style Point new_outersize; Point selend, selstart; @@ -135,9 +135,12 @@ WindowClass window_class; WindowNumber window_number; - int userdata; + int userdata; // Stores information about the function that called highlighting TileIndex redsq; -}; + + bool diagonal; // true if dragging a 45° rotated rectangle + bool new_diagonal; +} TileHighlightData; /* common button handler */