(svn r5946) -Add: merged the TGP branch to mainline. TGP adds:
- New optional landscape generator (TerraGenesis Perlin) - Load heightmaps (either BMP or PNG) - Progress dialog while generating worlds (no longer a 'hanging' screen) - New dialogs for NewGame, Create Scenario and Play Heightmap - Easier to configure your landscape - More things to configure (tree-placer, ..) - Speedup of world generation - New console command 'restart': restart the map EXACTLY as it was when you first started it (needs a game made after or with this commit) - New console command 'getseed': get the seed of your map and share it with others (of course only works with generated maps) - Many new, world generation related, things - Many internal cleanups and rewrites Many tnx to those people who helped making this: Belugas, DaleStan, glx, KUDr, RichK67, Rubidium, and TrueLight (alfabetic) Many tnx to those who helped testing: Arnau, Bjarni, and tokai (alfabetic) And to all other people who helped testing and sending comments / bugs Stats: 673 lines changed, 3534 new lines, 79 new stringspull/155/head
parent
d3f2180438
commit
7abad2b20e
@ -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);
|
||||
}
|
@ -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 */
|
@ -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();
|
||||
}
|
@ -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 */
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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 */
|
@ -0,0 +1,826 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <math.h>
|
||||
#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();
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/* $Id$ */
|
||||
|
||||
#ifndef TGP_H
|
||||
#define TGP_H
|
||||
|
||||
void GenerateTerrainPerlin(void);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue