diff --git a/Makefile b/Makefile index 4e03dcdf9b..cd155a6f11 100644 --- a/Makefile +++ b/Makefile @@ -639,6 +639,7 @@ SRCS += aircraft_gui.c SRCS += airport.c SRCS += airport_gui.c SRCS += aystar.c +SRCS += bmp.c SRCS += bridge_gui.c SRCS += bridge_map.c SRCS += callback_table.c @@ -661,9 +662,12 @@ SRCS += engine.c SRCS += engine_gui.c SRCS += fileio.c SRCS += fios.c +SRCS += genworld.c +SRCS += genworld_gui.c SRCS += gfx.c SRCS += gfxinit.c SRCS += graph_gui.c +SRCS += heightmap.c SRCS += industry_cmd.c SRCS += industry_gui.c SRCS += intro_gui.c @@ -732,6 +736,7 @@ SRCS += strings.c SRCS += subsidy_gui.c SRCS += terraform_gui.c SRCS += texteff.c +SRCS += tgp.c SRCS += thread.c SRCS += tile.c SRCS += town_cmd.c diff --git a/bmp.c b/bmp.c new file mode 100644 index 0000000000..ee53821237 --- /dev/null +++ b/bmp.c @@ -0,0 +1,378 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "gfx.h" +#include "bmp.h" +#include "macros.h" + +void BmpInitializeBuffer(BmpBuffer *buffer, FILE *file) { + buffer->pos = -1; + buffer->file = file; + buffer->read = 0; + buffer->real_pos = ftell(file); +} + +static inline void AdvanceBuffer(BmpBuffer *buffer) +{ + buffer->read = fread(buffer->data, 1, BMP_BUFFER_SIZE, buffer->file); + buffer->pos = 0; +} + +static inline bool EndOfBuffer(BmpBuffer *buffer) +{ + if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer); + return buffer->pos == buffer->read; +} + +static inline byte ReadByte(BmpBuffer *buffer) +{ + if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer); + buffer->real_pos++; + return buffer->data[buffer->pos++]; +} + +static inline uint16 ReadWord(BmpBuffer *buffer) +{ + uint16 var = ReadByte(buffer); + return var | (ReadByte(buffer) << 8); +} + +static inline uint32 ReadDword(BmpBuffer *buffer) +{ + uint32 var = ReadWord(buffer); + return var | (ReadWord(buffer) << 16); +} + +static inline void SkipBytes(BmpBuffer *buffer, int bytes) +{ + int i; + for (i = 0; i < bytes; i++) ReadByte(buffer); +} + +static inline void SetStreamOffset(BmpBuffer *buffer, int offset) +{ + fseek(buffer->file, offset, SEEK_SET); + buffer->pos = -1; + buffer->real_pos = offset; + AdvanceBuffer(buffer); +} + +/** + * Reads a 1 bpp uncompressed bitmap + * The bitmap is converted to a 8 bpp bitmap + */ +static inline bool BmpRead1(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint x, y, i; + byte pad = GB(4 - info->width / 8, 0, 2); + byte *pixel_row; + byte b; + for (y = info->height; y > 0; y--) { + x = 0; + pixel_row = &data->bitmap[(y - 1) * info->width]; + while (x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + b = ReadByte(buffer); + for (i = 8; i > 0; i--) { + if (x < info->width) *pixel_row++ = GB(b, i - 1, 1); + x++; + } + } + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/** + * Reads a 4 bpp uncompressed bitmap + * The bitmap is converted to a 8 bpp bitmap + */ +static inline bool BmpRead4(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint x, y; + byte pad = GB(4 - info->width / 2, 0, 2); + byte *pixel_row; + byte b; + for (y = info->height; y > 0; y--) { + x = 0; + pixel_row = &data->bitmap[(y - 1) * info->width]; + while (x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + b = ReadByte(buffer); + *pixel_row++ = GB(b, 4, 4); + x++; + if (x < info->width) { + *pixel_row++ = GB(b, 0, 4); + x++; + } + } + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/** + * Reads a 4-bit RLE compressed bitmap + * The bitmap is converted to a 8 bpp bitmap + */ +static inline bool BmpRead4Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint i; + uint x = 0; + uint y = info->height - 1; + byte n, c, b; + byte *pixel = &data->bitmap[y * info->width]; + while (y != 0 || x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + n = ReadByte(buffer); + c = ReadByte(buffer); + if (n == 0) { + switch (c) { + case 0: // end of line + x = 0; + pixel = &data->bitmap[--y * info->width]; + break; + case 1: // end of bitmap + x = info->width; + y = 0; + pixel = NULL; + break; + case 2: // delta + x += ReadByte(buffer); + i = ReadByte(buffer); + if (x >= info->width || (y == 0 && i > 0)) return false; + y -= i; + pixel = &data->bitmap[y * info->width + x]; + break; + default: // uncompressed + i = 0; + while (i++ < c) { + if (EndOfBuffer(buffer) || x >= info->width) return false; + b = ReadByte(buffer); + *pixel++ = GB(b, 4, 4); + x++; + if (x < info->width && i++ < c) { + *pixel++ = GB(b, 0, 4); + x++; + } + } + /* Padding for 16 bit align */ + SkipBytes(buffer, ((c + 1) / 2) % 2); + break; + } + } else { + i = 0; + while (i++ < n) { + if (EndOfBuffer(buffer) || x >= info->width) return false; + *pixel++ = GB(c, 4, 4); + x++; + if (x < info->width && i++ < n) { + *pixel++ = GB(c, 0, 4); + x++; + } + } + } + } + return true; +} + +/** + * Reads a 8 bpp bitmap + */ +static inline bool BmpRead8(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint i; + uint y; + byte pad = GB(4 - info->width, 0, 2); + byte *pixel; + for (y = info->height; y > 0; y--) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + pixel = &data->bitmap[(y - 1) * info->width]; + for (i = 0; i < info->width; i++) *pixel++ = ReadByte(buffer); + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/** + * Reads a 8-bit RLE compressed bpp bitmap + */ +static inline bool BmpRead8Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint i; + uint x = 0; + uint y = info->height - 1; + byte n, c; + byte *pixel = &data->bitmap[y * info->width]; + while (y != 0 || x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + n = ReadByte(buffer); + c = ReadByte(buffer); + if (n == 0) { + switch (c) { + case 0: // end of line + x = 0; + pixel = &data->bitmap[--y * info->width]; + break; + case 1: // end of bitmap + x = info->width; + y = 0; + pixel = NULL; + break; + case 2: // delta + x += ReadByte(buffer); + i = ReadByte(buffer); + if (x >= info->width || (y == 0 && i > 0)) return false; + y -= i; + pixel = &data->bitmap[y * info->width + x]; + break; + default: // uncompressed + if ((x += c) > info->width) return false; + for (i = 0; i < c; i++) *pixel++ = ReadByte(buffer); + /* Padding for 16 bit align */ + SkipBytes(buffer, c % 2); + break; + } + } else { + for (i = 0; i < n; i++) { + if (x >= info->width) return false; + *pixel++ = c; + x++; + } + } + } + return true; +} + +/** + * Reads a 24 bpp uncompressed bitmap + */ +static inline bool BmpRead24(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint x, y; + byte pad = GB(4 - info->width * 3, 0, 2); + byte *pixel_row; + for (y = info->height; y > 0; y--) { + pixel_row = &data->bitmap[(y - 1) * info->width * 3]; + for (x = 0; x < info->width; x++) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + *(pixel_row + 2) = ReadByte(buffer); // green + *(pixel_row + 1) = ReadByte(buffer); // blue + *pixel_row = ReadByte(buffer); // red + pixel_row += 3; + } + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/* + * Reads bitmap headers, and palette (if any) + */ +bool BmpReadHeader(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint32 header_size; + assert(info != NULL); + + /* Reading BMP header */ + if (ReadWord(buffer) != 0x4D42) return false; // signature should be 'BM' + SkipBytes(buffer, 8); // skip file size and reserved + info->offset = ReadDword(buffer); + + /* Reading info header */ + header_size = ReadDword(buffer); + if (header_size < 12) return false; // info header should be at least 12 bytes long + + info->os2_bmp = (header_size == 12); // OS/2 1.x or windows 2.x info header is 12 bytes long + + if (info->os2_bmp) { + info->width = ReadWord(buffer); + info->height = ReadWord(buffer); + header_size -= 8; + } else { + info->width = ReadDword(buffer); + info->height = ReadDword(buffer); + header_size -= 12; + } + + if (ReadWord(buffer) != 1) return false; // BMP can have only 1 plane + + info->bpp = ReadWord(buffer); + if (info->bpp != 1 && info->bpp != 4 && info->bpp != 8 && info->bpp != 24) { + /* Only 1 bpp, 4 bpp, 8bpp and 24 bpp bitmaps are supported */ + return false; + } + + /* Reads compression method if available in info header*/ + if ((header_size -= 4) >= 4) { + info->compression = ReadDword(buffer); + header_size -= 4; + } + + /* Only 4-bit and 8-bit rle compression is supported */ + if (info->compression > 2 || (info->compression > 0 && !(info->bpp == 4 || info->bpp == 8))) return false; + + if (info->bpp <= 8) { + uint i; + + /* Reads number of colors if available in info header */ + if (header_size >= 16) { + SkipBytes(buffer, 12); // skip image size and resolution + info->palette_size = ReadDword(buffer); // number of colors in palette + SkipBytes(buffer, header_size - 16); // skip the end of info header + } + if (info->palette_size == 0) info->palette_size = 1 << info->bpp; + + data->palette = calloc(info->palette_size, sizeof(*(data->palette))); + if (data->palette == NULL) return false; + + for (i = 0; i < info->palette_size; i++) { + data->palette[i].b = ReadByte(buffer); + data->palette[i].g = ReadByte(buffer); + data->palette[i].r = ReadByte(buffer); + if (!info->os2_bmp) SkipBytes(buffer, 1); // unused + } + } + + return buffer->real_pos <= info->offset; +} + +/* + * Reads the bitmap + * 1 bpp and 4 bpp bitmaps are converted to 8 bpp bitmaps + */ +bool BmpReadBitmap(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + assert(info != NULL && data != NULL); + + data->bitmap = calloc(info->width * info->height, ((info->bpp == 24) ? 3 : 1) * sizeof(byte)); + if (data->bitmap == NULL) return false; + + /* Load image */ + SetStreamOffset(buffer, info->offset); + switch (info->compression) { + case 0: // no compression + switch (info->bpp) { + case 1: return BmpRead1(buffer, info, data); + case 4: return BmpRead4(buffer, info, data); + case 8: return BmpRead8(buffer, info, data); + case 24: return BmpRead24(buffer, info, data); + default: NOT_REACHED(); return false; + } + case 1: return BmpRead8Rle(buffer, info, data); // 8-bit RLE compression + case 2: return BmpRead4Rle(buffer, info, data); // 4-bit RLE compression + default: NOT_REACHED(); return false; + } +} + +void BmpDestroyData(BmpData *data) +{ + assert(data != NULL); + free(data->palette); + free(data->bitmap); +} diff --git a/bmp.h b/bmp.h new file mode 100644 index 0000000000..0587bab656 --- /dev/null +++ b/bmp.h @@ -0,0 +1,36 @@ +/* $Id$ */ + +#ifndef BMP_H +#define BMP_H + +typedef struct { + uint32 offset; ///< offset of bitmap data from .bmp file begining + uint32 width; ///< bitmap width + uint32 height; ///< bitmap height + bool os2_bmp; ///< true if OS/2 1.x or windows 2.x bitmap + uint16 bpp; ///< bits per pixel + uint32 compression; ///< compression method (0 = none, 1 = 8-bit RLE, 2 = 4-bit RLE) + uint32 palette_size; ///< number of colors in palette +} BmpInfo; + +typedef struct { + Colour *palette; + byte *bitmap; +} BmpData; + +#define BMP_BUFFER_SIZE 1024 + +typedef struct { + byte data[BMP_BUFFER_SIZE]; + int pos; + int read; + FILE *file; + uint real_pos; +} BmpBuffer; + +void BmpInitializeBuffer(BmpBuffer *buffer, FILE *file); +bool BmpReadHeader(BmpBuffer *buffer, BmpInfo *info, BmpData *data); +bool BmpReadBitmap(BmpBuffer *buffer, BmpInfo *info, BmpData *data); +void BmpDestroyData(BmpData *data); + +#endif /* BMP_H */ diff --git a/clear_cmd.c b/clear_cmd.c index 6d1016daa0..eab094d5d0 100644 --- a/clear_cmd.c +++ b/clear_cmd.c @@ -15,6 +15,7 @@ #include "variables.h" #include "table/sprites.h" #include "unmovable_map.h" +#include "genworld.h" typedef struct TerraformerHeightMod { TileIndex tile; @@ -693,22 +694,28 @@ static void TileLoop_Clear(TileIndex tile) void GenerateClearTile(void) { - uint i; + uint i, gi; TileIndex tile; - /* add hills */ + /* add rough tiles */ i = ScaleByMapSize(GB(Random(), 0, 10) + 0x400); + gi = ScaleByMapSize(GB(Random(), 0, 7) + 0x80); + + SetGeneratingWorldProgress(GWP_ROUGH_ROCKY, gi + i); do { + IncreaseGeneratingWorldProgress(GWP_ROUGH_ROCKY); tile = RandomTile(); - if (IsTileType(tile, MP_CLEAR)) SetClearGroundDensity(tile, CLEAR_ROUGH, 3); + if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_DESERT)) SetClearGroundDensity(tile, CLEAR_ROUGH, 3); } while (--i); - /* add grey squares */ - i = ScaleByMapSize(GB(Random(), 0, 7) + 0x80); + /* add rocky tiles */ + i = gi; do { uint32 r = Random(); tile = RandomTileSeed(r); - if (IsTileType(tile, MP_CLEAR)) { + + IncreaseGeneratingWorldProgress(GWP_ROUGH_ROCKY); + if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_DESERT)) { uint j = GB(r, 16, 4) + 5; for (;;) { TileIndex tile_new; @@ -717,7 +724,7 @@ void GenerateClearTile(void) do { if (--j == 0) goto get_out; tile_new = tile + TileOffsByDir(GB(Random(), 0, 2)); - } while (!IsTileType(tile_new, MP_CLEAR)); + } while (!IsTileType(tile_new, MP_CLEAR) || IsClearGround(tile_new, CLEAR_DESERT)); tile = tile_new; } get_out:; diff --git a/command.c b/command.c index ef8ef74dbe..a03c3c85b8 100644 --- a/command.c +++ b/command.c @@ -10,6 +10,7 @@ #include "player.h" #include "network.h" #include "variables.h" +#include "genworld.h" const char* _cmd_text = NULL; @@ -456,7 +457,7 @@ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, CommandCallback *callback, _docommand_recursive = 1; // cost estimation only? - if (_shift_pressed && IsLocalPlayer() && !(cmd & (CMD_NETWORK_COMMAND | CMD_SHOW_NO_ERROR))) { + if (!IsGeneratingWorld() && _shift_pressed && IsLocalPlayer() && !(cmd & (CMD_NETWORK_COMMAND | CMD_SHOW_NO_ERROR))) { // estimate the cost. res = proc(tile, flags, p1, p2); if (CmdFailed(res)) { diff --git a/console_cmds.c b/console_cmds.c index d006af5b78..ae716d79a4 100644 --- a/console_cmds.c +++ b/console_cmds.c @@ -20,6 +20,7 @@ #include "station.h" #include "strings.h" #include "screenshot.h" +#include "genworld.h" #include "date.h" #ifdef ENABLE_NETWORK @@ -871,17 +872,44 @@ DEF_CONSOLE_CMD(ConEchoC) return true; } +DEF_CONSOLE_CMD(ConNewGame) +{ + if (argc == 0) { + IConsoleHelp("Start a new game. Usage: 'newgame [seed]'"); + IConsoleHelp("The server can force a new game using 'newgame'; any client joined will rejoin after the server is done generating the new game."); + return true; + } + + StartNewGameWithoutGUI((argc == 2) ? (uint)atoi(argv[1]) : GENERATE_NEW_SEED); + return true; +} + extern void SwitchMode(int new_mode); -DEF_CONSOLE_CMD(ConNewGame) +DEF_CONSOLE_CMD(ConRestart) +{ + if (argc == 0) { + IConsoleHelp("Restart game. Usage: 'restart'"); + IConsoleHelp("Restarts a game. It tries to reproduce the exact same map as the game started with."); + return true; + } + + /* Don't copy the _newgame pointers to the real pointers, so call SwitchMode directly */ + _patches.map_x = MapLogX(); + _patches.map_y = FindFirstBit(MapSizeY()); + SwitchMode(SM_NEWGAME); + return true; +} + +DEF_CONSOLE_CMD(ConGetSeed) { if (argc == 0) { - IConsoleHelp("Start a new game. Usage: 'newgame'"); - IConsoleHelp("The server can force a new game using 'newgame', any client using it will part and start a single-player game"); + IConsoleHelp("Returns the seed used to create this game. Usage: 'getseed'"); + IConsoleHelp("The seed can be used to reproduce the exact same map as the game started with."); return true; } - GenRandomNewGame(Random(), InteractiveRandom()); + IConsolePrintF(_icolour_def, "Generation Seed: %u", _patches.generation_seed); return true; } @@ -1413,6 +1441,8 @@ void IConsoleStdLibRegister(void) IConsoleCmdRegister("list_vars", ConListVariables); IConsoleCmdRegister("list_aliases", ConListAliases); IConsoleCmdRegister("newgame", ConNewGame); + IConsoleCmdRegister("restart", ConRestart); + IConsoleCmdRegister("getseed", ConGetSeed); IConsoleCmdRegister("quit", ConExit); IConsoleCmdRegister("resetengines", ConResetEngines); IConsoleCmdRegister("return", ConReturn); diff --git a/engine.c b/engine.c index 43e515e7d9..bd7e72a3b7 100644 --- a/engine.c +++ b/engine.c @@ -30,6 +30,10 @@ enum { ENGINE_PREVIEWING = 4, }; +enum { + YEAR_ENGINE_AGING_STOPS = 2050, +}; + /** Bitmasked values of what type of cargo is refittable for the given vehicle-type. * This coupled with the landscape information (_landscape_global_cargo_mask) gives * us exactly what is refittable and what is not */ @@ -135,6 +139,8 @@ void StartupEngines(void) { Engine *e; const EngineInfo *ei; + /* Aging of vehicles stops, so account for that when starting late */ + const uint16 aging_date = min(_date, ConvertYMDToDate(YEAR_ENGINE_AGING_STOPS, 0, 1)); SetupEngineNames(); @@ -152,7 +158,7 @@ void StartupEngines(void) r = Random(); e->intro_date = ei->base_intro <= ConvertYMDToDate(1922, 0, 1) ? ei->base_intro : (Date)GB(r, 0, 9) + ei->base_intro; if (e->intro_date <= _date) { - e->age = (_date - e->intro_date) >> 5; + e->age = (aging_date - e->intro_date) >> 5; e->player_avail = (byte)-1; e->flags |= ENGINE_AVAILABLE; } @@ -237,7 +243,7 @@ void EnginesDailyLoop(void) { EngineID i; - if (_cur_year >= 2050) return; + if (_cur_year >= YEAR_ENGINE_AGING_STOPS) return; for (i = 0; i != lengthof(_engines); i++) { Engine *e = &_engines[i]; @@ -358,7 +364,7 @@ void EnginesMonthlyLoop(void) { Engine *e; - if (_cur_year < 2050) { + if (_cur_year < YEAR_ENGINE_AGING_STOPS) { for (e = _engines; e != endof(_engines); e++) { // Age the vehicle if (e->flags & ENGINE_AVAILABLE && e->age != 0xFFFF) { diff --git a/fios.c b/fios.c index e211929602..ef28557912 100644 --- a/fios.c +++ b/fios.c @@ -10,6 +10,7 @@ #include "string.h" #include "variables.h" #include "functions.h" +#include "heightmap.h" #include "table/strings.h" #include "fios.h" #include @@ -135,7 +136,10 @@ char *FiosBrowseTo(const FiosItem *item) case FIOS_TYPE_FILE: case FIOS_TYPE_OLDFILE: case FIOS_TYPE_SCENARIO: - case FIOS_TYPE_OLD_SCENARIO: { + case FIOS_TYPE_OLD_SCENARIO: + case FIOS_TYPE_PNG: + case FIOS_TYPE_BMP: + { static char str_buffr[512]; #if defined(__MORPHOS__) || defined(__AMIGAOS__) @@ -357,15 +361,45 @@ static byte FiosGetScenarioListCallback(int mode, const char *file, const char * */ FiosItem *FiosGetScenarioList(int mode) { - static char *fios_scn_path = NULL; + static char *_fios_scn_path = NULL; - /* Copy the default path on first run or on 'New Game' */ - if (mode == SLD_NEW_GAME || fios_scn_path == NULL) { - if (fios_scn_path == NULL) fios_scn_path = malloc(MAX_PATH); - ttd_strlcpy(fios_scn_path, _path.scenario_dir, MAX_PATH); + if (_fios_scn_path == NULL) { + _fios_scn_path = malloc(MAX_PATH); + ttd_strlcpy(_fios_scn_path, _path.scenario_dir, MAX_PATH); } - _fios_path = fios_scn_path; + _fios_path = _fios_scn_path; return FiosGetFileList(mode, &FiosGetScenarioListCallback); } + +static byte FiosGetHeightmapListCallback(int mode, const char *file, const char *ext, char *title) +{ + /* Show heightmap files + * .PNG PNG Based heightmap files + * .BMP BMP Based heightmap files + */ + +#ifdef WITH_PNG + if (strcasecmp(ext, ".png") == 0) return FIOS_TYPE_PNG; +#endif /* WITH_PNG */ + + if (strcasecmp(ext, ".bmp") == 0) return FIOS_TYPE_BMP; + + return FIOS_TYPE_INVALID; +} + +// Get a list of Heightmaps +FiosItem *FiosGetHeightmapList(int mode) +{ + static char *_fios_hmap_path = NULL; + + if (_fios_hmap_path == NULL) { + _fios_hmap_path = malloc(MAX_PATH); + strcpy(_fios_hmap_path, _path.heightmap_dir); + } + + _fios_path = _fios_hmap_path; + + return FiosGetFileList(mode, &FiosGetHeightmapListCallback); +} diff --git a/fios.h b/fios.h index 5b29c40b63..882490bc4b 100644 --- a/fios.h +++ b/fios.h @@ -20,6 +20,8 @@ enum { FIOS_TYPE_SCENARIO = 5, FIOS_TYPE_OLD_SCENARIO = 6, FIOS_TYPE_DIRECT = 7, + FIOS_TYPE_PNG = 8, + FIOS_TYPE_BMP = 9, FIOS_TYPE_INVALID = 255, }; @@ -32,6 +34,8 @@ extern int _saveload_mode; // defined in misc_gui.c FiosItem *FiosGetSavegameList(int mode); // Get a list of scenarios FiosItem *FiosGetScenarioList(int mode); +// Get a list of Heightmaps +FiosItem *FiosGetHeightmapList(int mode); // Free the list of savegames void FiosFreeSavegameList(void); // Browse to. Returns a filename w/path if we reached a file. diff --git a/functions.h b/functions.h index a108d5f833..e9cad80c9e 100644 --- a/functions.h +++ b/functions.h @@ -204,8 +204,6 @@ TileIndex AdjustTileCoordRandomly(TileIndex a, byte rng); void AfterLoadTown(void); void UpdatePatches(void); -void GenRandomNewGame(uint32 rnd1, uint32 rnd2); -void StartScenarioEditor(uint32 rnd1, uint32 rnd2); void AskExitGame(void); void AskExitToGameMenu(void); @@ -216,11 +214,12 @@ StringID RemapOldStringID(StringID s); void UpdateViewportSignPos(ViewportSign *sign, int left, int top, StringID str); enum { - SLD_LOAD_GAME = 0, - SLD_LOAD_SCENARIO = 1, - SLD_SAVE_GAME = 2, - SLD_SAVE_SCENARIO = 3, - SLD_NEW_GAME = 4, + SLD_LOAD_GAME, + SLD_LOAD_SCENARIO, + SLD_SAVE_GAME, + SLD_SAVE_SCENARIO, + SLD_LOAD_HEIGHTMAP, + SLD_NEW_GAME, }; void ShowSaveLoadDialog(int mode); diff --git a/genworld.c b/genworld.c new file mode 100644 index 0000000000..3ecb0132b1 --- /dev/null +++ b/genworld.c @@ -0,0 +1,273 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "functions.h" +#include "player.h" +#include "table/sprites.h" +#include "variables.h" +#include "thread.h" +#include "genworld.h" +#include "gfx.h" +#include "gfxinit.h" +#include "gui.h" +#include "network.h" +#include "debug.h" +#include "settings.h" +#include "heightmap.h" + +void GenerateLandscape(byte mode); +void GenerateClearTile(void); +void GenerateIndustries(void); +void GenerateUnmovables(void); +bool GenerateTowns(void); +void GenerateTrees(void); + +void StartupEconomy(void); +void StartupPlayers(void); +void StartupDisasters(void); + +void InitializeGame(int mode, uint size_x, uint size_y); + +void ConvertGroundTilesIntoWaterTiles(void); + +/* Please only use this variable in genworld.h and genworld.c and + * nowhere else. For speed improvements we need it to be global, but + * in no way the meaning of it is to use it anywhere else besides + * in the genworld.h and genworld.c! -- TrueLight */ +gw_info _gw; + +/** + * Set the status of the Paint flag. + * If it is true, the thread will hold with any futher generating till + * the drawing of the screen is done. This is handled by + * SetGeneratingWorldProgress(), so calling that function will stall + * from time to time. + */ +void SetGeneratingWorldPaintStatus(bool status) +{ + _gw.wait_for_draw = status; +} + +/** + * Returns true if the thread wants the main program to do a (full) paint. + * If this returns false, please do not update the screen. Because we are + * writing in a thread, it can cause damaged data (reading and writing the + * same tile at the same time). + */ +bool IsGeneratingWorldReadyForPaint(void) +{ + /* If we are in quit_thread mode, ignore this and always return false. This + * forces the screen to not be drawn, and the GUI not to wait for a draw. */ + if (!_gw.active || _gw.quit_thread || !_gw.threaded) return false; + + return _gw.wait_for_draw; +} + +/** + * Tells if the world generation is done in a thread or not. + */ +bool IsGenerateWorldThreaded(void) +{ + return _gw.threaded && !_gw.quit_thread; +} + +/** + * The internal, real, generate function. + */ +static void *_GenerateWorld(void *arg) +{ + _generating_world = true; + if (_network_dedicated) DEBUG(net, 0)("Generating map, please wait..."); + /* Set the Random() seed to generation_seed so we produce the same map with the same seed */ + if (_patches.generation_seed == GENERATE_NEW_SEED) _patches.generation_seed = _patches_newgame.generation_seed = InteractiveRandom(); + _random_seeds[0][0] = _random_seeds[0][1] = _patches.generation_seed; + SetGeneratingWorldProgress(GWP_MAP_INIT, 2); + SetObjectToPlace(SPR_CURSOR_ZZZ, 0, 0, 0); + + 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 == GW_EMPTY) { + SetGeneratingWorldProgress(GWP_UNMOVABLE, 1); + + /* Make the map the height of the patch setting */ + if (_game_mode != GM_MENU) FlatEmptyWorld(_patches.se_flat_world_height); + + ConvertGroundTilesIntoWaterTiles(); + IncreaseGeneratingWorldProgress(GWP_UNMOVABLE); + } else { + GenerateLandscape(_gw.mode); + GenerateClearTile(); + + // only generate towns, tree and industries in newgame mode. + if (_game_mode != GM_EDITOR) { + GenerateTowns(); + GenerateIndustries(); + GenerateUnmovables(); + GenerateTrees(); + } + } + + // These are probably pointless when inside the scenario editor. + SetGeneratingWorldProgress(GWP_GAME_INIT, 3); + StartupPlayers(); + 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 != GW_EMPTY) { + uint i; + + SetGeneratingWorldProgress(GWP_RUNTILELOOP, 0x500); + for (i = 0; i < 0x500; i++) { + RunTileLoop(); + IncreaseGeneratingWorldProgress(GWP_RUNTILELOOP); + } + } + + ResetObjectToPlace(); + _local_player = _gw.lp; + + SetGeneratingWorldProgress(GWP_GAME_START, 1); + /* Call any callback */ + if (_gw.proc != NULL) _gw.proc(); + IncreaseGeneratingWorldProgress(GWP_GAME_START); + + if (_cursor.sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE); + /* Show all vital windows again, because we have hidden them */ + if (_gw.threaded) ShowVitalWindows(); + _gw.active = false; + _gw.thread = NULL; + _gw.proc = NULL; + _gw.threaded = false; + + DeleteWindowById(WC_GENERATE_PROGRESS_WINDOW, 0); + MarkWholeScreenDirty(); + + if (_network_dedicated) DEBUG(net, 0)("Map generated, starting game"); + + return NULL; +} + +/** + * Set here the function, if any, that you want to be called when landscape + * generation is done. + */ +void GenerateWorldSetCallback(gw_done_proc *proc) +{ + _gw.proc = proc; +} + +/** + * This will wait for the thread to finish up his work. It will not continue + * till the work is done. + */ +void WaitTillGeneratedWorld(void) +{ + if (_gw.thread == NULL) return; + _gw.quit_thread = true; + OTTDJoinThread(_gw.thread); + _gw.thread = NULL; + _gw.threaded = false; +} + +/** + * Initializes the abortion process + */ +void AbortGeneratingWorld(void) +{ + _gw.abort = true; +} + +/** + * Is the generation being aborted? + */ +bool IsGeneratingWorldAborted(void) +{ + return _gw.abort; +} + +/** + * Really handle the abortion, i.e. clean up some of the mess + */ +void HandleGeneratingWorldAbortion(void) +{ + /* Clean up - in SE create an empty map, otherwise, go to intro menu */ + _switch_mode = (_game_mode == GM_EDITOR) ? SM_EDITOR : SM_MENU; + + if (_cursor.sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE); + /* Show all vital windows again, because we have hidden them */ + if (_gw.threaded) ShowVitalWindows(); + _gw.active = false; + _gw.thread = NULL; + _gw.proc = NULL; + _gw.threaded = false; + + DeleteWindowById(WC_GENERATE_PROGRESS_WINDOW, 0); + MarkWholeScreenDirty(); + + OTTDExitThread(); +} + +/** + * Generate a world. + * @param mode The mode of world generation (@see GenerateWorldModes). + * @param size_x The X-size of the map. + * @param size_y The Y-size of the map. + */ +void GenerateWorld(int mode, uint size_x, uint size_y) +{ + if (_gw.active) return; + _gw.mode = mode; + _gw.size_x = size_x; + _gw.size_y = size_y; + _gw.active = true; + _gw.abort = false; + _gw.lp = _local_player; + _gw.wait_for_draw = false; + _gw.quit_thread = false; + _gw.threaded = true; + + /* This disables some commands and stuff */ + _local_player = OWNER_SPECTATOR; + /* Make sure everything is done via OWNER_NONE */ + _current_player = OWNER_NONE; + + InitializeGame(IG_DATE_RESET, _gw.size_x, _gw.size_y); + PrepareGenerateWorldProgress(); + + /* Re-init the windowing system */ + ResetWindowSystem(); + LoadStringWidthTable(); + + /* Create toolbars */ + SetupColorsAndInitialWindow(); + + if (_network_dedicated || (_gw.thread = OTTDCreateThread(&_GenerateWorld, (void *)"")) == NULL) { + _gw.threaded = false; + _GenerateWorld(NULL); + } else { + /* Remove any open window */ + DeleteAllNonVitalWindows(); + + /* Don't show the dialog if we don't have a thread */ + ShowGenerateWorldProgress(); + } + + /* Zoom out and center on the map (is pretty ;)) */ + if (FindWindowById(WC_MAIN_WINDOW, 0) != NULL) { + while (DoZoomInOutWindow(ZOOM_OUT, FindWindowById(WC_MAIN_WINDOW, 0) ) ) {} + ScrollMainWindowToTile(TileXY(MapSizeX() / 2, MapSizeY() / 2)); + } + + /* Hide vital windows, because we don't allow to use them */ + /* XXX -- Ideal it is done after ShowGenerateWorldProgress, but stupid + * enough, DoZoomInOutWindow _needs_ the toolbar to exist... */ + if (_gw.thread != NULL) HideVitalWindows(); +} diff --git a/genworld.h b/genworld.h new file mode 100644 index 0000000000..bb882b1c19 --- /dev/null +++ b/genworld.h @@ -0,0 +1,91 @@ +/* $Id$ */ + +#ifndef GENWORLD_H +#define GENWORLD_H + +/* If OTTDThread isn't defined, define it to a void, but make sure to undefine + * it after this include. This makes including genworld.h easier, as you + * don't need to include thread.h before it, while it stays possible to + * include it after it, and still work. + */ +#ifndef OTTDThread +#define TEMPORARY_OTTDTHREAD_DEFINITION +#define OTTDThread void +#endif + +/* + * Order of these enums has to be the same as in lang/english.txt + * Otherwise you will get inconsistent behaviour. + */ +enum { + LG_ORIGINAL = 0, //! The original landscape generator + LG_TERRAGENESIS = 1, //! TerraGenesis Perlin landscape generator + + GENERATE_NEW_SEED = (uint)-1, //! Create a new random seed +}; + +typedef void gw_done_proc(void); + +typedef struct gw_info { + bool active; //! Is generating world active + bool abort; //! Whether to abort the thread ASAP + bool wait_for_draw; //! Are we waiting on a draw event + bool quit_thread; //! Do we want to quit the active thread + bool threaded; //! Whether we run _GenerateWorld threaded + int mode; //! What mode are we making a world in + byte lp; //! The local_player before generating + uint size_x; //! X-size of the map + uint size_y; //! Y-size of the map + gw_done_proc *proc; //! Proc that is called when done (can be NULL) + OTTDThread *thread; //! The thread we are in (can be NULL) +} gw_info; + +#ifdef TEMPORARY_OTTDTHREAD_DEFINITION +#undef OTTDThread +#undef TEMPORARY_OTTDTHREAD_DEFINITION +#endif + +typedef enum gwp_classes { + GWP_MAP_INIT, /* Initialize/allocate the map, start economy */ + GWP_LANDSCAPE, /* Create the landscape */ + GWP_ROUGH_ROCKY, /* Make rough and rocky areas */ + GWP_TOWN, /* Generate towns */ + GWP_INDUSTRY, /* Generate industries */ + GWP_UNMOVABLE, /* Generate unmovables (radio tower, light houses) */ + GWP_TREE, /* Generate trees */ + GWP_GAME_INIT, /* Initialize the game */ + GWP_RUNTILELOOP, /* Runs the tile loop 1280 times to make snow etc */ + GWP_GAME_START, /* Really prepare to start the game */ + GWP_CLASS_COUNT +} gwp_class; + +/** + * Check if we are currently in the process of generating a world. + */ +static inline bool IsGeneratingWorld(void) +{ + extern gw_info _gw; + + return _gw.active; +} + +/* genworld.c */ +void SetGeneratingWorldPaintStatus(bool status); +bool IsGeneratingWorldReadyForPaint(void); +bool IsGenerateWorldThreaded(void); +void GenerateWorldSetCallback(gw_done_proc *proc); +void WaitTillGeneratedWorld(void); +void GenerateWorld(int mode, uint size_x, uint size_y); +void AbortGeneratingWorld(void); +bool IsGeneratingWorldAborted(void); +void HandleGeneratingWorldAbortion(void); + +/* genworld_gui.c */ +void SetGeneratingWorldProgress(gwp_class class, uint total); +void IncreaseGeneratingWorldProgress(gwp_class class); +void PrepareGenerateWorldProgress(void); +void ShowGenerateWorldProgress(void); +void StartNewGameWithoutGUI(uint seed); +void ShowCreateScenario(void); + +#endif /* GENWORLD_H */ diff --git a/genworld_gui.c b/genworld_gui.c new file mode 100644 index 0000000000..fc21a6ab8d --- /dev/null +++ b/genworld_gui.c @@ -0,0 +1,912 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "heightmap.h" +#include "functions.h" +#include "table/strings.h" +#include "window.h" +#include "gui.h" +#include "gfx.h" +#include "strings.h" +#include "gfxinit.h" +#include "player.h" +#include "command.h" +#include "sound.h" +#include "variables.h" +#include "string.h" +#include "settings.h" +#include "debug.h" +#include "genworld.h" +#include "network.h" +#include "thread.h" +#include "date.h" + +enum { + START_DATE_QUERY, + SNOW_LINE_QUERY, + FLAT_WORLD_HEIGHT_QUERY, + + LEN_RND_SEED = 11, + SEED_EDIT = 15, +}; + +/** + * In what 'mode' the GenerateLandscapeWindowProc is. + */ +typedef enum glwp_modes { + GLWP_GENERATE, + GLWP_HEIGHTMAP, + GLWP_SCENARIO, + GLWP_END +} glwp_modes; + +static char _edit_str_buf[LEN_RND_SEED]; +static uint _heightmap_x = 0; +static uint _heightmap_y = 0; +static StringID _heightmap_str = STR_NULL; +static bool _goto_editor = false; + +extern void SwitchMode(int new_mode); + +static inline void SetNewLandscapeType(byte landscape) +{ + _opt_newgame.landscape = landscape; + InvalidateWindowClasses(WC_SELECT_GAME); + InvalidateWindowClasses(WC_GENERATE_LANDSCAPE); +} + +// no longer static to allow calling from outside module +const Widget _generate_landscape_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 13, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 13, 11, 337, 0, 13, STR_WORLD_GENERATION_CAPTION,STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 13, 0, 337, 14, 267, STR_NULL, STR_NULL}, + +{ WWT_PANEL_2, RESIZE_NONE, 12, 10, 86, 24, 78, 0x1312, STR_030E_SELECT_TEMPERATE_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 90, 166, 24, 78, 0x1314, STR_030F_SELECT_SUB_ARCTIC_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 170, 246, 24, 78, 0x1316, STR_0310_SELECT_SUB_TROPICAL_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 250, 326, 24, 78, 0x1318, STR_0311_SELECT_TOYLAND_LANDSCAPE}, + +{ WWT_PANEL, RESIZE_NONE, 12, 114, 149, 90, 101, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 150, 161, 90, 101, STR_0225, STR_NULL}, // Mapsize X +{ WWT_PANEL, RESIZE_NONE, 12, 180, 215, 90, 101, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 216, 227, 90, 101, STR_0225, STR_NULL}, // Mapsize Y + +{ WWT_PANEL, RESIZE_NONE, 12, 114, 163, 112, 123, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 164, 175, 112, 123, STR_0225, STR_NULL}, // Number of towns +{ WWT_PANEL, RESIZE_NONE, 12, 114, 163, 130, 141, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 164, 175, 130, 141, STR_0225, STR_NULL}, // Number of industries + +{ WWT_IMGBTN, RESIZE_NONE, 15, 114, 194, 152, 163, STR_NULL, STR_RANDOM_SEED_HELP}, // Edit box for seed +{ WWT_TEXTBTN, RESIZE_NONE, 12, 203, 285, 152, 163, STR_RANDOM, STR_RANDOM_HELP}, + +{ WWT_TEXTBTN, RESIZE_NONE, 6, 243, 326, 228, 257, STR_GENERATE, STR_NULL}, // Generate button + +{ WWT_IMGBTN, RESIZE_NONE, 12, 216, 227, 112, 123, SPR_ARROW_DOWN, STR_029E_MOVE_THE_STARTING_DATE}, +{ WWT_PANEL, RESIZE_NONE, 12, 228, 314, 112, 123, 0x0, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 12, 315, 326, 112, 123, SPR_ARROW_UP, STR_029F_MOVE_THE_STARTING_DATE}, + +{ WWT_IMGBTN, RESIZE_NONE, 12, 282, 293, 130, 141, SPR_ARROW_DOWN, STR_SNOW_LINE_DOWN}, +{ WWT_PANEL, RESIZE_NONE, 12, 294, 314, 130, 141, 0x0, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 12, 315, 326, 130, 141, SPR_ARROW_UP, STR_SNOW_LINE_UP}, + +{ WWT_PANEL, RESIZE_NONE, 12, 114, 219, 192, 203, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 220, 231, 192, 203, STR_0225, STR_NULL}, // Tree placer + +{ WWT_EMPTY, RESIZE_NONE, 12, 114, 231, 174, 185, STR_NULL, STR_NULL}, +//{ WWT_PANEL, RESIZE_NONE, 12, 114, 219, 174, 185, STR_NULL, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 12, 114, 231, 174, 185, STR_NULL, STR_NULL}, +//{ WWT_TEXTBTN, RESIZE_NONE, 12, 220, 231, 174, 185, STR_0225, STR_NULL}, // Landscape generator +//{ WWT_PANEL, RESIZE_NONE, 12, 114, 219, 210, 221, STR_NULL, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 12, 114, 231, 210, 221, STR_NULL, STR_NULL}, +//{ WWT_TEXTBTN, RESIZE_NONE, 12, 220, 231, 210, 221, STR_0225, STR_NULL}, // Terrain type +//{ WWT_PANEL, RESIZE_NONE, 12, 114, 219, 228, 239, STR_NULL, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 12, 114, 231, 228, 239, STR_NULL, STR_NULL}, +//{ WWT_TEXTBTN, RESIZE_NONE, 12, 220, 231, 228, 239, STR_0225, STR_NULL}, // Water quantity +{ WWT_PANEL, RESIZE_NONE, 12, 113, 219, 246, 257, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 220, 231, 246, 257, STR_0225, STR_NULL}, // Map smoothness +{ WIDGETS_END}, +}; + +const Widget _heightmap_load_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 13, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 13, 11, 337, 0, 13, STR_WORLD_GENERATION_CAPTION,STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 13, 0, 337, 14, 235, STR_NULL, STR_NULL}, + +{ WWT_PANEL_2, RESIZE_NONE, 12, 10, 86, 24, 78, 0x1312, STR_030E_SELECT_TEMPERATE_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 90, 166, 24, 78, 0x1314, STR_030F_SELECT_SUB_ARCTIC_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 170, 246, 24, 78, 0x1316, STR_0310_SELECT_SUB_TROPICAL_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 250, 326, 24, 78, 0x1318, STR_0311_SELECT_TOYLAND_LANDSCAPE}, + +{ WWT_PANEL, RESIZE_NONE, 12, 114, 149, 112, 123, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 150, 161, 112, 123, STR_0225, STR_NULL}, // Mapsize X +{ WWT_PANEL, RESIZE_NONE, 12, 180, 215, 112, 123, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 216, 227, 112, 123, STR_0225, STR_NULL}, // Mapsize Y + +{ WWT_PANEL, RESIZE_NONE, 12, 114, 163, 134, 145, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 164, 175, 134, 145, STR_0225, STR_NULL}, // Number of towns +{ WWT_PANEL, RESIZE_NONE, 12, 114, 163, 152, 163, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 164, 175, 152, 163, STR_0225, STR_NULL}, // Number of industries + +{ WWT_IMGBTN, RESIZE_NONE, 15, 114, 194, 174, 185, STR_NULL, STR_RANDOM_SEED_HELP}, // Edit box for seed +{ WWT_TEXTBTN, RESIZE_NONE, 12, 203, 285, 174, 185, STR_RANDOM, STR_RANDOM_HELP}, + +{ WWT_TEXTBTN, RESIZE_NONE, 6, 243, 326, 196, 225, STR_GENERATE, STR_NULL}, // Generate button + +{ WWT_IMGBTN, RESIZE_NONE, 12, 216, 227, 134, 145, SPR_ARROW_DOWN, STR_029E_MOVE_THE_STARTING_DATE}, +{ WWT_PANEL, RESIZE_NONE, 12, 228, 314, 134, 145, 0x0, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 12, 315, 326, 134, 145, SPR_ARROW_UP, STR_029F_MOVE_THE_STARTING_DATE}, + +{ WWT_IMGBTN, RESIZE_NONE, 12, 282, 293, 152, 163, SPR_ARROW_DOWN, STR_SNOW_LINE_DOWN}, +{ WWT_PANEL, RESIZE_NONE, 12, 294, 314, 152, 163, 0x0, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 12, 315, 326, 152, 163, SPR_ARROW_UP, STR_SNOW_LINE_UP}, + +{ WWT_PANEL, RESIZE_NONE, 12, 114, 219, 196, 207, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 220, 231, 196, 207, STR_0225, STR_NULL}, // Tree placer + +{ WWT_PANEL, RESIZE_NONE, 12, 114, 219, 214, 225, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 220, 231, 214, 225, STR_0225, STR_NULL}, // Heightmap rotation +{ WIDGETS_END}, +}; + +static void StartGeneratingLandscape(glwp_modes mode) +{ + /* If we want to go to the editor, and aren't yet, we need to delay + * it as long as possible, else it gives nasty side-effects (aborting + * results in ending up in the SE, which you don't want. Therefor we + * use this switch to do it at the very end. + */ + if (_goto_editor) _game_mode = GM_EDITOR; + + DeleteWindowByClass(WC_GENERATE_LANDSCAPE); + DeleteWindowByClass(WC_INDUSTRY_VIEW); + DeleteWindowByClass(WC_TOWN_VIEW); + DeleteWindowByClass(WC_LAND_INFO); + + /* Copy all XXX_newgame to XXX */ + UpdatePatches(); + _opt_ptr = &_opt; + memcpy(_opt_ptr, &_opt_newgame, sizeof(GameOptions)); + /* Load the right landscape stuff */ + GfxLoadSprites(); + + SndPlayFx(SND_15_BEEP); + switch (mode) { + case GLWP_GENERATE: _switch_mode = (_game_mode == GM_EDITOR) ? SM_GENRANDLAND : SM_NEWGAME; break; + case GLWP_HEIGHTMAP: _switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_HEIGHTMAP : SM_START_HEIGHTMAP; break; + case GLWP_SCENARIO: _switch_mode = SM_EDITOR; break; + default: NOT_REACHED(); return; + } +} + +static void HeightmapScaledTooMuchCallback(bool ok_clicked) +{ + if (ok_clicked) { + Window *w; + glwp_modes mode = 0; + for (mode = 0; mode < GLWP_END; mode++) { + w = FindWindowById(WC_GENERATE_LANDSCAPE, mode); + if (w != NULL) StartGeneratingLandscape(mode); + } + } +} + +void GenerateLandscapeWndProc(Window *w, WindowEvent *e) +{ + static const StringID mapsizes[] = {STR_64, STR_128, STR_256, STR_512, STR_1024, STR_2048, INVALID_STRING_ID}; + static const StringID elevations[] = {STR_682A_VERY_FLAT, STR_682B_FLAT, STR_682C_HILLY, STR_682D_MOUNTAINOUS, INVALID_STRING_ID}; + static const StringID sea_lakes[] = {STR_VERY_LOW, STR_6820_LOW, STR_6821_MEDIUM, STR_6822_HIGH, INVALID_STRING_ID}; + static const StringID smoothness[] = {STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_SMOOTH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_SMOOTH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_ROUGH, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_ROUGH, INVALID_STRING_ID}; + static const StringID tree_placer[] = {STR_CONFIG_PATCHES_TREE_PLACER_NONE, STR_CONFIG_PATCHES_TREE_PLACER_ORIGINAL, STR_CONFIG_PATCHES_TREE_PLACER_IMPROVED, INVALID_STRING_ID}; + static const StringID rotation[] = {STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_COUNTER_CLOCKWISE, STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_CLOCKWISE, INVALID_STRING_ID}; + static const StringID landscape[] = {STR_CONFIG_PATCHES_LAND_GENERATOR_ORIGINAL, STR_CONFIG_PATCHES_LAND_GENERATOR_TERRA_GENESIS, INVALID_STRING_ID}; + static const StringID num_towns[] = {STR_6816_LOW, STR_6817_NORMAL, STR_6818_HIGH, INVALID_STRING_ID}; + static const StringID num_inds[] = {STR_26816_NONE, STR_6816_LOW, STR_6817_NORMAL, STR_6818_HIGH, INVALID_STRING_ID}; + + uint mode = w->window_number; + uint y; + + switch (e->event) { + case WE_PAINT: + w->disabled_state = 0; + /* TODO -- Above and below you see some lines commented out with '//' in + * front of it. This is because currently the widget system can't handle + * more than 32 widgets per window, and we need 34. Therefor we draw + * parts of the widgets manually below, reducing the number to 32. + * Of course someone is already hard working to replace the system with + * one that can scale past the 32 limit. When this is done you should + * re-enable the lines and remove the ones that came instead. Better, + * revert revision 5817 (from TGP branch), and you should be just fine. + * If you have any questions about it, bug TrueLight. + */ + + /* You can't select smoothness if not terragenesis */ +// if (_patches_newgame.land_generator == 0) w->disabled_state |= (1 << 32 | 1 << 33); + if (_patches_newgame.land_generator == 0) w->disabled_state |= (1 << 30 | 1 << 31); + /* Disable snowline if not hilly */ + if (_opt_newgame.landscape != LT_HILLY) w->disabled_state |= (1 << 21 | 1 << 22 | 1 << 23); + /* Disable town and industry in SE */ + if (_game_mode == GM_EDITOR) w->disabled_state |= (1 << 11 | 1 << 12 | 1 << 13 | 1 << 14 | 1 << 24 | 1 << 25); + + if (_patches_newgame.starting_year <= MIN_YEAR) SETBIT(w->disabled_state, 18); + if (_patches_newgame.starting_year >= MAX_YEAR) SETBIT(w->disabled_state, 20); + if (_patches_newgame.snow_line_height <= 2 ) SETBIT(w->disabled_state, 21); + if (_patches_newgame.snow_line_height >= 13) SETBIT(w->disabled_state, 23); + + w->click_state = (w->click_state & ~(0xF << 3)) | (1 << (_opt_newgame.landscape + 3)); + DrawWindowWidgets(w); + + y = (mode == GLWP_HEIGHTMAP) ? 22 : 0; + + DrawString( 12, 91 + y, STR_MAPSIZE, 0); + DrawString(119, 91 + y, mapsizes[_patches_newgame.map_x - 6], 0x10); + DrawString(168, 91 + y, STR_BY, 0); + DrawString(182, 91 + y, mapsizes[_patches_newgame.map_y - 6], 0x10); + + DrawString( 12, 113 + y, STR_NUMBER_OF_TOWNS, 0); + DrawString( 12, 131 + y, STR_NUMBER_OF_INDUSTRIES, 0); + if (_game_mode == GM_EDITOR) { + DrawString(118, 113 + y, STR_6836_OFF, 0x10); + DrawString(118, 131 + y, STR_6836_OFF, 0x10); + } else { + DrawString(118, 113 + y, num_towns[_opt_newgame.diff.number_towns], 0x10); + DrawString(118, 131 + y, num_inds[_opt_newgame.diff.number_industries], 0x10); + } + + DrawString( 12, 153 + y, STR_RANDOM_SEED, 0); + DrawEditBox(w, &WP(w, querystr_d), SEED_EDIT); + + DrawString(182, 113 + y, STR_DATE, 0); + SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); + DrawStringCentered(271, 113 + y, STR_GENERATE_DATE, 0); + + DrawString(182, 131 + y, STR_SNOW_LINE_HEIGHT, 0); + SetDParam(0, _patches_newgame.snow_line_height); + DrawStringCentered(303, 131 + y, STR_SNOW_LINE_HEIGHT_NUM, 0x10); + + if (mode == GLWP_GENERATE) { + /* TODO -- Remove next 2 lines if 32 widget limit is removed */ + DrawFrameRect(114, 174, 219, 185, 12, 0); + DrawString(222, 175, STR_0225, 0x10); + DrawString( 12, 175, STR_LAND_GENERATOR, 0); + DrawString(118, 175, landscape[_patches_newgame.land_generator], 0x10); + + DrawString( 12, 193, STR_TREE_PLACER, 0); + DrawString(118, 193, tree_placer[_patches_newgame.tree_placer], 0x10); + + /* TODO -- Remove next 2 lines if 32 widget limit is removed */ + DrawFrameRect(114, 210, 219, 221, 12, 0); + DrawString(222, 211, STR_0225, 0x10); + DrawString( 12, 211, STR_TERRAIN_TYPE, 0); + DrawString(118, 211, elevations[_opt_newgame.diff.terrain_type], 0x10); + + /* TODO -- Remove next 2 lines if 32 widget limit is removed */ + DrawFrameRect(114, 228, 219, 239, 12, 0); + DrawString(222, 229, STR_0225, 0x10); + DrawString( 12, 229, STR_QUANTITY_OF_SEA_LAKES, 0); + DrawString(118, 229, sea_lakes[_opt_newgame.diff.quantity_sea_lakes], 0x10); + + DrawString( 12, 247, STR_SMOOTHNESS, 0); + DrawString(118, 247, smoothness[_patches_newgame.tgen_smoothness], 0x10); + } else { + char buffer[512]; + + if (_patches_newgame.heightmap_rotation == HM_CLOCKWISE) { + SetDParam(0, _heightmap_y); + SetDParam(1, _heightmap_x); + } else { + SetDParam(0, _heightmap_x); + SetDParam(1, _heightmap_y); + } + GetString(buffer, STR_HEIGHTMAP_SIZE); + DrawStringRightAligned(326, 91, STR_HEIGHTMAP_SIZE, 0x10); + + DrawString( 12, 91, STR_HEIGHTMAP_NAME, 0x10); + SetDParam(0, _heightmap_str); + DrawStringTruncated(114, 91, STR_ORANGE, 0x10, 326 - 114 - GetStringWidth(buffer) - 5); + + /* TODO -- Remove next 2 lines if 32 widget limit is removed */ + DrawFrameRect(114, 196, 219, 207, 12, 0); + DrawString(222, 197, STR_0225, 0x10); + DrawString( 12, 197, STR_TREE_PLACER, 0); + DrawString(118, 197, tree_placer[_patches_newgame.tree_placer], 0x10); + + DrawString( 12, 215, STR_HEIGHTMAP_ROTATION, 0); + DrawString(118, 215, rotation[_patches_newgame.heightmap_rotation], 0x10); + } + + break; + case WE_CLICK: + switch (e->click.widget) { + case 0: DeleteWindow(w); break; + case 3: case 4: case 5: case 6: + SetNewLandscapeType(e->click.widget - 3); + break; + case 7: case 8: // Mapsize X + ShowDropDownMenu(w, mapsizes, _patches_newgame.map_x - 6, 8, 0, 0); + break; + case 9: case 10: // Mapsize Y + ShowDropDownMenu(w, mapsizes, _patches_newgame.map_y - 6, 10, 0, 0); + break; + case 11: case 12: // Number of towns + ShowDropDownMenu(w, num_towns, _opt_newgame.diff.number_towns, 12, 0, 0); + break; + case 13: case 14: // Number of industries + ShowDropDownMenu(w, num_inds, _opt_newgame.diff.number_industries, 14, 0, 0); + break; + case 16: // Random seed + _patches_newgame.generation_seed = InteractiveRandom(); + ttd_strlcpy(_edit_str_buf, str_fmt("%u", _patches_newgame.generation_seed), lengthof(_edit_str_buf)); + UpdateTextBufferSize(&((querystr_d *)&WP(w, querystr_d))->text); + SetWindowDirty(w); + break; + case 17: // Generate + if (mode == GLWP_HEIGHTMAP && ( + _heightmap_x * 2 < (1U << _patches_newgame.map_x) || _heightmap_x / 2 > (1U << _patches_newgame.map_x) || + _heightmap_y * 2 < (1U << _patches_newgame.map_y) || _heightmap_y / 2 > (1U << _patches_newgame.map_y))) { + ShowQuery(STR_HEIGHTMAP_SCALE_WARNING_CAPTION, STR_HEIGHTMAP_SCALE_WARNING_MESSAGE, HeightmapScaledTooMuchCallback, WC_GENERATE_LANDSCAPE, mode); + } else { + StartGeneratingLandscape(mode); + } + break; + case 18: case 20: // Year buttons + /* Don't allow too fast scrolling */ + if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) { + HandleButtonClick(w, e->click.widget); + SetWindowDirty(w); + + _patches_newgame.starting_year = clamp(_patches_newgame.starting_year + e->click.widget - 19, MIN_YEAR, MAX_YEAR); + } + _left_button_clicked = false; + break; + case 19: // Year text + WP(w, def_d).data_3 = START_DATE_QUERY; + SetDParam(0, _patches_newgame.starting_year); + ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_START_DATE_QUERY_CAPT, 5, 100, WC_GENERATE_LANDSCAPE, mode, CS_NUMERAL); + break; + case 21: case 23: // Snow line buttons + /* Don't allow too fast scrolling */ + if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) { + HandleButtonClick(w, e->click.widget); + SetWindowDirty(w); + + _patches_newgame.snow_line_height = clamp(_patches_newgame.snow_line_height + e->click.widget - 22, 2, 13); + } + _left_button_clicked = false; + break; + case 22: // Snow line text + WP(w, def_d).data_3 = SNOW_LINE_QUERY; + SetDParam(0, _patches_newgame.snow_line_height); + ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_SNOW_LINE_QUERY_CAPT, 3, 100, WC_GENERATE_LANDSCAPE, mode, CS_NUMERAL); + break; + case 24: case 25: // Tree placer + ShowDropDownMenu(w, tree_placer, _patches_newgame.tree_placer, 25, 0, 0); + break; +// case 26: case 27: // Landscape generator OR Heightmap rotation + case 27: + if (mode == GLWP_HEIGHTMAP) { + ShowDropDownMenu(w, rotation, _patches_newgame.heightmap_rotation, 27, 0, 0); + } else { + ShowDropDownMenu(w, landscape, _patches_newgame.land_generator, 27, 0, 0); + } + break; +// case 28: case 29: // Terrain type + case 28: +// ShowDropDownMenu(w, elevations, _opt_newgame.diff.terrain_type, 29, 0, 0); + ShowDropDownMenu(w, elevations, _opt_newgame.diff.terrain_type, 28, 0, 0); + break; +// case 30: case 31: // Water quantity + case 29: +// ShowDropDownMenu(w, sea_lakes, _opt_newgame.diff.quantity_sea_lakes, 31, 0, 0); + ShowDropDownMenu(w, sea_lakes, _opt_newgame.diff.quantity_sea_lakes, 29, 0, 0); + break; +// case 32: case 33: // Map smoothness + case 30: case 31: +// ShowDropDownMenu(w, smoothness, _patches_newgame.tgen_smoothness, 33, 0, 0); + ShowDropDownMenu(w, smoothness, _patches_newgame.tgen_smoothness, 31, 0, 0); + break; + } + break; + + case WE_MESSAGE: + ttd_strlcpy(_edit_str_buf, str_fmt("%u", _patches_newgame.generation_seed), lengthof(_edit_str_buf)); + DrawEditBox(w, &WP(w, querystr_d), SEED_EDIT); + break; + + case WE_MOUSELOOP: + HandleEditBox(w, &WP(w, querystr_d), SEED_EDIT); + break; + + case WE_KEYPRESS: + HandleEditBoxKey(w, &WP(w, querystr_d), SEED_EDIT, e, CS_NUMERAL); + _patches_newgame.generation_seed = atoi(_edit_str_buf); + break; + + case WE_DROPDOWN_SELECT: + switch (e->dropdown.button) { + case 8: _patches_newgame.map_x = e->dropdown.index + 6; break; + case 10: _patches_newgame.map_y = e->dropdown.index + 6; break; + case 12: + _opt_newgame.diff.number_towns = e->dropdown.index; + if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0); + DoCommandP(0, 2, _opt_newgame.diff.number_towns, NULL, CMD_CHANGE_DIFFICULTY_LEVEL); + break; + case 14: + _opt_newgame.diff.number_industries = e->dropdown.index; + if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0); + DoCommandP(0, 3, _opt_newgame.diff.number_industries, NULL, CMD_CHANGE_DIFFICULTY_LEVEL); + break; + case 25: + _patches_newgame.tree_placer = e->dropdown.index; + break; + case 27: + if (mode == GLWP_HEIGHTMAP) { + _patches_newgame.heightmap_rotation = e->dropdown.index; + } else { + _patches_newgame.land_generator = e->dropdown.index; + } + break; +// case 29: + case 28: + _opt_newgame.diff.terrain_type = e->dropdown.index; + if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0); + DoCommandP(0, 12, _opt_newgame.diff.terrain_type, NULL, CMD_CHANGE_DIFFICULTY_LEVEL); + break; +// case 31: + case 29: + _opt_newgame.diff.quantity_sea_lakes = e->dropdown.index; + if (_opt_newgame.diff_level != 3) ShowErrorMessage(INVALID_STRING_ID, STR_DIFFICULTY_TO_CUSTOM, 0, 0); + DoCommandP(0, 13, _opt_newgame.diff.quantity_sea_lakes, NULL, CMD_CHANGE_DIFFICULTY_LEVEL); + break; +// case 33: + case 31: + _patches_newgame.tgen_smoothness = e->dropdown.index; + break; + } + SetWindowDirty(w); + break; + + case WE_ON_EDIT_TEXT: { + if (e->edittext.str != NULL) { + int32 value = atoi(e->edittext.str); + + switch (WP(w, def_d).data_3) { + case START_DATE_QUERY: + InvalidateWidget(w, 19); + _patches_newgame.starting_year = clamp(value, MIN_YEAR, MAX_YEAR); + break; + case SNOW_LINE_QUERY: + InvalidateWidget(w, 22); + _patches_newgame.snow_line_height = clamp(value, 2, 13); + break; + } + + SetWindowDirty(w); + } + break; + } + } +} + +const WindowDesc _generate_landscape_desc = { + WDP_CENTER, WDP_CENTER, 338, 268, + WC_GENERATE_LANDSCAPE, 0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _generate_landscape_widgets, + GenerateLandscapeWndProc, +}; + +const WindowDesc _heightmap_load_desc = { + WDP_CENTER, WDP_CENTER, 338, 236, + WC_GENERATE_LANDSCAPE, 0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _heightmap_load_widgets, + GenerateLandscapeWndProc, +}; + +static void _ShowGenerateLandscape(glwp_modes mode) +{ + Window *w; + + /* Don't kill WC_GENERATE_LANDSCAPE:GLWP_SCENARIO, because it resets + * _goto_editor, which we maybe need later on. */ + DeleteWindowById(WC_GENERATE_LANDSCAPE, GLWP_GENERATE); + DeleteWindowById(WC_GENERATE_LANDSCAPE, GLWP_HEIGHTMAP); + + /* Always give a new seed if not editor */ + if (_game_mode != GM_EDITOR) _patches_newgame.generation_seed = InteractiveRandom(); + + if (mode == GLWP_HEIGHTMAP) { + if (_heightmap_str != STR_NULL) DeleteName(_heightmap_str); + + _heightmap_x = 0; + _heightmap_y = 0; + _heightmap_str = AllocateName(_file_to_saveload.title, 0); + /* If the function returns negative, it means there was a problem loading the heightmap */ + if (!GetHeightmapDimensions(_file_to_saveload.name, &_heightmap_x, &_heightmap_y)) + return; + } + + w = AllocateWindowDescFront((mode == GLWP_HEIGHTMAP) ? &_heightmap_load_desc : &_generate_landscape_desc, mode); + + if (w != NULL) { + querystr_d *querystr = &WP(w, querystr_d); + + ttd_strlcpy(_edit_str_buf, str_fmt("%u", _patches_newgame.generation_seed), lengthof(_edit_str_buf)); + + querystr->text.caret = true; + querystr->text.maxlength = lengthof(_edit_str_buf); + querystr->text.maxwidth = 120; + querystr->text.buf = _edit_str_buf; + querystr->caption = STR_NULL; + UpdateTextBufferSize(&querystr->text); + + InvalidateWindow(WC_GENERATE_LANDSCAPE, mode); + } +} + +void ShowGenerateLandscape(void) +{ + _ShowGenerateLandscape(GLWP_GENERATE); +} + +void ShowHeightmapLoad(void) +{ + _ShowGenerateLandscape(GLWP_HEIGHTMAP); +} + +void StartNewGameWithoutGUI(uint seed) +{ + /* GenerateWorld takes care of the possible GENERATE_NEW_SEED value in 'seed' */ + _patches_newgame.generation_seed = seed; + + StartGeneratingLandscape(GLWP_GENERATE); +} + + +void CreateScenarioWndProc(Window *w, WindowEvent *e) +{ + static const StringID mapsizes[] = {STR_64, STR_128, STR_256, STR_512, STR_1024, STR_2048, INVALID_STRING_ID}; + + switch (e->event) { + case WE_PAINT: + w->disabled_state = 0; + if (_patches_newgame.starting_year <= MIN_YEAR) SETBIT(w->disabled_state, 14); + if (_patches_newgame.starting_year >= MAX_YEAR) SETBIT(w->disabled_state, 16); + if (_patches_newgame.se_flat_world_height <= 0) SETBIT(w->disabled_state, 17); + if (_patches_newgame.se_flat_world_height >= 15) SETBIT(w->disabled_state, 19); + + w->click_state = (w->click_state & ~(0xF << 3)) | (1 << (_opt_newgame.landscape + 3)); + DrawWindowWidgets(w); + + DrawString( 12, 96, STR_MAPSIZE, 0); + DrawString( 89, 96, mapsizes[_patches_newgame.map_x - 6], 0x10); + DrawString(138, 96, STR_BY, 0); + DrawString(152, 96, mapsizes[_patches_newgame.map_y - 6], 0x10); + + DrawString(162, 118, STR_DATE, 0); + SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); + DrawStringCentered(271, 118, STR_GENERATE_DATE, 0); + + DrawString(162, 136, STR_FLAT_WORLD_HEIGHT, 0); + SetDParam(0, _patches_newgame.se_flat_world_height); + DrawStringCentered(303, 136, STR_FLAT_WORLD_HEIGHT_NUM, 0x10); + + break; + case WE_CLICK: + switch (e->click.widget) { + case 0: DeleteWindow(w); break; + case 3: case 4: case 5: case 6: + SetNewLandscapeType(e->click.widget - 3); + break; + case 7: case 8: // Mapsize X + ShowDropDownMenu(w, mapsizes, _patches_newgame.map_x - 6, 8, 0, 0); + break; + case 9: case 10: // Mapsize Y + ShowDropDownMenu(w, mapsizes, _patches_newgame.map_y - 6, 10, 0, 0); + break; + case 11: // Empty world / flat world + StartGeneratingLandscape(GLWP_SCENARIO); + break; + case 12: // Generate + _goto_editor = true; + ShowGenerateLandscape(); + break; + case 13: // Heightmap + _goto_editor = true; + ShowSaveLoadDialog(SLD_LOAD_HEIGHTMAP); + break; + case 14: case 16: // Year buttons + /* Don't allow too fast scrolling */ + if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) { + HandleButtonClick(w, e->click.widget); + SetWindowDirty(w); + + _patches_newgame.starting_year = clamp(_patches_newgame.starting_year + e->click.widget - 15, MIN_YEAR, MAX_YEAR); + } + _left_button_clicked = false; + break; + case 15: // Year text + WP(w, def_d).data_3 = START_DATE_QUERY; + SetDParam(0, _patches_newgame.starting_year); + ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_START_DATE_QUERY_CAPT, 5, 100, WC_GENERATE_LANDSCAPE, GLWP_SCENARIO, CS_NUMERAL); + break; + case 17: case 19: // Height level buttons + /* Don't allow too fast scrolling */ + if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) { + HandleButtonClick(w, e->click.widget); + SetWindowDirty(w); + + _patches_newgame.se_flat_world_height = clamp(_patches_newgame.se_flat_world_height + e->click.widget - 18, 0, 15); + } + _left_button_clicked = false; + break; + case 18: // Height level text + WP(w, def_d).data_3 = FLAT_WORLD_HEIGHT_QUERY; + SetDParam(0, _patches_newgame.se_flat_world_height); + ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_FLAT_WORLD_HEIGHT_QUERY_CAPT, 3, 100, WC_GENERATE_LANDSCAPE, GLWP_SCENARIO, CS_NUMERAL); + break; + } + break; + + case WE_DROPDOWN_SELECT: + switch (e->dropdown.button) { + case 8: _patches_newgame.map_x = e->dropdown.index + 6; break; + case 10: _patches_newgame.map_y = e->dropdown.index + 6; break; + } + SetWindowDirty(w); + break; + + case WE_DESTROY: + _goto_editor = false; + break; + + case WE_ON_EDIT_TEXT: { + if (e->edittext.str != NULL) { + int32 value = atoi(e->edittext.str); + + switch (WP(w, def_d).data_3) { + case START_DATE_QUERY: + InvalidateWidget(w, 15); + _patches_newgame.starting_year = clamp(value, MIN_YEAR, MAX_YEAR); + break; + case FLAT_WORLD_HEIGHT_QUERY: + InvalidateWidget(w, 18); + _patches_newgame.se_flat_world_height = clamp(value, 0, 15); + break; + } + + SetWindowDirty(w); + } + break; + } + } +} + +const Widget _create_scenario_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 13, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 13, 11, 337, 0, 13, STR_SE_CAPTION, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 13, 0, 337, 14, 179, STR_NULL, STR_NULL}, + +{ WWT_PANEL_2, RESIZE_NONE, 12, 10, 86, 24, 78, 0x1312, STR_030E_SELECT_TEMPERATE_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 90, 166, 24, 78, 0x1314, STR_030F_SELECT_SUB_ARCTIC_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 170, 246, 24, 78, 0x1316, STR_0310_SELECT_SUB_TROPICAL_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 250, 326, 24, 78, 0x1318, STR_0311_SELECT_TOYLAND_LANDSCAPE}, + +{ WWT_PANEL, RESIZE_NONE, 12, 84, 119, 95, 106, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 120, 131, 95, 106, STR_0225, STR_NULL}, // Mapsize X +{ WWT_PANEL, RESIZE_NONE, 12, 150, 185, 95, 106, STR_NULL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 186, 197, 95, 106, STR_0225, STR_NULL}, // Mapsize Y + +{ WWT_TEXTBTN, RESIZE_NONE, 6, 12, 145, 117, 128, STR_SE_FLAT_WORLD, STR_SE_FLAT_WORLD}, // Empty (sea-level) map +{ WWT_TEXTBTN, RESIZE_NONE, 6, 12, 145, 135, 146, STR_SE_RANDOM_LAND, STR_022A_GENERATE_RANDOM_LAND}, // Generate +{ WWT_TEXTBTN, RESIZE_NONE, 6, 12, 145, 153, 164, STR_LOAD_GAME_HEIGHTMAP, STR_LOAD_SCEN_HEIGHTMAP}, // Heightmap + +{ WWT_IMGBTN, RESIZE_NONE, 12, 216, 227, 117, 128, SPR_ARROW_DOWN, STR_029E_MOVE_THE_STARTING_DATE}, +{ WWT_PANEL, RESIZE_NONE, 12, 228, 314, 117, 128, 0x0, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 12, 315, 326, 117, 128, SPR_ARROW_UP, STR_029F_MOVE_THE_STARTING_DATE}, + +{ WWT_IMGBTN, RESIZE_NONE, 12, 282, 293, 135, 146, SPR_ARROW_DOWN, STR_FLAT_WORLD_HEIGHT_DOWN}, +{ WWT_PANEL, RESIZE_NONE, 12, 294, 314, 135, 146, 0x0, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 12, 315, 326, 135, 146, SPR_ARROW_UP, STR_FLAT_WORLD_HEIGHT_UP}, +{ WIDGETS_END}, +}; + +const WindowDesc _create_scenario_desc = { + WDP_CENTER, WDP_CENTER, 338, 180, + WC_GENERATE_LANDSCAPE, 0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _create_scenario_widgets, + CreateScenarioWndProc, +}; + +void ShowCreateScenario(void) +{ + DeleteWindowByClass(WC_GENERATE_LANDSCAPE); + AllocateWindowDescFront(&_create_scenario_desc, GLWP_SCENARIO); +} + + +static const Widget _show_terrain_progress_widgets[] = { +{ WWT_CAPTION, RESIZE_NONE, 14, 0, 180, 0, 13, STR_GENERATION_WORLD, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_IMGBTN, RESIZE_NONE, 14, 0, 180, 14, 96, 0x0, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 15, 20, 161, 74, 85, STR_GENERATION_ABORT, STR_NULL}, // Abort button +{ WIDGETS_END}, +}; + +typedef struct tp_info { + uint percent; + StringID class; + uint current; + uint total; + int timer; +} tp_info; + +static tp_info _tp; + +static void AbortGeneratingWorldCallback(bool ok_clicked) +{ + if (ok_clicked) AbortGeneratingWorld(); + else if (IsGeneratingWorld() && !IsGeneratingWorldAborted()) SetMouseCursor(SPR_CURSOR_ZZZ); +} + +static void ShowTerrainProgressProc(Window* w, WindowEvent* e) +{ + switch (e->event) { + case WE_CLICK: + switch (e->click.widget) { + case 2: + if (_cursor.sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE); + ShowQuery(STR_GENERATION_ABORT_CAPTION, STR_GENERATION_ABORT_MESSAGE, AbortGeneratingWorldCallback, WC_GENERATE_PROGRESS_WINDOW, 0); + break; + } + break; + + case WE_PAINT: + DrawWindowWidgets(w); + + /* Draw the % complete with a bar and a text */ + DrawFrameRect(19, 20, (w->width - 18), 37, 14, FR_BORDERONLY); + DrawFrameRect(20, 21, (int)((w->width - 40) * _tp.percent / 100) + 20, 36, 10, 0); + SetDParam(0, _tp.percent); + DrawStringCentered(90, 25, STR_PROGRESS, 0); + + /* Tell which class we are generating */ + DrawStringCentered(90, 46, _tp.class, 0); + + /* And say where we are in that class */ + SetDParam(0, _tp.current); + SetDParam(1, _tp.total); + DrawStringCentered(90, 58, STR_GENERATION_PROGRESS, 0); + + SetWindowDirty(w); + break; + } +} + +static const WindowDesc _show_terrain_progress_desc = { + WDP_CENTER, WDP_CENTER, 181, 97, + WC_GENERATE_PROGRESS_WINDOW, 0, + WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _show_terrain_progress_widgets, + ShowTerrainProgressProc +}; + +/** + * Initializes the progress counters to the starting point. + */ +void PrepareGenerateWorldProgress(void) +{ + _tp.class = STR_WORLD_GENERATION; + _tp.current = 0; + _tp.total = 0; + _tp.percent = 0; + _tp.timer = 0; // Forces to paint the progress window immediatelly +} + +/** + * Show the window where a user can follow the process of the map generation. + */ +void ShowGenerateWorldProgress(void) +{ + AllocateWindowDescFront(&_show_terrain_progress_desc, 0); +} + +static void _SetGeneratingWorldProgress(gwp_class class, uint progress, uint total) +{ + static const int percent_table[GWP_CLASS_COUNT + 1] = {0, 5, 15, 20, 40, 60, 65, 80, 85, 99, 100 }; + static const StringID class_table[GWP_CLASS_COUNT] = { + STR_WORLD_GENERATION, + STR_022E_LANDSCAPE_GENERATION, + STR_CLEARING_TILES, + STR_022F_TOWN_GENERATION, + STR_0230_INDUSTRY_GENERATION, + STR_UNMOVABLE_GENERATION, + STR_TREE_GENERATION, + STR_SETTINGUP_GAME, + STR_PREPARING_TILELOOP, + STR_PREPARING_GAME + }; + + assert(class < GWP_CLASS_COUNT); + + /* Do not run this function if we aren't in a thread */ + if (!IsGenerateWorldThreaded() && !_network_dedicated) return; + + if (IsGeneratingWorldAborted()) HandleGeneratingWorldAbortion(); + + if (total == 0) { + assert(_tp.class == class_table[class]); + _tp.current += progress; + } else { + _tp.class = class_table[class]; + _tp.current = progress; + _tp.total = total; + _tp.percent = percent_table[class]; + } + + /* Don't update the screen too often. So update it once in the + * _patches.progress_update_interval. However, the _tick_counter + * increases with 8 every 30ms, so compensate for that. */ + if (!_network_dedicated && _tp.timer != 0 && _timer_counter - _tp.timer < (_patches.progress_update_interval * 8 / 30)) return; + + /* Percentage is about the number of completed tasks, so 'current - 1' */ + _tp.percent = percent_table[class] + (percent_table[class + 1] - percent_table[class]) * (_tp.current == 0 ? 0 : _tp.current - 1) / _tp.total; + _tp.timer = _timer_counter; + + if (_network_dedicated) { + static uint last_percent = 0; + + /* Never display 0% */ + if (_tp.percent == 0) return; + /* Reset if percent is lower then the last recorded */ + if (_tp.percent < last_percent) last_percent = 0; + /* Display every 5%, but 6% is also very valid.. just not smaller steps then 5% */ + if (_tp.percent % 5 != 0 && _tp.percent <= last_percent + 5) return; + /* Never show steps smaller then 2%, even if it is a mod 5% */ + if (_tp.percent <= last_percent + 2) return; + + DEBUG(net, 1)("Percent complete: %d", _tp.percent); + last_percent = _tp.percent; + + /* Don't continue as dedicated never has a thread running */ + return; + } + + InvalidateWindow(WC_GENERATE_PROGRESS_WINDOW, 0); + MarkWholeScreenDirty(); + SetGeneratingWorldPaintStatus(true); + + /* We wait here till the paint is done, so we don't read and write + * on the same tile at the same moment. Nasty hack, but that happens + * if you implement threading afterwards */ + while (IsGeneratingWorldReadyForPaint()) { CSleep(10); } +} + +/** + * Set the total of a stage of the world generation. + * @param class the current class we are in. + * @param total Set the total expected items for this class. + * + * Warning: this function isn't clever. Don't go from class 4 to 3. Go upwards, always. + * Also, progress works if total is zero, total works if progress is zero. + */ +void SetGeneratingWorldProgress(gwp_class class, uint total) +{ + if (total == 0) return; + + _SetGeneratingWorldProgress(class, 0, total); +} + +/** + * Increases the current stage of the world generation with one. + * @param class the current class we are in. + * + * Warning: this function isn't clever. Don't go from class 4 to 3. Go upwards, always. + * Also, progress works if total is zero, total works if progress is zero. + */ +void IncreaseGeneratingWorldProgress(gwp_class class) +{ + /* In fact the param 'class' isn't needed.. but for some security reasons, we want it around */ + _SetGeneratingWorldProgress(class, 1, 0); +} diff --git a/gfx.c b/gfx.c index 028feaa425..58c22767e3 100644 --- a/gfx.c +++ b/gfx.c @@ -12,6 +12,7 @@ #include "table/sprites.h" #include "hal.h" #include "variables.h" +#include "genworld.h" #ifdef _DEBUG bool _dbg_screen_rect; @@ -1753,6 +1754,8 @@ void DrawDirtyBlocks(void) int x; int y; + if (IsGeneratingWorld() && !IsGeneratingWorldReadyForPaint()) return; + y = 0; do { x = 0; @@ -1819,6 +1822,12 @@ void DrawDirtyBlocks(void) _invalid_rect.top = h; _invalid_rect.right = 0; _invalid_rect.bottom = 0; + + /* If we are generating a world, and waiting for a paint run, mark it here + * as done painting, so we can continue generating. */ + if (IsGeneratingWorld() && IsGeneratingWorldReadyForPaint()) { + SetGeneratingWorldPaintStatus(false); + } } diff --git a/gui.h b/gui.h index 5421c6e9aa..1af237f087 100644 --- a/gui.h +++ b/gui.h @@ -64,6 +64,10 @@ void ShowPlayerAircraft(PlayerID player, StationID station); /* terraform_gui.c */ void ShowTerraformToolbar(void); +/* tgp_gui.c */ +void ShowGenerateLandscape(void); +void ShowHeightmapLoad(void); + void PlaceProc_DemolishArea(TileIndex tile); void PlaceProc_LevelLand(TileIndex tile); bool GUIPlaceProcDragXY(const WindowEvent *we); @@ -103,7 +107,6 @@ void SetVScroll2Count(Window *w, int num); void SetHScrollCount(Window *w, int num); void ShowCheatWindow(void); -void AskForNewGameToStart(void); void DrawEditBox(Window *w, querystr_d *string, int wid); void HandleEditBox(Window *w, querystr_d *string, int wid); @@ -138,6 +141,7 @@ enum { bool DoZoomInOutWindow(int how, Window *w); void ShowBuildIndustryWindow(void); void ShowQueryString(StringID str, StringID caption, uint maxlen, uint maxwidth, WindowClass window_class, WindowNumber window_number, CharSetFilter afilter); +void ShowQuery(StringID caption, StringID message, void (*ok_cancel_callback)(bool ok_clicked), WindowClass window_class, WindowNumber window_number); void ShowMusicWindow(void); /* main_gui.c */ diff --git a/heightmap.c b/heightmap.c new file mode 100644 index 0000000000..8bc3f63a5c --- /dev/null +++ b/heightmap.c @@ -0,0 +1,459 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "variables.h" +#include "functions.h" +#include "heightmap.h" +#include "clear_map.h" +#include "table/strings.h" +#include "void_map.h" +#include "debug.h" +#include "gfx.h" +#include "gui.h" +#include "saveload.h" +#include "bmp.h" + +/** + * Convert RGB colors to Grayscale using 29.9% Red, 58.7% Green, 11.4% Blue + * (average luminosity formula) -- Dalestan + * This in fact is the NTSC Color Space -- TrueLight + */ +static inline byte RGBToGrayscale(byte red, byte green, byte blue) +{ + /* To avoid doubles and stuff, multiple it with a total of 65536 (16bits), then + * divide by it to normalize the value to a byte again. */ + return ((red * 19595) + (green * 38470) + (blue * 7471)) / 65536; +} + + +#ifdef WITH_PNG + +#include "png.h" + +/** + * The PNG Heightmap loader. + */ +static void ReadHeightmapPNGImageData(byte *map, png_structp png_ptr, png_infop info_ptr) +{ + uint x, y; + byte gray_palette[256]; + png_bytep *row_pointers = NULL; + + /* Get palette and convert it to grayscale */ + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) { + int i; + int palette_size; + png_color *palette; + bool all_gray = true; + + png_get_PLTE(png_ptr, info_ptr, &palette, &palette_size); + for (i = 0; i < palette_size && (palette_size != 16 || all_gray); i++) { + all_gray &= palette[i].red == palette[i].green && palette[i].red == palette[i].blue; + gray_palette[i] = RGBToGrayscale(palette[i].red, palette[i].green, palette[i].blue); + } + + /** + * For a non-gray palette of size 16 we assume that + * the order of the palette determines the height; + * the first entry is the sea (level 0), the second one + * level 1, etc. + */ + if (palette_size == 16 && !all_gray) { + for (i = 0; i < palette_size; i++) { + gray_palette[i] = 256 * i / palette_size; + } + } + } + + row_pointers = png_get_rows(png_ptr, info_ptr); + + /* Read the raw image data and convert in 8-bit grayscale */ + for (x = 0; x < info_ptr->width; x++) { + for (y = 0; y < info_ptr->height; y++) { + byte *pixel = &map[y * info_ptr->width + x]; + uint x_offset = x * info_ptr->channels; + + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) { + *pixel = gray_palette[row_pointers[y][x_offset]]; + } else if (info_ptr->channels == 3) { + *pixel = RGBToGrayscale(row_pointers[y][x_offset + 0], + row_pointers[y][x_offset + 1], row_pointers[y][x_offset + 2]); + } else { + *pixel = row_pointers[y][x_offset]; + } + } + } +} + +/** + * Reads the heightmap and/or size of the heightmap from a PNG file. + * If map == NULL only the size of the PNG is read, otherwise a map + * with grayscale pixels is allocated and assigned to *map. + */ +static bool ReadHeightmapPNG(char *filename, uint *x, uint *y, byte **map) +{ + FILE *fp; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + fp = fopen(filename, "rb"); + if (fp == NULL) { + ShowErrorMessage(STR_PNGMAP_ERR_FILE_NOT_FOUND, STR_PNGMAP_ERROR, 0, 0); + return false; + } + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_PNGMAP_ERROR, 0, 0); + fclose(fp); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL || setjmp(png_jmpbuf(png_ptr))) { + ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_PNGMAP_ERROR, 0, 0); + fclose(fp); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return false; + } + + png_init_io(png_ptr, fp); + + /* Allocate memory and read image, without alpha or 16-bit samples + * (result is either 8-bit indexed/grayscale or 24-bit RGB) */ + png_set_packing(png_ptr); + png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_STRIP_16, NULL); + + /* Maps of wrong color-depth are not used. + * (this should have been taken care of by stripping alpha and 16-bit samples on load) */ + if ((info_ptr->channels != 1) && (info_ptr->channels != 3) && (info_ptr->bit_depth != 8)) { + ShowErrorMessage(STR_PNGMAP_ERR_IMAGE_TYPE, STR_PNGMAP_ERROR, 0, 0); + fclose(fp); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return false; + } + + if (map != NULL) { + *map = malloc(info_ptr->width * info_ptr->height * sizeof(byte)); + + if (*map == NULL) { + ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_PNGMAP_ERROR, 0, 0); + fclose(fp); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return false; + } + + ReadHeightmapPNGImageData(*map, png_ptr, info_ptr); + } + + *x = info_ptr->width; + *y = info_ptr->height; + + fclose(fp); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return true; +} + +#endif /* WITH_PNG */ + + +/** + * The BMP Heightmap loader. + */ +static void ReadHeightmapBMPImageData(byte *map, BmpInfo *info, BmpData *data) +{ + uint x, y; + byte gray_palette[256]; + + if (data->palette != NULL) { + uint i; + bool all_gray = true; + + if (info->palette_size != 2) { + for (i = 0; i < info->palette_size && (info->palette_size != 16 || all_gray); i++) { + all_gray &= data->palette[i].r == data->palette[i].g && data->palette[i].r == data->palette[i].b; + gray_palette[i] = RGBToGrayscale(data->palette[i].r, data->palette[i].g, data->palette[i].b); + } + + /** + * For a non-gray palette of size 16 we assume that + * the order of the palette determines the height; + * the first entry is the sea (level 0), the second one + * level 1, etc. + */ + if (info->palette_size == 16 && !all_gray) { + for (i = 0; i < info->palette_size; i++) { + gray_palette[i] = 256 * i / info->palette_size; + } + } + } else { + /** + * For a palette of size 2 we assume that the order of the palette determines the height; + * the first entry is the sea (level 0), the second one is the land (level 1) + */ + gray_palette[0] = 0; + gray_palette[1] = 16; + } + } + + /* Read the raw image data and convert in 8-bit grayscale */ + for (y = 0; y < info->height; y++) { + byte *pixel = &map[y * info->width]; + byte *bitmap = &data->bitmap[y * info->width * (info->bpp == 24 ? 3 : 1)]; + + for (x = 0; x < info->width; x++) { + if (info->bpp != 24) { + *pixel++ = gray_palette[*bitmap++]; + } else { + *pixel++ = RGBToGrayscale(*bitmap, *(bitmap + 1), *(bitmap + 2)); + bitmap += 3; + } + } + } +} + +/** + * Reads the heightmap and/or size of the heightmap from a BMP file. + * If map == NULL only the size of the BMP is read, otherwise a map + * with grayscale pixels is allocated and assigned to *map. + */ +static bool ReadHeightmapBMP(char *filename, uint *x, uint *y, byte **map) +{ + FILE *f; + BmpInfo info; + BmpData data; + BmpBuffer buffer; + + f = fopen(filename, "rb"); + if (f == NULL) { + ShowErrorMessage(STR_PNGMAP_ERR_FILE_NOT_FOUND, STR_BMPMAP_ERROR, 0, 0); + return false; + } + + BmpInitializeBuffer(&buffer, f); + + if (!BmpReadHeader(&buffer, &info, &data)) { + ShowErrorMessage(STR_BMPMAP_ERR_IMAGE_TYPE, STR_BMPMAP_ERROR, 0, 0); + fclose(f); + BmpDestroyData(&data); + return false; + } + + if (map != NULL) { + if (!BmpReadBitmap(&buffer, &info, &data)) { + ShowErrorMessage(STR_BMPMAP_ERR_IMAGE_TYPE, STR_BMPMAP_ERROR, 0, 0); + fclose(f); + BmpDestroyData(&data); + return false; + } + + *map = malloc(info.width * info.height * sizeof(byte)); + if (*map == NULL) { + ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_BMPMAP_ERROR, 0, 0); + fclose(f); + BmpDestroyData(&data); + return false; + } + + ReadHeightmapBMPImageData(*map, &info, &data); + + } + + BmpDestroyData(&data); + + *x = info.width; + *y = info.height; + + fclose(f); + return true; +} + +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 (_patches.heightmap_rotation) { + case HM_COUNTER_CLOCKWISE: + width = MapSizeX(); + height = MapSizeY(); + break; + case HM_CLOCKWISE: + width = MapSizeY(); + height = MapSizeX(); + break; + default: + NOT_REACHED(); + /* Avoids compiler warnings */ + return; + } + + 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 = (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 = (width - ((img_width * img_scale) / num_div)) / 2; + } + + /* Form the landscape */ + for (row = 0; row < height - 1; row++) { + for (col = 0; col < width - 1; col++) { + switch (_patches.heightmap_rotation) { + case HM_COUNTER_CLOCKWISE: tile = TileXY(col, row); break; + case HM_CLOCKWISE: tile = TileXY(row, col); break; + default: NOT_REACHED(); return; + } + + /* Check if current tile is within the 1-pixel map edge or padding regions */ + if ((DistanceFromEdge(tile) <= 1) || + (row < row_pad) || (row >= (height - row_pad)) || + (col < col_pad) || (col >= (width - col_pad))) { + 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 (_patches.heightmap_rotation) { + 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; + default: + NOT_REACHED(); + /* Avoids compiler warnings */ + return; + } + + assert(img_row < img_height); + assert(img_col < img_width); + + /* Color scales from 0 to 255, OpenTTD height scales from 0 to 15 */ + SetTileHeight(tile, map[img_row * img_width + img_col] / 16); + } + MakeClear(tile, CLEAR_GRASS, 3); + } + } +} + +/** + * This function takes care of the fact that land in OpenTTD can never differ + * more than 1 in height + */ +static void FixSlopes(void) +{ + uint width, height; + uint row, col; + byte current_tile; + + /* Adjust height difference to maximum one horizontal/vertical change. */ + width = MapSizeX(); + height = MapSizeY(); + + /* Top and left edge */ + for (row = 1; row < height - 2; row++) { + for (col = 1; col < width - 2; col++) { + /* Find lowest tile; either the top or left one */ + current_tile = TileHeight(TileXY(col - 1, row)); // top edge + 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 - 2; row > 0; row--) { + for (col = width - 2; col > 0; col--) { + /* Find lowest tile; either the bottom and right one */ + current_tile = TileHeight(TileXY(col + 1, row)); // bottom edge + 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); + } + } + } +} + +/** + * Reads the heightmap with the correct file reader + */ +static bool ReadHeightMap(char *filename, uint *x, uint *y, byte **map) +{ + switch (_file_to_saveload.mode) { +#ifdef WITH_PNG + case SL_PNG: + return ReadHeightmapPNG(filename, x, y, map); +#endif /* WITH_PNG */ + case SL_BMP: + return ReadHeightmapBMP(filename, x, y, map); + + default: + NOT_REACHED(); + /* Avoids compiler warnings */ + return false; + } +} + +bool GetHeightmapDimensions(char *filename, uint *x, uint *y) +{ + return ReadHeightMap(filename, x, y, NULL); +} + +void LoadHeightmap(char *filename) +{ + uint x, y; + byte *map = NULL; + + if (!ReadHeightMap(filename, &x, &y, &map)) { + free(map); + return; + } + + GrayscaleToMapHeights(x, y, map); + free(map); + + FixSlopes(); + MarkWholeScreenDirty(); +} + +void FlatEmptyWorld(byte tile_height) +{ + uint width, height; + uint row, col; + + width = MapSizeX(); + height = MapSizeY(); + + for (row = 2; row < height - 2; row++) { + for (col = 2; col < width - 2; col++) { + SetTileHeight(TileXY(col, row), tile_height); + } + } + + FixSlopes(); + MarkWholeScreenDirty(); +} diff --git a/heightmap.h b/heightmap.h new file mode 100644 index 0000000000..d7592bf27b --- /dev/null +++ b/heightmap.h @@ -0,0 +1,33 @@ +/* $Id$ */ + +#ifndef HEIGHTMAP_H +#define HEIGHTMAP_H + +/* + * Order of these enums has to be the same as in lang/english.txt + * Otherwise you will get inconsistent behaviour. + */ +enum { + HM_COUNTER_CLOCKWISE, //! Rotate the map counter clockwise 45 degrees + HM_CLOCKWISE, //! Rotate the map clockwise 45 degrees +}; + +/** + * Get the dimensions of a heightmap. + * @return Returns false if loading of the image failed. + */ +bool GetHeightmapDimensions(char *filename, uint *x, uint *y); + +/** + * Load a heightmap from file and change the map in his current dimensions + * to a landscape representing the heightmap. + * It converts pixels to height. The brighter, the higher. + */ +void LoadHeightmap(char *filename); + +/** + * Make an empty world where all tiles are of height 'tile_height'. + */ +void FlatEmptyWorld(byte tile_height); + +#endif /* HEIGHTMAP_H */ diff --git a/industry_cmd.c b/industry_cmd.c index a678ee703e..148a391c8a 100644 --- a/industry_cmd.c +++ b/industry_cmd.c @@ -22,6 +22,7 @@ #include "variables.h" #include "table/industry_land.h" #include "table/build_industry.h" +#include "genworld.h" #include "date.h" enum { @@ -1026,7 +1027,7 @@ static bool CheckNewIndustry_Forest(TileIndex tile) static bool CheckNewIndustry_OilRefinery(TileIndex tile) { if (_game_mode == GM_EDITOR) return true; - if (DistanceFromEdge(TILE_ADDXY(tile, 1, 1)) < 16) return true; + if (DistanceFromEdge(TILE_ADDXY(tile, 1, 1)) < _patches.oil_refinery_limit) return true; _error_message = STR_483B_CAN_ONLY_BE_POSITIONED; return false; @@ -1038,7 +1039,7 @@ static bool CheckNewIndustry_OilRig(TileIndex tile) { if (_game_mode == GM_EDITOR && _ignore_restrictions) return true; if (TileHeight(tile) == 0 && - DistanceFromEdge(TILE_ADDXY(tile, 1, 1)) < 16) return true; + DistanceFromEdge(TILE_ADDXY(tile, 1, 1)) < _patches.oil_refinery_limit) return true; _error_message = STR_483B_CAN_ONLY_BE_POSITIONED; return false; @@ -1161,7 +1162,7 @@ static const byte _industry_section_bits[] = { 16, 16, 16, 16, 16, 16, 16, }; -static bool CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTileTable *it, int type, const Town *t) +static bool CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTileTable *it, int type) { _error_message = STR_0239_SITE_UNSUITABLE; @@ -1191,22 +1192,27 @@ static bool CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTileTable tileh = GetTileSlope(cur_tile, NULL); if (IsSteepSlope(tileh)) return false; - if (tileh != SLOPE_FLAT) { - Slope t; - byte bits = _industry_section_bits[it->gfx]; + if (_patches.land_generator == LG_TERRAGENESIS || !_generating_world) { + /* It is almost impossible to have a fully flat land in TG, so what we + * do is that we check if we can make the land flat later on. See + * CheckIfCanLevelIndustryPlatform(). */ + if (tileh != SLOPE_FLAT) { + Slope t; + byte bits = _industry_section_bits[it->gfx]; - if (bits & 0x10) return false; + if (bits & 0x10) return false; - t = ComplementSlope(tileh); + t = ComplementSlope(tileh); - if (bits & 1 && (t & SLOPE_NW)) return false; - if (bits & 2 && (t & SLOPE_NE)) return false; - if (bits & 4 && (t & SLOPE_SW)) return false; - if (bits & 8 && (t & SLOPE_SE)) return false; + if (bits & 1 && (t & SLOPE_NW)) return false; + if (bits & 2 && (t & SLOPE_NE)) return false; + if (bits & 4 && (t & SLOPE_SW)) return false; + if (bits & 8 && (t & SLOPE_SE)) return false; + } } if (type == IT_BANK_TEMP) { - if (!IsTileType(cur_tile, MP_HOUSE) || t->population < 1200) { + if (!IsTileType(cur_tile, MP_HOUSE)) { _error_message = STR_029D_CAN_ONLY_BE_BUILT_IN_TOWNS; return false; } @@ -1216,7 +1222,6 @@ static bool CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTileTable return false; } } else if (type == IT_TOY_SHOP) { - if (DistanceMax(t->xy, cur_tile) > 9) return false; if (!IsTileType(cur_tile, MP_HOUSE)) goto do_clear; } else if (type == IT_WATER_TOWER) { if (!IsTileType(cur_tile, MP_HOUSE)) { @@ -1235,6 +1240,115 @@ do_clear: return true; } +static bool CheckIfIndustryIsAllowed(TileIndex tile, int type, const Town *t) +{ + if (type == IT_BANK_TEMP && t->population < 1200) { + _error_message = STR_029D_CAN_ONLY_BE_BUILT_IN_TOWNS; + return false; + } + + if (type == IT_TOY_SHOP && DistanceMax(t->xy, tile) > 9) { + _error_message = STR_0239_SITE_UNSUITABLE; + return false; + } + + return true; +} + +static bool CheckCanTerraformSurroundingTiles(TileIndex tile, uint height, int internal) +{ + int size_x, size_y; + uint curh; + + size_x = 2; + size_y = 2; + + /* Check if we don't leave the map */ + if (TileX(tile) == 0 || TileY(tile) == 0 || GetTileType(tile) == MP_VOID) return false; + + tile += TileDiffXY(-1, -1); + BEGIN_TILE_LOOP(tile_walk, size_x, size_y, tile) { + curh = TileHeight(tile_walk); + /* Is the tile clear? */ + if ((GetTileType(tile_walk) != MP_CLEAR) && (GetTileType(tile_walk) != MP_TREES)) + return false; + + /* Don't allow too big of a change if this is the sub-tile check */ + if (internal != 0 && myabs(curh - height) > 1) return false; + + /* Different height, so the surrounding tiles of this tile + * has to be correct too (in level, or almost in level) + * else you get a chain-reaction of terraforming. */ + if (internal == 0 && curh != height) { + if (!CheckCanTerraformSurroundingTiles(tile_walk + TileDiffXY(-1, -1), height, internal + 1)) + return false; + } + } END_TILE_LOOP(tile_walk, size_x, size_y, tile); + + return true; +} + +/** + * This function tries to flatten out the land below an industry, without + * damaging the surroundings too much. + */ +static bool CheckIfCanLevelIndustryPlatform(TileIndex tile, uint32 flags, const IndustryTileTable* it, int type) +{ + const int MKEND = -0x80; // used for last element in an IndustryTileTable (see build_industry.h) + int max_x = 0; + int max_y = 0; + TileIndex cur_tile; + uint size_x, size_y; + uint h, curh; + + /* Finds dimensions of largest variant of this industry */ + do { + if (it->ti.x > max_x) max_x = it->ti.x; + if (it->ti.y > max_y) max_y = it->ti.y; + } while ((++it)->ti.x != MKEND); + + /* Remember level height */ + h = TileHeight(tile); + + /* Check that all tiles in area and surrounding are clear + * this determines that there are no obstructing items */ + cur_tile = tile + TileDiffXY(-1, -1); + size_x = max_x + 4; + size_y = max_y + 4; + + /* Check if we don't leave the map */ + if (TileX(cur_tile) == 0 || TileY(cur_tile) == 0 || GetTileType(cur_tile) == MP_VOID) return false; + + BEGIN_TILE_LOOP(tile_walk, size_x, size_y, cur_tile) { + curh = TileHeight(tile_walk); + if (curh != h) { + /* This tile needs terraforming. Check if we can do that without + * damaging the surroundings too much. */ + if (!CheckCanTerraformSurroundingTiles(tile_walk, h, 0)) return false; + /* This is not 100% correct check, but the best we can do without modifying the map. + * What is missing, is if the difference in height is more than 1.. */ + if (CmdFailed(DoCommand(tile_walk, 8, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND))) return false; + } + } END_TILE_LOOP(tile_walk, size_x, size_y, cur_tile) + + if (flags & DC_EXEC) { + /* Terraform the land under the industry */ + BEGIN_TILE_LOOP(tile_walk, size_x, size_y, cur_tile) { + curh = TileHeight(tile_walk); + while (curh != h) { + /* We give the terraforming for free here, because we can't calculate + * exact cost in the test-round, and as we all know, that will cause + * a nice assert if they don't match ;) */ + DoCommand(tile_walk, 8, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND); + curh += (curh > h) ? -1 : 1; + } + } END_TILE_LOOP(tile_walk, size_x, size_y, cur_tile) + } + + return true; +} + + static bool CheckIfTooCloseToIndustry(TileIndex tile, int type) { const IndustrySpec *indspec = GetIndustrySpec(type); @@ -1373,6 +1487,33 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, int type, const Ind InvalidateWindow(WC_INDUSTRY_DIRECTORY, 0); } +static Industry *CreateNewIndustryHelper(TileIndex tile, IndustryType type, uint32 flags, const IndustrySpec *indspec, const IndustryTileTable *it) +{ + const Town *t; + Industry *i; + + if (!CheckIfIndustryTilesAreFree(tile, it, type)) return NULL; + if (_patches.land_generator == LG_TERRAGENESIS && _generating_world && !CheckIfCanLevelIndustryPlatform(tile, 0, it, type)) return NULL; + if (!_check_new_industry_procs[indspec->check_proc](tile)) return NULL; + if (!CheckIfTooCloseToIndustry(tile, type)) return NULL; + + t = CheckMultipleIndustryInTown(tile, type); + if (t == NULL) return NULL; + + if (!CheckIfIndustryIsAllowed(tile, type, t)) return NULL; + if (!CheckSuitableIndustryPos(tile)) return NULL; + + i = AllocateIndustry(); + if (i == NULL) return NULL; + + if (flags & DC_EXEC) { + CheckIfCanLevelIndustryPlatform(tile, DC_EXEC, it, type); + DoCreateNewIndustry(i, tile, type, it, t, OWNER_NONE); + } + + return i; +} + /** Build/Fund an industry * @param tile tile where industry is built * @param p1 industry type @see build_industry.h and @see industry.h @@ -1380,8 +1521,6 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, int type, const Ind */ int32 CmdBuildIndustry(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) { - const Town *t; - Industry *i; int num; const IndustryTileTable * const *itt; const IndustryTileTable *it; @@ -1389,8 +1528,6 @@ int32 CmdBuildIndustry(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) SET_EXPENSES_TYPE(EXPENSES_OTHER); - if (!CheckSuitableIndustryPos(tile)) return CMD_ERROR; - /* Check if the to-be built/founded industry is available for this climate. * Unfortunately we have no easy way of checking, except for looping the table */ { @@ -1418,25 +1555,14 @@ int32 CmdBuildIndustry(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) return CMD_ERROR; } - if (!_check_new_industry_procs[indspec->check_proc](tile)) return CMD_ERROR; - - t = CheckMultipleIndustryInTown(tile, p1); - if (t == NULL) return CMD_ERROR; - num = indspec->num_table; itt = indspec->table; do { if (--num < 0) return_cmd_error(STR_0239_SITE_UNSUITABLE); - } while (!CheckIfIndustryTilesAreFree(tile, it = itt[num], p1, t)); - - - if (!CheckIfTooCloseToIndustry(tile, p1)) return CMD_ERROR; - - i = AllocateIndustry(); - if (i == NULL) return CMD_ERROR; + } while (!CheckIfIndustryTilesAreFree(tile, it = itt[num], p1)); - if (flags & DC_EXEC) DoCreateNewIndustry(i, tile, p1, it, t, OWNER_NONE); + if (CreateNewIndustryHelper(tile, p1, flags, indspec, it) == NULL) return CMD_ERROR; return (_price.build_industry >> 5) * indspec->cost_multiplier; } @@ -1444,33 +1570,10 @@ int32 CmdBuildIndustry(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) Industry *CreateNewIndustry(TileIndex tile, IndustryType type) { - const Town *t; - const IndustryTileTable *it; - Industry *i; - - const IndustrySpec *indspec; - - if (!CheckSuitableIndustryPos(tile)) return NULL; - - indspec = GetIndustrySpec(type); - - if (!_check_new_industry_procs[indspec->check_proc](tile)) return NULL; - - t = CheckMultipleIndustryInTown(tile, type); - if (t == NULL) return NULL; - - /* pick a random layout */ - it = indspec->table[RandomRange(indspec->num_table)]; - - if (!CheckIfIndustryTilesAreFree(tile, it, type, t)) return NULL; - if (!CheckIfTooCloseToIndustry(tile, type)) return NULL; - - i = AllocateIndustry(); - if (i == NULL) return NULL; - - DoCreateNewIndustry(i, tile, type, it, t, OWNER_NONE); + const IndustrySpec *indspec = GetIndustrySpec(type); + const IndustryTileTable *it = indspec->table[RandomRange(indspec->num_table)]; - return i; + return CreateNewIndustryHelper(tile, type, DC_EXEC, indspec, it); } static const byte _numof_industry_table[4][12] = { @@ -1500,6 +1603,8 @@ static void PlaceInitialIndustry(IndustryType type, int amount) do { uint i; + IncreaseGeneratingWorldProgress(GWP_INDUSTRY); + for (i = 0; i < 2000; i++) { if (CreateNewIndustry(RandomTile(), type) != NULL) break; } @@ -1512,6 +1617,23 @@ static void PlaceInitialIndustry(IndustryType type, int amount) void GenerateIndustries(void) { const byte *b; + uint i = 0; + + /* Find the total amount of industries */ + b = _industry_create_table[_opt.landscape]; + do { + int num = _numof_industry_table[_opt.diff.number_industries][b[0]]; + + if (b[1] == IT_OIL_REFINERY || b[1] == IT_OIL_RIG) { + /* These are always placed next to the coastline, so we scale by the perimeter instead. */ + num = ScaleByMapSize1D(num); + } else { + num = ScaleByMapSize(num); + } + + i += num; + } while ( (b+=2)[0] != 0); + SetGeneratingWorldProgress(GWP_INDUSTRY, i); b = _industry_create_table[_opt.landscape]; do { diff --git a/intro_gui.c b/intro_gui.c index f674dbb26c..a896386e3d 100644 --- a/intro_gui.c +++ b/intro_gui.c @@ -11,40 +11,34 @@ #include "network.h" #include "variables.h" #include "settings.h" - -extern void SwitchMode(int new_mode); +#include "heightmap.h" +#include "genworld.h" static const Widget _select_game_widgets[] = { { WWT_CAPTION, RESIZE_NONE, 13, 0, 335, 0, 13, STR_0307_OPENTTD, STR_NULL}, -{ WWT_IMGBTN, RESIZE_NONE, 13, 0, 335, 14, 196, STR_NULL, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 13, 0, 335, 14, 176, STR_NULL, STR_NULL}, { WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 22, 33, STR_0140_NEW_GAME, STR_02FB_START_A_NEW_GAME}, { WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 22, 33, STR_0141_LOAD_GAME, STR_02FC_LOAD_A_SAVED_GAME}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 40, 51, STR_0220_CREATE_SCENARIO,STR_02FE_CREATE_A_CUSTOMIZED_GAME}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 40, 51, STR_029A_PLAY_SCENARIO, STR_0303_START_A_NEW_GAME_USING}, -{ WWT_PANEL_2, RESIZE_NONE, 12, 10, 86, 59, 113, 0x1312, STR_030E_SELECT_TEMPERATE_LANDSCAPE}, -{ WWT_PANEL_2, RESIZE_NONE, 12, 90, 166, 59, 113, 0x1314, STR_030F_SELECT_SUB_ARCTIC_LANDSCAPE}, -{ WWT_PANEL_2, RESIZE_NONE, 12, 170, 246, 59, 113, 0x1316, STR_0310_SELECT_SUB_TROPICAL_LANDSCAPE}, -{ WWT_PANEL_2, RESIZE_NONE, 12, 250, 326, 59, 113, 0x1318, STR_0311_SELECT_TOYLAND_LANDSCAPE}, - -{ WWT_PANEL, RESIZE_NONE, 12, 219, 254, 120, 131, STR_NULL, STR_NULL}, -{ WWT_TEXTBTN, RESIZE_NONE, 12, 255, 266, 120, 131, STR_0225, STR_NULL}, -{ WWT_PANEL, RESIZE_NONE, 12, 279, 314, 120, 131, STR_NULL, STR_NULL}, -{ WWT_TEXTBTN, RESIZE_NONE, 12, 315, 326, 120, 131, STR_0225, STR_NULL}, - -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 138, 149, STR_SINGLE_PLAYER, STR_02FF_SELECT_SINGLE_PLAYER_GAME}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 138, 149, STR_MULTIPLAYER, STR_0300_SELECT_MULTIPLAYER_GAME}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 159, 170, STR_0148_GAME_OPTIONS, STR_0301_DISPLAY_GAME_OPTIONS}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 159, 170, STR_01FE_DIFFICULTY, STR_0302_DISPLAY_DIFFICULTY_OPTIONS}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 177, 188, STR_CONFIG_PATCHES, STR_CONFIG_PATCHES_TIP}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 177, 188, STR_0304_QUIT, STR_0305_QUIT_OPENTTD}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 40, 51, STR_029A_PLAY_SCENARIO, STR_0303_START_A_NEW_GAME_USING}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 40, 51, STR_PLAY_HEIGHTMAP, STR_PLAY_HEIGHTMAP_HINT}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 58, 69, STR_0220_CREATE_SCENARIO,STR_02FE_CREATE_A_CUSTOMIZED_GAME}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 58, 69, STR_MULTIPLAYER, STR_0300_SELECT_MULTIPLAYER_GAME}, + +{ WWT_PANEL_2, RESIZE_NONE, 12, 10, 86, 77, 131, 0x1312, STR_030E_SELECT_TEMPERATE_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 90, 166, 77, 131, 0x1314, STR_030F_SELECT_SUB_ARCTIC_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 170, 246, 77, 131, 0x1316, STR_0310_SELECT_SUB_TROPICAL_LANDSCAPE}, +{ WWT_PANEL_2, RESIZE_NONE, 12, 250, 326, 77, 131, 0x1318, STR_0311_SELECT_TOYLAND_LANDSCAPE}, + +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 139, 150, STR_0148_GAME_OPTIONS, STR_0301_DISPLAY_GAME_OPTIONS}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 139, 150, STR_01FE_DIFFICULTY, STR_0302_DISPLAY_DIFFICULTY_OPTIONS}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 10, 167, 157, 168, STR_CONFIG_PATCHES, STR_CONFIG_PATCHES_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 12, 168, 325, 157, 168, STR_0304_QUIT, STR_0305_QUIT_OPENTTD}, { WIDGETS_END }, }; extern void HandleOnEditText(WindowEvent *e); extern void HandleOnEditTextCancel(void); -static inline void CreateScenario(void) {_switch_mode = SM_EDITOR;} - static inline void SetNewLandscapeType(byte landscape) { _opt_newgame.landscape = landscape; @@ -53,37 +47,21 @@ static inline void SetNewLandscapeType(byte landscape) static void SelectGameWndProc(Window *w, WindowEvent *e) { - /* We do +/- 6 for the map_xy because 64 is 2^6, but it is the lowest available element */ - static const StringID mapsizes[] = {STR_64, STR_128, STR_256, STR_512, STR_1024, STR_2048, INVALID_STRING_ID}; - switch (e->event) { case WE_PAINT: - w->click_state = (w->click_state & ~(1 << 14) & ~(0xF << 6)) | (1 << (_opt_newgame.landscape + 6)) | (1 << 14); + w->click_state = (w->click_state & ~(0xF << 8)) | (1 << (_opt_newgame.landscape + 8)); SetDParam(0, STR_6801_EASY + _opt_newgame.diff_level); DrawWindowWidgets(w); - - DrawStringRightAligned(216, 121, STR_MAPSIZE, 0); - DrawString(223, 121, mapsizes[_patches_newgame.map_x - 6], 0x10); - DrawString(270, 121, STR_BY, 0); - DrawString(283, 121, mapsizes[_patches_newgame.map_y - 6], 0x10); break; case WE_CLICK: switch (e->click.widget) { - case 2: AskForNewGameToStart(); break; + case 2: ShowGenerateLandscape(); break; case 3: ShowSaveLoadDialog(SLD_LOAD_GAME); break; - case 4: CreateScenario(); break; - case 5: ShowSaveLoadDialog(SLD_LOAD_SCENARIO); break; - case 6: case 7: case 8: case 9: - SetNewLandscapeType(e->click.widget - 6); - break; - case 10: case 11: /* Mapsize X */ - ShowDropDownMenu(w, mapsizes, _patches_newgame.map_x - 6, 11, 0, 0); - break; - case 12: case 13: /* Mapsize Y */ - ShowDropDownMenu(w, mapsizes, _patches_newgame.map_y - 6, 13, 0, 0); - break; - case 15: + case 4: ShowSaveLoadDialog(SLD_LOAD_SCENARIO); break; + case 5: ShowSaveLoadDialog(SLD_LOAD_HEIGHTMAP); break; + case 6: ShowCreateScenario(); break; + case 7: #ifdef ENABLE_NETWORK if (!_network_available) { ShowErrorMessage(INVALID_STRING_ID, STR_NETWORK_ERR_NOTAVAILABLE, 0, 0); @@ -94,32 +72,20 @@ static void SelectGameWndProc(Window *w, WindowEvent *e) ShowErrorMessage(INVALID_STRING_ID ,STR_NETWORK_ERR_NOTAVAILABLE, 0, 0); #endif break; - case 16: ShowGameOptions(); break; - case 17: ShowGameDifficulty(); break; - case 18: ShowPatchesSelection(); break; - case 19: AskExitGame(); break; - } - break; - - case WE_ON_EDIT_TEXT: HandleOnEditText(e); break; - case WE_ON_EDIT_TEXT_CANCEL: HandleOnEditTextCancel(); break; - - case WE_DROPDOWN_SELECT: /* Mapsize selection */ - switch (e->dropdown.button) { - /* We need a *hacky* here because generateworld is called with _patches - * but it only gets the new value INSIDE generateworld so not setting it would - * break generating a new game on the run (eg MP) */ - case 11: _patches.map_x = _patches_newgame.map_x = e->dropdown.index + 6; break; - case 13: _patches.map_y = _patches_newgame.map_y = e->dropdown.index + 6; break; + case 8: case 9: case 10: case 11: + SetNewLandscapeType(e->click.widget - 8); + break; + case 12: ShowGameOptions(); break; + case 13: ShowGameDifficulty(); break; + case 14: ShowPatchesSelection(); break; + case 15: AskExitGame(); break; } - SetWindowDirty(w); break; } - } static const WindowDesc _select_game_desc = { - WDP_CENTER, WDP_CENTER, 336, 197, + WDP_CENTER, WDP_CENTER, 336, 177, WC_SELECT_GAME,0, WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, _select_game_widgets, @@ -131,22 +97,6 @@ void ShowSelectGameWindow(void) AllocateWindowDesc(&_select_game_desc); } -void GenRandomNewGame(uint32 rnd1, uint32 rnd2) -{ - _random_seeds[0][0] = rnd1; - _random_seeds[0][1] = rnd2; - - SwitchMode(SM_NEWGAME); -} - -void StartScenarioEditor(uint32 rnd1, uint32 rnd2) -{ - _random_seeds[0][0] = rnd1; - _random_seeds[0][1] = rnd2; - - SwitchMode(SM_START_SCENARIO); -} - static const Widget _ask_abandon_game_widgets[] = { { WWT_CLOSEBOX, RESIZE_NONE, 4, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, { WWT_CAPTION, RESIZE_NONE, 4, 11, 179, 0, 13, STR_00C7_QUIT, STR_NULL}, diff --git a/landscape.c b/landscape.c index 007d653e0c..a1d5e2c4c2 100644 --- a/landscape.c +++ b/landscape.c @@ -2,6 +2,7 @@ #include "stdafx.h" #include "openttd.h" +#include "heightmap.h" #include "clear_map.h" #include "functions.h" #include "map.h" @@ -16,6 +17,9 @@ #include "variables.h" #include "void_map.h" #include "water_map.h" +#include "tgp.h" +#include "genworld.h" +#include "heightmap.h" extern const TileTypeProcs _tile_type_clear_procs, @@ -409,10 +413,34 @@ void InitializeLandscape(void) void ConvertGroundTilesIntoWaterTiles(void) { TileIndex tile; + uint z; + Slope slope; for (tile = 0; tile < MapSize(); ++tile) { - if (IsTileType(tile, MP_CLEAR) && GetTileMaxZ(tile) == 0) { - MakeWater(tile); + slope = GetTileSlope(tile, &z); + if (IsTileType(tile, MP_CLEAR) && z == 0) { + /* Make both water for tiles at level 0 + * and make shore, as that looks much better + * during the generation. */ + switch (slope) { + case SLOPE_FLAT: + MakeWater(tile); + break; + + case SLOPE_N: + case SLOPE_E: + case SLOPE_S: + case SLOPE_W: + case SLOPE_NW: + case SLOPE_SW: + case SLOPE_SE: + case SLOPE_NE: + MakeShore(tile); + break; + + default: + break; + } } } } @@ -547,10 +575,13 @@ static void GenerateTerrain(int type, int flag) static void CreateDesertOrRainForest(void) { TileIndex tile; + TileIndex update_freq = MapSize() / 4; const TileIndexDiffC *data; uint i; for (tile = 0; tile != MapSize(); ++tile) { + if ((tile % update_freq) == 0) IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + for (data = _make_desert_or_rainforest_data; data != endof(_make_desert_or_rainforest_data); ++data) { TileIndex t = TILE_MASK(tile + ToTileIndexDiff(*data)); @@ -560,10 +591,15 @@ static void CreateDesertOrRainForest(void) SetTropicZone(tile, TROPICZONE_DESERT); } - for (i = 0; i != 256; i++) + for (i = 0; i != 256; i++) { + if ((i % 64) == 0) IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + RunTileLoop(); + } for (tile = 0; tile != MapSize(); ++tile) { + if ((tile % update_freq) == 0) IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + for (data = _make_desert_or_rainforest_data; data != endof(_make_desert_or_rainforest_data); ++data) { TileIndex t = TILE_MASK(tile + ToTileIndexDiff(*data)); @@ -574,49 +610,71 @@ static void CreateDesertOrRainForest(void) } } -void GenerateLandscape(void) +void GenerateLandscape(byte mode) { + const int gwp_desert_amount = 4 + 8; uint i; uint flag; uint32 r; - switch (_opt.landscape) { - case LT_HILLY: - for (i = ScaleByMapSize((Random() & 0x7F) + 950); i != 0; --i) { - GenerateTerrain(2, 0); - } + if (mode == GW_HEIGHTMAP) { + SetGeneratingWorldProgress(GWP_LANDSCAPE, (_opt.landscape == LT_DESERT) ? 1 + gwp_desert_amount : 1); + LoadHeightmap(_file_to_saveload.name); + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + } else if (_patches.land_generator == LG_TERRAGENESIS) { + SetGeneratingWorldProgress(GWP_LANDSCAPE, (_opt.landscape == LT_DESERT) ? 3 + gwp_desert_amount : 3); + GenerateTerrainPerlin(); + } else { + switch (_opt.landscape) { + case LT_HILLY: + SetGeneratingWorldProgress(GWP_LANDSCAPE, 2); - r = Random(); - flag = GB(r, 0, 2) | 4; - for (i = ScaleByMapSize(GB(r, 16, 7) + 450); i != 0; --i) { - GenerateTerrain(4, flag); - } - break; + for (i = ScaleByMapSize((Random() & 0x7F) + 950); i != 0; --i) { + GenerateTerrain(2, 0); + } + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); - case LT_DESERT: - for (i = ScaleByMapSize((Random() & 0x7F) + 170); i != 0; --i) { - GenerateTerrain(0, 0); - } + r = Random(); + flag = GB(r, 0, 2) | 4; + for (i = ScaleByMapSize(GB(r, 16, 7) + 450); i != 0; --i) { + GenerateTerrain(4, flag); + } + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + break; - r = Random(); - flag = GB(r, 0, 2) | 4; - for (i = ScaleByMapSize(GB(r, 16, 8) + 1700); i != 0; --i) { - GenerateTerrain(0, flag); - } + case LT_DESERT: + SetGeneratingWorldProgress(GWP_LANDSCAPE, 3 + gwp_desert_amount); - flag ^= 2; + for (i = ScaleByMapSize((Random() & 0x7F) + 170); i != 0; --i) { + GenerateTerrain(0, 0); + } + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); - for (i = ScaleByMapSize((Random() & 0x7F) + 410); i != 0; --i) { - GenerateTerrain(3, flag); - } - break; + r = Random(); + flag = GB(r, 0, 2) | 4; + for (i = ScaleByMapSize(GB(r, 16, 8) + 1700); i != 0; --i) { + GenerateTerrain(0, flag); + } + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); - default: - i = ScaleByMapSize((Random() & 0x7F) + (3 - _opt.diff.quantity_sea_lakes) * 256 + 100); - for (; i != 0; --i) { - GenerateTerrain(_opt.diff.terrain_type, 0); - } - break; + flag ^= 2; + + for (i = ScaleByMapSize((Random() & 0x7F) + 410); i != 0; --i) { + GenerateTerrain(3, flag); + } + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + break; + + default: + SetGeneratingWorldProgress(GWP_LANDSCAPE, 1); + + i = ScaleByMapSize((Random() & 0x7F) + (3 - _opt.diff.quantity_sea_lakes) * 256 + 100); + for (; i != 0; --i) { + GenerateTerrain(_opt.diff.terrain_type, 0); + } + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + break; + } } ConvertGroundTilesIntoWaterTiles(); diff --git a/lang/english.txt b/lang/english.txt index 99f220c6e6..d8f985064e 100644 --- a/lang/english.txt +++ b/lang/english.txt @@ -604,6 +604,8 @@ STR_0229_DECREASE_SIZE_OF_LAND_AREA :{BLACK}Decrease STR_022A_GENERATE_RANDOM_LAND :{BLACK}Generate random land STR_022B_RESET_LANDSCAPE :{BLACK}Reset landscape STR_022C_RESET_LANDSCAPE :{WHITE}Reset Landscape +STR_LOAD_GAME_HEIGHTMAP :{WHITE}Use Heightmap +STR_LOAD_SCEN_HEIGHTMAP :{BLACK}Use Heightmap STR_022D_ARE_YOU_SURE_YOU_WANT_TO :{WHITE}Are you sure you want to reset the landscape? STR_022E_LANDSCAPE_GENERATION :{BLACK}Landscape generation STR_022F_TOWN_GENERATION :{BLACK}Town generation @@ -716,6 +718,8 @@ STR_0297_SAVE_SCENARIO_LOAD_SCENARIO :{BLACK}Save sce STR_0298_LOAD_SCENARIO :{WHITE}Load Scenario STR_0299_SAVE_SCENARIO :{WHITE}Save Scenario STR_029A_PLAY_SCENARIO :{BLACK}Play Scenario +STR_PLAY_HEIGHTMAP :{BLACK}Play Heightmap +STR_PLAY_HEIGHTMAP_HINT :{BLACK}Start a new game, using a heightmap as landscape STR_029B_ARE_YOU_SURE_YOU_WANT_TO :{YELLOW}Are you sure you want to quit this scenario ? STR_029C_QUIT_EDITOR :{WHITE}Quit Editor STR_029D_CAN_ONLY_BE_BUILT_IN_TOWNS :{WHITE}...can only be built in towns with a population of at least 1200 @@ -1039,7 +1043,27 @@ STR_CONFIG_PATCHES_AUTORENEW_MONEY :{LTBLUE}Autoren STR_CONFIG_PATCHES_ERRMSG_DURATION :{LTBLUE}Duration of error message: {ORANGE}{STRING1} STR_CONFIG_PATCHES_POPULATION_IN_LABEL :{LTBLUE}Show town population in the town name label: {ORANGE}{STRING1} STR_CONFIG_PATCHES_INVISIBLE_TREES :{LTBLUE}Invisible trees (with transparent buildings): {ORANGE}{STRING1} + +STR_CONFIG_PATCHES_LAND_GENERATOR :{LTBLUE}Land generator: {ORANGE}{STRING1} +STR_CONFIG_PATCHES_LAND_GENERATOR_ORIGINAL :Original +STR_CONFIG_PATCHES_LAND_GENERATOR_TERRA_GENESIS :TerraGenesis +STR_CONFIG_PATCHES_OIL_REF_EDGE_DISTANCE :{LTBLUE}Max distance from edge for Oil Refineries {ORANGE}{STRING1} STR_CONFIG_PATCHES_SNOWLINE_HEIGHT :{LTBLUE}Snow line height: {ORANGE}{STRING1} +STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN :{LTBLUE}Roughness of terrain (TerraGenesis only) : {ORANGE}{STRING1} +STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_SMOOTH :Very Smooth +STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_SMOOTH :Smooth +STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_ROUGH :Rough +STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN_VERY_ROUGH :Very Rough +STR_CONFIG_PATCHES_TREE_PLACER :{LTBLUE}Tree placer algorithm: {ORANGE}{STRING1} +STR_CONFIG_PATCHES_TREE_PLACER_NONE :None +STR_CONFIG_PATCHES_TREE_PLACER_ORIGINAL :Original +STR_CONFIG_PATCHES_TREE_PLACER_IMPROVED :Improved +STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION :{LTBLUE}Heightmap rotation: {ORANGE}{STRING1} +STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_COUNTER_CLOCKWISE :Counter clockwise +STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION_CLOCKWISE :Clockwise +STR_CONFIG_PATCHES_PROGRESS_UPDATE_INTERVAL :{LTBLUE}Progress update interval: {ORANGE}{STRING1} ms +STR_CONFIG_PATCHES_SE_FLAT_WORLD_HEIGHT :{LTBLUE}The height level a flat scenario map gets: {ORANGE}{STRING1} + STR_CONFIG_PATCHES_STATION_SPREAD :{LTBLUE}Max station spread: {ORANGE}{STRING1} {RED}Warning: High setting slows game STR_CONFIG_PATCHES_SERVICEATHELIPAD :{LTBLUE}Service helicopters at helipads automatically: {ORANGE}{STRING1} STR_CONFIG_PATCHES_LINK_TERRAFORM_TOOLBAR :{LTBLUE}Link landscape toolbar to rail/road/water/airport toolbars: {ORANGE}{STRING1} @@ -1434,6 +1458,17 @@ STR_NETWORK_SEND :{BLACK}Send STR_CONFIG_PATCHES_MAP_X :{LTBLUE}X-size of map: {ORANGE}{STRING1} STR_CONFIG_PATCHES_MAP_Y :{LTBLUE}Y-size of map: {ORANGE}{STRING1} + +##### PNG-MAP-Loader + +STR_PNGMAP_ERROR :{WHITE}Cannot load landscape from PNG... +STR_PNGMAP_ERR_FILE_NOT_FOUND :{WHITE}...file not found. +STR_PNGMAP_ERR_IMAGE_TYPE :{WHITE}...could not convert image type. 8 or 24-bit PNG image needed. +STR_PNGMAP_ERR_MISC :{WHITE}...something just went wrong. Sorry. (probably corrupted file) + +STR_BMPMAP_ERROR :{WHITE}Cannot load landscape from BMP... +STR_BMPMAP_ERR_IMAGE_TYPE :{WHITE}...could not convert image type. + ##id 0x0800 STR_0800_COST :{TINYFONT}{RED}Cost: {CURRENCY} STR_0801_COST :{RED}Cost: {CURRENCY} @@ -1757,6 +1792,7 @@ STR_400D_SAVE_THE_CURRENT_GAME_USING :{BLACK}Save the STR_400E_SELECT_NEW_GAME_TYPE :{WHITE}Select New Game Type STR_400F_SELECT_SCENARIO_GREEN_PRE :{BLACK}Select scenario (green), pre-set game (blue), or random new game STR_4010_GENERATE_RANDOM_NEW_GAME :Generate random new game +STR_4011_LOAD_HEIGHTMAP :{WHITE}Load Heightmap ##id 0x4800 STR_4800_IN_THE_WAY :{WHITE}{STRING} in the way @@ -2863,6 +2899,58 @@ STR_PURCHASE_INFO_COST_SPEED :{BLACK}Cost: {G STR_PURCHASE_INFO_AIRCRAFT_CAPACITY :{BLACK}Capacity: {GOLD}{COMMA} passengers, {COMMA} bags of mail STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT :{BLACK}Powered Wagons: {GOLD}+{POWER}{BLACK} Weight: {GOLD}+{WEIGHT_S} +########### String for New Landscape Generator + +STR_GENERATE :{WHITE}Generate +STR_RANDOM :{BLACK}Randomise +STR_RANDOM_HELP :{BLACK}Change the random seed used for Terrain Generation +STR_WORLD_GENERATION_CAPTION :{WHITE}World generation +STR_RANDOM_SEED :{BLACK}Random Seed: +STR_RANDOM_SEED_HELP :{BLACK}Click to enter a random seed +STR_LAND_GENERATOR :{BLACK}Land generator: +STR_TREE_PLACER :{BLACK}Tree algorithm: +STR_HEIGHTMAP_ROTATION :{BLACK}Heightmap rotation: +STR_TERRAIN_TYPE :{BLACK}Terrain type: +STR_QUANTITY_OF_SEA_LAKES :{BLACK}Sea level: +STR_SMOOTHNESS :{BLACK}Smoothness: +STR_SNOW_LINE_HEIGHT :{BLACK}Snow line height: +STR_DATE :{BLACK}Date: +STR_NUMBER_OF_TOWNS :{BLACK}No. of towns: +STR_NUMBER_OF_INDUSTRIES :{BLACK}No. of industries: +STR_GENERATE_DATE :{BLACK}{DATE_LONG} +STR_SNOW_LINE_UP :{BLACK}Move the snow line height one up +STR_SNOW_LINE_DOWN :{BLACK}Move the snow line height one down +STR_SNOW_LINE_QUERY_CAPT :{WHITE}Change snow line height +STR_START_DATE_QUERY_CAPT :{WHITE}Change starting year +STR_HEIGHTMAP_SCALE_WARNING_CAPTION :{WHITE}Scale warning +STR_HEIGHTMAP_SCALE_WARNING_MESSAGE :{YELLOW}Resizing source map too much is not recommended. Continue with the generation? +STR_SNOW_LINE_HEIGHT_NUM :{NUM} +STR_HEIGHTMAP_NAME :{BLACK}Heightmap name: +STR_HEIGHTMAP_SIZE :{BLACK}Size: {ORANGE}{NUM} x {NUM} +STR_GENERATION_WORLD :{WHITE}Generating world... +STR_GENERATION_ABORT :{BLACK}Abort +STR_GENERATION_ABORT_CAPTION :{WHITE}Abort World Generation +STR_GENERATION_ABORT_MESSAGE :{YELLOW}Do you really want to abort the generation? +STR_PROGRESS :{WHITE}{NUM}% complete +STR_GENERATION_PROGRESS :{BLACK}{NUM} / {NUM} +STR_WORLD_GENERATION :{BLACK}World generation +STR_TREE_GENERATION :{BLACK}Tree generation +STR_UNMOVABLE_GENERATION :{BLACK}Unmovable generation +STR_CLEARING_TILES :{BLACK}Rough and rocky area generation +STR_SETTINGUP_GAME :{BLACK}Setting up game +STR_PREPARING_TILELOOP :{BLACK}Running tile-loop +STR_PREPARING_GAME :{BLACK}Preparing game +STR_DIFFICULTY_TO_CUSTOM :{WHITE}This action changed the difficulty level to custom +STR_SE_FLAT_WORLD :{WHITE}Flat land +STR_SE_RANDOM_LAND :{WHITE}Random land +STR_SE_NEW_WORLD :{BLACK}Create new scenario +STR_SE_CAPTION :{WHITE}Scenario type +STR_FLAT_WORLD_HEIGHT_DOWN :{BLACK}Move the height of flat land one down +STR_FLAT_WORLD_HEIGHT_UP :{BLACK}Move the height of flat land one up +STR_FLAT_WORLD_HEIGHT_QUERY_CAPT :{WHITE}Change height of flat land +STR_FLAT_WORLD_HEIGHT :{BLACK}Height of flat land: +STR_FLAT_WORLD_HEIGHT_NUM :{NUM} + ########### String for new airports STR_SMALL_AIRPORT :{BLACK}Small STR_CITY_AIRPORT :{BLACK}City diff --git a/macros.h b/macros.h index 55007c5940..f5c78cc35d 100644 --- a/macros.h +++ b/macros.h @@ -180,4 +180,10 @@ static inline uint16 ReadLE16Unaligned(const void* x) */ #define ALIGN(x, n) (((x) + (n) - 1) & ~((n) - 1)) +/** return the largest value that can be entered in a variable. + * known to work for uint32. + * used by TGP to set the max value of the _patches.generation_seed in its definition + */ +#define MAX_UVALUE(type) ((type)~(type)0) + #endif /* MACROS_H */ diff --git a/main_gui.c b/main_gui.c index eab140e915..3a9564d08f 100644 --- a/main_gui.c +++ b/main_gui.c @@ -2,6 +2,7 @@ #include "stdafx.h" #include "openttd.h" +#include "heightmap.h" #include "currency.h" #include "functions.h" #include "spritecache.h" @@ -29,22 +30,19 @@ #include "train.h" #include "unmovable_map.h" #include "screenshot.h" +#include "genworld.h" +#include "settings.h" #include "date.h" #include "network_data.h" #include "network_client.h" #include "network_server.h" -/* Min/Max date for scenario editor */ -static const Date MinDate = 0; // 1920-01-01 (MAX_YEAR_BEGIN_REAL) -static const Date MaxDate = 29220; // 2000-01-01 - static int _rename_id; static int _rename_what; static byte _terraform_size = 1; static RailType _last_built_railtype; -extern void GenerateWorld(int mode, uint size_x, uint size_y); extern void GenerateIndustries(void); extern bool GenerateTowns(void); @@ -1002,9 +1000,10 @@ static void ToolbarScenDateBackward(Window *w) // don't allow too fast scrolling if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) { HandleButtonClick(w, 6); - InvalidateWidget(w, 5); + SetWindowDirty(w); - if (_date > MinDate) SetDate(ConvertYMDToDate(_cur_year - 1, 0, 1)); + _patches_newgame.starting_year = clamp(_patches_newgame.starting_year - 1, MIN_YEAR, MAX_YEAR); + SetDate(ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); } _left_button_clicked = false; } @@ -1014,9 +1013,10 @@ static void ToolbarScenDateForward(Window *w) // don't allow too fast scrolling if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) { HandleButtonClick(w, 7); - InvalidateWidget(w, 5); + SetWindowDirty(w); - if (_date < MaxDate) SetDate(ConvertYMDToDate(_cur_year + 1, 0, 1)); + _patches_newgame.starting_year = clamp(_patches_newgame.starting_year + 1, MIN_YEAR, MAX_YEAR); + SetDate(ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); } _left_button_clicked = false; } @@ -1051,7 +1051,7 @@ void ZoomInOrOutToCursorWindow(bool in, Window *w) vp = w->viewport; - if (_game_mode != GM_MENU) { + if (_game_mode != GM_MENU && !IsGeneratingWorld()) { if ((in && vp->zoom == 0) || (!in && vp->zoom == 2)) return; @@ -1064,74 +1064,6 @@ void ZoomInOrOutToCursorWindow(bool in, Window *w) } } -static void ResetLandscape(void) -{ - _random_seeds[0][0] = InteractiveRandom(); - _random_seeds[0][1] = InteractiveRandom(); - - GenerateWorld(GW_EMPTY, 1 << _patches.map_x, 1 << _patches.map_y); - MarkWholeScreenDirty(); -} - -static const Widget _ask_reset_landscape_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 4, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, RESIZE_NONE, 4, 11, 179, 0, 13, STR_022C_RESET_LANDSCAPE, STR_NULL}, -{ WWT_IMGBTN, RESIZE_NONE, 4, 0, 179, 14, 91, 0x0, STR_NULL}, -{ WWT_TEXTBTN, RESIZE_NONE, 12, 25, 84, 72, 83, STR_00C9_NO, STR_NULL}, -{ WWT_TEXTBTN, RESIZE_NONE, 12, 95, 154, 72, 83, STR_00C8_YES, STR_NULL}, -{ WIDGETS_END}, -}; - -// Ask first to reset landscape or to make a random landscape -static void AskResetLandscapeWndProc(Window *w, WindowEvent *e) -{ - uint mode = w->window_number; - - switch (e->event) { - case WE_PAINT: - DrawWindowWidgets(w); - DrawStringMultiCenter( - 90, 38, - mode ? STR_022D_ARE_YOU_SURE_YOU_WANT_TO : STR_GENERATE_RANDOM_LANDSCAPE, - 168 - ); - break; - case WE_CLICK: - switch (e->click.widget) { - case 3: - DeleteWindow(w); - break; - case 4: - DeleteWindow(w); - DeleteWindowByClass(WC_INDUSTRY_VIEW); - DeleteWindowByClass(WC_TOWN_VIEW); - DeleteWindowByClass(WC_LAND_INFO); - - if (mode) { // reset landscape - ResetLandscape(); - } else { // make random landscape - SndPlayFx(SND_15_BEEP); - _switch_mode = SM_GENRANDLAND; - } - break; - } - break; - } -} - -static const WindowDesc _ask_reset_landscape_desc = { - 230,205, 180, 92, - WC_ASK_RESET_LANDSCAPE,0, - WDF_STD_BTN | WDF_DEF_WIDGET, - _ask_reset_landscape_widgets, - AskResetLandscapeWndProc, -}; - -static void AskResetLandscape(uint mode) -{ - AllocateWindowDescFront(&_ask_reset_landscape_desc, mode); -} - // TODO - Incorporate into game itself to allow for ingame raising/lowering of // larger chunks at the same time OR remove altogether, as we have 'level land' ? /** @@ -1238,8 +1170,7 @@ static const Widget _scen_edit_land_gen_widgets[] = { { WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, { WWT_CAPTION, RESIZE_NONE, 7, 11, 169, 0, 13, STR_0223_LAND_GENERATION, STR_018C_WINDOW_TITLE_DRAG_THIS}, { WWT_STICKYBOX, RESIZE_NONE, 7, 170, 181, 0, 13, STR_NULL, STR_STICKY_BUTTON}, -{ WWT_IMGBTN, RESIZE_NONE, 7, 0, 181, 14, 101, STR_NULL, STR_NULL}, - +{ WWT_IMGBTN, RESIZE_NONE, 7, 0, 181, 14, 95, STR_NULL, STR_NULL}, { WWT_IMGBTN, RESIZE_NONE, 14, 2, 23, 14, 35, SPR_IMG_DYNAMITE, STR_018D_DEMOLISH_BUILDINGS_ETC}, { WWT_IMGBTN, RESIZE_NONE, 14, 24, 45, 14, 35, SPR_IMG_TERRAFORM_DOWN, STR_018E_LOWER_A_CORNER_OF_LAND}, { WWT_IMGBTN, RESIZE_NONE, 14, 46, 67, 14, 35, SPR_IMG_TERRAFORM_UP, STR_018F_RAISE_A_CORNER_OF_LAND}, @@ -1250,8 +1181,7 @@ static const Widget _scen_edit_land_gen_widgets[] = { { WWT_IMGBTN, RESIZE_NONE, 14, 158, 179, 14, 35, SPR_IMG_TRANSMITTER, STR_028E_PLACE_TRANSMITTER}, { WWT_TEXTBTN, RESIZE_NONE, 14, 139, 149, 43, 54, STR_0224, STR_0228_INCREASE_SIZE_OF_LAND_AREA}, { WWT_TEXTBTN, RESIZE_NONE, 14, 139, 149, 56, 67, STR_0225, STR_0229_DECREASE_SIZE_OF_LAND_AREA}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 34, 149, 75, 86, STR_0226_RANDOM_LAND, STR_022A_GENERATE_RANDOM_LAND}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 34, 149, 88, 99, STR_0227_RESET_LAND, STR_022B_RESET_LANDSCAPE}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 34, 149, 75, 86, STR_SE_NEW_WORLD, STR_022A_GENERATE_RANDOM_LAND}, { WIDGETS_END}, }; @@ -1388,11 +1318,7 @@ static void ScenEditLandGenWndProc(Window *w, WindowEvent *e) } break; case 14: /* gen random land */ HandleButtonClick(w, 14); - AskResetLandscape(0); - break; - case 15: /* reset landscape */ - HandleButtonClick(w,15); - AskResetLandscape(1); + ShowCreateScenario(); break; } break; @@ -1422,7 +1348,7 @@ static void ScenEditLandGenWndProc(Window *w, WindowEvent *e) } static const WindowDesc _scen_edit_land_gen_desc = { - -1,-1, 182, 102, + -1,-1, 182, 96, WC_SCEN_LAND_GEN,0, WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON, _scen_edit_land_gen_widgets, @@ -1879,6 +1805,8 @@ static void MainToolbarWndProc(Window *w, WindowEvent *e) case WE_KEYPRESS: { PlayerID local = (_local_player != OWNER_SPECTATOR) ? _local_player : 0; + if (IsGeneratingWorld()) break; + switch (e->keypress.keycode) { case WKC_F1: case WKC_PAUSE: ToolbarPauseClick(w); @@ -2062,13 +1990,12 @@ static void ScenEditToolbarWndProc(Window *w, WindowEvent *e) { switch (e->event) { case WE_PAINT: - /* XXX look for better place for these */ - if (_date <= MinDate) { + if (_patches_newgame.starting_year <= MIN_YEAR) { SETBIT(w->disabled_state, 6); } else { CLRBIT(w->disabled_state, 6); } - if (_date >= MaxDate) { + if (_patches_newgame.starting_year >= MAX_YEAR) { SETBIT(w->disabled_state, 7); } else { CLRBIT(w->disabled_state, 7); @@ -2080,10 +2007,10 @@ static void ScenEditToolbarWndProc(Window *w, WindowEvent *e) DrawWindowWidgets(w); - SetDParam(0, _date); + SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); DrawStringCentered(298, 6, STR_00AF, 0); - SetDParam(0, _date); + SetDParam(0, ConvertYMDToDate(_patches_newgame.starting_year, 0, 1)); DrawStringCentered(161, 1, STR_0221_OPENTTD, 0); DrawStringCentered(161, 11,STR_0222_SCENARIO_EDITOR, 0); @@ -2207,7 +2134,7 @@ static void StatusBarWndProc(Window *w, WindowEvent *e) 70, 1, (_pause || _patches.status_long_date) ? STR_00AF : STR_00AE, 0 ); - if (p != NULL) { + if (p != NULL && !IsGeneratingWorld()) { // Draw player money SetDParam64(0, p->money64); DrawStringCentered(570, 1, p->player_money >= 0 ? STR_0004 : STR_0005, 0); @@ -2225,7 +2152,7 @@ static void StatusBarWndProc(Window *w, WindowEvent *e) if (!DrawScrollingStatusText(&_statusbar_news_item, WP(w,def_d).data_1)) WP(w,def_d).data_1 = -1280; } else { - if (p != NULL) { + if (p != NULL && !IsGeneratingWorld()) { // This is the default text SetDParam(0, p->name_1); SetDParam(1, p->name_2); @@ -2327,7 +2254,7 @@ static void MainWindowWndProc(Window *w, WindowEvent *e) case WE_KEYPRESS: if (e->keypress.keycode == WKC_BACKQUOTE) { - IConsoleSwitch(); + if (!IsGeneratingWorld()) IConsoleSwitch(); e->keypress.cont = false; break; } @@ -2339,7 +2266,7 @@ static void MainWindowWndProc(Window *w, WindowEvent *e) break; } - if (_game_mode == GM_MENU) break; + if (_game_mode == GM_MENU || IsGeneratingWorld()) break; switch (e->keypress.keycode) { case 'C': @@ -2435,11 +2362,7 @@ void SetupColorsAndInitialWindow(void) w = AllocateWindow(0, 0, width, height, MainWindowWndProc, WC_MAIN_WINDOW, NULL); AssignWindowViewport(w, 0, 0, width, height, 0, 0); - w = AllocateWindowDesc(&_toolb_scen_desc); - w->disabled_state = 1 << 9; - CLRBITS(w->flags4, WF_WHITE_BORDER_MASK); - - PositionMainToolbar(w); // already WC_MAIN_TOOLBAR passed (&_toolb_scen_desc) + ShowVitalWindows(); break; default: NOT_REACHED(); @@ -2450,17 +2373,31 @@ void ShowVitalWindows(void) { Window *w; - w = AllocateWindowDesc(&_toolb_normal_desc); - w->disabled_state = 1 << 17; // disable zoom-in button (by default game is zoomed in) + if (_game_mode != GM_EDITOR) { + w = AllocateWindowDesc(&_toolb_normal_desc); + /* Disable zoom-in for normal things, and zoom-out if we come + * from world-generating. */ + w->disabled_state = IsGeneratingWorld() ? (1 << 18) : (1 << 17); + } else { + w = AllocateWindowDesc(&_toolb_scen_desc); + /* Disable zoom-in for normal things, and zoom-out if we come + * from world-generating. */ + w->disabled_state = IsGeneratingWorld() ? (1 << 10) : (1 << 9); + } CLRBITS(w->flags4, WF_WHITE_BORDER_MASK); - if (_networking) { // if networking, disable fast-forward button + if (_networking) { + /* If networking, disable fast-forward button */ SETBIT(w->disabled_state, 1); - if (!_network_server) // if not server, disable pause button - SETBIT(w->disabled_state, 0); + /* If not server, disable pause button */ + if (!_network_server) SETBIT(w->disabled_state, 0); } - PositionMainToolbar(w); // already WC_MAIN_TOOLBAR passed (&_toolb_normal_desc) + /* 'w' is for sure a WC_MAIN_TOOLBAR */ + PositionMainToolbar(w); + + /* Status bad only for normal games */ + if (_game_mode == GM_EDITOR) return; _main_status_desc.top = _screen.height - 12; w = AllocateWindowDesc(&_main_status_desc); diff --git a/misc.c b/misc.c index e853fc579f..c74b68055f 100644 --- a/misc.c +++ b/misc.c @@ -19,8 +19,6 @@ #include "table/landscape_const.h" #include "date.h" -extern void StartupEconomy(void); - char _name_array[512][32]; #ifndef MERSENNE_TWISTER @@ -96,19 +94,6 @@ void InitializePlayers(void); static void InitializeCheats(void); void InitializeNPF(void); -void GenerateLandscape(void); -void GenerateClearTile(void); - -void GenerateIndustries(void); -void GenerateUnmovables(void); -bool GenerateTowns(void); - -void StartupPlayers(void); -void StartupDisasters(void); -void GenerateTrees(void); - -void ConvertGroundTilesIntoWaterTiles(void); - void InitializeGame(int mode, uint size_x, uint size_y) { AllocateMap(size_x, size_y); @@ -165,53 +150,6 @@ void InitializeGame(int mode, uint size_x, uint size_y) ResetObjectToPlace(); } -void GenerateWorld(int mode, uint size_x, uint size_y) -{ - // Make sure everything is done via OWNER_NONE - _current_player = OWNER_NONE; - - UpdatePatches(); - - _generating_world = true; - InitializeGame(mode == GW_RANDOM ? 0 : IG_DATE_RESET, size_x, size_y); - SetObjectToPlace(SPR_CURSOR_ZZZ, 0, 0, 0); - - // Must start economy early because of the costs. - StartupEconomy(); - - // Don't generate landscape items when in the scenario editor. - if (mode == GW_EMPTY) { - // empty world in scenario editor - ConvertGroundTilesIntoWaterTiles(); - } else { - GenerateLandscape(); - GenerateClearTile(); - - // only generate towns, tree and industries in newgame mode. - if (mode == GW_NEWGAME) { - GenerateTowns(); - GenerateTrees(); - GenerateIndustries(); - GenerateUnmovables(); - } - } - - // These are probably pointless when inside the scenario editor. - StartupPlayers(); - StartupEngines(); - StartupDisasters(); - _generating_world = false; - - // No need to run the tile loop in the scenario editor. - if (mode != GW_EMPTY) { - uint i; - - for (i = 0; i < 0x500; i++) RunTileLoop(); - } - - ResetObjectToPlace(); -} - void DeleteName(StringID id) { if ((id & 0xF800) == 0x7800) { diff --git a/misc_gui.c b/misc_gui.c index 6899c13131..043b64070a 100644 --- a/misc_gui.c +++ b/misc_gui.c @@ -3,6 +3,7 @@ #include "stdafx.h" #include "openttd.h" #include "hal.h" +#include "heightmap.h" #include "debug.h" #include "functions.h" #include "gfxinit.h" @@ -26,6 +27,8 @@ #include "variables.h" #include "vehicle.h" #include "train.h" +#include "tgp.h" +#include "settings.h" #include "date.h" #include "fios.h" @@ -33,6 +36,9 @@ FiosItem *_fios_list; int _saveload_mode; +extern void GenerateLandscape(byte mode); +extern void SwitchMode(int new_mode); + static bool _fios_path_changed; static bool _savegame_sort_dirty; @@ -1094,6 +1100,72 @@ void ShowQueryString(StringID str, StringID caption, uint maxlen, uint maxwidth, UpdateTextBufferSize(&WP(w, querystr_d).text); } +static void QueryWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: + SetDParam(0, WP(w, query_d).caption); + DrawWindowWidgets(w); + + DrawStringMultiCenter(90, 38, WP(w, query_d).message, 178); + break; + + case WE_CLICK: + switch (e->click.widget) { + case 3: + case 4: + WP(w, query_d).calledback = true; + if (WP(w, query_d).ok_cancel_callback != NULL) WP(w, query_d).ok_cancel_callback(e->click.widget == 4); + DeleteWindow(w); + break; + } + break; + + case WE_MOUSELOOP: + if (!FindWindowById(WP(w, query_d).wnd_class, WP(w, query_d).wnd_num)) DeleteWindow(w); + break; + + case WE_DESTROY: + if (!WP(w, query_d).calledback && WP(w, query_d).ok_cancel_callback != NULL) WP(w, query_d).ok_cancel_callback(false); + break; + } +} + +static const Widget _query_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 4, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 4, 11, 179, 0, 13, STR_012D, STR_NULL}, +{ WWT_IMGBTN, RESIZE_NONE, 4, 0, 179, 14, 91, 0x0, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 25, 84, 72, 83, STR_012E_CANCEL, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 12, 95, 154, 72, 83, STR_012F_OK, STR_NULL}, +{ WIDGETS_END }, +}; + +static const WindowDesc _query_desc = { + WDP_CENTER, WDP_CENTER, 180, 92, + WC_OK_CANCEL_QUERY, 0, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, + _query_widgets, + QueryWndProc +}; + +void ShowQuery(StringID caption, StringID message, void (*ok_cancel_callback)(bool ok_clicked), WindowClass window_class, WindowNumber window_number) +{ + Window *w; + + DeleteWindowById(WC_OK_CANCEL_QUERY, 0); + + w = AllocateWindowDesc(&_query_desc); + + w->click_state = 1 << 5; + WP(w, query_d).caption = caption; + WP(w, query_d).message = message; + WP(w, query_d).wnd_class = window_class; + WP(w, query_d).wnd_num = window_number; + WP(w, query_d).ok_cancel_callback = ok_cancel_callback; + WP(w, query_d).calledback = false; +} + + static const Widget _load_dialog_1_widgets[] = { { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, { WWT_CAPTION, RESIZE_RIGHT, 14, 11, 256, 0, 13, STR_4001_LOAD_GAME, STR_018C_WINDOW_TITLE_DRAG_THIS}, @@ -1122,6 +1194,20 @@ static const Widget _load_dialog_2_widgets[] = { { WIDGETS_END}, }; +static const Widget _load_dialog_3_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_RIGHT, 14, 11, 256, 0, 13, STR_4011_LOAD_HEIGHTMAP,STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 127, 14, 25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 128, 256, 14, 25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP}, +{ WWT_IMGBTN, RESIZE_RIGHT, 14, 0, 256, 26, 47, 0x0, STR_NULL}, +{ WWT_IMGBTN, RESIZE_RB, 14, 0, 256, 48, 293, 0x0, STR_NULL}, +{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 245, 256, 48, 59, SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON}, +{ WWT_6, RESIZE_RB, 14, 2, 243, 50, 291, 0x0, STR_400A_LIST_OF_DRIVES_DIRECTORIES}, +{ WWT_SCROLLBAR, RESIZE_LRB, 14, 245, 256, 60, 281, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, +{ WWT_RESIZEBOX, RESIZE_LRTB, 14, 245, 256, 282, 293, 0x0, STR_RESIZE_BUTTON}, +{ WIDGETS_END}, +}; + static const Widget _save_dialog_widgets[] = { { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, { WWT_CAPTION, RESIZE_RIGHT, 14, 11, 256, 0, 13, STR_4000_SAVE_GAME, STR_018C_WINDOW_TITLE_DRAG_THIS}, @@ -1158,9 +1244,8 @@ static const Widget _save_dialog_scen_widgets[] = { { WIDGETS_END}, }; - // Colors for fios types -const byte _fios_colors[] = {13, 9, 9, 6, 5, 6, 5}; +const byte _fios_colors[] = {13, 9, 9, 6, 5, 6, 5, 6, 6, 8}; void BuildFileList(void) { @@ -1172,6 +1257,8 @@ void BuildFileList(void) case SLD_LOAD_SCENARIO: case SLD_SAVE_SCENARIO: _fios_list = FiosGetScenarioList(_saveload_mode); break; + case SLD_LOAD_HEIGHTMAP: + _fios_list = FiosGetHeightmapList(_saveload_mode); break; default: _fios_list = FiosGetSavegameList(_saveload_mode); break; } @@ -1249,6 +1336,10 @@ static void SaveLoadDlgWndProc(Window *w, WindowEvent *e) ttd_strlcpy(&o_dir.name[0], _path.scenario_dir, sizeof(o_dir.name)); break; + case SLD_LOAD_HEIGHTMAP: + ttd_strlcpy(&o_dir.name[0], _path.heightmap_dir, sizeof(o_dir.name)); + break; + default: ttd_strlcpy(&o_dir.name[0], _path.personal_dir, sizeof(o_dir.name)); } @@ -1331,6 +1422,13 @@ static void SaveLoadDlgWndProc(Window *w, WindowEvent *e) ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title)); DeleteWindow(w); + } else if (_saveload_mode == SLD_LOAD_HEIGHTMAP) { + SetFiosType(file->type); + ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name)); + ttd_strlcpy(_file_to_saveload.title, file->title, sizeof(_file_to_saveload.title)); + + DeleteWindow(w); + ShowHeightmapLoad(); } else { // SLD_SAVE_GAME, SLD_SAVE_SCENARIO copy clicked name to editbox ttd_strlcpy(WP(w, querystr_d).text.buf, file->title, WP(w, querystr_d).text.maxlength); @@ -1442,11 +1540,20 @@ static const WindowDesc _save_dialog_scen_desc = { SaveLoadDlgWndProc, }; +static const WindowDesc _load_dialog_heightmap_desc = { + WDP_CENTER, WDP_CENTER, 257, 294, + WC_SAVELOAD,0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE, + _load_dialog_3_widgets, + SaveLoadDlgWndProc, +}; + static const WindowDesc * const _saveload_dialogs[] = { &_load_dialog_desc, &_load_dialog_scen_desc, &_save_dialog_desc, &_save_dialog_scen_desc, + &_load_dialog_heightmap_desc, }; void ShowSaveLoadDialog(int mode) @@ -1493,107 +1600,6 @@ void RedrawAutosave(void) SetWindowDirty(FindWindowById(WC_STATUS_BAR, 0)); } -static const Widget _select_scenario_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, RESIZE_RIGHT, 7, 11, 256, 0, 13, STR_400E_SELECT_NEW_GAME_TYPE, STR_018C_WINDOW_TITLE_DRAG_THIS}, -{ WWT_IMGBTN, RESIZE_RIGHT, 7, 0, 256, 14, 25, 0x0, STR_NULL}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 7, 0, 127, 14, 25, STR_SORT_BY_NAME, STR_SORT_ORDER_TIP}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 7, 128, 256, 14, 25, STR_SORT_BY_DATE, STR_SORT_ORDER_TIP}, -{ WWT_IMGBTN, RESIZE_RB, 7, 0, 244, 26, 319, 0x0, STR_NULL}, -{ WWT_6, RESIZE_RB, 7, 2, 243, 28, 317, 0x0, STR_400F_SELECT_SCENARIO_GREEN_PRE}, -{ WWT_SCROLLBAR, RESIZE_LRB, 7, 245, 256, 26, 307, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, -{ WWT_RESIZEBOX, RESIZE_LRTB, 7, 245, 256, 308, 319, 0x0, STR_RESIZE_BUTTON}, -{ WIDGETS_END}, -}; - -static void SelectScenarioWndProc(Window *w, WindowEvent *e) -{ - const int list_start = 45; - - switch (e->event) { - case WE_PAINT: { - int y,pos; - const FiosItem *item; - - if (_savegame_sort_dirty) { - _savegame_sort_dirty = false; - MakeSortedSaveGameList(); - } - - SetVScrollCount(w, _fios_num); - - DrawWindowWidgets(w); - DoDrawString( - _savegame_sort_order & SORT_DESCENDING ? DOWNARROW : UPARROW, - _savegame_sort_order & SORT_BY_NAME ? w->widget[3].right - 9 : w->widget[4].right - 9, - 15, 16 - ); - DrawString(4, 32, STR_4010_GENERATE_RANDOM_NEW_GAME, 9); - - y = list_start; - pos = w->vscroll.pos; - while (pos < _fios_num) { - item = _fios_list + pos; - DoDrawString(item->title, 4, y, _fios_colors[item->type]); - pos++; - y += 10; - if (y >= w->vscroll.cap * 10 + list_start) break; - } - } - break; - - case WE_CLICK: - switch (e->click.widget) { - case 3: /* Sort scenario names by name */ - _savegame_sort_order = (_savegame_sort_order == SORT_BY_NAME) ? - SORT_BY_NAME | SORT_DESCENDING : SORT_BY_NAME; - _savegame_sort_dirty = true; - SetWindowDirty(w); - break; - - case 4: /* Sort scenario names by date */ - _savegame_sort_order = (_savegame_sort_order == SORT_BY_DATE) ? - SORT_BY_DATE | SORT_DESCENDING : SORT_BY_DATE; - _savegame_sort_dirty = true; - SetWindowDirty(w); - break; - - case 6: /* Click the listbox */ - if (e->click.pt.y < list_start) { - GenRandomNewGame(Random(), InteractiveRandom()); - } else { - int y = (e->click.pt.y - list_start) / 10; - const char *name; - const FiosItem *file; - - if (y < 0 || (y += w->vscroll.pos) >= w->vscroll.count) return; - - file = _fios_list + y; - - name = FiosBrowseTo(file); - if (name != NULL) { - SetFiosType(file->type); - strcpy(_file_to_saveload.name, name); - DeleteWindow(w); - StartScenarioEditor(Random(), InteractiveRandom()); - } - } - break; - } - break; - - case WE_RESIZE: { - /* Widget 3 and 4 have to go with halve speed, make it so obiwan */ - uint diff = e->sizing.diff.x / 2; - w->widget[3].right += diff; - w->widget[4].left += diff; - w->widget[4].right += e->sizing.diff.x; - - w->vscroll.cap += e->sizing.diff.y / 10; - } break; - } -} - void SetFiosType(const byte fiostype) { switch (fiostype) { @@ -1607,37 +1613,22 @@ void SetFiosType(const byte fiostype) _file_to_saveload.mode = SL_OLD_LOAD; break; +#ifdef WITH_PNG + case FIOS_TYPE_PNG: + _file_to_saveload.mode = SL_PNG; + break; +#endif /* WITH_PNG */ + + case FIOS_TYPE_BMP: + _file_to_saveload.mode = SL_BMP; + break; + default: _file_to_saveload.mode = SL_INVALID; break; } } -static const WindowDesc _select_scenario_desc = { - WDP_CENTER, WDP_CENTER, 257, 320, - WC_SAVELOAD,0, - WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE, - _select_scenario_widgets, - SelectScenarioWndProc -}; - -void AskForNewGameToStart(void) -{ - Window *w; - - DeleteWindowById(WC_QUERY_STRING, 0); - DeleteWindowById(WC_SAVELOAD, 0); - - _saveload_mode = SLD_NEW_GAME; - BuildFileList(); - - w = AllocateWindowDesc(&_select_scenario_desc); - w->vscroll.cap = 27; - w->resize.step_width = 2; - w->resize.step_height = 10; - w->resize.height = w->height - 10 * 17; // Minimum of 10 in the list -} - static int32 ClickMoneyCheat(int32 p1, int32 p2) { DoCommandP(0, -10000000, 0, NULL, CMD_MONEY_CHEAT); diff --git a/network_gui.c b/network_gui.c index 6cd8f7b3b5..2fab56335f 100644 --- a/network_gui.c +++ b/network_gui.c @@ -24,6 +24,7 @@ #include "variables.h" #include "network_server.h" #include "network_udp.h" +#include "settings.h" #include "string.h" #define BGC 5 @@ -57,6 +58,7 @@ static char _edit_str_buf[64]; static void ShowNetworkStartServerWindow(void); static void ShowNetworkLobbyWindow(NetworkGameList *ngl); +extern void SwitchMode(int new_mode); static const StringID _connection_types_dropdown[] = { STR_NETWORK_LAN_INTERNET, @@ -683,7 +685,7 @@ static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e) _is_network_server = true; if (nd->map == NULL) { // start random new game - GenRandomNewGame(Random(), InteractiveRandom()); + ShowGenerateLandscape(); } else { // load a scenario char *name = FiosBrowseTo(nd->map); if (name != NULL) { @@ -692,7 +694,7 @@ static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e) ttd_strlcpy(_file_to_saveload.title, nd->map->title, sizeof(_file_to_saveload.title)); DeleteWindow(w); - StartScenarioEditor(Random(), InteractiveRandom()); + SwitchMode(SM_START_SCENARIO); } } break; @@ -1392,8 +1394,6 @@ void ShowClientList(void) if (w != NULL) w->window_number = 0; } -extern void SwitchMode(int new_mode); - static void NetworkJoinStatusWindowWndProc(Window *w, WindowEvent *e) { switch (e->event) { diff --git a/network_server.c b/network_server.c index 1144840ddc..9a3c649306 100644 --- a/network_server.c +++ b/network_server.c @@ -21,6 +21,7 @@ #include "vehicle.h" #include "station.h" #include "variables.h" +#include "genworld.h" // This file handles all the server-commands @@ -1308,18 +1309,13 @@ void NetworkUpdateClientInfo(uint16 client_index) } } -extern void SwitchMode(int new_mode); - /* Check if we want to restart the map */ static void NetworkCheckRestartMap(void) { if (_network_restart_game_year != 0 && _cur_year >= _network_restart_game_year) { DEBUG(net, 0)("Auto-restarting map. Year %d reached.", _cur_year); - _random_seeds[0][0] = Random(); - _random_seeds[0][1] = InteractiveRandom(); - - SwitchMode(SM_NEWGAME); + StartNewGameWithoutGUI(GENERATE_NEW_SEED); } } diff --git a/openttd.c b/openttd.c index 3e1ff28c43..ed425de9d9 100644 --- a/openttd.c +++ b/openttd.c @@ -48,11 +48,11 @@ #include "train.h" #include "yapf/yapf.h" #include "settings.h" +#include "genworld.h" #include "date.h" #include -void GenerateWorld(int mode, uint size_x, uint size_y); void CallLandscapeTick(void); void IncreaseDate(void); void DoPaletteAnimations(void); @@ -295,6 +295,7 @@ static void LoadIntroGame(void) #endif if (SaveOrLoad(filename, SL_LOAD) != SL_OK) { GenerateWorld(GW_EMPTY, 64, 64); // if failed loading, make empty world. + WaitTillGeneratedWorld(); } _pause = 0; @@ -317,7 +318,7 @@ int ttd_main(int argc, char *argv[]) char musicdriver[16], sounddriver[16], videodriver[16]; int resolution[2] = {0,0}; Year startyear = INVALID_YEAR; - + uint generation_seed = GENERATE_NEW_SEED; bool dedicated = false; bool network = false; char *network_conn = NULL; @@ -376,7 +377,7 @@ int ttd_main(int argc, char *argv[]) _switch_mode = SM_NEWGAME; } break; - case 'G': _random_seeds[0][0] = atoi(mgo.opt); break; + case 'G': generation_seed = atoi(mgo.opt); break; case 'c': _config_file = strdup(mgo.opt); break; case -2: case 'h': @@ -409,6 +410,7 @@ int ttd_main(int argc, char *argv[]) if (videodriver[0]) ttd_strlcpy(_ini_videodriver, videodriver, sizeof(_ini_videodriver)); if (resolution[0]) { _cur_resolution[0] = resolution[0]; _cur_resolution[1] = resolution[1]; } if (startyear != INVALID_YEAR) _patches_newgame.starting_year = startyear; + if (generation_seed != GENERATE_NEW_SEED) _patches_newgame.generation_seed = generation_seed; if (_dedicated_forks && !dedicated) _dedicated_forks = false; @@ -457,6 +459,13 @@ int ttd_main(int argc, char *argv[]) /* XXX - ugly hack, if diff_level is 9, it means we got no setting from the config file */ if (_opt_newgame.diff_level == 9) SetDifficultyLevel(0, &_opt_newgame); + /* Make sure _patches is filled with _patches_newgame if we switch to a game directly */ + if (_switch_mode != SM_NONE) { + memcpy(&_opt, &_opt_newgame, sizeof(_opt)); + GfxLoadSprites(); + UpdatePatches(); + } + // initialize the ingame console IConsoleInit(); _cursor.in_window = true; @@ -464,6 +473,7 @@ int ttd_main(int argc, char *argv[]) IConsoleCmdExec("exec scripts/autoexec.scr 0"); GenerateWorld(GW_EMPTY, 64, 64); // Make the viewport initialization happy + WaitTillGeneratedWorld(); #ifdef ENABLE_NETWORK if (network && _network_available) { @@ -567,36 +577,35 @@ static void ShowScreenshotResult(bool b) } -static void MakeNewGame(void) +static void MakeNewGameDone(void) { - _game_mode = GM_NORMAL; - - // Copy in game options - _opt_ptr = &_opt; - memcpy(_opt_ptr, &_opt_newgame, sizeof(*_opt_ptr)); + /* In a dedicated server, the server does not play */ + if (_network_dedicated) { + _local_player = OWNER_SPECTATOR; + return; + } - GfxLoadSprites(); + /* Create a single player */ + DoStartupNewPlayer(false); - // Reinitialize windows - ResetWindowSystem(); - LoadStringWidthTable(); + _local_player = 0; + _current_player = _local_player; + DoCommandP(0, (_patches.autorenew << 15 ) | (_patches.autorenew_months << 16) | 4, _patches.autorenew_money, NULL, CMD_REPLACE_VEHICLE); - SetupColorsAndInitialWindow(); + MarkWholeScreenDirty(); +} - // Randomize world - GenerateWorld(GW_NEWGAME, 1<<_patches.map_x, 1<<_patches.map_y); +static void MakeNewGame(bool from_heightmap) +{ + _game_mode = GM_NORMAL; - // In a dedicated server, the server does not play - if (_network_dedicated) { - _local_player = OWNER_SPECTATOR; - } else { - // Create a single player - DoStartupNewPlayer(false); + GenerateWorldSetCallback(&MakeNewGameDone); + GenerateWorld(from_heightmap ? GW_HEIGHTMAP : GW_NEWGAME, 1 << _patches.map_x, 1 << _patches.map_y); +} - _local_player = 0; - _current_player = _local_player; - DoCommandP(0, (_patches.autorenew << 15 ) | (_patches.autorenew_months << 16) | 4, _patches.autorenew_money, NULL, CMD_REPLACE_VEHICLE); - } +static void MakeNewEditorWorldDone(void) +{ + _local_player = OWNER_NONE; MarkWholeScreenDirty(); } @@ -605,23 +614,8 @@ static void MakeNewEditorWorld(void) { _game_mode = GM_EDITOR; - // Copy in game options - _opt_ptr = &_opt; - memcpy(_opt_ptr, &_opt_newgame, sizeof(GameOptions)); - - GfxLoadSprites(); - - // Re-init the windowing system - ResetWindowSystem(); - - // Create toolbars - SetupColorsAndInitialWindow(); - - // Startup the game system + GenerateWorldSetCallback(&MakeNewEditorWorldDone); GenerateWorld(GW_EMPTY, 1 << _patches.map_x, 1 << _patches.map_y); - - _local_player = OWNER_NONE; - MarkWholeScreenDirty(); } void StartupPlayers(void); @@ -688,7 +682,7 @@ bool SafeSaveOrLoad(const char *filename, int mode, int newgm) switch (ogm) { case GM_MENU: LoadIntroGame(); break; case GM_EDITOR: MakeNewEditorWorld(); break; - default: MakeNewGame(); break; + default: MakeNewGame(false); break; } return false; @@ -738,7 +732,7 @@ void SwitchMode(int new_mode) snprintf(_network_game_info.map_name, lengthof(_network_game_info.map_name), "Random Map"); } #endif /* ENABLE_NETWORK */ - MakeNewGame(); + MakeNewGame(false); break; case SM_START_SCENARIO: /* New Game --> Choose one of the preset scenarios */ @@ -768,6 +762,22 @@ void SwitchMode(int new_mode) break; } + case SM_START_HEIGHTMAP: /* Load a heightmap and start a new game from it */ +#ifdef ENABLE_NETWORK + if (_network_server) { + snprintf(_network_game_info.map_name, lengthof(_network_game_info.map_name), "%s (Heightmap)", _file_to_saveload.title); + } +#endif /* ENABLE_NETWORK */ + MakeNewGame(true); + break; + + case SM_LOAD_HEIGHTMAP: /* Load heightmap from scenario editor */ + _local_player = OWNER_NONE; + + GenerateWorld(GW_HEIGHTMAP, 1 << _patches.map_x, 1 << _patches.map_y); + MarkWholeScreenDirty(); + break; + case SM_LOAD_SCENARIO: { /* Load scenario from scenario editor */ if (SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_EDITOR)) { Player *p; @@ -784,6 +794,7 @@ void SwitchMode(int new_mode) } } _generating_world = false; + _patches_newgame.starting_year = BASE_YEAR + _cur_year; // delete all stations owned by a player DeleteAllPlayerStations(); } else { @@ -805,9 +816,9 @@ void SwitchMode(int new_mode) break; case SM_GENRANDLAND: /* Generate random land within scenario editor */ + _local_player = OWNER_NONE; GenerateWorld(GW_RANDOM, 1 << _patches.map_x, 1 << _patches.map_y); // XXX: set date - _local_player = OWNER_NONE; MarkWholeScreenDirty(); break; } @@ -826,6 +837,7 @@ void StateGameLoop(void) { // dont execute the state loop during pause if (_pause) return; + if (IsGeneratingWorld()) return; if (_game_mode == GM_EDITOR) { RunTileLoop(); @@ -884,7 +896,7 @@ static void DoAutosave(void) static void ScrollMainViewport(int x, int y) { - if (_game_mode != GM_MENU) { + if (_game_mode != GM_MENU && !IsGeneratingWorld()) { Window *w = FindWindowById(WC_MAIN_WINDOW, 0); assert(w); @@ -963,7 +975,7 @@ void GameLoop(void) if (_network_available) NetworkUDPGameLoop(); - if (_networking) { + if (_networking && !IsGeneratingWorld()) { // Multiplayer NetworkGameLoop(); } else { diff --git a/openttd.h b/openttd.h index 21650b922b..bf309e27b6 100644 --- a/openttd.h +++ b/openttd.h @@ -77,14 +77,17 @@ enum SwitchModes { SM_GENRANDLAND = 6, SM_LOAD_SCENARIO = 9, SM_START_SCENARIO = 10, + SM_START_HEIGHTMAP = 11, + SM_LOAD_HEIGHTMAP = 12, }; /* Modes for GenerateWorld */ enum GenerateWorldModes { - GW_NEWGAME = 0, /* Generate a map for a new game */ - GW_EMPTY = 1, /* Generate an empty map (sea-level) */ - GW_RANDOM = 2, /* Generate a random map for SE */ + GW_NEWGAME = 0, /* Generate a map for a new game */ + GW_EMPTY = 1, /* Generate an empty map (sea-level) */ + GW_RANDOM = 2, /* Generate a random map for SE */ + GW_HEIGHTMAP = 3, /* Generate a newgame from a heightmap */ }; /* Modes for InitializeGame, those are _bits_! */ @@ -410,6 +413,9 @@ enum { WC_HIGHSCORE = 0x4D, WC_ENDSCREEN = 0x4E, WC_SIGN_LIST = 0x4F, + WC_GENERATE_LANDSCAPE = 0x50, + WC_GENERATE_PROGRESS_WINDOW = 0x51, + WC_OK_CANCEL_QUERY = 0x52, }; diff --git a/openttd.vcproj b/openttd.vcproj index a06fd11f70..f7a5d89bdc 100644 --- a/openttd.vcproj +++ b/openttd.vcproj @@ -168,6 +168,9 @@ + + @@ -231,12 +234,18 @@ + + + + @@ -378,6 +387,9 @@ + + @@ -424,6 +436,9 @@ + + @@ -463,6 +478,10 @@ + + @@ -475,6 +494,9 @@ + + @@ -604,6 +626,9 @@ + + @@ -662,6 +687,9 @@ + + diff --git a/openttd_vs80.vcproj b/openttd_vs80.vcproj index 62f548aab6..f1d714196c 100644 --- a/openttd_vs80.vcproj +++ b/openttd_vs80.vcproj @@ -455,6 +455,9 @@ + + + @@ -564,6 +571,10 @@ RelativePath=".\gfxinit.c" > + + @@ -752,6 +763,10 @@ RelativePath=".\texteff.c" > + + @@ -850,6 +865,9 @@ + + + @@ -927,6 +949,10 @@ RelativePath=".\hal.h" > + + @@ -1103,6 +1129,10 @@ RelativePath=".\string.h" > + + @@ -1183,6 +1213,10 @@ RelativePath=".\engine_gui.c" > + + diff --git a/os/linux/openttd.spec b/os/linux/openttd.spec index bd71a09669..3fb8e39a9a 100644 --- a/os/linux/openttd.spec +++ b/os/linux/openttd.spec @@ -143,4 +143,4 @@ rm -rf $RPM_BUILD_ROOT - Upgraded to 0.3.4 * Wed Jul 31 2004 Dominik Scherer <> 0.3.3-1mdk -- Initial release \ No newline at end of file +- Initial release diff --git a/os/macosx/splash.c b/os/macosx/splash.c index 351aa9e0bd..e09b8fccbd 100644 --- a/os/macosx/splash.c +++ b/os/macosx/splash.c @@ -137,8 +137,8 @@ void DisplaySplashImage(void) -#else // WITH_PNG +#else /* WITH_PNG */ void DisplaySplashImage(void) {} -#endif // WITH_PNG +#endif /* WITH_PNG */ diff --git a/os2.c b/os2.c index 18dcf9f0f2..91531eb58f 100644 --- a/os2.c +++ b/os2.c @@ -180,6 +180,7 @@ void DeterminePaths(void) _path.save_dir = str_fmt("%ssave", _path.personal_dir); _path.autosave_dir = str_fmt("%s\\autosave", _path.save_dir); _path.scenario_dir = str_fmt("%sscenario", _path.personal_dir); + _path.heightmap_dir = str_fmt("%sscenario\\heightmap", _path.personal_dir); _path.gm_dir = str_fmt("%sgm\\", _path.game_data_dir); _path.data_dir = str_fmt("%sdata\\", _path.game_data_dir); @@ -202,6 +203,7 @@ void DeterminePaths(void) mkdir(_path.save_dir); mkdir(_path.autosave_dir); mkdir(_path.scenario_dir); + mkdir(_path.heightmap_dir); } /** diff --git a/saveload.c b/saveload.c index 6b62724be9..e02693ba8d 100644 --- a/saveload.c +++ b/saveload.c @@ -30,7 +30,7 @@ #include "variables.h" #include -const uint16 SAVEGAME_VERSION = 29; +const uint16 SAVEGAME_VERSION = 30; uint16 _sl_version; /// the major savegame version identifier byte _sl_minor_version; /// the minor savegame version, DO NOT USE! diff --git a/saveload.h b/saveload.h index d8898a540f..6734b00148 100644 --- a/saveload.h +++ b/saveload.h @@ -14,6 +14,8 @@ typedef enum SaveOrLoadMode { SL_LOAD = 0, SL_SAVE = 1, SL_OLD_LOAD = 2, + SL_PNG = 3, + SL_BMP = 4, } SaveOrLoadMode; SaveOrLoadResult SaveOrLoad(const char *filename, int mode); diff --git a/screenshot.c b/screenshot.c index 1acb9d4688..a1bbd3fa91 100644 --- a/screenshot.c +++ b/screenshot.c @@ -254,7 +254,7 @@ static bool MakePNGImage(const char *name, ScreenshotCallback *callb, void *user fclose(f); return true; } -#endif // WITH_PNG +#endif /* WITH_PNG */ //************************************************ diff --git a/settings.c b/settings.c index 82be0baec8..6d999a2609 100644 --- a/settings.c +++ b/settings.c @@ -36,6 +36,7 @@ #include "npf.h" #include "yapf/yapf.h" #include "newgrf.h" +#include "genworld.h" #include "date.h" /** The patch values that are used for new games and/or modified in config file */ @@ -1435,6 +1436,17 @@ const SettingDesc _patch_settings[] = { SDT_CONDVAR (Patches, yapf.road_slope_penalty , SLE_UINT, 28, SL_MAX_VERSION,NS, 0, 2 * YAPF_TILE_LENGTH, 0, 1000000, STR_NULL, NULL), SDT_CONDVAR (Patches, yapf.road_crossing_penalty , SLE_UINT, 28, SL_MAX_VERSION,NS, 0, 3 * YAPF_TILE_LENGTH, 0, 1000000, STR_NULL, NULL), + /***************************************************************************/ + /* Terrain genation related patch options */ + SDT_CONDVAR(Patches, land_generator, SLE_UINT8, 30, SL_MAX_VERSION, 0, MS, 1, 0, 1, STR_CONFIG_PATCHES_LAND_GENERATOR, NULL), + SDT_CONDVAR(Patches, oil_refinery_limit, SLE_UINT8, 30, SL_MAX_VERSION, 0, 0, 16, 12, 48, STR_CONFIG_PATCHES_OIL_REF_EDGE_DISTANCE, NULL), + SDT_CONDVAR(Patches, tgen_smoothness, SLE_UINT8, 30, SL_MAX_VERSION, 0, MS, 1, 0, 3, STR_CONFIG_PATCHES_ROUGHNESS_OF_TERRAIN, NULL), + SDT_CONDVAR(Patches, generation_seed, SLE_UINT32, 30, SL_MAX_VERSION, 0, 0, GENERATE_NEW_SEED, 0, MAX_UVALUE(uint32), STR_NULL, NULL), + SDT_CONDVAR(Patches, tree_placer, SLE_UINT8, 30, SL_MAX_VERSION, 0, MS, 2, 0, 2, STR_CONFIG_PATCHES_TREE_PLACER, NULL), + SDT_VAR (Patches, heightmap_rotation, SLE_UINT8, S, MS, 0, 0, 1, STR_CONFIG_PATCHES_HEIGHTMAP_ROTATION, NULL), + SDT_VAR (Patches, progress_update_interval, SLE_UINT16, S, 0, 200, 0, 5000, STR_CONFIG_PATCHES_PROGRESS_UPDATE_INTERVAL, NULL), + SDT_VAR (Patches, se_flat_world_height, SLE_UINT8, S, 0, 0, 0, 15, STR_CONFIG_PATCHES_SE_FLAT_WORLD_HEIGHT, NULL), + SDT_END() }; diff --git a/settings_gui.c b/settings_gui.c index 2d140d4b13..edb5804b28 100644 --- a/settings_gui.c +++ b/settings_gui.c @@ -568,9 +568,8 @@ static const char *_patches_ui[] = { "window_snap_radius", "invisible_trees", "population_in_label", - "map_x", - "map_y", "link_terraform_toolbar", + "progress_update_interval", }; static const char *_patches_construction[] = { @@ -580,6 +579,7 @@ static const char *_patches_construction[] = { "signal_side", "always_small_airport", "drag_signals_density", + "oil_refinery_limit", }; static const char *_patches_stations[] = { @@ -600,9 +600,7 @@ static const char *_patches_economy[] = { "multiple_industry_per_town", "same_industry_close", "bribe", - "snow_line_height", "colored_news_year", - "starting_year", "ending_year", "smooth_economy", "allow_shares", diff --git a/string.c b/string.c index d025426995..bbf8627d3d 100644 --- a/string.c +++ b/string.c @@ -65,18 +65,15 @@ void str_validate(char *str) if (!IsValidAsciiChar(*str, CS_ALPHANUMERAL)) *str = '?'; } -void strtolower(char *str) -{ - for (; *str != '\0'; str++) *str = tolower(*str); -} - -/** Only allow valid ascii-function codes. Filter special codes like BELL and - * so on [we need a special filter here later] +/** + * Only allow certain keys. You can define the filter to be used. This makes + * sure no invalid keys can get into an editbox, like BELL. * @param key character to be checked - * @return true or false depending if the character is printable/valid or not */ + * @param afilter the filter to use + * @return true or false depending if the character is printable/valid or not + */ bool IsValidAsciiChar(byte key, CharSetFilter afilter) { - // XXX This filter stops certain crashes, but may be too restrictive. bool firsttest = false; switch (afilter) { @@ -84,8 +81,9 @@ bool IsValidAsciiChar(byte key, CharSetFilter afilter) firsttest = (key >= ' ' && key < 127); break; - case CS_NUMERAL://we are quite strict, here - return (key >= 48 && key <= 57); + /* We are very strict here */ + case CS_NUMERAL: + return (key >= '0' && key <= '9'); case CS_ALPHA: default: @@ -93,7 +91,13 @@ bool IsValidAsciiChar(byte key, CharSetFilter afilter) break; } + /* Allow some special chars too that are non-ASCII but still valid (like '^' above 'a') */ return (firsttest || (key >= 160 && key != 0xAA && key != 0xAC && key != 0xAD && key != 0xAF && key != 0xB5 && key != 0xB6 && key != 0xB7 && key != 0xB9)); } + +void strtolower(char *str) +{ + for (; *str != '\0'; str++) *str = tolower(*str); +} diff --git a/string.h b/string.h index eb928c519c..781586515a 100644 --- a/string.h +++ b/string.h @@ -29,19 +29,25 @@ char* CDECL str_fmt(const char* str, ...); * replaces them with a question mark '?' */ void str_validate(char *str); -/** Convert the given string to lowercase */ -void strtolower(char *str); - -typedef enum CharSetFilter { //valid char filtering - CS_ALPHANUMERAL, //both numeric and alphabetic - CS_NUMERAL, //only numeric ones. - CS_ALPHA, //only alphabetic values +/** + * Valid filter types for IsValidAsciiChar. + */ +typedef enum CharSetFilter { + CS_ALPHANUMERAL, //! Both numeric and alphabetic and spaces and stuff + CS_NUMERAL, //! Only numeric ones + CS_ALPHA, //! Only alphabetic values } CharSetFilter; -/** Only allow valid ascii-function codes. Filter special codes like BELL and - * so on [we need a special filter here later] +/** + * Only allow certain keys. You can define the filter to be used. This makes + * sure no invalid keys can get into an editbox, like BELL. * @param key character to be checked - * @return true or false depending if the character is printable/valid or not */ + * @param afilter the filter to use + * @return true or false depending if the character is printable/valid or not + */ bool IsValidAsciiChar(byte key, CharSetFilter afilter); +/** Convert the given string to lowercase */ +void strtolower(char *str); + #endif /* STRING_H */ diff --git a/tgp.c b/tgp.c new file mode 100644 index 0000000000..b32810f565 --- /dev/null +++ b/tgp.c @@ -0,0 +1,826 @@ +/* $Id$ */ + +#include "stdafx.h" +#include +#include "openttd.h" +#include "clear_map.h" +#include "functions.h" +#include "map.h" +#include "table/strings.h" +#include "clear_map.h" +#include "tile.h" +#include "variables.h" +#include "void_map.h" +#include "tgp.h" +#include "console.h" +#include "genworld.h" + +/* + * OTTD Perlin Noise Landscape Generator, aka TerraGenesis Perlin + * + * Quickie guide to Perlin Noise + * Perlin noise is a predictable pseudo random number sequence. By generating + * it in 2 dimensions, it becomes a useful random map, that for a given seed + * and starting X & Y is entirely predictable. On the face of it, that may not + * be useful. However, it means that if you want to replay a map in a different + * terrain, or just vary the sea level, you just re-run the generator with the + * same seed. The seed is an int32, and is randomised on each run of New Game. + * The Scenario Generator does not randomise the value, so that you can + * experiment with one terrain until you are happy, or click "Random" for a new + * random seed. + * + * Perlin Noise is a series of "octaves" of random noise added together. By + * reducing the amplitude of the noise with each octave, the first octave of + * noise defines the main terrain sweep, the next the ripples on that, and the + * next the ripples on that. I use 6 octaves, with the amplitude controlled by + * a power ratio, usually known as a persistence or p value. This I vary by the + * smoothness selection, as can be seen in the table below. The closer to 1, + * the more of that octave is added. Each octave is however raised to the power + * of its position in the list, so the last entry in the "smooth" row, 0.35, is + * raised to the power of 6, so can only add 0.001838... of the amplitude to + * the running total. + * + * In other words; the first p value sets the general shape of the terrain, the + * second sets the major variations to that, ... until finally the smallest + * bumps are added. + * + * Usefully, this routine is totally scaleable; so when 32bpp comes along, the + * terrain can be as bumpy as you like! It is also infinitely expandable; a + * single random seed terrain continues in X & Y as far as you care to + * calculate. In theory, we could use just one seed value, but randomly select + * where in the Perlin XY space we use for the terrain. Personally I prefer + * using a simple (0, 0) to (X, Y), with a varying seed. + * + * + * Other things i have had to do: mountainous wasnt mountainous enough, and + * since we only have 0..15 heights available, I add a second generated map + * (with a modified seed), onto the original. This generally raises the + * terrain, which then needs scaling back down. Overall effect is a general + * uplift. + * + * However, the values on the top of mountains are then almost guaranteed to go + * too high, so large flat plateaus appeared at height 15. To counter this, I + * scale all heights above 12 to proportion up to 15. It still makes the + * mountains have flatish tops, rather than craggy peaks, but at least they + * arent smooth as glass. + * + * + * For a full discussion of Perlin Noise, please visit: + * http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + * + * + * Evolution II + * + * The algorithm as described in the above link suggests to compute each tile height + * as composition of several noise waves. Some of them are computed directly by + * noise(x, y) function, some are calculated using linear approximation. Our + * first implementation of perlin_noise_2D() used 4 noise(x, y) calls plus + * 3 linear interpolations. It was called 6 times for each tile. This was a bit + * CPU expensive. + * + * The following implementation uses optimized algorithm that should produce + * the same quality result with much less computations, but more memory accesses. + * The overal speedup should be 300% to 800% depending on CPU and memory speed. + * + * I will try to explain it on the example below: + * + * Have a map of 4 x 4 tiles, our simplifiead noise generator produces only two + * values -1 and +1, use 3 octaves with wave lenght 1, 2 and 4, with amplitudes + * 3, 2, 1. Original algorithm produces: + * + * h00 = lerp(lerp(-3, 3, 0/4), lerp(3, -3, 0/4), 0/4) + lerp(lerp(-2, 2, 0/2), lerp( 2, -2, 0/2), 0/2) + -1 = lerp(-3.0, 3.0, 0/4) + lerp(-2, 2, 0/2) + -1 = -3.0 + -2 + -1 = -6.0 + * h01 = lerp(lerp(-3, 3, 1/4), lerp(3, -3, 1/4), 0/4) + lerp(lerp(-2, 2, 1/2), lerp( 2, -2, 1/2), 0/2) + 1 = lerp(-1.5, 1.5, 0/4) + lerp( 0, 0, 0/2) + 1 = -1.5 + 0 + 1 = -0.5 + * h02 = lerp(lerp(-3, 3, 2/4), lerp(3, -3, 2/4), 0/4) + lerp(lerp( 2, -2, 0/2), lerp(-2, 2, 0/2), 0/2) + -1 = lerp( 0, 0, 0/4) + lerp( 2, -2, 0/2) + -1 = 0 + 2 + -1 = 1.0 + * h03 = lerp(lerp(-3, 3, 3/4), lerp(3, -3, 3/4), 0/4) + lerp(lerp( 2, -2, 1/2), lerp(-2, 2, 1/2), 0/2) + 1 = lerp( 1.5, -1.5, 0/4) + lerp( 0, 0, 0/2) + 1 = 1.5 + 0 + 1 = 2.5 + * + * h10 = lerp(lerp(-3, 3, 0/4), lerp(3, -3, 0/4), 1/4) + lerp(lerp(-2, 2, 0/2), lerp( 2, -2, 0/2), 1/2) + 1 = lerp(-3.0, 3.0, 1/4) + lerp(-2, 2, 1/2) + 1 = -1.5 + 0 + 1 = -0.5 + * h11 = lerp(lerp(-3, 3, 1/4), lerp(3, -3, 1/4), 1/4) + lerp(lerp(-2, 2, 1/2), lerp( 2, -2, 1/2), 1/2) + -1 = lerp(-1.5, 1.5, 1/4) + lerp( 0, 0, 1/2) + -1 = -0.75 + 0 + -1 = -1.75 + * h12 = lerp(lerp(-3, 3, 2/4), lerp(3, -3, 2/4), 1/4) + lerp(lerp( 2, -2, 0/2), lerp(-2, 2, 0/2), 1/2) + 1 = lerp( 0, 0, 1/4) + lerp( 2, -2, 1/2) + 1 = 0 + 0 + 1 = 1.0 + * h13 = lerp(lerp(-3, 3, 3/4), lerp(3, -3, 3/4), 1/4) + lerp(lerp( 2, -2, 1/2), lerp(-2, 2, 1/2), 1/2) + -1 = lerp( 1.5, -1.5, 1/4) + lerp( 0, 0, 1/2) + -1 = 0.75 + 0 + -1 = -0.25 + * + * + * Optimization 1: + * + * 1) we need to allocate a bit more tiles: (size_x + 1) * (size_y + 1) = (5 * 5): + * + * 2) setup corner values using amplitude 3 + * { -3.0 X X X 3.0 } + * { X X X X X } + * { X X X X X } + * { X X X X X } + * { 3.0 X X X -3.0 } + * + * 3a) interpolate values in the middle + * { -3.0 X 0.0 X 3.0 } + * { X X X X X } + * { 0.0 X 0.0 X 0.0 } + * { X X X X X } + * { 3.0 X 0.0 X -3.0 } + * + * 3b) add patches with amplitude 2 to them + * { -5.0 X 2.0 X 1.0 } + * { X X X X X } + * { 2.0 X -2.0 X 2.0 } + * { X X X X X } + * { 1.0 X 2.0 X -5.0 } + * + * 4a) interpolate values in the middle + * { -5.0 -1.5 2.0 1.5 1.0 } + * { -1.5 -0.75 0.0 0.75 1.5 } + * { 2.0 0.0 -2.0 0.0 2.0 } + * { 1.5 0.75 0.0 -0.75 -1.5 } + * { 1.0 1.5 2.0 -1.5 -5.0 } + * + * 4b) add patches with amplitude 1 to them + * { -6.0 -0.5 1.0 2.5 0.0 } + * { -0.5 -1.75 1.0 -0.25 2.5 } + * { 1.0 1.0 -3.0 1.0 1.0 } + * { 2.5 -0.25 1.0 -1.75 -0.5 } + * { 0.0 2.5 1.0 -0.5 -6.0 } + * + * + * + * Optimization 2: + * + * As you can see above, each noise function was called just once. Therefore + * we don't need to use noise function that calculates the noise from x, y and + * some prime. The same quality result we can obtain using standard Random() + * function instead. + * + */ + +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 +#define M_PI 3.14159265358979323846 +#endif /* M_PI_2 */ + +/** Fixed point type for heights */ +typedef int16 height_t; +static const int height_decimal_bits = 4; +static const height_t _invalid_height = -32768; + +/** Fixed point array for amplitudes (and percent values) */ +typedef int amplitude_t; +static const int amplitude_decimal_bits = 10; + +/** Height map - allocated array of heights (MapSizeX() + 1) x (MapSizeY() + 1) */ +typedef struct HeightMap +{ + height_t *h; //! array of heights + uint dim_x; //! height map size_x MapSizeX() + 1 + uint total_size; //! height map total size + uint size_x; //! MapSizeX() + uint size_y; //! MapSizeY() +} HeightMap; + +/** Global height map instance */ +static HeightMap _height_map = {NULL, 0, 0, 0, 0}; + +/** Height map accessors */ +#define HeightMapXY(x, y) _height_map.h[(x) + (y) * _height_map.dim_x] + +/** Conversion: int to height_t */ +#define I2H(i) ((i) << height_decimal_bits) +/** Conversion: height_t to int */ +#define H2I(i) ((i) >> height_decimal_bits) + +/** Conversion: int to amplitude_t */ +#define I2A(i) ((i) << amplitude_decimal_bits) +/** Conversion: amplitude_t to int */ +#define A2I(i) ((i) >> amplitude_decimal_bits) + +/** Conversion: amplitude_t to height_t */ +#define A2H(a) ((height_decimal_bits < amplitude_decimal_bits) \ + ? ((a) >> (amplitude_decimal_bits - height_decimal_bits)) \ + : ((a) << (height_decimal_bits - amplitude_decimal_bits))) + +/** Walk through all items of _height_map.h */ +#define FOR_ALL_TILES_IN_HEIGHT(h) for (h = _height_map.h; h < &_height_map.h[_height_map.total_size]; h++) + +/** Noise amplitudes (multiplied by 1024) + * - indexed by "smoothness setting" and log2(frequency) */ +static const amplitude_t _amplitudes_by_smoothness_and_frequency[4][12] = { + // Very smooth + {1000, 350, 123, 43, 15, 1, 1, 0, 0, 0, 0, 0}, + // Smooth + {1000, 1000, 403, 200, 64, 8, 1, 0, 0, 0, 0, 0}, + // Rough + {1000, 1200, 800, 500, 200, 16, 4, 0, 0, 0, 0, 0}, + // Very Rough + {1500, 1000, 1200, 1000, 500, 32, 20, 0, 0, 0, 0, 0}, +}; + +/** Desired water percentage (100% == 1024) - indexed by _opt.diff.quantity_sea_lakes */ +static const amplitude_t _water_percent[4] = {20, 80, 250, 400}; + +/** Desired maximum height - indexed by _opt.diff.terrain_type */ +static const int8 _max_height[4] = { + 6, // Very flat + 9, // Flat + 12, // Hilly + 15 // Mountainous +}; + +/** Check if a X/Y set are within the map. */ +static inline bool IsValidXY(uint x, uint y) +{ + return ((int)x) >= 0 && x < _height_map.size_x && ((int)y) >= 0 && y < _height_map.size_y; +} + + +/** Allocate array of (MapSizeX()+1)*(MapSizeY()+1) heights and init the _height_map structure members */ +static inline bool AllocHeightMap(void) +{ + height_t *h; + + _height_map.size_x = MapSizeX(); + _height_map.size_y = MapSizeY(); + + /* Allocate memory block for height map row pointers */ + _height_map.total_size = (_height_map.size_x + 1) * (_height_map.size_y + 1); + _height_map.dim_x = _height_map.size_x + 1; + _height_map.h = calloc(_height_map.total_size, sizeof(*_height_map.h)); + if (_height_map.h == NULL) return false; + + /* Iterate through height map initialize values */ + FOR_ALL_TILES_IN_HEIGHT(h) *h = _invalid_height; + + return true; +} + +/** Free height map */ +static inline void FreeHeightMap(void) +{ + if (_height_map.h == NULL) return; + free(_height_map.h); + _height_map.h = NULL; +} + +/** RandomHeight() generator */ +static inline height_t RandomHeight(amplitude_t rMax) +{ + amplitude_t ra = (Random() << 16) | (Random() & 0x0000FFFF); + height_t rh; + /* Scale the amplitude for better resolution */ + rMax *= 16; + /* Spread height into range -rMax..+rMax */ + rh = A2H(ra % (2 * rMax + 1) - rMax); + return rh; +} + +/** One interpolation and noise round */ +static bool ApplyNoise(uint log_frequency, amplitude_t amplitude) +{ + uint size_min = min(_height_map.size_x, _height_map.size_y); + uint step = size_min >> log_frequency; + uint x, y; + + assert(_height_map.h != NULL); + + /* Are we finished? */ + if (step == 0) return false; + + if (log_frequency == 0) { + /* This is first round, we need to establish base heights with step = size_min */ + for (y = 0; y <= _height_map.size_y; y += step) { + for (x = 0; x <= _height_map.size_x; x += step) { + height_t height = (amplitude > 0) ? RandomHeight(amplitude) : 0; + HeightMapXY(x, y) = height; + } + } + return true; + } + + /* It is regular iteration round. + * Interpolate height values at odd x, even y tiles */ + for (y = 0; y <= _height_map.size_y; y += 2 * step) { + for (x = 0; x < _height_map.size_x; x += 2 * step) { + height_t h00 = HeightMapXY(x + 0 * step, y); + height_t h02 = HeightMapXY(x + 2 * step, y); + height_t h01 = (h00 + h02) / 2; + HeightMapXY(x + 1 * step, y) = h01; + } + } + + /* Interpolate height values at odd y tiles */ + for (y = 0; y < _height_map.size_y; y += 2 * step) { + for (x = 0; x <= _height_map.size_x; x += step) { + height_t h00 = HeightMapXY(x, y + 0 * step); + height_t h20 = HeightMapXY(x, y + 2 * step); + height_t h10 = (h00 + h20) / 2; + HeightMapXY(x, y + 1 * step) = h10; + } + } + + for (y = 0; y <= _height_map.size_y; y += step) { + for (x = 0; x <= _height_map.size_x; x += step) { + HeightMapXY(x, y) += RandomHeight(amplitude); + } + } + return (step > 1); +} + +/** Base Perlin noise generator - fills height map with raw Perlin noise */ +static void HeightMapGenerate(void) +{ + uint size_min = min(_height_map.size_x, _height_map.size_y); + uint iteration_round = 0; + amplitude_t amplitude; + bool continue_iteration; + uint log_size_min, log_frequency_min; + int log_frequency; + + /* Find first power of two that fits */ + for (log_size_min = 6; (1U << log_size_min) < size_min; log_size_min++) { } + log_frequency_min = log_size_min - 6; + + do { + log_frequency = iteration_round - log_frequency_min; + if (log_frequency >= 0) { + amplitude = _amplitudes_by_smoothness_and_frequency[_patches.tgen_smoothness][log_frequency]; + } else { + amplitude = 0; + } + continue_iteration = ApplyNoise(iteration_round, amplitude); + iteration_round++; + } while(continue_iteration); +} + +/** Returns min, max and average height from height map */ +static void HeightMapGetMinMaxAvg(height_t *min_ptr, height_t *max_ptr, height_t *avg_ptr) +{ + height_t h_min, h_max, h_avg, *h; + int64 h_accu = 0; + h_min = h_max = HeightMapXY(0, 0); + + /* Get h_min, h_max and accumulate heights into h_accu */ + FOR_ALL_TILES_IN_HEIGHT(h) { + if (*h < h_min) h_min = *h; + if (*h > h_max) h_max = *h; + h_accu += *h; + } + + /* Get average height */ + h_avg = (height_t)(h_accu / (_height_map.size_x * _height_map.size_y)); + + /* Return required results */ + if (min_ptr != NULL) *min_ptr = h_min; + if (max_ptr != NULL) *max_ptr = h_max; + if (avg_ptr != NULL) *avg_ptr = h_avg; +} + +/** Dill histogram and return pointer to its base point - to the count of zero heights */ +static int *HeightMapMakeHistogram(height_t h_min, height_t h_max, int *hist_buf) +{ + int *hist = hist_buf - h_min; + height_t *h; + + /* Fill histogram */ + FOR_ALL_TILES_IN_HEIGHT(h) { + assert(*h >= h_min); + assert(*h <= h_max); + hist[*h]++; + } + return hist; +} + +/** Applies sine wave redistribution onto height map */ +static void HeightMapSineTransform(height_t h_min, height_t h_max) +{ + height_t *h; + + FOR_ALL_TILES_IN_HEIGHT(h) { + double fheight; + + if (*h < h_min) continue; + + /* Transform height into 0..1 space */ + fheight = (double)(*h - h_min) / (double)(h_max - h_min); + /* Apply sine transform depending on landscape type */ + switch(_opt.landscape) { + case LT_CANDY: + case LT_NORMAL: + /* Move and scale 0..1 into -1..+1 */ + fheight = 2 * fheight - 1; + /* Sine transform */ + fheight = sin(fheight * M_PI_2); + /* Transform it back from -1..1 into 0..1 space */ + fheight = 0.5 * (fheight + 1); + break; + + case LT_HILLY: + { + /* Arctic terrain needs special height distribution. + * Redistribute heights to have more tiles at highest (75%..100%) range */ + double sine_upper_limit = 0.75; + double linear_compression = 2; + if (fheight >= sine_upper_limit) { + /* Over the limit we do linear compression up */ + fheight = 1.0 - (1.0 - fheight) / linear_compression; + } else { + double m = 1.0 - (1.0 - sine_upper_limit) / linear_compression; + /* Get 0..sine_upper_limit into -1..1 */ + fheight = 2.0 * fheight / sine_upper_limit - 1.0; + /* Sine wave transform */ + fheight = sin(fheight * M_PI_2); + /* Get -1..1 back to 0..(1 - (1 - sine_upper_limit) / linear_compression) == 0.0..m */ + fheight = 0.5 * (fheight + 1.0) * m; + } + } + break; + + case LT_DESERT: + { + /* Desert terrain needs special height distribution. + * Half of tiles should be at lowest (0..25%) heights */ + double sine_lower_limit = 0.5; + double linear_compression = 2; + if (fheight <= sine_lower_limit) { + /* Under the limit we do linear compression down */ + fheight = fheight / linear_compression; + } else { + double m = sine_lower_limit / linear_compression; + /* Get sine_lower_limit..1 into -1..1 */ + fheight = 2.0 * ((fheight - sine_lower_limit) / (1.0 - sine_lower_limit)) - 1.0; + /* Sine wave transform */ + fheight = sin(fheight * M_PI_2); + /* Get -1..1 back to (sine_lower_limit / linear_compression)..1.0 */ + fheight = 0.5 * ((1.0 - m) * fheight + (1.0 + m)); + } + } + break; + + default: + NOT_REACHED(); + break; + } + /* Transform it back into h_min..h_max space */ + *h = fheight * (h_max - h_min) + h_min; + if (*h < 0) *h = I2H(0); + if (*h >= h_max) *h = h_max - 1; + } +} + +/** Adjusts heights in height map to contain required amount of water tiles */ +static void HeightMapAdjustWaterLevel(amplitude_t water_percent, height_t h_max_new) +{ + height_t h_min, h_max, h_avg, h_water_level; + int water_tiles, desired_water_tiles; + height_t *h; + int *hist_buf, *hist; + + HeightMapGetMinMaxAvg(&h_min, &h_max, &h_avg); + + /* Allocate histogram buffer and clear its cells */ + hist_buf = calloc(h_max - h_min + 1, sizeof(*hist_buf)); + /* Fill histogram */ + hist = HeightMapMakeHistogram(h_min, h_max, hist_buf); + + /* How many water tiles do we want? */ + desired_water_tiles = (int)(((int64)water_percent) * (int64)(_height_map.size_x * _height_map.size_y)) >> amplitude_decimal_bits; + + /* Raise water_level and accumulate values from histogram until we reach required number of water tiles */ + for (h_water_level = h_min, water_tiles = 0; h_water_level < h_max; h_water_level++) { + water_tiles += hist[h_water_level]; + if (water_tiles >= desired_water_tiles) break; + } + + /* We now have the proper water level value. + * Transform the height map into new (normalized) height map: + * values from range: h_min..h_water_level will become negative so it will be clamped to 0 + * values from range: h_water_level..h_max are transformed into 0..h_max_new + * , where h_max_new is 4, 8, 12 or 16 depending on terrain type (very flat, flat, hilly, mountains) + */ + FOR_ALL_TILES_IN_HEIGHT(h) { + /* Transform height from range h_water_level..h_max into 0..h_max_new range */ + *h = (height_t)(((int)h_max_new) * (*h - h_water_level) / (h_max - h_water_level)) + I2H(1); + /* Make sure all values are in the proper range (0..h_max_new) */ + if (*h < 0) *h = I2H(0); + if (*h >= h_max_new) *h = h_max_new - 1; + } + + free(hist_buf); +} + +static double perlin_coast_noise_2D(const double x, const double y, const double p, const int prime); + +/** +* This routine sculpts in from the edge a random amount, again a Perlin +* sequence, to avoid the rigid flat-edge slopes that were present before. The +* Perlin noise map doesnt know where we are going to slice across, and so we +* often cut straight through high terrain. the smoothing routine makes it +* legal, gradually increasing up from the edge to the original terrain height. +* By cutting parts of this away, it gives a far more irregular edge to the +* map-edge. Sometimes it works beautifully with the existing sea & lakes, and +* creates a very realistic coastline. Other times the variation is less, and +* the map-edge shows its cliff-like roots. +* +* This routine may be extended to randomly sculpt the height of the terrain +* near the edge. This will have the coast edge at low level (1-3), rising in +* smoothed steps inland to about 15 tiles in. This should make it look as +* though the map has been built for the map size, rather than a slice through +* a larger map. +* +* Please note that all the small numbers; 53, 101, 167, etc. are small primes +* to help give the perlin noise a bit more of a random feel. +*/ +static void HeightMapCoastLines(void) +{ + int smallest_size = min(_patches.map_x, _patches.map_y); + const int margin = 4; + uint y, x; + uint max_x; + uint max_y; + + /* Lower to sea level */ + for (y = 0; y <= _height_map.size_y; y++) { + /* Top right */ + max_x = myabs((perlin_coast_noise_2D(_height_map.size_y - y, y, 0.9, 53) + 0.25) * 5 + (perlin_coast_noise_2D(y, y, 0.35, 179) + 1) * 12); + max_x = max((smallest_size * smallest_size / 16) + max_x, (smallest_size * smallest_size / 16) + margin - max_x); + if (smallest_size < 8 && max_x > 5) max_x /= 1.5; + for (x = 0; x < max_x; x++) { + HeightMapXY(x, y) = 0; + } + + /* Bottom left */ + max_x = myabs((perlin_coast_noise_2D(_height_map.size_y - y, y, 0.85, 101) + 0.3) * 6 + (perlin_coast_noise_2D(y, y, 0.45, 67) + 0.75) * 8); + max_x = max((smallest_size * smallest_size / 16) + max_x, (smallest_size * smallest_size / 16) + margin - max_x); + if (smallest_size < 8 && max_x > 5) max_x /= 1.5; + for (x = _height_map.size_x; x > (_height_map.size_x - 1 - max_x); x--) { + HeightMapXY(x, y) = 0; + } + } + + /* Lower to sea level */ + for (x = 0; x <= _height_map.size_x; x++) { + /* Top left */ + max_y = myabs((perlin_coast_noise_2D(x, _height_map.size_y / 2, 0.9, 167) + 0.4) * 5 + (perlin_coast_noise_2D(x, _height_map.size_y / 3, 0.4, 211) + 0.7) * 9); + max_y = max((smallest_size * smallest_size / 16) + max_y, (smallest_size * smallest_size / 16) + margin - max_y); + if (smallest_size < 8 && max_y > 5) max_y /= 1.5; + for (y = 0; y < max_y; y++) { + HeightMapXY(x, y) = 0; + } + + + /* Bottom right */ + max_y = myabs((perlin_coast_noise_2D(x, _height_map.size_y / 3, 0.85, 71) + 0.25) * 6 + (perlin_coast_noise_2D(x, _height_map.size_y / 3, 0.35, 193) + 0.75) * 12); + max_y = max((smallest_size * smallest_size / 16) + max_y, (smallest_size * smallest_size / 16) + margin - max_y); + if (smallest_size < 8 && max_y > 5) max_y /= 1.5; + for (y = _height_map.size_y; y > (_height_map.size_y - 1 - max_y); y--) { + HeightMapXY(x, y) = 0; + } + } +} + +/** Start at given point, move in given direction, find and Smooth coast in that direction */ +static void HeightMapSmoothCoastInDirection(int org_x, int org_y, int dir_x, int dir_y) +{ + const int max_coast_dist_from_edge = 35; + const int max_coast_Smooth_depth = 35; + + int x, y; + int ed; // coast distance from edge + int depth; + + height_t h_prev = 16; + height_t h; + + assert(IsValidXY(org_x, org_y)); + + /* Search for the coast (first non-water tile) */ + for (x = org_x, y = org_y, ed = 0; IsValidXY(x, y) && ed < max_coast_dist_from_edge; x += dir_x, y += dir_y, ed++) { + /* Coast found? */ + if (HeightMapXY(x, y) > 15) break; + + /* Coast found in the neighborhood? */ + if (IsValidXY(x + dir_y, y + dir_x) && HeightMapXY(x + dir_y, y + dir_x) > 0) break; + + /* Coast found in the neighborhood on the other side */ + if (IsValidXY(x - dir_y, y - dir_x) && HeightMapXY(x - dir_y, y - dir_x) > 0) break; + } + + /* Coast found or max_coast_dist_from_edge has been reached. + * Soften the coast slope */ + for (depth = 0; IsValidXY(x, y) && depth <= max_coast_Smooth_depth; depth++, x += dir_x, y += dir_y) { + h = HeightMapXY(x, y); + h = min(h, h_prev + (4 + depth)); // coast softening formula + HeightMapXY(x, y) = h; + h_prev = h; + } +} + +/** Smooth coasts by modulating height of tiles close to map edges with cosine of distance from edge */ +static void HeightMapSmoothCoasts(void) +{ + uint x, y; + /* First Smooth NW and SE coasts (y close to 0 and y close to size_y) */ + for (x = 0; x < _height_map.size_x; x++) { + HeightMapSmoothCoastInDirection(x, 0, 0, 1); + HeightMapSmoothCoastInDirection(x, _height_map.size_y - 1, 0, -1); + } + /* First Smooth NE and SW coasts (x close to 0 and x close to size_x) */ + for (y = 0; y < _height_map.size_y; y++) { + HeightMapSmoothCoastInDirection(0, y, 1, 0); + HeightMapSmoothCoastInDirection(_height_map.size_x - 1, y, -1, 0); + } +} + +/** +* This routine provides the essential cleanup necessary before OTTD can +* display the terrain. When generated, the terrain heights can jump more than +* one level between tiles. This routine smooths out those differences so that +* the most it can change is one level. When OTTD can support cliffs, this +* routine may not be necessary. +*/ +static void HeightMapSmoothSlopes(height_t dh_max) +{ + int x, y; + for (y = 1; y <= (int)_height_map.size_y; y++) { + for (x = 1; x <= (int)_height_map.size_x; x++) { + height_t h_max = min(HeightMapXY(x - 1, y), HeightMapXY(x, y - 1)) + dh_max; + if (HeightMapXY(x, y) > h_max) HeightMapXY(x, y) = h_max; + } + } + for (y = _height_map.size_y - 1; y >= 0; y--) { + for (x = _height_map.size_x - 1; x >= 0; x--) { + height_t h_max = min(HeightMapXY(x + 1, y), HeightMapXY(x, y + 1)) + dh_max; + if (HeightMapXY(x, y) > h_max) HeightMapXY(x, y) = h_max; + } + } +} + +/** Height map terraform post processing: + * - water level adjusting + * - coast Smoothing + * - slope Smoothing + * - height histogram redistribution by sine wave transform */ +static void HeightMapNormalize(void) +{ + const amplitude_t water_percent = _water_percent[_opt.diff.quantity_sea_lakes]; + const height_t h_max_new = I2H(_max_height[_opt.diff.terrain_type]); + const height_t roughness = 7 + 3 * _patches.tgen_smoothness; + + HeightMapAdjustWaterLevel(water_percent, h_max_new); + + HeightMapCoastLines(); + HeightMapSmoothSlopes(roughness); + + HeightMapSmoothCoasts(); + HeightMapSmoothSlopes(roughness); + + HeightMapSineTransform(12, h_max_new); + HeightMapSmoothSlopes(16); +} + +static inline int perlin_landXY(uint x, uint y) +{ + return HeightMapXY(x, y); +} + + +/* The following decimals are the octave power modifiers for the Perlin noise */ +static const double _perlin_p_values[][7] = { // perlin frequency per power + { 0.35, 0.35, 0.35, 0.35, 0.35, 0.25, 0.539 }, // Very smooth + { 0.45, 0.55, 0.45, 0.45, 0.35, 0.25, 0.89 }, // Smooth + { 0.85, 0.80, 0.70, 0.45, 0.45, 0.35, 1.825 }, // Rough 1.825 + { 0.95, 0.85, 0.80, 0.55, 0.55, 0.45, 2.245 } // Very Rough 2.25 +}; + +/** + * The Perlin Noise calculation using large primes + * The initial number is adjusted by two values; the generation_seed, and the + * passed parameter; prime. + * prime is used to allow the perlin noise generator to create useful random + * numbers from slightly different series. + */ +static double int_noise(const long x, const long y, const int prime) +{ + long n = x + y * prime + _patches.generation_seed; + + n = (n << 13) ^ n; + + /* Pseudo-random number generator, using several large primes */ + return 1.0 - (double)((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0; +} + + +/** + * Hj. Malthaner's routine included 2 different noise smoothing methods. + * We now use the "raw" int_noise one. + * However, it may be useful to move to the other routine in future. + * So it is included too. + */ +static double smoothed_noise(const int x, const int y, const int prime) +{ +#if 0 + /* A hilly world (four corner smooth) */ + const double sides = int_noise(x - 1, y) + int_noise(x + 1, y) + int_noise(x, y - 1) + int_noise(x, y + 1); + const double center = int_noise(x, y); + return (sides + sides + center * 4) / 8.0; +#endif + + /* This gives very hilly world */ + return int_noise(x, y, prime); +} + + +/** + * This routine determines the interpolated value between a and b + */ +static inline double linear_interpolate(const double a, const double b, const double x) +{ + return a + x * (b - a); +} + + +/** + * This routine returns the smoothed interpolated noise for an x and y, using + * the values from the surrounding positions. + */ +static double interpolated_noise(const double x, const double y, const int prime) +{ + const int integer_X = (int)x; + const int integer_Y = (int)y; + + const double fractional_X = x - (double)integer_X; + const double fractional_Y = y - (double)integer_Y; + + const double v1 = smoothed_noise(integer_X, integer_Y, prime); + const double v2 = smoothed_noise(integer_X + 1, integer_Y, prime); + const double v3 = smoothed_noise(integer_X, integer_Y + 1, prime); + const double v4 = smoothed_noise(integer_X + 1, integer_Y + 1, prime); + + const double i1 = linear_interpolate(v1, v2, fractional_X); + const double i2 = linear_interpolate(v3, v4, fractional_X); + + return linear_interpolate(i1, i2, fractional_Y); +} + + +/** + * This is a similar function to the main perlin noise calculation, but uses + * the value p passed as a parameter rather than selected from the predefined + * sequences. as you can guess by its title, i use this to create the indented + * coastline, which is just another perlin sequence. + */ +static double perlin_coast_noise_2D(const double x, const double y, const double p, const int prime) +{ + double total = 0.0; + int i; + + for (i = 0; i < 6; i++) { + const double frequency = (double)(1 << i); + const double amplitude = pow(p, (double)i); + + total += interpolated_noise((x * frequency) / 64.0, (y * frequency) / 64.0, prime) * amplitude; + } + + return total; +} + + +/** A small helper function */ +static void TgenSetTileHeight(TileIndex tile, int height) +{ + SetTileHeight(tile, height); + MakeClear(tile, CLEAR_GRASS, 3); +} + +/** + * 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(void) +{ + uint x, y; + + if (!AllocHeightMap()) return; + HeightMapGenerate(); + + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + + HeightMapNormalize(); + + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + + /* Transfer height map into OTTD map */ + for (y = 2; y < _height_map.size_y - 2; y++) { + for (x = 2; x < _height_map.size_x - 2; x++) { + int height = H2I(HeightMapXY(x, y)); + if (height < 0) height = 0; + if (height > 15) height = 15; + TgenSetTileHeight(TileXY(x, y), height); + } + } + + IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); + + /* Recreate void tiles at the border in case they have been affected by generation */ + for (y = 0; y < _height_map.size_y - 1; y++) MakeVoid(_height_map.size_x * y + _height_map.size_x - 1); + for (x = 0; x < _height_map.size_x; x++) MakeVoid(_height_map.size_x * y + x); + + FreeHeightMap(); +} diff --git a/tgp.h b/tgp.h new file mode 100644 index 0000000000..e229c9d85b --- /dev/null +++ b/tgp.h @@ -0,0 +1,8 @@ +/* $Id$ */ + +#ifndef TGP_H +#define TGP_H + +void GenerateTerrainPerlin(void); + +#endif diff --git a/thread.c b/thread.c index e9d5d0ff1f..a2475408b3 100644 --- a/thread.c +++ b/thread.c @@ -7,7 +7,7 @@ #if defined(__AMIGA__) || defined(__MORPHOS__) OTTDThread* OTTDCreateThread(OTTDThreadFunc function, void* arg) { return NULL; } void* OTTDJoinThread(OTTDThread* t) { return NULL; } - +void OTTDExitThread() { NOT_REACHED(); }; #elif defined(__OS2__) @@ -57,6 +57,10 @@ void* OTTDJoinThread(OTTDThread* t) return ret; } +void OTTDExitThread(void) +{ + _endthread(); +} #elif defined(UNIX) @@ -91,6 +95,10 @@ void* OTTDJoinThread(OTTDThread* t) return ret; } +void OTTDExitThread(void) +{ + pthread_exit(NULL); +} #elif defined(WIN32) @@ -141,4 +149,9 @@ void* OTTDJoinThread(OTTDThread* t) free(t); return ret; } + +void OTTDExitThread(void) +{ + ExitThread(0); +} #endif diff --git a/thread.h b/thread.h index 98f4f5ceef..900b878497 100644 --- a/thread.h +++ b/thread.h @@ -8,6 +8,7 @@ typedef struct OTTDThread OTTDThread; typedef void* (*OTTDThreadFunc)(void*); OTTDThread* OTTDCreateThread(OTTDThreadFunc, void*); -void* OTTDJoinThread(OTTDThread*); +void* OTTDJoinThread(OTTDThread*); +void OTTDExitThread(void); #endif /* THREAD_H */ diff --git a/town_cmd.c b/town_cmd.c index e0c3c41d40..95bc3ec8fe 100644 --- a/town_cmd.c +++ b/town_cmd.c @@ -28,6 +28,7 @@ #include "bridge.h" #include "date.h" #include "table/town_land.h" +#include "genworld.h" enum { /* Max towns: 64000 (8 * 8000) */ @@ -1054,7 +1055,10 @@ bool GenerateTowns(void) uint num = 0; uint n = ScaleByMapSize(_num_initial_towns[_opt.diff.number_towns] + (Random() & 7)); + SetGeneratingWorldProgress(GWP_TOWN, n); + do { + IncreaseGeneratingWorldProgress(GWP_TOWN); // try 20 times to create a random-sized town for the first loop. if (CreateRandomTown(20, 0) != NULL) num++; } while (--n); diff --git a/tree_cmd.c b/tree_cmd.c index 99b2de384d..6366df469e 100644 --- a/tree_cmd.c +++ b/tree_cmd.c @@ -15,6 +15,13 @@ #include "town.h" #include "sound.h" #include "variables.h" +#include "genworld.h" + +enum TreePlacer { + TP_NONE, + TP_ORIGINAL, + TP_IMPROVED, +}; static TreeType GetRandomTreeType(TileIndex tile, uint seed) { @@ -83,18 +90,70 @@ static void PlaceMoreTrees(void) } while (--i); } -void PlaceTreesRandomly(void) +/** + * Place a tree at the same height as an existing tree. + * This gives cool effects to the map. + */ +void PlaceTreeAtSameHeight(TileIndex tile, uint height) { uint i; + for (i = 0; i < 1000; i++) { + uint32 r = Random(); + int x = GB(r, 0, 5) - 16; + int y = GB(r, 8, 5) - 16; + TileIndex cur_tile = TILE_MASK(tile + TileDiffXY(x, y)); + + /* Keep in range of the existing tree */ + if (myabs(x) + myabs(y) > 16) continue; + + /* Clear tile, no farm-tiles or rocks */ + if (!IsTileType(cur_tile, MP_CLEAR) || + IsClearGround(cur_tile, CLEAR_FIELDS) || + IsClearGround(cur_tile, CLEAR_ROCKS)) + continue; + + /* Not too much height difference */ + if (myabs(GetTileZ(cur_tile) - height) > 2) continue; + + /* Place one tree and quit */ + PlaceTree(cur_tile, r); + break; + } +} + +void PlaceTreesRandomly(void) +{ + uint i, j, ht; + i = ScaleByMapSize(1000); do { uint32 r = Random(); TileIndex tile = RandomTileSeed(r); + + IncreaseGeneratingWorldProgress(GWP_TREE); + if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_FIELDS) && !IsClearGround(tile, CLEAR_ROCKS)) { PlaceTree(tile, r); + if (_patches.tree_placer != TP_IMPROVED) continue; + + /* Place a number of trees based on the tile height. + * This gives a cool effect of multiple trees close together. + * It is almost real life ;) */ + ht = GetTileZ(tile); + /* The higher we get, the more trees we plant */ + j = GetTileZ(tile) / TILE_HEIGHT * 2; + while (j--) { + /* Above snowline more trees! */ + if (_opt.landscape == LT_HILLY && ht > _opt.snow_line) { + PlaceTreeAtSameHeight(tile, ht); + PlaceTreeAtSameHeight(tile, ht); + }; + + PlaceTreeAtSameHeight(tile, ht); + } } } while (--i); @@ -105,7 +164,10 @@ void PlaceTreesRandomly(void) do { uint32 r = Random(); TileIndex tile = RandomTileSeed(r); - if (IsTileType(tile, MP_CLEAR) && GetTropicZone(tile) == TROPICZONE_RAINFOREST) { + + IncreaseGeneratingWorldProgress(GWP_TREE); + + if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_FIELDS) && GetTropicZone(tile) == TROPICZONE_RAINFOREST) { PlaceTree(tile, r); } } while (--i); @@ -114,11 +176,24 @@ void PlaceTreesRandomly(void) void GenerateTrees(void) { - uint i; + uint i, total; + + if (_patches.tree_placer == TP_NONE) return; if (_opt.landscape != LT_CANDY) PlaceMoreTrees(); - for (i = _opt.landscape == LT_HILLY ? 15 : 6; i != 0; i--) { + switch (_patches.tree_placer) { + case TP_ORIGINAL: i = _opt.landscape == LT_HILLY ? 15 : 6; break; + case TP_IMPROVED: i = _opt.landscape == LT_HILLY ? 4 : 2; break; + default: NOT_REACHED(); return; + } + + total = ScaleByMapSize(1000); + if (_opt.landscape == LT_DESERT) total += ScaleByMapSize(15000); + total *= i; + SetGeneratingWorldProgress(GWP_TREE, total); + + for (; i != 0; i--) { PlaceTreesRandomly(); } } diff --git a/unix.c b/unix.c index 0fc66591cf..9127f800df 100644 --- a/unix.c +++ b/unix.c @@ -214,6 +214,7 @@ void DeterminePaths(void) _path.save_dir = str_fmt("%ssave", _path.personal_dir); _path.autosave_dir = str_fmt("%s/autosave", _path.save_dir); _path.scenario_dir = str_fmt("%sscenario", _path.personal_dir); + _path.heightmap_dir = str_fmt("%sscenario/heightmap", _path.personal_dir); _path.gm_dir = str_fmt("%sgm/", _path.game_data_dir); _path.data_dir = str_fmt("%sdata/", _path.game_data_dir); @@ -236,6 +237,7 @@ void DeterminePaths(void) mkdir(_path.save_dir, 0755); mkdir(_path.autosave_dir, 0755); mkdir(_path.scenario_dir, 0755); + mkdir(_path.heightmap_dir, 0755); } bool InsertTextBufferClipboard(Textbuf *tb) diff --git a/unmovable_cmd.c b/unmovable_cmd.c index 0188900e65..7bcca89409 100644 --- a/unmovable_cmd.c +++ b/unmovable_cmd.c @@ -18,6 +18,7 @@ #include "unmovable_map.h" #include "variables.h" #include "table/unmovable_land.h" +#include "genworld.h" /** Destroy a HQ. * During normal gameplay you can only implicitely destroy a HQ when you are @@ -309,12 +310,13 @@ static bool IsRadioTowerNearby(TileIndex tile) BEGIN_TILE_LOOP(tile, 9, 9, tile_s) if (IsTransmitterTile(tile)) return true; END_TILE_LOOP(tile, 9, 9, tile_s) + return false; } void GenerateUnmovables(void) { - int i,j; + int i, li, j, loop_count; TileIndex tile; uint h; uint maxx; @@ -324,12 +326,16 @@ void GenerateUnmovables(void) /* add radio tower */ i = ScaleByMapSize(1000); - j = ScaleByMapSize(40); // maximum number of radio towers on the map + j = ScaleByMapSize(15); // maximum number of radio towers on the map + li = ScaleByMapSize1D((Random() & 3) + 7); + SetGeneratingWorldProgress(GWP_UNMOVABLE, j + li); + do { tile = RandomTile(); if (IsTileType(tile, MP_CLEAR) && GetTileSlope(tile, &h) == SLOPE_FLAT && h >= TILE_HEIGHT * 4) { if (IsRadioTowerNearby(tile)) continue; MakeTransmitter(tile); + IncreaseGeneratingWorldProgress(GWP_UNMOVABLE); if (--j == 0) break; } } while (--i); @@ -337,16 +343,27 @@ void GenerateUnmovables(void) if (_opt.landscape == LT_DESERT) return; /* add lighthouses */ - i = ScaleByMapSize1D((Random() & 3) + 7); + i = li; maxx = MapMaxX(); maxy = MapMaxY(); + loop_count = 0; do { uint32 r; DiagDirection dir; + int perimeter; restart: + /* Avoid infinite loops */ + if (++loop_count > 1000) break; + r = Random(); - dir = GB(r, 30, 2); + + /* Scatter the lighthouses more evenly around the perimeter */ + perimeter = (GB(r, 16, 16) % (2 * (maxx + maxy))) - maxy; + for (dir = DIAGDIR_NE; perimeter > 0; dir++) { + perimeter -= (DiagDirToAxis(dir) == AXIS_X) ? maxx : maxy; + } + switch (dir) { default: case DIAGDIR_NE: tile = TileXY(maxx, r % maxy); break; @@ -363,6 +380,7 @@ restart: assert(tile == TILE_MASK(tile)); MakeLighthouse(tile); + IncreaseGeneratingWorldProgress(GWP_UNMOVABLE); } while (--i); } diff --git a/variables.h b/variables.h index d90be7709f..3fd9488f95 100644 --- a/variables.h +++ b/variables.h @@ -107,7 +107,15 @@ typedef struct Patches { bool roadveh_queue; // buggy road vehicle queueing bool autoscroll; // scroll when moving mouse to the edge. byte errmsg_duration; // duration of error message + byte land_generator; // the landscape generator + byte oil_refinery_limit; // distance oil refineries allowed from map edge byte snow_line_height; // a number 0-15 that configured snow line height + byte tgen_smoothness; // how rough is the terrain from 0-3 + uint32 generation_seed; // noise seed for world generation + byte tree_placer; // the tree placer algorithm + byte heightmap_rotation;// rotation director for the heightmap + uint16 progress_update_interval;// interval between two updates of the progress in hundreds of milliseconds + byte se_flat_world_height; // land height a flat world gets in SE bool bribe; // enable bribing the local authority bool nonuniform_stations;// allow nonuniform train stations bool always_small_airport; // always allow small airports @@ -244,6 +252,7 @@ typedef struct Paths { char *save_dir; char *autosave_dir; char *scenario_dir; + char *heightmap_dir; char *second_data_dir; } Paths; diff --git a/video/cocoa_v.m b/video/cocoa_v.m index 4de6af703c..758120f035 100644 --- a/video/cocoa_v.m +++ b/video/cocoa_v.m @@ -56,6 +56,7 @@ extern void HideMenuBar(void); #include "../window.h" #include "../network.h" #include "../variables.h" +#include "../genworld.h" #include "../os/macosx/splash.h" #include "cocoa_v.h" @@ -709,7 +710,7 @@ static void QZ_GameLoop(void) #endif { if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; - } else if (_fast_forward & 2) { + } else if (_fast_forward & 2 && !IsGeneratingWorld()) { _fast_forward = 0; } diff --git a/video/dedicated_v.c b/video/dedicated_v.c index 3c367efe92..953368f998 100644 --- a/video/dedicated_v.c +++ b/video/dedicated_v.c @@ -12,6 +12,7 @@ #include "../window.h" #include "../console.h" #include "../variables.h" +#include "../genworld.h" #include "dedicated_v.h" #ifdef BEOS_NET_SERVER @@ -240,8 +241,9 @@ static void DedicatedVideoMainLoop(void) /* If SwitchMode is SM_LOAD, it means that the user used the '-g' options */ if (_switch_mode != SM_LOAD) { + StartNewGameWithoutGUI(GENERATE_NEW_SEED); + SwitchMode(_switch_mode); _switch_mode = SM_NONE; - GenRandomNewGame(Random(), InteractiveRandom()); } else { _switch_mode = SM_NONE; /* First we need to test if the savegame can be loaded, else we will end up playing the diff --git a/video/sdl_v.c b/video/sdl_v.c index 275db9dba4..14675ba52b 100644 --- a/video/sdl_v.c +++ b/video/sdl_v.c @@ -13,6 +13,7 @@ #include "../window.h" #include "../network.h" #include "../variables.h" +#include "../genworld.h" #include "sdl_v.h" #include @@ -461,7 +462,7 @@ static void SdlVideoMainLoop(void) if (keys[SDLK_TAB]) #endif { - if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; + if (!_networking && _game_mode != GM_MENU && !IsGeneratingWorld()) _fast_forward |= 2; } else if (_fast_forward & 2) { _fast_forward = 0; } diff --git a/video/win32_v.c b/video/win32_v.c index 4d25b6e651..a50ff6d72f 100644 --- a/video/win32_v.c +++ b/video/win32_v.c @@ -9,6 +9,7 @@ #include "../variables.h" #include "../win32.h" #include "../window.h" +#include "../genworld.h" #include "win32_v.h" #include @@ -792,7 +793,7 @@ static void Win32GdiMainLoop(void) * real key is in the upper 16 bits (see WM_SYSKEYDOWN in WndProcGdi()) */ if ((_pressed_key >> 16) & WKC_TAB && #endif - !_networking && _game_mode != GM_MENU) + !_networking && _game_mode != GM_MENU && !IsGeneratingWorld()) _fast_forward |= 2; } else if (_fast_forward & 2) { _fast_forward = 0; diff --git a/win32.c b/win32.c index eea7646b32..099cc8f98f 100644 --- a/win32.c +++ b/win32.c @@ -899,6 +899,7 @@ void DeterminePaths(void) _path.save_dir = str_fmt("%ssave", cfg); _path.autosave_dir = str_fmt("%s\\autosave", _path.save_dir); _path.scenario_dir = str_fmt("%sscenario", cfg); + _path.heightmap_dir = str_fmt("%sscenario\\heightmap", cfg); _path.gm_dir = str_fmt("%sgm\\", cfg); _path.data_dir = str_fmt("%sdata\\", cfg); _path.lang_dir = str_fmt("%slang\\", cfg); @@ -913,6 +914,7 @@ void DeterminePaths(void) CreateDirectory(_path.save_dir, NULL); CreateDirectory(_path.autosave_dir, NULL); CreateDirectory(_path.scenario_dir, NULL); + CreateDirectory(_path.heightmap_dir, NULL); } int CDECL snprintf(char *str, size_t size, const char *format, ...) diff --git a/window.c b/window.c index b003bd06da..b22629b33d 100644 --- a/window.c +++ b/window.c @@ -12,6 +12,7 @@ #include "console.h" #include "variables.h" #include "table/sprites.h" +#include "genworld.h" // delta between mouse cursor and upper left corner of dragged window static Point _drag_delta; @@ -1323,10 +1324,11 @@ static void HandleKeypress(uint32 key) we.keypress.cont = true; // check if we have a query string window open before allowing hotkeys - if (FindWindowById(WC_QUERY_STRING, 0) != NULL || - FindWindowById(WC_SEND_NETWORK_MSG, 0) != NULL || - FindWindowById(WC_CONSOLE, 0) != NULL || - FindWindowById(WC_SAVELOAD, 0) != NULL) { + if (FindWindowById(WC_QUERY_STRING, 0) != NULL || + FindWindowById(WC_SEND_NETWORK_MSG, 0) != NULL || + FindWindowById(WC_GENERATE_LANDSCAPE, 0) != NULL || + FindWindowById(WC_CONSOLE, 0) != NULL || + FindWindowById(WC_SAVELOAD, 0) != NULL) { query_open = true; } @@ -1337,6 +1339,7 @@ static void HandleKeypress(uint32 key) if (query_open && w->window_class != WC_QUERY_STRING && w->window_class != WC_SEND_NETWORK_MSG && + w->window_class != WC_GENERATE_LANDSCAPE && w->window_class != WC_CONSOLE && w->window_class != WC_SAVELOAD) { continue; @@ -1376,7 +1379,7 @@ static void MouseLoop(int click, int mousewheel) y = _cursor.pos.y; if (click == 0 && mousewheel == 0) { - if (_patches.autoscroll && _game_mode != GM_MENU) { + if (_patches.autoscroll && _game_mode != GM_MENU && !IsGeneratingWorld()) { w = FindWindowFromPt(x, y); if (w == NULL || w->flags4 & WF_DISABLE_VP_SCROLL) return; vp = IsPtInWindowViewport(w, x, y); @@ -1406,7 +1409,7 @@ static void MouseLoop(int click, int mousewheel) w = MaybeBringWindowToFront(w); vp = IsPtInWindowViewport(w, x, y); if (vp != NULL) { - if (_game_mode == GM_MENU) return; + if (_game_mode == GM_MENU || IsGeneratingWorld()) return; // only allow zooming in-out in main window, or in viewports if (mousewheel && @@ -1455,7 +1458,16 @@ void InputLoop(void) int click; int mousewheel; - _current_player = _local_player; + /* + * During the generation of the world, there might be + * another thread that is currently building for example + * a road. To not interfere with those tasks, we should + * NOT change the _current_player here. + * + * This is not necessary either, as the only events that + * can be handled are the 'close application' events + */ + if (!IsGeneratingWorld()) _current_player = _local_player; // Handle pressed keys if (_pressed_key != 0) { diff --git a/window.h b/window.h index 8134e929c6..ae99ddc93d 100644 --- a/window.h +++ b/window.h @@ -304,6 +304,16 @@ typedef struct querystr_d { } querystr_d; assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(querystr_d)); +typedef struct query_d { + StringID caption; + StringID message; + WindowClass wnd_class; + WindowNumber wnd_num; + void (*ok_cancel_callback)(bool ok_clicked); + bool calledback; +} query_d; +assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(query_d)); + typedef struct { byte item_count; /* follow_vehicle */ byte sel_index; /* scrollpos_x */