/** * The main new land generator using Perlin noise. Desert landscape is handled * different to all others to give a desert valley between two high mountains. * Clearly if a low height terrain (flat/very flat) is chosen, then the tropic * areas won't be high enough, and there will be very little tropic on the map. * Thus Tropic works best on Hilly or Mountainous. */ void GenerateTerrainPerlin() { if (!AllocHeightMap()) return; GenerateWorldSetAbortCallback(FreeHeightMap); HeightMapGenerate(); IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); HeightMapNormalize(); IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); /* First make sure the tiles at the north border are void tiles if needed. */ if (_settings_game.construction.freeform_edges) { for (uint x = 0; x < MapSizeX(); x++) MakeVoid(TileXY(x, 0)); for (uint y = 0; y < MapSizeY(); y++) MakeVoid(TileXY(0, y)); } int max_height = H2I(TGPGetMaxHeight()); /* Transfer height map into OTTD map */ for (int y = 0; y < _height_map.size_y; y++) { for (int x = 0; x < _height_map.size_x; x++) { TgenSetTileHeight(TileXY(x, y), Clamp(H2I(_height_map.height(x, y)), 0, max_height)); } } IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); FreeHeightMap(); GenerateWorldSetAbortCallback(NULL); }
TileIndex TileAdd(TileIndex tile, TileIndexDiff add, const char *exp, const char *file, int line) { int dx; int dy; uint x; uint y; dx = add & MapMaxX(); if (dx >= (int)MapSizeX() / 2) dx -= MapSizeX(); dy = (add - dx) / (int)MapSizeX(); x = TileX(tile) + dx; y = TileY(tile) + dy; if (x >= MapSizeX() || y >= MapSizeY()) { char buf[512]; snprintf(buf, lengthof(buf), "TILE_ADD(%s) when adding 0x%.4X and 0x%.4X failed", exp, tile, add); #if !defined(_MSC_VER) || defined(WINCE) fprintf(stderr, "%s:%d %s\n", file, line, buf); #else _assert(buf, (char*)file, line); #endif } assert(TileXY(x, y) == TILE_MASK(tile + add)); return TileXY(x, y); }
/** Allocates space for a new tile in the matrix. * @param tile Tile to add. */ void AllocateStorage(TileIndex tile) { uint old_left = TileX(this->area.tile) / N; uint old_top = TileY(this->area.tile) / N; uint old_w = this->area.w / N; uint old_h = this->area.h / N; /* Add the square the tile is in to the tile area. We do this * by adding top-left and bottom-right of the square. */ uint grid_x = (TileX(tile) / N) * N; uint grid_y = (TileY(tile) / N) * N; this->area.Add(TileXY(grid_x, grid_y)); this->area.Add(TileXY(grid_x + N - 1, grid_y + N - 1)); /* Allocate new storage. */ T *new_data = CallocT<T>(this->area.w / N * this->area.h / N); if (old_w > 0) { /* Copy old data if present. */ uint offs_x = old_left - TileX(this->area.tile) / N; uint offs_y = old_top - TileY(this->area.tile) / N; for (uint row = 0; row < old_h; row++) { MemCpyT(&new_data[(row + offs_y) * this->area.w / N + offs_x], &this->data[row * old_w], old_w); } } free(this->data); this->data = new_data; }
/** * Configure a ViewPort for rendering (a part of) the map into a screenshot. * @param t Screenshot type * @param [out] vp Result viewport */ void SetupScreenshotViewport(ScreenshotType t, ViewPort *vp) { /* Determine world coordinates of screenshot */ if (t == SC_WORLD) { vp->zoom = ZOOM_LVL_WORLD_SCREENSHOT; TileIndex north_tile = _settings_game.construction.freeform_edges ? TileXY(1, 1) : TileXY(0, 0); TileIndex south_tile = MapSize() - 1; /* We need to account for a hill or high building at tile 0,0. */ int extra_height_top = TilePixelHeight(north_tile) + 150; /* If there is a hill at the bottom don't create a large black area. */ int reclaim_height_bottom = TilePixelHeight(south_tile); vp->virtual_left = RemapCoords(TileX(south_tile) * TILE_SIZE, TileY(north_tile) * TILE_SIZE, 0).x; vp->virtual_top = RemapCoords(TileX(north_tile) * TILE_SIZE, TileY(north_tile) * TILE_SIZE, extra_height_top).y; vp->virtual_width = RemapCoords(TileX(north_tile) * TILE_SIZE, TileY(south_tile) * TILE_SIZE, 0).x - vp->virtual_left + 1; vp->virtual_height = RemapCoords(TileX(south_tile) * TILE_SIZE, TileY(south_tile) * TILE_SIZE, reclaim_height_bottom).y - vp->virtual_top + 1; } else { vp->zoom = (t == SC_ZOOMEDIN) ? _settings_client.gui.zoom_min : ZOOM_LVL_VIEWPORT; Window *w = FindWindowById(WC_MAIN_WINDOW, 0); vp->virtual_left = w->viewport->virtual_left; vp->virtual_top = w->viewport->virtual_top; vp->virtual_width = w->viewport->virtual_width; vp->virtual_height = w->viewport->virtual_height; } /* Compute pixel coordinates */ vp->left = 0; vp->top = 0; vp->width = UnScaleByZoom(vp->virtual_width, vp->zoom); vp->height = UnScaleByZoom(vp->virtual_height, vp->zoom); vp->overlay = NULL; }
/** * Create the DemandCalculator and immediately do the calculation. * @param job Job to calculate the demands for. */ DemandCalculator::DemandCalculator(LinkGraphJob &job) : max_distance(DistanceMaxPlusManhattan(TileXY(0,0), TileXY(MapMaxX(), MapMaxY()))) { const LinkGraphSettings &settings = job.Settings(); CargoID cargo = job.Cargo(); this->accuracy = settings.accuracy; this->mod_dist = settings.demand_distance; if (this->mod_dist > 100) { /* Increase effect of mod_dist > 100 */ int over100 = this->mod_dist - 100; this->mod_dist = 100 + over100 * over100; } switch (settings.GetDistributionType(cargo)) { case DT_SYMMETRIC: this->CalcDemand<SymmetricScaler>(job, SymmetricScaler(settings.demand_size)); break; case DT_ASYMMETRIC: this->CalcDemand<AsymmetricScaler>(job, AsymmetricScaler()); break; default: /* Nothing to do. */ break; } }
/** * Check whether station tiles of the given station id exist in the given rectangle * @param st_id Station ID to look for in the rectangle * @param left_a Minimal tile X edge of the rectangle * @param top_a Minimal tile Y edge of the rectangle * @param right_a Maximal tile X edge of the rectangle (inclusive) * @param bottom_a Maximal tile Y edge of the rectangle (inclusive) * @return \c true if a station tile with the given \a st_id exists in the rectangle, \c false otherwise */ /* static */ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a) { TileArea ta(TileXY(left_a, top_a), TileXY(right_a, bottom_a)); TILE_AREA_LOOP(tile, ta) { if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true; } return false; }
/** * This function takes care of the fact that land in OpenTTD can never differ * more than 1 in height */ void FixSlopes() { uint width, height; int row, col; byte current_tile; /* Adjust height difference to maximum one horizontal/vertical change. */ width = MapSizeX(); height = MapSizeY(); /* Top and left edge */ for (row = 0; (uint)row < height; row++) { for (col = 0; (uint)col < width; col++) { current_tile = MAX_TILE_HEIGHT; if (col != 0) { /* Find lowest tile; either the top or left one */ current_tile = TileHeight(TileXY(col - 1, row)); // top edge } if (row != 0) { if (TileHeight(TileXY(col, row - 1)) < current_tile) { current_tile = TileHeight(TileXY(col, row - 1)); // left edge } } /* Does the height differ more than one? */ if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) { /* Then change the height to be no more than one */ SetTileHeight(TileXY(col, row), current_tile + 1); } } } /* Bottom and right edge */ for (row = height - 1; row >= 0; row--) { for (col = width - 1; col >= 0; col--) { current_tile = MAX_TILE_HEIGHT; if ((uint)col != width - 1) { /* Find lowest tile; either the bottom and right one */ current_tile = TileHeight(TileXY(col + 1, row)); // bottom edge } if ((uint)row != height - 1) { if (TileHeight(TileXY(col, row + 1)) < current_tile) { current_tile = TileHeight(TileXY(col, row + 1)); // right edge } } /* Does the height differ more than one? */ if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) { /* Then change the height to be no more than one */ SetTileHeight(TileXY(col, row), current_tile + 1); } } } }
void Load_TOWN() { int index; while ((index = SlIterateArray()) != -1) { Town *t = new (index) Town(); SlObject(t, _town_desc); t->LoadCargoSourceSink(); if (IsSavegameVersionBefore(161)) continue; SlObject(&t->cargo_accepted, GetTileMatrixDesc()); if (t->cargo_accepted.area.w != 0) { uint arr_len = t->cargo_accepted.area.w / AcceptanceMatrix::GRID * t->cargo_accepted.area.h / AcceptanceMatrix::GRID; t->cargo_accepted.data = MallocT<uint32>(arr_len); SlArray(t->cargo_accepted.data, arr_len, SLE_UINT32); /* Rebuild total cargo acceptance. */ UpdateTownCargoTotal(t); } /* Cache the aligned tile index of the centre tile. */ uint town_x = (TileX(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; uint town_y = (TileY(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; t->xy_aligned= TileXY(town_x, town_y); } }
/** * Add a single tile to a tile area; enlarge if needed. * @param to_add The tile to add */ void TileArea::Add(TileIndex to_add) { if (this->tile == INVALID_TILE) { this->tile = to_add; this->w = 1; this->h = 1; return; } uint sx = TileX(this->tile); uint sy = TileY(this->tile); uint ex = sx + this->w - 1; uint ey = sy + this->h - 1; uint ax = TileX(to_add); uint ay = TileY(to_add); sx = min(ax, sx); sy = min(ay, sy); ex = max(ax, ex); ey = max(ay, ey); this->tile = TileXY(sx, sy); this->w = ex - sx + 1; this->h = ey - sy + 1; }
/** * Generate a world. * @param mode The mode of world generation (see GenWorldMode). * @param size_x The X-size of the map. * @param size_y The Y-size of the map. * @param reset_settings Whether to reset the game configuration (used for restart) */ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_settings) { if (HasModalProgress()) return; _gw.mode = mode; _gw.size_x = size_x; _gw.size_y = size_y; SetModalProgress(true); _gw.abort = false; _gw.abortp = NULL; _gw.lc = _local_company; _gw.quit_thread = false; _gw.threaded = true; /* This disables some commands and stuff */ SetLocalCompany(COMPANY_SPECTATOR); InitializeGame(_gw.size_x, _gw.size_y, true, reset_settings); PrepareGenerateWorldProgress(); /* Load the right landscape stuff, and the NewGRFs! */ GfxLoadSprites(); LoadStringWidthTable(); /* Re-init the windowing system */ ResetWindowSystem(); /* Create toolbars */ SetupColoursAndInitialWindow(); SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0); if (_gw.thread != NULL) { _gw.thread->Join(); delete _gw.thread; _gw.thread = NULL; } if (!VideoDriver::GetInstance()->HasGUI() || !ThreadObject::New(&_GenerateWorld, NULL, &_gw.thread, "ottd:genworld")) { DEBUG(misc, 1, "Cannot create genworld thread, reverting to single-threaded mode"); _gw.threaded = false; _modal_progress_work_mutex->EndCritical(); _GenerateWorld(NULL); _modal_progress_work_mutex->BeginCritical(); return; } UnshowCriticalError(); /* Remove any open window */ DeleteAllNonVitalWindows(); /* Hide vital windows, because we don't allow to use them */ HideVitalWindows(); /* Don't show the dialog if we don't have a thread */ ShowGenerateWorldProgress(); /* Centre the view on the map */ if (FindWindowById(WC_MAIN_WINDOW, 0) != NULL) { ScrollMainWindowToTile(TileXY(MapSizeX() / 2, MapSizeY() / 2), true); } }
/** * Recomputes Station::industries_near, list of industries possibly * accepting cargo in station's catchment radius */ void Station::RecomputeIndustriesNear() { this->industries_near.Clear(); if (this->rect.IsEmpty()) return; RectAndIndustryVector riv = { this->GetCatchmentRect(), &this->industries_near }; /* Compute maximum extent of acceptance rectangle wrt. station sign */ TileIndex start_tile = this->xy; uint max_radius = max( max(DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.bottom))), max(DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.bottom))) ); CircularTileSearch(&start_tile, 2 * max_radius + 1, &FindIndustryToDeliver, &riv); }
/** * Marks the acceptance tiles of the station as dirty. * * @ingroup dirty */ void Station::MarkAcceptanceTilesDirty() const { Rect rec = this->GetCatchmentRect(); TileIndex top_left = TileXY(rec.left, rec.top); int width = rec.right - rec.left + 1; int height = rec.bottom - rec.top + 1; TILE_AREA_LOOP(tile, TileArea(top_left, width, height) ) { MarkTileDirtyByTile(tile); }
/** * Test if this is safe to copy and paste contents of the map instantly, without * using an intermediate buffer. * * If the copy and the paste areas are close enough (especially when they intersect), * sequential copy-pasting may alter at some point of time those tile of the copy * area which hasn't been copied yet. In this case, further copy-pasting will read * modified values, not the original and this is somthing we don't want to happen. * We can deal with it by firstly copying all the content to the clipboard buffer and * then pasting it onto the map. This function tels us whether we should use the * clipboard as an intermediate buffer because there may happen such a colision. * * @param copy_paste What, where and how we are copying. * @return \c true if intermediate buffer might be required, \c false if it's surely not required * * @pre booth the source area and the destination area are on the main map * * @see CalcMaxPasteRange */ static bool CopyPasteAreasMayColide(const CopyPasteParams ©_paste) { /* No need to check surroundings if we are not terraforming. Just test for content intersection. */ if ((copy_paste.mode & CPM_TERRAFORM_MASK) == CPM_TERRAFORM_NONE) return copy_paste.src_area.Intersects(copy_paste.dst_area); /* As we are interested in tile heights, increase areas to include all tile * corners, also these at SW and SE borders. */ TileArea src_corner_area(AsMainMapTile(copy_paste.src_area.tile), copy_paste.src_area.w + 1, copy_paste.src_area.h + 1); TileArea dst_corner_area(AsMainMapTile(copy_paste.dst_area.tile), copy_paste.dst_area.w + 1, copy_paste.dst_area.h + 1); DirTransformation inv_transformation = InvertDirTransform(copy_paste.transformation); /* source of the destination area most northern tile corner */ TileIndex source_of_north = dst_corner_area.TransformedNorth(src_corner_area.tile, inv_transformation); /* calculate current and new heights on destination area corners */ /* N */ TileIndex dst_corner = dst_corner_area.tile; TileIndex src_corner = source_of_north; uint curr_n = TileHeight(dst_corner); uint new_n = TileHeight(src_corner) + copy_paste.height_delta; /* W */ dst_corner = TILE_ADDXY(dst_corner_area.tile, dst_corner_area.w, 0); src_corner = dst_corner_area.TransformTile(dst_corner, source_of_north, inv_transformation); uint curr_w = TileHeight(dst_corner); uint new_w = TileHeight(src_corner) + copy_paste.height_delta; /* S */ dst_corner = TILE_ADDXY(dst_corner_area.tile, dst_corner_area.w, dst_corner_area.h); src_corner = dst_corner_area.TransformTile(dst_corner, source_of_north, inv_transformation); uint curr_s = TileHeight(dst_corner); uint new_s = TileHeight(src_corner) + copy_paste.height_delta; /* E */ dst_corner = TILE_ADDXY(dst_corner_area.tile, 0, dst_corner_area.h); src_corner = dst_corner_area.TransformTile(dst_corner, source_of_north, inv_transformation); uint curr_e = TileHeight(dst_corner); uint new_e = TileHeight(src_corner) + copy_paste.height_delta; /* calculate how far tiles can be altered beyon the paste area (safe approximation) */ uint range_ne = CalcMaxPasteRange(curr_n, new_n, curr_e, new_e, dst_corner_area.h - 1); uint range_sw = CalcMaxPasteRange(curr_s, new_s, curr_w, new_w, dst_corner_area.h - 1); uint range_nw = CalcMaxPasteRange(curr_n, new_n, curr_w, new_w, dst_corner_area.w - 1); uint range_se = CalcMaxPasteRange(curr_s, new_s, curr_e, new_e, dst_corner_area.w - 1); /* calculate the exact area which may be altered by the paste process */ uint x = TileX(dst_corner_area.tile); uint y = TileY(dst_corner_area.tile); range_ne = max(range_ne, x); // cut the area at the NE border (don't let x to go below 0) range_nw = max(range_nw, y); // cut the area at the NW border (don't let y to go below 0) TileArea forbidden_area( TileXY(x - range_ne, y - range_nw), dst_corner_area.w + range_ne + range_sw, dst_corner_area.h + range_nw + range_se); /* test if the source area is within the paste range */ return src_corner_area.Intersects(forbidden_area); }
/** * Check whether station tiles of the given station id exist in the given rectangle * @param st_id Station ID to look for in the rectangle * @param left_a Minimal tile X edge of the rectangle * @param top_a Minimal tile Y edge of the rectangle * @param right_a Maximal tile X edge of the rectangle (inclusive) * @param bottom_a Maximal tile Y edge of the rectangle (inclusive) * @return \c true if a station tile with the given \a st_id exists in the rectangle, \c false otherwise */ /* static */ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a) { TileIndex top_left = TileXY(left_a, top_a); int width = right_a - left_a + 1; int height = bottom_a - top_a + 1; TILE_LOOP(tile, width, height, top_left) { if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true; } return false; }
MainWindow(WindowDesc *desc) : Window(desc) { this->InitNested(0); CLRBITS(this->flags, WF_WHITE_BORDER); ResizeWindow(this, _screen.width, _screen.height); NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_M_VIEWPORT); nvp->InitializeViewport(this, TileXY(32, 32), ZOOM_LVL_VIEWPORT); this->viewport->overlay = new LinkGraphOverlay(this, WID_M_VIEWPORT, 0, 0, 3); this->refresh = LINKGRAPH_DELAY; }
void FlatEmptyWorld(byte tile_height) { int edge_distance = _settings_game.construction.freeform_edges ? 0 : 2; for (uint row = edge_distance; row < MapSizeY() - edge_distance; row++) { for (uint col = edge_distance; col < MapSizeX() - edge_distance; col++) { SetTileHeight(TileXY(col, row), tile_height); } } FixSlopes(); MarkWholeScreenDirty(); }
/** * Returns the tile height for a coordinate outside map. Such a height is * needed for painting the area outside map using completely black tiles. * The idea is descending to heightlevel 0 as fast as possible. * @param x The X-coordinate (same unit as TileX). * @param y The Y-coordinate (same unit as TileY). * @return The height in the same unit as TileHeight. */ uint TileHeightOutsideMap(int x, int y) { /* In all cases: Descend to heightlevel 0 as fast as possible. * So: If we are at the 0-side of the map (x<0 or y<0), we must * subtract the distance to coordinate 0 from the heightlevel at * coordinate 0. * In other words: Subtract e.g. -x. If we are at the MapMax * side of the map, we also need to subtract the distance to * the edge of map, e.g. MapMaxX - x. * * NOTE: Assuming constant heightlevel outside map would be * simpler here. However, then we run into painting problems, * since whenever a heightlevel change at the map border occurs, * we would need to repaint anything outside map. * In contrast, by doing it this way, we can localize this change, * which means we may assume constant heightlevel for all tiles * at more than <heightlevel at map border> distance from the * map border. */ if (x < 0) { if (y < 0) { return max((int)TileHeight(TileXY(0, 0)) - (-x) - (-y), 0); } else if (y < (int)MapMaxY()) { return max((int)TileHeight(TileXY(0, y)) - (-x), 0); } else { return max((int)TileHeight(TileXY(0, (int)MapMaxY())) - (-x) - (y - (int)MapMaxY()), 0); } } else if (x < (int)MapMaxX()) { if (y < 0) { return max((int)TileHeight(TileXY(x, 0)) - (-y), 0); } else if (y < (int)MapMaxY()) { return TileHeight(TileXY(x, y)); } else { return max((int)TileHeight(TileXY(x, (int)MapMaxY())) - (y - (int)MapMaxY()), 0); } } else { if (y < 0) { return max((int)TileHeight(TileXY((int)MapMaxX(), 0)) - (x - (int)MapMaxX()) - (-y), 0); } else if (y < (int)MapMaxY()) { return max((int)TileHeight(TileXY((int)MapMaxX(), y)) - (x - (int)MapMaxX()), 0); } else { return max((int)TileHeight(TileXY((int)MapMaxX(), (int)MapMaxY())) - (x - (int)MapMaxX()) - (y - (int)MapMaxY()), 0); } } }
/** * This function checks if we add addx/addy to tile, if we * do wrap around the edges. For example, tile = (10,2) and * addx = +3 and addy = -4. This function will now return * INVALID_TILE, because the y is wrapped. This is needed in * for example, farmland. When the tile is not wrapped, * the result will be tile + TileDiffXY(addx, addy) * * @param tile the 'starting' point of the adding * @param addx the amount of tiles in the X direction to add * @param addy the amount of tiles in the Y direction to add * @return translated tile, or INVALID_TILE when it would've wrapped. */ TileIndex TileAddWrap(TileIndex tile, int addx, int addy) { uint x = TileX(tile) + addx; uint y = TileY(tile) + addy; /* Disallow void tiles at the north border. */ if ((x == 0 || y == 0) && _settings_game.construction.freeform_edges) return INVALID_TILE; /* Are we about to wrap? */ if (x >= MapMaxX() || y >= MapMaxY()) return INVALID_TILE; return TileXY(x, y); }
/** * Construct this tile area based on two points. * @param start the start of the area * @param end the end of the area */ TileArea::TileArea(TileIndex start, TileIndex end) { uint sx = TileX(start); uint sy = TileY(start); uint ex = TileX(end); uint ey = TileY(end); if (sx > ex) Swap(sx, ex); if (sy > ey) Swap(sy, ey); this->tile = TileXY(sx, sy); this->w = ex - sx + 1; this->h = ey - sy + 1; }
/** * Move ourselves to the next tile in the rectange on the map. */ TileIterator &DiagonalTileIterator::operator++() { assert(this->tile != INVALID_TILE); /* Determine the next tile, while clipping at map borders */ bool new_line = false; do { /* Iterate using the rotated coordinates. */ if (this->a_max == 1 || this->a_max == -1) { /* Special case: Every second column has zero length, skip them completely */ this->a_cur = 0; if (this->b_max > 0) { this->b_cur = min(this->b_cur + 2, this->b_max); } else { this->b_cur = max(this->b_cur - 2, this->b_max); } } else { /* Every column has at least one tile to process */ if (this->a_max > 0) { this->a_cur += 2; new_line = this->a_cur >= this->a_max; } else { this->a_cur -= 2; new_line = this->a_cur <= this->a_max; } if (new_line) { /* offset of initial a_cur: one tile in the same direction as a_max * every second line. */ this->a_cur = abs(this->a_cur) % 2 ? 0 : (this->a_max > 0 ? 1 : -1); if (this->b_max > 0) { ++this->b_cur; } else { --this->b_cur; } } } /* And convert the coordinates back once we've gone to the next tile. */ uint x = this->base_x + (this->a_cur - this->b_cur) / 2; uint y = this->base_y + (this->b_cur + this->a_cur) / 2; /* Prevent wrapping around the map's borders. */ this->tile = x >= MapSizeX() || y >= MapSizeY() ? INVALID_TILE : TileXY(x, y); } while (this->tile > MapSize() && this->b_max != this->b_cur); if (this->b_max == this->b_cur) this->tile = INVALID_TILE; return *this; }
/** * Callback for generating a heightmap. Supports 8bpp grayscale only. * @param userdata Pointer to user data. * @param buf Destination buffer. * @param y Line number of the first line to write. * @param pitch Number of pixels to write (1 byte for 8bpp, 4 bytes for 32bpp). @see Colour * @param n Number of lines to write. * @see ScreenshotCallback */ static void HeightmapCallback(void *userdata, void *buffer, uint y, uint pitch, uint n) { byte *buf = (byte *)buffer; while (n > 0) { TileIndex ti = TileXY(MapMaxX(), y); for (uint x = MapMaxX(); true; x--) { *buf = 16 * TileHeight(ti); buf++; if (x == 0) break; ti = TILE_ADDXY(ti, -1, 0); } y++; n--; } }
/** * Copy content of a given tile area into the clipboard buffer. * @param ta The area to copy * @param buffer Clipboard buffer to copy into */ static void CopyToClipboard(Map *buffer, const TileArea &ta) { AllocateClipboardBuffer(buffer, ta.w, ta.h); CopyPasteParams copy_paste = { GenericTileArea(ta), // src_area GenericTileArea(TileXY(0, 0, buffer), ta.w, ta.h), // dst_area CPM_ALL_TRANSPORT_MASK | CPM_TERRAFORM_FULL, // mode INVALID_RAILTYPE, // railtype DTR_IDENTITY, // transformation 0 // height_delta }; DoCopyPaste(copy_paste); }
/** * Finds the distance for the closest tile with water/land given a tile * @param tile the tile to find the distance too * @param water whether to find water or land * @return distance to nearest water (max 0x7F) / land (max 0x1FF; 0x200 if there is no land) */ uint GetClosestWaterDistance(TileIndex tile, bool water) { if (HasTileWaterGround(tile) == water) return 0; uint max_dist = water ? 0x7F : 0x200; int x = TileX(tile); int y = TileY(tile); uint max_x = MapMaxX(); uint max_y = MapMaxY(); uint min_xy = _settings_game.construction.freeform_edges ? 1 : 0; /* go in a 'spiral' with increasing manhattan distance in each iteration */ for (uint dist = 1; dist < max_dist; dist++) { /* next 'diameter' */ y--; /* going counter-clockwise around this square */ for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { static const int8 ddx[DIAGDIR_END] = { -1, 1, 1, -1}; static const int8 ddy[DIAGDIR_END] = { 1, 1, -1, -1}; int dx = ddx[dir]; int dy = ddy[dir]; /* each side of this square has length 'dist' */ for (uint a = 0; a < dist; a++) { /* MP_VOID tiles are not checked (interval is [min; max) for IsInsideMM())*/ if (IsInsideMM(x, min_xy, max_x) && IsInsideMM(y, min_xy, max_y)) { TileIndex t = TileXY(x, y); if (HasTileWaterGround(t) == water) return dist; } x += dx; y += dy; } } } if (!water) { /* no land found - is this a water-only map? */ for (TileIndex t = 0; t < MapSize(); t++) { if (!IsTileType(t, MP_VOID) && !IsTileType(t, MP_WATER)) return 0x1FF; } } return max_dist; }
/** * Get the area of the matrix square that contains a specific tile. * @param The tile to get the map area for. * @param extend Extend the area by this many squares on all sides. * @return Tile area containing the tile. */ static TileArea GetAreaForTile(TileIndex tile, uint extend = 0) { uint tile_x = (TileX(tile) / N) * N; uint tile_y = (TileY(tile) / N) * N; uint w = N, h = N; w += min(extend * N, tile_x); h += min(extend * N, tile_y); tile_x -= min(extend * N, tile_x); tile_y -= min(extend * N, tile_y); w += min(extend * N, MapSizeX() - tile_x - w); h += min(extend * N, MapSizeY() - tile_y - h); return TileArea(TileXY(tile_x, tile_y), w, h); }
/** * Paste onto the main map content of a clipboard buffer. * @param buffer The buffer. * @param tile Tile where to paste (most northern). * @param flags Flags for the command. * @param mode Copy-paste mode. * @param transformation Transformation to perform. * @param railtype #RailType to convert to. * @param additional_height_delta Additional amount of units to add to each tile height. * @return Total cost. */ static CommandCost PasteFromClipboard(Map *buffer, TileIndex tile, DoCommandFlag flags, CopyPasteMode mode, DirTransformation transformation, RailType railtype, int additional_height_delta) { assert(!IsClipboardBufferEmpty(buffer)); CopyPasteParams copy_paste; /* calculate and validate copy/paste area */ copy_paste.src_area = GenericTileArea(TileXY(0, 0, buffer), MapMaxX(buffer), MapMaxY(buffer)); copy_paste.dst_area = TransformTileArea(copy_paste.src_area, GenericTileIndex(tile), transformation); CommandCost ret = ValParamCopyPasteArea(copy_paste.dst_area); if (ret.Failed()) return ret; copy_paste.mode = mode; copy_paste.railtype = railtype; copy_paste.transformation = transformation; copy_paste.height_delta = CalcCopyPasteHeightDelta(copy_paste.src_area, copy_paste.dst_area, transformation, additional_height_delta); /* do sequential copy-pasting */ InitializePasting(flags, copy_paste); DoCopyPaste(copy_paste); return FinalizePasting(); }
/** * Tests whether given delivery is subsidised and possibly awards the subsidy to delivering company * @param cargo_type type of cargo * @param company company delivering the cargo * @param src_type type of #src * @param src index of source * @param st station where the cargo is delivered to * @return is the delivery subsidised? */ bool CheckSubsidised(CargoID cargo_type, CompanyID company, SourceType src_type, SourceID src, const Station *st) { /* If the source isn't subsidised, don't continue */ if (src == INVALID_SOURCE) return false; switch (src_type) { case ST_INDUSTRY: if (!(Industry::Get(src)->part_of_subsidy & POS_SRC)) return false; break; case ST_TOWN: if (!( Town::Get(src)->part_of_subsidy & POS_SRC)) return false; break; default: return false; } /* Remember all towns near this station (at least one house in its catchment radius) * which are destination of subsidised path. Do that only if needed */ SmallVector<const Town *, 2> towns_near; if (!st->rect.IsEmpty()) { Subsidy *s; FOR_ALL_SUBSIDIES(s) { /* Don't create the cache if there is no applicable subsidy with town as destination */ if (s->dst_type != ST_TOWN) continue; if (s->cargo_type != cargo_type || s->src_type != src_type || s->src != src) continue; if (s->IsAwarded() && s->awarded != company) continue; Rect rect = st->GetCatchmentRect(); for (int y = rect.top; y <= rect.bottom; y++) { for (int x = rect.left; x <= rect.right; x++) { TileIndex tile = TileXY(x, y); if (!IsTileType(tile, MP_HOUSE)) continue; const Town *t = Town::GetByTile(tile); if (t->part_of_subsidy & POS_DST) towns_near.Include(t); } } break; } }
/** * Generalized circular search allowing for rectangles and a hole. * Function performing a search around a center rectangle and going outward. * The center rectangle is left out from the search. To do a rectangular search * without a hole, set either h or w to zero. * Every tile will be tested by means of the callback function proc, * which will determine if yes or no the given tile meets criteria of search. * @param tile to start the search from. Upon completion, it will return the tile matching the search. * This tile should be directly north of the hole (if any). * @param radius How many tiles to search outwards. Note: This is a radius and thus different * from the size parameter of the other CircularTileSearch function, which is a diameter. * @param w the width of the inner rectangle * @param h the height of the inner rectangle * @param proc callback testing function pointer. * @param user_data to be passed to the callback function. Depends on the implementation * @return result of the search * @pre proc != NULL * @pre radius > 0 */ bool CircularTileSearch(TileIndex *tile, uint radius, uint w, uint h, TestTileOnSearchProc proc, void *user_data) { assert(proc != NULL); assert(radius > 0); uint x = TileX(*tile) + w + 1; uint y = TileY(*tile); const uint extent[DIAGDIR_END] = { w, h, w, h }; for (uint n = 0; n < radius; n++) { for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { /* Is the tile within the map? */ for (uint j = extent[dir] + n * 2 + 1; j != 0; j--) { if (x < MapSizeX() && y < MapSizeY()) { TileIndex t = TileXY(x, y); /* Is the callback successful? */ if (proc(t, user_data)) { /* Stop the search */ *tile = t; return true; } } /* Step to the next 'neighbour' in the circular line */ x += _tileoffs_by_diagdir[dir].x; y += _tileoffs_by_diagdir[dir].y; } } /* Jump to next circle to test */ x += _tileoffs_by_dir[DIR_W].x; y += _tileoffs_by_dir[DIR_W].y; } *tile = INVALID_TILE; return false; }
/** * The main new land generator using Perlin noise. Desert landscape is handled * different to all others to give a desert valley between two high mountains. * Clearly if a low height terrain (flat/very flat) is chosen, then the tropic * areas wont be high enough, and there will be very little tropic on the map. * Thus Tropic works best on Hilly or Mountainous. */ void GenerateTerrainPerlin() { uint x, y; if (!AllocHeightMap()) return; GenerateWorldSetAbortCallback(FreeHeightMap); HeightMapGenerate(); IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); HeightMapNormalize(); IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); /* First make sure the tiles at the north border are void tiles if needed. */ if (_settings_game.construction.freeform_edges) { for (y = 0; y < _height_map.size_y - 1; y++) MakeVoid(_height_map.size_x * y); for (x = 0; x < _height_map.size_x; x++) MakeVoid(x); } /* Transfer height map into OTTD map */ for (y = 0; y < _height_map.size_y; y++) { for (x = 0; x < _height_map.size_x; x++) { int height = H2I(_height_map.height(x, y)); if (height < 0) height = 0; if (height > 15) height = 15; TgenSetTileHeight(TileXY(x, y), height); } } IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); FreeHeightMap(); GenerateWorldSetAbortCallback(NULL); }
/** * Converts a given grayscale map to something that fits in OTTD map system * and create a map of that data. * @param img_width the with of the image in pixels/tiles * @param img_height the height of the image in pixels/tiles * @param map the input map */ static void GrayscaleToMapHeights(uint img_width, uint img_height, byte *map) { /* Defines the detail of the aspect ratio (to avoid doubles) */ const uint num_div = 16384; uint width, height; uint row, col; uint row_pad = 0, col_pad = 0; uint img_scale; uint img_row, img_col; TileIndex tile; /* Get map size and calculate scale and padding values */ switch (_settings_game.game_creation.heightmap_rotation) { default: NOT_REACHED(); case HM_COUNTER_CLOCKWISE: width = MapSizeX(); height = MapSizeY(); break; case HM_CLOCKWISE: width = MapSizeY(); height = MapSizeX(); break; } if ((img_width * num_div) / img_height > ((width * num_div) / height)) { /* Image is wider than map - center vertically */ img_scale = (width * num_div) / img_width; row_pad = (1 + height - ((img_height * img_scale) / num_div)) / 2; } else { /* Image is taller than map - center horizontally */ img_scale = (height * num_div) / img_height; col_pad = (1 + width - ((img_width * img_scale) / num_div)) / 2; } if (_settings_game.construction.freeform_edges) { for (uint x = 0; x < MapSizeX(); x++) MakeVoid(TileXY(x, 0)); for (uint y = 0; y < MapSizeY(); y++) MakeVoid(TileXY(0, y)); } /* Form the landscape */ for (row = 0; row < height; row++) { for (col = 0; col < width; col++) { switch (_settings_game.game_creation.heightmap_rotation) { default: NOT_REACHED(); case HM_COUNTER_CLOCKWISE: tile = TileXY(col, row); break; case HM_CLOCKWISE: tile = TileXY(row, col); break; } /* Check if current tile is within the 1-pixel map edge or padding regions */ if ((!_settings_game.construction.freeform_edges && DistanceFromEdge(tile) <= 1) || (row < row_pad) || (row >= (height - row_pad - (_settings_game.construction.freeform_edges ? 0 : 1))) || (col < col_pad) || (col >= (width - col_pad - (_settings_game.construction.freeform_edges ? 0 : 1)))) { SetTileHeight(tile, 0); } else { /* Use nearest neighbor resizing to scale map data. * We rotate the map 45 degrees (counter)clockwise */ img_row = (((row - row_pad) * num_div) / img_scale); switch (_settings_game.game_creation.heightmap_rotation) { default: NOT_REACHED(); case HM_COUNTER_CLOCKWISE: img_col = (((width - 1 - col - col_pad) * num_div) / img_scale); break; case HM_CLOCKWISE: img_col = (((col - col_pad) * num_div) / img_scale); break; } assert(img_row < img_height); assert(img_col < img_width); /* Colour scales from 0 to 255, OpenTTD height scales from 0 to 15 */ SetTileHeight(tile, map[img_row * img_width + img_col] / 16); } /* Only clear the tiles within the map area. */ if (TileX(tile) != MapMaxX() && TileY(tile) != MapMaxY() && (!_settings_game.construction.freeform_edges || (TileX(tile) != 0 && TileY(tile) != 0))) { MakeClear(tile, CLEAR_GRASS, 3); } } } }
/** * The internal, real, generate function. */ static void _GenerateWorld(void *) { /* Make sure everything is done via OWNER_NONE. */ Backup<CompanyByte> _cur_company(_current_company, OWNER_NONE, FILE_LINE); try { _generating_world = true; _modal_progress_work_mutex->BeginCritical(); if (_network_dedicated) DEBUG(net, 1, "Generating map, please wait..."); /* Set the Random() seed to generation_seed so we produce the same map with the same seed */ if (_settings_game.game_creation.generation_seed == GENERATE_NEW_SEED) _settings_game.game_creation.generation_seed = _settings_newgame.game_creation.generation_seed = InteractiveRandom(); _random.SetSeed(_settings_game.game_creation.generation_seed); SetGeneratingWorldProgress(GWP_MAP_INIT, 2); SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0); BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP); IncreaseGeneratingWorldProgress(GWP_MAP_INIT); /* Must start economy early because of the costs. */ StartupEconomy(); /* Don't generate landscape items when in the scenario editor. */ if (_gw.mode == GWM_EMPTY) { SetGeneratingWorldProgress(GWP_OBJECT, 1); /* Make sure the tiles at the north border are void tiles if needed. */ if (_settings_game.construction.freeform_edges) { for (uint row = 0; row < MapSizeY(); row++) MakeVoid(TileXY(0, row)); for (uint col = 0; col < MapSizeX(); col++) MakeVoid(TileXY(col, 0)); } /* Make the map the height of the setting */ if (_game_mode != GM_MENU) FlatEmptyWorld(_settings_game.game_creation.se_flat_world_height); ConvertGroundTilesIntoWaterTiles(); IncreaseGeneratingWorldProgress(GWP_OBJECT); } else { GenerateLandscape(_gw.mode); GenerateClearTile(); /* only generate towns, tree and industries in newgame mode. */ if (_game_mode != GM_EDITOR) { if (!GenerateTowns(_settings_game.economy.town_layout)) { _cur_company.Restore(); HandleGeneratingWorldAbortion(); return; } GenerateIndustries(); GenerateObjects(); GenerateTrees(); } } /* These are probably pointless when inside the scenario editor. */ SetGeneratingWorldProgress(GWP_GAME_INIT, 3); StartupCompanies(); IncreaseGeneratingWorldProgress(GWP_GAME_INIT); StartupEngines(); IncreaseGeneratingWorldProgress(GWP_GAME_INIT); StartupDisasters(); _generating_world = false; /* No need to run the tile loop in the scenario editor. */ if (_gw.mode != GWM_EMPTY) { uint i; SetGeneratingWorldProgress(GWP_RUNTILELOOP, 0x500); for (i = 0; i < 0x500; i++) { RunTileLoop(); _tick_counter++; IncreaseGeneratingWorldProgress(GWP_RUNTILELOOP); } if (_game_mode != GM_EDITOR) { Game::StartNew(); if (Game::GetInstance() != NULL) { SetGeneratingWorldProgress(GWP_RUNSCRIPT, 2500); _generating_world = true; for (i = 0; i < 2500; i++) { Game::GameLoop(); IncreaseGeneratingWorldProgress(GWP_RUNSCRIPT); if (Game::GetInstance()->IsSleeping()) break; } _generating_world = false; } } } BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP); ResetObjectToPlace(); _cur_company.Trash(); _current_company = _local_company = _gw.lc; SetGeneratingWorldProgress(GWP_GAME_START, 1); /* Call any callback */ if (_gw.proc != NULL) _gw.proc(); IncreaseGeneratingWorldProgress(GWP_GAME_START); CleanupGeneration(); _modal_progress_work_mutex->EndCritical(); ShowNewGRFError(); if (_network_dedicated) DEBUG(net, 1, "Map generated, starting game"); DEBUG(desync, 1, "new_map: %08x", _settings_game.game_creation.generation_seed); if (_debug_desync_level > 0) { char name[MAX_PATH]; seprintf(name, lastof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date); SaveOrLoad(name, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false); } } catch (...) { BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP, true); if (_cur_company.IsValid()) _cur_company.Restore(); _generating_world = false; _modal_progress_work_mutex->EndCritical(); throw; } }