@@ -10,13 +10,13 @@
.Nm
.Op Fl efhQxX
.Op Fl b Ar blitter
.Op Fl c Ar config_file
.Op Fl d Op Ar level | Ar cat Ns = Ns Ar lvl Ns Op , Ns Ar ...
.Op Fl D Oo Ar host Oc Ns Op : Ns Ar port
.Op Fl g Op Ar savegame
.Op Fl g Op Ar file
.Op Fl G Ar seed
.Op Fl I Ar graphicsset
.Op Fl m Ar driver
.Op Fl M Ar musicset
.Op Fl n Ar host Ns Oo : Ns Ar port Oc Ns Op # Ns Ar company
.Op Fl p Ar password
@@ -59,17 +59,17 @@ after setting
.Fl D .
.It Fl e
Start in world editor mode.
.It Fl f
Fork into background (dedicated server only, see
.Fl D ) .
.It Fl g Op Ar savegame
.It Fl g Op Ar file
Load
.Ar savegame
at start or start a new game if omitted.
.Ar file
(can be either a savegame, scenario, or heightmap) at start or start a new game if omitted.
must be either an absolute path or one relative to the current path or one of
the search paths.
.It Fl G Ar seed
Seed the pseudo random number generator with
.Ar seed .
.It Fl h
@@ -444,15 +444,12 @@ std::tuple<FiosType, std::string> FiosGe
/* Show savegame files
* .SAV OpenTTD saved game
* .SS1 Transport Tycoon Deluxe preset game
* .SV1 Transport Tycoon Deluxe (Patch) saved game
* .SV2 Transport Tycoon Deluxe (Patch) saved 2-player game */
/* Don't crash if we supply no extension */
if (ext.empty()) return { FIOS_TYPE_INVALID, {} };
if (StrEqualsIgnoreCase(ext, ".sav")) {
return { FIOS_TYPE_FILE, GetFileTitle(file, SAVE_DIR) };
}
if (fop == SLO_LOAD) {
if (StrEqualsIgnoreCase(ext, ".ss1") || StrEqualsIgnoreCase(ext, ".sv1") ||
@@ -487,13 +484,13 @@ void FiosGetSavegameList(SaveLoadOperati
* @param file Name of the file to check.
* @param ext A pointer to the extension identifier inside file
* @return a FIOS_TYPE_* type of the found file, FIOS_TYPE_INVALID if not a scenario and the title of the file (if any).
* @see FiosGetFileList
* @see FiosGetScenarioList
*/
static std::tuple<FiosType, std::string> FiosGetScenarioListCallback(SaveLoadOperation fop, const std::string &file, const std::string_view ext)
std::tuple<FiosType, std::string> FiosGetScenarioListCallback(SaveLoadOperation fop, const std::string &file, const std::string_view ext)
{
/* Show scenario files
* .SCN OpenTTD style scenario file
* .SV0 Transport Tycoon Deluxe (Patch) scenario
* .SS0 Transport Tycoon Deluxe preset scenario */
if (StrEqualsIgnoreCase(ext, ".scn")) {
@@ -527,13 +524,13 @@ void FiosGetScenarioList(SaveLoadOperati
std::string base_path = FioFindDirectory(SCENARIO_DIR);
Subdirectory subdir = (fop == SLO_LOAD && base_path == *_fios_path) ? SCENARIO_DIR : NO_DIRECTORY;
FiosGetFileList(fop, &FiosGetScenarioListCallback, subdir, file_list);
static std::tuple<FiosType, std::string> FiosGetHeightmapListCallback(SaveLoadOperation, const std::string &file, const std::string_view ext)
std::tuple<FiosType, std::string> FiosGetHeightmapListCallback(SaveLoadOperation, const std::string &file, const std::string_view ext)
/* Show heightmap files
* .PNG PNG Based heightmap files
* .BMP BMP Based heightmap files
@@ -114,12 +114,14 @@ std::string FiosGetCurrentPath();
std::optional<uint64_t> FiosGetDiskFreeSpace(const std::string &path);
bool FiosDelete(const char *name);
std::string FiosMakeHeightmapName(const char *name);
std::string FiosMakeSavegameName(const char *name);
std::tuple<FiosType, std::string> FiosGetSavegameListCallback(SaveLoadOperation fop, const std::string &file, const std::string_view ext);
std::tuple<FiosType, std::string> FiosGetScenarioListCallback(SaveLoadOperation fop, const std::string &file, const std::string_view ext);
std::tuple<FiosType, std::string> FiosGetHeightmapListCallback(SaveLoadOperation fop, const std::string &file, const std::string_view ext);
void ScanScenarios();
const char *FindScenario(const ContentInfo *ci, bool md5sum);
/**
* A savegame name automatically numbered.
@@ -100,14 +100,20 @@ static void _GenerateWorld()
BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP);
IncreaseGeneratingWorldProgress(GWP_MAP_INIT);
/* Must start economy early because of the costs. */
StartupEconomy();
bool landscape_generated = false;
/* Don't generate landscape items when in the scenario editor. */
if (_gw.mode == GWM_EMPTY) {
if (_gw.mode != GWM_EMPTY) {
landscape_generated = GenerateLandscape(_gw.mode);
if (!landscape_generated) {
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 x = 0; x < Map::SizeX(); x++) MakeVoid(TileXY(x, 0));
for (uint y = 0; y < Map::SizeY(); y++) MakeVoid(TileXY(0, y));
@@ -118,13 +124,12 @@ static void _GenerateWorld()
ConvertGroundTilesIntoWaterTiles();
IncreaseGeneratingWorldProgress(GWP_OBJECT);
_settings_game.game_creation.snow_line_height = DEF_SNOWLINE_HEIGHT;
} 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)) {
HandleGeneratingWorldAbortion();
@@ -517,27 +517,29 @@ bool GetHeightmapDimensions(DetailedFile
* Load a heightmap from file and change the map in its current dimensions
* to a landscape representing the heightmap.
* It converts pixels to height. The brighter, the higher.
* @param dft Type of image file.
* @param filename of the heightmap file to be imported
void LoadHeightmap(DetailedFileType dft, const char *filename)
bool LoadHeightmap(DetailedFileType dft, const char *filename)
uint x, y;
byte *map = nullptr;
if (!ReadHeightMap(dft, filename, &x, &y, &map)) {
free(map);
return;
return false;
GrayscaleToMapHeights(x, y, map);
FixSlopes();
MarkWholeScreenDirty();
return true;
* Make an empty world where all tiles are of height 'tile_height'.
* @param tile_height of the desired new empty world
@@ -19,11 +19,11 @@
enum HeightmapRotation {
HM_COUNTER_CLOCKWISE, ///< Rotate the map counter clockwise 45 degrees
HM_CLOCKWISE, ///< Rotate the map clockwise 45 degrees
};
bool GetHeightmapDimensions(DetailedFileType dft, const char *filename, uint *x, uint *y);
void LoadHeightmap(DetailedFileType dft, const char *filename);
bool LoadHeightmap(DetailedFileType dft, const char *filename);
void FlatEmptyWorld(byte tile_height);
void FixSlopes();
#endif /* HEIGHTMAP_H */
@@ -1554,13 +1554,13 @@ static void CalculateSnowLine()
static uint8_t CalculateDesertLine()
/* CalculateCoverageLine() runs from top to bottom, so we need to invert the coverage. */
return CalculateCoverageLine(100 - _settings_game.game_creation.desert_coverage, 4);
void GenerateLandscape(byte mode)
bool GenerateLandscape(byte mode)
/** Number of steps of landscape generation */
enum GenLandscapeSteps {
GLS_HEIGHTMAP = 3, ///< Loading a heightmap
GLS_TERRAGENESIS = 5, ///< Terragenesis generator
GLS_ORIGINAL = 2, ///< Original generator
@@ -1568,13 +1568,15 @@ void GenerateLandscape(byte mode)
GLS_OTHER = 0, ///< Extra steps for other landscapes
uint steps = (_settings_game.game_creation.landscape == LT_TROPIC) ? GLS_TROPIC : GLS_OTHER;
if (mode == GWM_HEIGHTMAP) {
SetGeneratingWorldProgress(GWP_LANDSCAPE, steps + GLS_HEIGHTMAP);
LoadHeightmap(_file_to_saveload.detail_ftype, _file_to_saveload.name.c_str());
if (!LoadHeightmap(_file_to_saveload.detail_ftype, _file_to_saveload.name.c_str())) {
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);
} else if (_settings_game.game_creation.land_generator == LG_TERRAGENESIS) {
SetGeneratingWorldProgress(GWP_LANDSCAPE, steps + GLS_TERRAGENESIS);
GenerateTerrainPerlin();
SetGeneratingWorldProgress(GWP_LANDSCAPE, steps + GLS_ORIGINAL);
@@ -1654,12 +1656,13 @@ void GenerateLandscape(byte mode)
default:
break;
CreateRivers();
void OnTick_Town();
void OnTick_Trees();
void OnTick_Station();
void OnTick_Industry();
@@ -136,9 +136,9 @@ bool HasFoundationNW(TileIndex tile, Slo
bool HasFoundationNE(TileIndex tile, Slope slope_here, uint z_here);
void DoClearSquare(TileIndex tile);
void RunTileLoop();
void InitializeLandscape();
void GenerateLandscape(byte mode);
bool GenerateLandscape(byte mode);
#endif /* LANDSCAPE_H */
@@ -162,13 +162,13 @@ static void ShowHelp()
" -b drv = Set the blitter to use (see below)\n"
" -r res = Set resolution (for instance 800x600)\n"
" -h = Display this help text\n"
" -t year = Set starting year\n"
" -d [[fac=]lvl[,...]]= Debug mode\n"
" -e = Start Editor\n"
" -g [savegame] = Start new/save game immediately\n"
" -g [savegame|scenario|heightmap] = Start new/savegame/scenario/heightmap immediately\n"
" -G seed = Set random seed\n"
" -n host[:port][#company]= Join network game\n"
" -p password = Password to join server\n"
" -P password = Password to join company\n"
" -D [host][:port] = Start dedicated server\n"
#if !defined(_WIN32)
@@ -574,27 +574,43 @@ int openttd_main(int argc, char *argv[])
#if defined(_WIN32)
CreateConsole();
#endif
if (mgo.opt != nullptr) SetDebugString(mgo.opt, ShowInfoI);
case 'e': _switch_mode = (_switch_mode == SM_LOAD_GAME || _switch_mode == SM_LOAD_SCENARIO ? SM_LOAD_SCENARIO : SM_EDITOR); break;
case 'e':
/* Allow for '-e' before or after '-g'. */
switch (_switch_mode) {
case SM_MENU: _switch_mode = SM_EDITOR; break;
case SM_LOAD_GAME: _switch_mode = SM_LOAD_SCENARIO; break;
case SM_START_HEIGHTMAP: _switch_mode = SM_LOAD_HEIGHTMAP; break;
default: break;
case 'g':
if (mgo.opt != nullptr) {
_file_to_saveload.name = mgo.opt;
bool is_scenario = _switch_mode == SM_EDITOR || _switch_mode == SM_LOAD_SCENARIO;
_switch_mode = is_scenario ? SM_LOAD_SCENARIO : SM_LOAD_GAME;
_file_to_saveload.SetMode(SLO_LOAD, is_scenario ? FT_SCENARIO : FT_SAVEGAME, DFT_GAME_FILE);
/* if the file doesn't exist or it is not a valid savegame, let the saveload code show an error */
auto t = _file_to_saveload.name.find_last_of('.');
if (t != std::string::npos) {
auto [ft, _] = FiosGetSavegameListCallback(SLO_LOAD, _file_to_saveload.name, _file_to_saveload.name.substr(t));
if (ft != FIOS_TYPE_INVALID) _file_to_saveload.SetMode(ft);
std::string extension = std::filesystem::path(_file_to_saveload.name).extension().string();
auto [ft, _] = FiosGetSavegameListCallback(SLO_LOAD, _file_to_saveload.name, extension);
if (ft == FIOS_TYPE_INVALID) {
std::tie(ft, _) = FiosGetScenarioListCallback(SLO_LOAD, _file_to_saveload.name, extension);
std::tie(ft, _) = FiosGetHeightmapListCallback(SLO_LOAD, _file_to_saveload.name, extension);
switch (GetAbstractFileType(ft)) {
case FT_SAVEGAME: _switch_mode = (_switch_mode == SM_EDITOR ? SM_LOAD_SCENARIO : SM_LOAD_GAME); break;
case FT_SCENARIO: _switch_mode = (_switch_mode == SM_EDITOR ? SM_LOAD_SCENARIO : SM_LOAD_GAME); break;
case FT_HEIGHTMAP: _switch_mode = (_switch_mode == SM_EDITOR ? SM_LOAD_HEIGHTMAP : SM_START_HEIGHTMAP); break;
_file_to_saveload.SetMode(SLO_LOAD, GetAbstractFileType(ft), GetDetailedFileType(ft));
_switch_mode = SM_NEWGAME;
/* Give a random map if no seed has been given */
if (scanner->generation_seed == GENERATE_NEW_SEED) {
@@ -1128,12 +1144,14 @@ void SwitchToMode(SwitchMode new_mode)
UpdateSocialIntegration(GM_NORMAL);
case SM_LOAD_HEIGHTMAP: // Load heightmap from scenario editor
SetLocalCompany(OWNER_NONE);
_game_mode = GM_EDITOR;
GenerateWorld(GWM_HEIGHTMAP, 1 << _settings_game.game_creation.map_x, 1 << _settings_game.game_creation.map_y);
GenerateSavegameId();
UpdateSocialIntegration(GM_EDITOR);
@@ -201,14 +201,14 @@ void VideoDriver_Dedicated::MainLoop()
/* Load the dedicated server stuff */
_is_network_server = true;
_network_dedicated = true;
_current_company = _local_company = COMPANY_SPECTATOR;
/* If SwitchMode is SM_LOAD_GAME, it means that the user used the '-g' options */
if (_switch_mode != SM_LOAD_GAME) {
/* If SwitchMode is SM_LOAD_GAME / SM_START_HEIGHTMAP, it means that the user used the '-g' options */
if (_switch_mode != SM_LOAD_GAME && _switch_mode != SM_START_HEIGHTMAP) {
StartNewGameWithoutGUI(GENERATE_NEW_SEED);
this->is_game_threaded = false;
/* Done loading, start game! */
Status change: