|
|
|
#include "stdafx.h"
|
|
|
|
#include "string.h"
|
|
|
|
#include "table/strings.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "strings.h"
|
|
|
|
#include "map.h"
|
|
|
|
#include "tile.h"
|
|
|
|
|
|
|
|
#define VARDEF
|
|
|
|
#include "ttd.h"
|
|
|
|
#include "mixer.h"
|
|
|
|
#include "spritecache.h"
|
|
|
|
#include "gfx.h"
|
|
|
|
#include "gui.h"
|
|
|
|
#include "station.h"
|
|
|
|
#include "vehicle.h"
|
|
|
|
#include "viewport.h"
|
|
|
|
#include "window.h"
|
|
|
|
#include "player.h"
|
|
|
|
#include "command.h"
|
|
|
|
#include "town.h"
|
|
|
|
#include "industry.h"
|
|
|
|
#include "news.h"
|
|
|
|
#include "engine.h"
|
|
|
|
#include "sound.h"
|
|
|
|
#include "economy.h"
|
|
|
|
#include "fileio.h"
|
|
|
|
#include "hal.h"
|
|
|
|
#include "airport.h"
|
|
|
|
#include "saveload.h"
|
|
|
|
#include "ai.h"
|
|
|
|
#include "console.h"
|
|
|
|
#include "screenshot.h"
|
|
|
|
#include "network.h"
|
|
|
|
#include "signs.h"
|
|
|
|
#include "depot.h"
|
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
|
|
|
void GenerateWorld(int mode, uint log_x, uint log_y);
|
|
|
|
void CallLandscapeTick(void);
|
|
|
|
void IncreaseDate(void);
|
|
|
|
void RunOtherPlayersLoop(void);
|
|
|
|
void DoPaletteAnimations(void);
|
|
|
|
void MusicLoop(void);
|
|
|
|
void ResetMusic(void);
|
|
|
|
void InitializeStations(void);
|
|
|
|
void DeleteAllPlayerStations(void);
|
|
|
|
|
|
|
|
extern void SetDifficultyLevel(int mode, GameOptions *gm_opt);
|
|
|
|
extern void DoStartupNewPlayer(bool is_ai);
|
|
|
|
extern void ShowOSErrorBox(const char *buf);
|
|
|
|
|
|
|
|
void redsq_debug(int tile);
|
|
|
|
bool LoadSavegame(const char *filename);
|
|
|
|
|
|
|
|
extern void HalGameLoop(void);
|
|
|
|
|
|
|
|
uint32 _pixels_redrawn;
|
|
|
|
bool _dbg_screen_rect;
|
|
|
|
bool disable_computer; // We should get ride of this thing.. is only used for a debug-cheat
|
|
|
|
static byte _os_version = 0;
|
|
|
|
|
|
|
|
/* TODO: usrerror() for errors which are not of an internal nature but
|
|
|
|
* caused by the user, i.e. missing files or fatal configuration errors.
|
|
|
|
* Post-0.4.0 since Celestar doesn't want this in SVN before. --pasky */
|
|
|
|
|
|
|
|
void CDECL error(const char *s, ...) {
|
|
|
|
va_list va;
|
|
|
|
char buf[512];
|
|
|
|
va_start(va, s);
|
|
|
|
vsprintf(buf, s, va);
|
|
|
|
va_end(va);
|
|
|
|
|
|
|
|
ShowOSErrorBox(buf);
|
|
|
|
if (_video_driver)
|
|
|
|
_video_driver->stop();
|
|
|
|
|
|
|
|
assert(0);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDECL ShowInfoF(const char *str, ...)
|
|
|
|
{
|
|
|
|
va_list va;
|
|
|
|
char buf[1024];
|
|
|
|
va_start(va, str);
|
|
|
|
vsprintf(buf, str, va);
|
|
|
|
va_end(va);
|
|
|
|
ShowInfo(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
char * CDECL str_fmt(const char *str, ...)
|
|
|
|
{
|
|
|
|
char buf[4096];
|
|
|
|
va_list va;
|
|
|
|
int len;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
va_start(va, str);
|
|
|
|
len = vsprintf(buf, str, va);
|
|
|
|
va_end(va);
|
|
|
|
p = malloc(len + 1);
|
|
|
|
if (p)
|
|
|
|
memcpy(p, buf, len + 1);
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NULL midi driver
|
|
|
|
static const char *NullMidiStart(const char * const *parm) { return NULL; }
|
|
|
|
static void NullMidiStop(void) {}
|
|
|
|
static void NullMidiPlaySong(const char *filename) {}
|
|
|
|
static void NullMidiStopSong(void) {}
|
|
|
|
static bool NullMidiIsSongPlaying(void) { return true; }
|
|
|
|
static void NullMidiSetVolume(byte vol) {}
|
|
|
|
|
|
|
|
const HalMusicDriver _null_music_driver = {
|
|
|
|
NullMidiStart,
|
|
|
|
NullMidiStop,
|
|
|
|
NullMidiPlaySong,
|
|
|
|
NullMidiStopSong,
|
|
|
|
NullMidiIsSongPlaying,
|
|
|
|
NullMidiSetVolume,
|
|
|
|
};
|
|
|
|
|
|
|
|
// NULL video driver
|
|
|
|
static void *_null_video_mem;
|
|
|
|
static const char *NullVideoStart(const char * const *parm)
|
|
|
|
{
|
|
|
|
_screen.width = _screen.pitch = _cur_resolution[0];
|
|
|
|
_screen.height = _cur_resolution[1];
|
|
|
|
_null_video_mem = malloc(_cur_resolution[0]*_cur_resolution[1]);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
static void NullVideoStop(void) { free(_null_video_mem); }
|
|
|
|
static void NullVideoMakeDirty(int left, int top, int width, int height) {}
|
|
|
|
static int NullVideoMainLoop(void)
|
|
|
|
{
|
|
|
|
int i = 1000;
|
|
|
|
do {
|
|
|
|
GameLoop();
|
|
|
|
_screen.dst_ptr = _null_video_mem;
|
|
|
|
UpdateWindows();
|
|
|
|
} while (--i);
|
|
|
|
return ML_QUIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool NullVideoChangeRes(int w, int h) { return false; }
|
|
|
|
|
|
|
|
|
|
|
|
const HalVideoDriver _null_video_driver = {
|
|
|
|
NullVideoStart,
|
|
|
|
NullVideoStop,
|
|
|
|
NullVideoMakeDirty,
|
|
|
|
NullVideoMainLoop,
|
|
|
|
NullVideoChangeRes,
|
|
|
|
};
|
|
|
|
|
|
|
|
// NULL sound driver
|
|
|
|
static const char *NullSoundStart(const char * const *parm) { return NULL; }
|
|
|
|
static void NullSoundStop(void) {}
|
|
|
|
const HalSoundDriver _null_sound_driver = {
|
|
|
|
NullSoundStart,
|
|
|
|
NullSoundStop,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
DF_PRIORITY_MASK = 0xf,
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
const DriverDesc *descs;
|
|
|
|
const char *name;
|
|
|
|
void *var;
|
|
|
|
} DriverClass;
|
|
|
|
|
|
|
|
static DriverClass _driver_classes[] = {
|
|
|
|
{_video_driver_descs, "video", &_video_driver},
|
|
|
|
{_sound_driver_descs, "sound", &_sound_driver},
|
|
|
|
{_music_driver_descs, "music", &_music_driver},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const DriverDesc *GetDriverByName(const DriverDesc *dd, const char *name)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
if (!strcmp(dd->name, name))
|
|
|
|
return dd;
|
|
|
|
} while ((++dd)->name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const DriverDesc *ChooseDefaultDriver(const DriverDesc *dd)
|
|
|
|
{
|
|
|
|
const DriverDesc *best = NULL;
|
|
|
|
int best_pri = -1;
|
|
|
|
do {
|
|
|
|
if ((int)(dd->flags&DF_PRIORITY_MASK) > best_pri && _os_version >= (byte)dd->flags) {
|
|
|
|
best_pri = dd->flags&DF_PRIORITY_MASK;
|
|
|
|
best = dd;
|
|
|
|
}
|
|
|
|
} while ((++dd)->name);
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
|
|
|
|
{
|
|
|
|
FILE *in;
|
|
|
|
byte *mem;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
in = fopen(filename, "rb");
|
|
|
|
if (in == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
fseek(in, 0, SEEK_END);
|
|
|
|
len = ftell(in);
|
|
|
|
fseek(in, 0, SEEK_SET);
|
|
|
|
if (len > maxsize || (mem = malloc(len + 1)) == NULL) {
|
|
|
|
fclose(in);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
mem[len] = 0;
|
|
|
|
if (fread(mem, len, 1, in) != 1) {
|
|
|
|
fclose(in);
|
|
|
|
free(mem);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
fclose(in);
|
|
|
|
|
|
|
|
*lenp = len;
|
|
|
|
return mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LoadDriver(int driver, const char *name)
|
|
|
|
{
|
|
|
|
const DriverClass *dc = &_driver_classes[driver];
|
|
|
|
const DriverDesc *dd;
|
|
|
|
const void **var;
|
|
|
|
const void *drv;
|
|
|
|
const char *err;
|
|
|
|
char *parm;
|
|
|
|
char buffer[256];
|
|
|
|
const char *parms[32];
|
|
|
|
|
|
|
|
parms[0] = NULL;
|
|
|
|
|
|
|
|
if (!*name) {
|
|
|
|
dd = ChooseDefaultDriver(dc->descs);
|
|
|
|
} else {
|
|
|
|
// Extract the driver name and put parameter list in parm
|
|
|
|
ttd_strlcpy(buffer, name, sizeof(buffer));
|
|
|
|
parm = strchr(buffer, ':');
|
|
|
|
if (parm) {
|
|
|
|
uint np = 0;
|
|
|
|
// Tokenize the parm.
|
|
|
|
do {
|
|
|
|
*parm++ = 0;
|
|
|
|
if (np < lengthof(parms) - 1)
|
|
|
|
parms[np++] = parm;
|
|
|
|
while (*parm != 0 && *parm != ',')
|
|
|
|
parm++;
|
|
|
|
} while (*parm == ',');
|
|
|
|
parms[np] = NULL;
|
|
|
|
}
|
|
|
|
dd = GetDriverByName(dc->descs, buffer);
|
|
|
|
if (dd == NULL)
|
|
|
|
error("No such %s driver: %s\n", dc->name, buffer);
|
|
|
|
}
|
|
|
|
var = dc->var;
|
|
|
|
if (*var != NULL) ((const HalCommonDriver*)*var)->stop();
|
|
|
|
*var = NULL;
|
|
|
|
drv = dd->drv;
|
|
|
|
if ((err=((const HalCommonDriver*)drv)->start(parms)) != NULL)
|
|
|
|
error("Unable to load driver %s(%s). The error was: %s\n", dd->name, dd->longname, err);
|
|
|
|
*var = drv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void showhelp(void)
|
|
|
|
{
|
|
|
|
char buf[4096], *p;
|
|
|
|
const DriverClass *dc = _driver_classes;
|
|
|
|
const DriverDesc *dd;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
p = strecpy(buf,
|
|
|
|
"Command line options:\n"
|
|
|
|
" -v drv = Set video driver (see below)\n"
|
|
|
|
" -s drv = Set sound driver (see below)\n"
|
|
|
|
" -m drv = Set music driver (see below)\n"
|
|
|
|
" -r res = Set resolution (for instance 800x600)\n"
|
|
|
|
" -h = Display this help text\n"
|
|
|
|
" -t date = Set starting date\n"
|
|
|
|
" -d [[fac=]lvl[,...]]= Debug mode\n"
|
|
|
|
" -l lng = Select Language\n"
|
|
|
|
" -e = Start Editor\n"
|
|
|
|
" -g [savegame] = Start new/save game immediately\n"
|
|
|
|
" -G seed = Set random seed\n"
|
|
|
|
" -n [ip#player:port] = Start networkgame\n"
|
|
|
|
" -D = Start dedicated server\n"
|
|
|
|
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
|
|
|
|
" -f = Fork into the background (dedicated only)\n"
|
|
|
|
#endif
|
|
|
|
" -i = Force to use the DOS palette (use this if you see a lot of pink)\n"
|
|
|
|
" -p #player = Player as #player (deprecated) (network only)\n"
|
|
|
|
" -c config_file = Use 'config_file' instead of 'openttd.cfg'\n",
|
|
|
|
lastof(buf)
|
|
|
|
);
|
|
|
|
|
|
|
|
for(i=0; i!=lengthof(_driver_classes); i++,dc++) {
|
|
|
|
p += sprintf(p, "List of %s drivers:\n", dc->name);
|
|
|
|
dd = dc->descs;
|
|
|
|
do {
|
|
|
|
p += sprintf(p, "%10s: %s\n", dd->name, dd->longname);
|
|
|
|
} while ((++dd)->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
ShowInfo(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char *GetDriverParam(const char * const *parm, const char *name)
|
|
|
|
{
|
|
|
|
const char *p;
|
|
|
|
int len = strlen(name);
|
|
|
|
while ((p = *parm++) != NULL) {
|
|
|
|
if (!strncmp(p,name,len)) {
|
|
|
|
if (p[len] == '=') return p + len + 1;
|
|
|
|
if (p[len] == 0) return p + len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GetDriverParamBool(const char * const *parm, const char *name)
|
|
|
|
{
|
|
|
|
const char *p = GetDriverParam(parm, name);
|
|
|
|
return p != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetDriverParamInt(const char * const *parm, const char *name, int def)
|
|
|
|
{
|
|
|
|
const char *p = GetDriverParam(parm, name);
|
|
|
|
return p != NULL ? atoi(p) : def;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
char *opt;
|
|
|
|
int numleft;
|
|
|
|
char **argv;
|
|
|
|
const char *options;
|
|
|
|
char *cont;
|
|
|
|
} MyGetOptData;
|
|
|
|
|
|
|
|
static void MyGetOptInit(MyGetOptData *md, int argc, char **argv, const char *options)
|
|
|
|
{
|
|
|
|
md->cont = NULL;
|
|
|
|
md->numleft = argc;
|
|
|
|
md->argv = argv;
|
|
|
|
md->options = options;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int MyGetOpt(MyGetOptData *md)
|
|
|
|
{
|
|
|
|
char *s,*r,*t;
|
|
|
|
|
|
|
|
if ((s=md->cont) != NULL)
|
|
|
|
goto md_continue_here;
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
if (--md->numleft < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
s = *md->argv++;
|
|
|
|
if (*s == '-') {
|
|
|
|
md_continue_here:;
|
|
|
|
s++;
|
|
|
|
if (*s != 0) {
|
|
|
|
// Found argument, try to locate it in options.
|
|
|
|
if (*s == ':' || (r = strchr(md->options, *s)) == NULL) {
|
|
|
|
// ERROR!
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
if (r[1] == ':') {
|
|
|
|
// Item wants an argument. Check if the argument follows, or if it comes as a separate arg.
|
|
|
|
if (!*(t = s + 1)) {
|
|
|
|
// It comes as a separate arg. Check if out of args?
|
|
|
|
if (--md->numleft < 0 || *(t = *md->argv) == '-') {
|
|
|
|
// Check if item is optional?
|
|
|
|
if (r[2] != ':')
|
|
|
|
return -2;
|
|
|
|
md->numleft++;
|
|
|
|
t = NULL;
|
|
|
|
} else {
|
|
|
|
md->argv++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
md->opt = t;
|
|
|
|
md->cont = NULL;
|
|
|
|
return *s;
|
|
|
|
}
|
|
|
|
md->opt = NULL;
|
|
|
|
md->cont = s;
|
|
|
|
return *s;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// This is currently not supported.
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void ParseResolution(int res[2], char *s)
|
|
|
|
{
|
|
|
|
char *t = strchr(s, 'x');
|
|
|
|
if (t == NULL) {
|
|
|
|
ShowInfoF("Invalid resolution '%s'", s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
res[0] = strtoul(s, NULL, 0);
|
|
|
|
res[1] = strtoul(t + 1, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void InitializeDynamicVariables(void)
|
|
|
|
{
|
|
|
|
/* Dynamic stuff needs to be initialized somewhere... */
|
|
|
|
_station_sort = NULL;
|
|
|
|
_vehicle_sort = NULL;
|
|
|
|
_town_sort = NULL;
|
|
|
|
_industry_sort = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void UnInitializeDynamicVariables(void)
|
|
|
|
{
|
|
|
|
/* Dynamic stuff needs to be free'd somewhere... */
|
|
|
|
CleanPool(&_town_pool);
|
|
|
|
CleanPool(&_industry_pool);
|
|
|
|
CleanPool(&_station_pool);
|
|
|
|
CleanPool(&_vehicle_pool);
|
|
|
|
CleanPool(&_sign_pool);
|
|
|
|
CleanPool(&_order_pool);
|
|
|
|
|
|
|
|
free(_station_sort);
|
|
|
|
free(_vehicle_sort);
|
|
|
|
free(_town_sort);
|
|
|
|
free(_industry_sort);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void UnInitializeGame(void)
|
|
|
|
{
|
|
|
|
UnInitWindowSystem();
|
|
|
|
UnInitNewgrEngines();
|
|
|
|
|
|
|
|
free(_config_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void LoadIntroGame(void)
|
|
|
|
{
|
|
|
|
char filename[256];
|
|
|
|
|
|
|
|
_game_mode = GM_MENU;
|
|
|
|
CLRBITS(_display_opt, DO_TRANS_BUILDINGS); // don't make buildings transparent in intro
|
|
|
|
_opt_ptr = &_opt_newgame;
|
|
|
|
|
|
|
|
GfxLoadSprites();
|
|
|
|
LoadStringWidthTable();
|
|
|
|
|
|
|
|
// Setup main window
|
|
|
|
ResetWindowSystem();
|
|
|
|
SetupColorsAndInitialWindow();
|
|
|
|
|
|
|
|
// Generate a world.
|
|
|
|
sprintf(filename, "%sopntitle.dat", _path.data_dir);
|
|
|
|
if (SaveOrLoad(filename, SL_LOAD) != SL_OK) {
|
|
|
|
#if defined SECOND_DATA_DIR
|
|
|
|
sprintf(filename, "%sopntitle.dat", _path.second_data_dir);
|
|
|
|
if (SaveOrLoad(filename, SL_LOAD) != SL_OK)
|
|
|
|
#endif
|
|
|
|
GenerateWorld(1, 6, 6); // if failed loading, make empty world.
|
|
|
|
}
|
|
|
|
|
|
|
|
_pause = 0;
|
|
|
|
_local_player = 0;
|
|
|
|
MarkWholeScreenDirty();
|
|
|
|
|
|
|
|
// Play main theme
|
|
|
|
if (_music_driver->is_song_playing()) ResetMusic();
|
|
|
|
}
|
|
|
|
|
|
|
|
extern void DedicatedFork(void);
|
|
|
|
extern void CheckExternalFiles(void);
|
|
|
|
|
|
|
|
int ttd_main(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
MyGetOptData mgo;
|
|
|
|
int i;
|
|
|
|
bool network = false;
|
|
|
|
char *network_conn = NULL;
|
|
|
|
char *language = NULL;
|
|
|
|
const char *optformat;
|
|
|
|
char musicdriver[16], sounddriver[16], videodriver[16];
|
|
|
|
int resolution[2] = {0,0};
|
|
|
|
uint startdate = -1;
|
|
|
|
bool dedicated;
|
|
|
|
|
|
|
|
musicdriver[0] = sounddriver[0] = videodriver[0] = 0;
|
|
|
|
|
|
|
|
_game_mode = GM_MENU;
|
|
|
|
_switch_mode = SM_MENU;
|
|
|
|
_switch_mode_errorstr = INVALID_STRING_ID;
|
|
|
|
_dedicated_forks = false;
|
|
|
|
dedicated = false;
|
|
|
|
_config_file = NULL;
|
|
|
|
|
|
|
|
// The last param of the following function means this:
|
|
|
|
// a letter means: it accepts that param (e.g.: -h)
|
|
|
|
// a ':' behind it means: it need a param (e.g.: -m<driver>)
|
|
|
|
// a '::' behind it means: it can optional have a param (e.g.: -d<debug>)
|
|
|
|
#if !defined(__MORPHOS__) && !defined(__AMIGA__) && !defined(WIN32)
|
|
|
|
optformat = "m:s:v:hDfn::l:eit:d::r:g::G:p:c:";
|
|
|
|
#else
|
|
|
|
optformat = "m:s:v:hDn::l:eit:d::r:g::G:p:c:"; // no fork option
|
|
|
|
#endif
|
|
|
|
|
|
|
|
MyGetOptInit(&mgo, argc-1, argv+1, optformat);
|
|
|
|
while ((i = MyGetOpt(&mgo)) != -1) {
|
|
|
|
switch(i) {
|
|
|
|
case 'm': ttd_strlcpy(musicdriver, mgo.opt, sizeof(musicdriver)); break;
|
|
|
|
case 's': ttd_strlcpy(sounddriver, mgo.opt, sizeof(sounddriver)); break;
|
|
|
|
case 'v': ttd_strlcpy(videodriver, mgo.opt, sizeof(videodriver)); break;
|
|
|
|
case 'D': {
|
|
|
|
sprintf(musicdriver,"null");
|
|
|
|
sprintf(sounddriver,"null");
|
|
|
|
sprintf(videodriver,"dedicated");
|
|
|
|
dedicated = true;
|
|
|
|
} break;
|
|
|
|
case 'f': {
|
|
|
|
_dedicated_forks = true;
|
|
|
|
}; break;
|
|
|
|
case 'n': {
|
|
|
|
network = true;
|
|
|
|
if (mgo.opt)
|
|
|
|
// Optional, you can give an IP
|
|
|
|
network_conn = mgo.opt;
|
|
|
|
else
|
|
|
|
network_conn = NULL;
|
|
|
|
} break;
|
|
|
|
case 'r': ParseResolution(resolution, mgo.opt); break;
|
|
|
|
case 'l': {
|
|
|
|
language = mgo.opt;
|
|
|
|
} break;
|
|
|
|
case 't': {
|
|
|
|
startdate = atoi(mgo.opt);
|
|
|
|
} break;
|
|
|
|
case 'd': {
|
|
|
|
#if defined(WIN32)
|
|
|
|
CreateConsole();
|
|
|
|
#endif
|
|
|
|
if (mgo.opt)
|
|
|
|
SetDebugString(mgo.opt);
|
|
|
|
} break;
|
|
|
|
case 'e': _switch_mode = SM_EDITOR; break;
|
|
|
|
case 'i': _use_dos_palette = true; break;
|
|
|
|
case 'g':
|
|
|
|
if (mgo.opt) {
|
|
|
|
strcpy(_file_to_saveload.name, mgo.opt);
|
|
|
|
_switch_mode = SM_LOAD;
|
|
|
|
} else
|
|
|
|
_switch_mode = SM_NEWGAME;
|
|
|
|
break;
|
|
|
|
case 'G':
|
|
|
|
_random_seeds[0][0] = atoi(mgo.opt);
|
|
|
|
break;
|
|
|
|
case 'p': {
|
|
|
|
int i = atoi(mgo.opt);
|
|
|
|
// Play as an other player in network games
|
|
|
|
if (IS_INT_INSIDE(i, 1, MAX_PLAYERS)) _network_playas = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'c':
|
|
|
|
_config_file = strdup(mgo.opt);
|
|
|
|
break;
|
|
|
|
case -2:
|
|
|
|
case 'h':
|
|
|
|
showhelp();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DeterminePaths();
|
|
|
|
CheckExternalFiles();
|
|
|
|
|
|
|
|
#ifdef UNIX
|
|
|
|
// We must fork here, or we'll end up without some resources we need (like sockets)
|
|
|
|
if (_dedicated_forks)
|
|
|
|
DedicatedFork();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
LoadFromConfig();
|
|
|
|
LoadFromHighScore();
|
|
|
|
|
|
|
|
// override config?
|
|
|
|
if (musicdriver[0]) ttd_strlcpy(_ini_musicdriver, musicdriver, sizeof(_ini_musicdriver));
|
|
|
|
if (sounddriver[0]) ttd_strlcpy(_ini_sounddriver, sounddriver, sizeof(_ini_sounddriver));
|
|
|
|
if (videodriver[0]) ttd_strlcpy(_ini_videodriver, videodriver, sizeof(_ini_videodriver));
|
|
|
|
if (resolution[0]) { _cur_resolution[0] = resolution[0]; _cur_resolution[1] = resolution[1]; }
|
|
|
|
if (startdate != (uint)-1) _patches.starting_date = startdate;
|
|
|
|
|
|
|
|
if (_dedicated_forks && !dedicated)
|
|
|
|
_dedicated_forks = false;
|
|
|
|
|
|
|
|
// enumerate language files
|
|
|
|
InitializeLanguagePacks();
|
|
|
|
|
|
|
|
// initialize screenshot formats
|
|
|
|
InitializeScreenshotFormats();
|
|
|
|
|
|
|
|
// initialize airport state machines
|
|
|
|
InitializeAirports();
|
|
|
|
|
|
|
|
/* initialize all variables that are allocated dynamically */
|
|
|
|
InitializeDynamicVariables();
|
|
|
|
|
|
|
|
// Sample catalogue
|
|
|
|
DEBUG(misc, 1) ("Loading sound effects...");
|
|
|
|
_os_version = GetOSVersion();
|
|
|
|
MxInitialize(11025);
|
|
|
|
SoundInitialize("sample.cat");
|
|
|
|
|
|
|
|
// This must be done early, since functions use the InvalidateWindow* calls
|
|
|
|
InitWindowSystem();
|
|
|
|
|
|
|
|
GfxLoadSprites();
|
|
|
|
LoadStringWidthTable();
|
|
|
|
|
|
|
|
DEBUG(misc, 1) ("Loading drivers...");
|
|
|
|
LoadDriver(SOUND_DRIVER, _ini_sounddriver);
|
|
|
|
LoadDriver(MUSIC_DRIVER, _ini_musicdriver);
|
|
|
|
LoadDriver(VIDEO_DRIVER, _ini_videodriver); // load video last, to prevent an empty window while sound and music loads
|
|
|
|
MusicLoop();
|
|
|
|
_savegame_sort_order = 1; // default sorting of savegames is by date, newest first
|
|
|
|
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
// initialize network-core
|
|
|
|
NetworkStartUp();
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
|
|
|
|
_opt_ptr = &_opt_newgame;
|
|
|
|
|
|
|
|
/* XXX - ugly hack, if diff_level is 9, it means we got no setting from the config file */
|
|
|
|
if (_opt_newgame.diff_level == 9)
|
|
|
|
SetDifficultyLevel(0, &_opt_newgame);
|
|
|
|
|
|
|
|
// initialize the ingame console
|
|
|
|
IConsoleInit();
|
|
|
|
InitializeGUI();
|
|
|
|
IConsoleCmdExec("exec scripts/autoexec.scr 0");
|
|
|
|
|
|
|
|
InitPlayerRandoms();
|
|
|
|
|
|
|
|
GenerateWorld(1, 6, 6); // Make the viewport initialization happy
|
|
|
|
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
if ((network) && (_network_available)) {
|
|
|
|
if (network_conn != NULL) {
|
|
|
|
const char *port = NULL;
|
|
|
|
const char *player = NULL;
|
|
|
|
uint16 rport;
|
|
|
|
|
|
|
|
rport = NETWORK_DEFAULT_PORT;
|
|
|
|
|
|
|
|
ParseConnectionString(&player, &port, network_conn);
|
|
|
|
|
|
|
|
if (player != NULL) _network_playas = atoi(player);
|
|
|
|
if (port != NULL) rport = atoi(port);
|
|
|
|
|
|
|
|
LoadIntroGame();
|
|
|
|
_switch_mode = SM_NONE;
|
|
|
|
NetworkClientConnectGame(network_conn, rport);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
|
|
|
|
while (_video_driver->main_loop() == ML_SWITCHDRIVER) {}
|
|
|
|
|
|
|
|
IConsoleFree();
|
|
|
|
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
if (_network_available) {
|
|
|
|
// Shut down the network and close any open connections
|
|
|
|
NetworkDisconnect();
|
|
|
|
NetworkUDPClose();
|
|
|
|
NetworkShutDown();
|
|
|
|
}
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
|
|
|
|
_video_driver->stop();
|
|
|
|
_music_driver->stop();
|
|
|
|
_sound_driver->stop();
|
|
|
|
|
|
|
|
SaveToConfig();
|
|
|
|
SaveToHighScore();
|
|
|
|
|
|
|
|
// uninitialize airport state machines
|
|
|
|
UnInitializeAirports();
|
|
|
|
|
|
|
|
/* uninitialize variables that are allocated dynamic */
|
|
|
|
UnInitializeDynamicVariables();
|
|
|
|
|
|
|
|
/* Close all and any open filehandles */
|
|
|
|
FioCloseAll();
|
|
|
|
UnInitializeGame();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ShowScreenshotResult(bool b)
|
|
|
|
{
|
|
|
|
if (b) {
|
|
|
|
SetDParam(0, STR_SPEC_SCREENSHOT_NAME);
|
|
|
|
ShowErrorMessage(INVALID_STRING_ID, STR_031B_SCREENSHOT_SUCCESSFULLY, 0, 0);
|
|
|
|
} else {
|
|
|
|
ShowErrorMessage(INVALID_STRING_ID, STR_031C_SCREENSHOT_FAILED, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void MakeNewGame(void)
|
|
|
|
{
|
|
|
|
_game_mode = GM_NORMAL;
|
|
|
|
|
|
|
|
// Copy in game options
|
|
|
|
_opt_ptr = &_opt;
|
|
|
|
memcpy(_opt_ptr, &_opt_newgame, sizeof(GameOptions));
|
|
|
|
|
|
|
|
GfxLoadSprites();
|
|
|
|
|
|
|
|
// Reinitialize windows
|
|
|
|
ResetWindowSystem();
|
|
|
|
LoadStringWidthTable();
|
|
|
|
|
|
|
|
SetupColorsAndInitialWindow();
|
|
|
|
|
|
|
|
// Randomize world
|
|
|
|
GenerateWorld(0, _patches.map_x, _patches.map_y);
|
|
|
|
|
|
|
|
// In a dedicated server, the server does not play
|
|
|
|
if (_network_dedicated) {
|
|
|
|
_local_player = OWNER_SPECTATOR;
|
|
|
|
} else {
|
|
|
|
// Create a single player
|
|
|
|
DoStartupNewPlayer(false);
|
|
|
|
|
|
|
|
_local_player = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
MarkWholeScreenDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void MakeNewEditorWorld(void)
|
|
|
|
{
|
|
|
|
_game_mode = GM_EDITOR;
|
|
|
|
|
|
|
|
// Copy in game options
|
|
|
|
_opt_ptr = &_opt;
|
|
|
|
memcpy(_opt_ptr, &_opt_newgame, sizeof(GameOptions));
|
|
|
|
|
|
|
|
GfxLoadSprites();
|
|
|
|
|
|
|
|
// Re-init the windowing system
|
|
|
|
ResetWindowSystem();
|
|
|
|
|
|
|
|
// Create toolbars
|
|
|
|
SetupColorsAndInitialWindow();
|
|
|
|
|
|
|
|
// Startup the game system
|
|
|
|
GenerateWorld(1, _patches.map_x, _patches.map_y);
|
|
|
|
|
|
|
|
_local_player = OWNER_NONE;
|
|
|
|
MarkWholeScreenDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void StartupPlayers(void);
|
|
|
|
void StartupDisasters(void);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start Scenario starts a new game based on a scenario.
|
|
|
|
* Eg 'New Game' --> select a preset scenario
|
|
|
|
* This starts a scenario based on your current difficulty settings just
|
|
|
|
* fix the landscape as that can be different from what is selected in the intro
|
|
|
|
*/
|
|
|
|
static void StartScenario(void)
|
|
|
|
{
|
|
|
|
_game_mode = GM_NORMAL;
|
|
|
|
|
|
|
|
// invalid type
|
|
|
|
if (_file_to_saveload.mode == SL_INVALID) {
|
|
|
|
printf("Savegame is obsolete or invalid format: %s\n", _file_to_saveload.name);
|
|
|
|
ShowErrorMessage(_error_message, STR_4009_GAME_LOAD_FAILED, 0, 0);
|
|
|
|
_game_mode = GM_MENU;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
GfxLoadSprites();
|
|
|
|
|
|
|
|
// Reinitialize windows
|
|
|
|
ResetWindowSystem();
|
|
|
|
LoadStringWidthTable();
|
|
|
|
|
|
|
|
SetupColorsAndInitialWindow();
|
|
|
|
|
|
|
|
// Load game
|
|
|
|
if (SaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode) != SL_OK) {
|
|
|
|
LoadIntroGame();
|
|
|
|
ShowErrorMessage(_error_message, STR_4009_GAME_LOAD_FAILED, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
byte landscape = _opt.landscape; // backup loaded landscape;
|
|
|
|
_opt_ptr = &_opt;
|
|
|
|
memcpy(_opt_ptr, &_opt_newgame, sizeof(GameOptions));
|
|
|
|
_opt_ptr->landscape = landscape;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inititalize data
|
|
|
|
StartupPlayers();
|
|
|
|
StartupEngines();
|
|
|
|
StartupDisasters();
|
|
|
|
|
|
|
|
_local_player = 0;
|
|
|
|
|
|
|
|
MarkWholeScreenDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SafeSaveOrLoad(const char *filename, int mode, int newgm)
|
|
|
|
{
|
|
|
|
byte ogm = _game_mode;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
_game_mode = newgm;
|
|
|
|
r = SaveOrLoad(filename, mode);
|
|
|
|
if (r == SL_REINIT) {
|
|
|
|
if (ogm == GM_MENU)
|
|
|
|
LoadIntroGame();
|
|
|
|
else if (ogm == GM_EDITOR)
|
|
|
|
MakeNewEditorWorld();
|
|
|
|
else
|
|
|
|
MakeNewGame();
|
|
|
|
return false;
|
|
|
|
} else if (r != SL_OK) {
|
|
|
|
_game_mode = ogm;
|
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SwitchMode(int new_mode)
|
|
|
|
{
|
|
|
|
_in_state_game_loop = true;
|
|
|
|
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
// If we are saving something, the network stays in his current state
|
|
|
|
if (new_mode != SM_SAVE) {
|
|
|
|
// If the network is active, make it not-active
|
|
|
|
if (_networking) {
|
|
|
|
if (_network_server && (new_mode == SM_LOAD || new_mode == SM_NEWGAME)) {
|
|
|
|
NetworkReboot();
|
|
|
|
NetworkUDPClose();
|
|
|
|
} else {
|
|
|
|
NetworkDisconnect();
|
|
|
|
NetworkUDPClose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are a server, we restart the server
|
|
|
|
if (_is_network_server) {
|
|
|
|
// But not if we are going to the menu
|
|
|
|
if (new_mode != SM_MENU) {
|
|
|
|
NetworkServerStart();
|
|
|
|
} else {
|
|
|
|
// This client no longer wants to be a network-server
|
|
|
|
_is_network_server = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
|
|
|
|
switch (new_mode) {
|
|
|
|
case SM_EDITOR: /* Switch to scenario editor */
|
|
|
|
MakeNewEditorWorld();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SM_NEWGAME: /* New Game --> 'Random game' */
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
if (_network_server)
|
|
|
|
snprintf(_network_game_info.map_name, 40, "Random");
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
MakeNewGame();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SM_START_SCENARIO: /* New Game --> Choose one of the preset scenarios */
|
|
|
|
StartScenario();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SM_LOAD: { /* Load game, Play Scenario */
|
|
|
|
_opt_ptr = &_opt;
|
|
|
|
|
|
|
|
_error_message = INVALID_STRING_ID;
|
|
|
|
if (!SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_NORMAL)) {
|
|
|
|
LoadIntroGame();
|
|
|
|
ShowErrorMessage(_error_message, STR_4009_GAME_LOAD_FAILED, 0, 0);
|
|
|
|
} else {
|
|
|
|
_local_player = 0;
|
|
|
|
DoCommandP(0, 0, 0, NULL, CMD_PAUSE); // decrease pause counter (was increased from opening load dialog)
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
if (_network_server)
|
|
|
|
snprintf(_network_game_info.map_name, 40, "Loaded game");
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SM_LOAD_SCENARIO: { /* Load scenario from scenario editor */
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_EDITOR)) {
|
|
|
|
_opt_ptr = &_opt;
|
|
|
|
|
|
|
|
_local_player = OWNER_NONE;
|
|
|
|
_generating_world = true;
|
|
|
|
// delete all players.
|
|
|
|
for (i = 0; i != MAX_PLAYERS; i++) {
|
|
|
|
ChangeOwnershipOfPlayerItems(i, 0xff);
|
|
|
|
_players[i].is_active = false;
|
|
|
|
}
|
|
|
|
_generating_world = false;
|
|
|
|
// delete all stations owned by a player
|
|
|
|
DeleteAllPlayerStations();
|
|
|
|
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
if (_network_server)
|
|
|
|
snprintf(_network_game_info.map_name, 40, "Loaded scenario");
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
} else
|
|
|
|
ShowErrorMessage(INVALID_STRING_ID, STR_4009_GAME_LOAD_FAILED, 0, 0);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case SM_MENU: /* Switch to game intro menu */
|
|
|
|
LoadIntroGame();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SM_SAVE: /* Save game */
|
|
|
|
if (SaveOrLoad(_file_to_saveload.name, SL_SAVE) != SL_OK)
|
|
|
|
ShowErrorMessage(INVALID_STRING_ID, STR_4007_GAME_SAVE_FAILED, 0, 0);
|
|
|
|
else
|
|
|
|
DeleteWindowById(WC_SAVELOAD, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SM_GENRANDLAND: /* Generate random land within scenario editor */
|
|
|
|
GenerateWorld(2, _patches.map_x, _patches.map_y);
|
|
|
|
// XXX: set date
|
|
|
|
_local_player = OWNER_NONE;
|
|
|
|
MarkWholeScreenDirty();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_switch_mode_errorstr != INVALID_STRING_ID)
|
|
|
|
ShowErrorMessage(INVALID_STRING_ID,_switch_mode_errorstr,0,0);
|
|
|
|
|
|
|
|
_in_state_game_loop = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// State controlling game loop.
|
|
|
|
// The state must not be changed from anywhere
|
|
|
|
// but here.
|
|
|
|
// That check is enforced in DoCommand.
|
|
|
|
void StateGameLoop(void)
|
|
|
|
{
|
|
|
|
// dont execute the state loop during pause
|
|
|
|
if (_pause) return;
|
|
|
|
|
|
|
|
_in_state_game_loop = true;
|
|
|
|
// _frame_counter is increased somewhere else when in network-mode
|
|
|
|
// Sidenote: _frame_counter is ONLY used for _savedump in non-MP-games
|
|
|
|
// Should that not be deleted? If so, the next 2 lines can also be deleted
|
|
|
|
if (!_networking)
|
|
|
|
_frame_counter++;
|
|
|
|
|
|
|
|
if (_savedump_path[0] && (uint)_frame_counter >= _savedump_first && (uint)(_frame_counter -_savedump_first) % _savedump_freq == 0 ) {
|
|
|
|
char buf[100];
|
|
|
|
sprintf(buf, "%s%.5d.sav", _savedump_path, _frame_counter);
|
|
|
|
SaveOrLoad(buf, SL_SAVE);
|
|
|
|
if ((uint)_frame_counter >= _savedump_last) exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_game_mode == GM_EDITOR) {
|
|
|
|
RunTileLoop();
|
|
|
|
CallVehicleTicks();
|
|
|
|
CallLandscapeTick();
|
|
|
|
CallWindowTickEvent();
|
|
|
|
NewsLoop();
|
|
|
|
} else {
|
|
|
|
// All these actions has to be done from OWNER_NONE
|
|
|
|
// for multiplayer compatibility
|
|
|
|
uint p = _current_player;
|
|
|
|
_current_player = OWNER_NONE;
|
|
|
|
|
|
|
|
AnimateAnimatedTiles();
|
|
|
|
IncreaseDate();
|
|
|
|
RunTileLoop();
|
|
|
|
CallVehicleTicks();
|
|
|
|
CallLandscapeTick();
|
|
|
|
|
|
|
|
// To bad the AI does not work in multiplayer, because states are not saved
|
|
|
|
// perfectly
|
|
|
|
if (!disable_computer && !_networking)
|
|
|
|
RunOtherPlayersLoop();
|
|
|
|
|
|
|
|
CallWindowTickEvent();
|
|
|
|
NewsLoop();
|
|
|
|
_current_player = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
_in_state_game_loop = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void DoAutosave(void)
|
|
|
|
{
|
|
|
|
char buf[200];
|
|
|
|
|
|
|
|
if (_patches.keep_all_autosave && _local_player != OWNER_SPECTATOR) {
|
|
|
|
const Player *p = DEREF_PLAYER(_local_player);
|
|
|
|
char *s;
|
|
|
|
sprintf(buf, "%s%s", _path.autosave_dir, PATHSEP);
|
|
|
|
|
|
|
|
SetDParam(0, p->name_1);
|
|
|
|
SetDParam(1, p->name_2);
|
|
|
|
SetDParam(2, _date);
|
|
|
|
s = (char*)GetString(buf + strlen(_path.autosave_dir) + strlen(PATHSEP), STR_4004);
|
|
|
|
strcpy(s, ".sav");
|
|
|
|
} else { /* Save a maximum of 15 autosaves */
|
|
|
|
int n = _autosave_ctr;
|
|
|
|
_autosave_ctr = (_autosave_ctr + 1) & 15;
|
|
|
|
sprintf(buf, "%s%sautosave%d.sav", _path.autosave_dir, PATHSEP, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG(misc, 2) ("Autosaving to %s", buf);
|
|
|
|
if (SaveOrLoad(buf, SL_SAVE) != SL_OK)
|
|
|
|
ShowErrorMessage(INVALID_STRING_ID, STR_AUTOSAVE_FAILED, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ScrollMainViewport(int x, int y)
|
|
|
|
{
|
|
|
|
if (_game_mode != GM_MENU) {
|
|
|
|
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
|
|
|
|
assert(w);
|
|
|
|
|
|
|
|
WP(w,vp_d).scrollpos_x += x << w->viewport->zoom;
|
|
|
|
WP(w,vp_d).scrollpos_y += y << w->viewport->zoom;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const int8 scrollamt[16][2] = {
|
|
|
|
{ 0, 0},
|
|
|
|
{-2, 0}, // 1:left
|
|
|
|
{ 0,-2}, // 2:up
|
|
|
|
{-2,-1}, // 3:left + up
|
|
|
|
{ 2, 0}, // 4:right
|
|
|
|
{ 0, 0}, // 5:left + right
|
|
|
|
{ 2,-1}, // 6:right + up
|
|
|
|
{ 0,-2}, // 7:left + right + up = up
|
|
|
|
{ 0 ,2}, // 8:down
|
|
|
|
{-2 ,1}, // 9:down+left
|
|
|
|
{ 0, 0}, // 10:impossible
|
|
|
|
{-2, 0}, // 11:left + up + down = left
|
|
|
|
{ 2, 1}, // 12:down+right
|
|
|
|
{ 0, 2}, // 13:left + right + down = down
|
|
|
|
{ 0,-2}, // 14:left + right + up = up
|
|
|
|
{ 0, 0}, // 15:impossible
|
|
|
|
};
|
|
|
|
|
|
|
|
static void HandleKeyScrolling(void)
|
|
|
|
{
|
|
|
|
if (_dirkeys && !_no_scroll) {
|
|
|
|
int factor = _shift_pressed ? 50 : 10;
|
|
|
|
ScrollMainViewport(scrollamt[_dirkeys][0] * factor, scrollamt[_dirkeys][1] * factor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GameLoop(void)
|
|
|
|
{
|
|
|
|
int m;
|
|
|
|
|
|
|
|
// autosave game?
|
|
|
|
if (_do_autosave) {
|
|
|
|
_do_autosave = false;
|
|
|
|
DoAutosave();
|
|
|
|
RedrawAutosave();
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle scrolling of the main window
|
|
|
|
if (_dirkeys) HandleKeyScrolling();
|
|
|
|
|
|
|
|
// make a screenshot?
|
|
|
|
if ((m=_make_screenshot) != 0) {
|
|
|
|
_make_screenshot = 0;
|
|
|
|
switch(m) {
|
|
|
|
case 1: // make small screenshot
|
|
|
|
UndrawMouseCursor();
|
|
|
|
ShowScreenshotResult(MakeScreenshot());
|
|
|
|
break;
|
|
|
|
case 2: // make large screenshot
|
|
|
|
ShowScreenshotResult(MakeWorldScreenshot(-(int)MapMaxX() * 32, 0, MapMaxX() * 64, MapSizeY() * 32, 0));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// switch game mode?
|
|
|
|
if ((m=_switch_mode) != SM_NONE) {
|
|
|
|
_switch_mode = SM_NONE;
|
|
|
|
SwitchMode(m);
|
|
|
|
}
|
|
|
|
|
|
|
|
IncreaseSpriteLRU();
|
|
|
|
InteractiveRandom();
|
|
|
|
|
|
|
|
if (_scroller_click_timeout > 3)
|
|
|
|
_scroller_click_timeout -= 3;
|
|
|
|
else
|
|
|
|
_scroller_click_timeout = 0;
|
|
|
|
|
|
|
|
_caret_timer += 3;
|
|
|
|
_timer_counter+=8;
|
|
|
|
CursorTick();
|
|
|
|
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
// Check for UDP stuff
|
|
|
|
NetworkUDPGameLoop();
|
|
|
|
|
|
|
|
if (_networking) {
|
|
|
|
// Multiplayer
|
|
|
|
NetworkGameLoop();
|
|
|
|
} else {
|
|
|
|
if (_network_reconnect > 0 && --_network_reconnect == 0) {
|
|
|
|
// This means that we want to reconnect to the last host
|
|
|
|
// We do this here, because it means that the network is really closed
|
|
|
|
NetworkClientConnectGame(_network_last_host, _network_last_port);
|
|
|
|
}
|
|
|
|
// Singleplayer
|
|
|
|
StateGameLoop();
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
StateGameLoop();
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
|
|
|
|
if (!_pause && _display_opt&DO_FULL_ANIMATION)
|
|
|
|
DoPaletteAnimations();
|
|
|
|
|
|
|
|
if (!_pause || _cheats.build_in_pause.value)
|
|
|
|
MoveAllTextEffects();
|
|
|
|
|
|
|
|
MouseLoop();
|
|
|
|
|
|
|
|
if (_game_mode != GM_MENU)
|
|
|
|
MusicLoop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void BeforeSaveGame(void)
|
|
|
|
{
|
|
|
|
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
|
|
|
|
|
|
|
|
if (w != NULL) {
|
|
|
|
_saved_scrollpos_x = WP(w,vp_d).scrollpos_x;
|
|
|
|
_saved_scrollpos_y = WP(w,vp_d).scrollpos_y;
|
|
|
|
_saved_scrollpos_zoom = w->viewport->zoom;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ConvertTownOwner(void)
|
|
|
|
{
|
|
|
|
uint tile;
|
|
|
|
|
|
|
|
for (tile = 0; tile != MapSize(); tile++) {
|
|
|
|
if (IsTileType(tile, MP_STREET)) {
|
|
|
|
if ((_map5[tile] & 0xF0) == 0x10 && _map3_lo[tile] & 0x80)
|
|
|
|
_map3_lo[tile] = OWNER_TOWN;
|
|
|
|
|
|
|
|
if (_map_owner[tile] & 0x80)
|
|
|
|
_map_owner[tile] = OWNER_TOWN;
|
|
|
|
} else if (IsTileType(tile, MP_TUNNELBRIDGE)) {
|
|
|
|
if (_map_owner[tile] & 0x80)
|
|
|
|
_map_owner[tile] = OWNER_TOWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// before savegame version 4, the name of the company determined if it existed
|
|
|
|
static void CheckIsPlayerActive(void)
|
|
|
|
{
|
|
|
|
Player *p;
|
|
|
|
FOR_ALL_PLAYERS(p) {
|
|
|
|
if (p->name_1 != 0) {
|
|
|
|
p->is_active = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// since savegame version 4.1, exclusive transport rights are stored at towns
|
|
|
|
static void UpdateExclusiveRights(void)
|
|
|
|
{
|
|
|
|
Town *t;
|
|
|
|
FOR_ALL_TOWNS(t) if (t->xy != 0) {
|
|
|
|
t->exclusivity=(byte)-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME old exclusive rights status is not being imported (stored in s->blocked_months_obsolete)
|
|
|
|
could be implemented this way:
|
|
|
|
1.) Go through all stations
|
|
|
|
Build an array town_blocked[ town_id ][ player_id ]
|
|
|
|
that stores if at least one station in that town is blocked for a player
|
|
|
|
2.) Go through that array, if you find a town that is not blocked for
|
|
|
|
one player, but for all others, then give him exclusivity.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
const byte convert_currency[] = {
|
|
|
|
0, 1, 12, 8, 3,
|
|
|
|
10, 14, 19, 4, 5,
|
|
|
|
9, 11, 13, 6, 17,
|
|
|
|
16, 22, 21, 7, 15,
|
|
|
|
18, 2, 20, };
|
|
|
|
|
|
|
|
// since savegame version 4.2 the currencies are arranged differently
|
|
|
|
static void UpdateCurrencies(void)
|
|
|
|
{
|
|
|
|
_opt.currency = convert_currency[_opt.currency];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Up to revision 1413 the invisible tiles at the southern border have not been
|
|
|
|
* MP_VOID, even though they should have. This is fixed by this function
|
|
|
|
*/
|
|
|
|
static void UpdateVoidTiles(void)
|
|
|
|
{
|
|
|
|
uint i;
|
|
|
|
|
|
|
|
for (i = 0; i < MapMaxY(); ++i)
|
|
|
|
SetTileType(i * MapSizeX() + MapMaxX(), MP_VOID);
|
|
|
|
for (i = 0; i < MapSizeX(); ++i)
|
|
|
|
SetTileType(MapSizeX() * MapMaxY() + i, MP_VOID);
|
|
|
|
}
|
|
|
|
|
|
|
|
// since savegame version 6.0 each sign has an "owner", signs without owner (from old games are set to 255)
|
|
|
|
static void UpdateSignOwner(void)
|
|
|
|
{
|
|
|
|
SignStruct *ss;
|
|
|
|
FOR_ALL_SIGNS(ss) {
|
|
|
|
ss->owner = OWNER_NONE; // no owner
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extern void UpdateOldAircraft( void );
|
|
|
|
extern void UpdateOilRig( void );
|
|
|
|
|
|
|
|
bool AfterLoadGame(uint version)
|
|
|
|
{
|
|
|
|
Window *w;
|
|
|
|
ViewPort *vp;
|
|
|
|
|
|
|
|
// in version 2.1 of the savegame, town owner was unified.
|
|
|
|
if (version <= 0x200) {
|
|
|
|
ConvertTownOwner();
|
|
|
|
}
|
|
|
|
|
|
|
|
// from version 4.1 of the savegame, exclusive rights are stored at towns
|
|
|
|
if (version <= 0x400) {
|
|
|
|
UpdateExclusiveRights();
|
|
|
|
}
|
|
|
|
|
|
|
|
// from version 4.2 of the savegame, currencies are in a different order
|
|
|
|
if (version <= 0x401) {
|
|
|
|
UpdateCurrencies();
|
|
|
|
}
|
|
|
|
|
|
|
|
// from version 6.0 of the savegame, signs have an "owner"
|
|
|
|
if (version <= 0x600) {
|
|
|
|
UpdateSignOwner();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* In old version there seems to be a problem that water is owned by
|
|
|
|
OWNER_NONE, not OWNER_WATER.. I can't replicate it for the current
|
|
|
|
(0x402) version, so I just check when versions are older, and then
|
|
|
|
walk through the whole map.. */
|
|
|
|
if (version <= 0x402) {
|
|
|
|
TileIndex tile = TILE_XY(0,0);
|
|
|
|
uint w = MapSizeX();
|
|
|
|
uint h = MapSizeY();
|
|
|
|
|
|
|
|
BEGIN_TILE_LOOP(tile_cur, w, h, tile)
|
|
|
|
if (IsTileType(tile_cur, MP_WATER) && _map_owner[tile_cur] >= MAX_PLAYERS)
|
|
|
|
_map_owner[tile_cur] = OWNER_WATER;
|
|
|
|
END_TILE_LOOP(tile_cur, w, h, tile)
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert road side to my format.
|
|
|
|
if (_opt.road_side) _opt.road_side = 1;
|
|
|
|
|
|
|
|
// Load the sprites
|
|
|
|
GfxLoadSprites();
|
|
|
|
|
|
|
|
// Update current year
|
|
|
|
SetDate(_date);
|
|
|
|
|
|
|
|
// reinit the landscape variables (landscape might have changed)
|
|
|
|
InitializeLandscapeVariables(true);
|
|
|
|
|
|
|
|
// Update all vehicles
|
|
|
|
AfterLoadVehicles();
|
|
|
|
// in version 2.2 of the savegame, we have new airports
|
|
|
|
if (version <= 0x201) {
|
|
|
|
UpdateOldAircraft();
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateAllStationVirtCoord();
|
|
|
|
|
|
|
|
// Setup town coords
|
|
|
|
AfterLoadTown();
|
|
|
|
UpdateAllSignVirtCoords();
|
|
|
|
|
|
|
|
// make sure there is a town in the game
|
|
|
|
if (_game_mode == GM_NORMAL && !ClosestTownFromTile(0, (uint)-1))
|
|
|
|
{
|
|
|
|
_error_message = STR_NO_TOWN_IN_SCENARIO;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize windows
|
|
|
|
ResetWindowSystem();
|
|
|
|
SetupColorsAndInitialWindow();
|
|
|
|
|
|
|
|
w = FindWindowById(WC_MAIN_WINDOW, 0);
|
|
|
|
|
|
|
|
WP(w,vp_d).scrollpos_x = _saved_scrollpos_x;
|
|
|
|
WP(w,vp_d).scrollpos_y = _saved_scrollpos_y;
|
|
|
|
|
|
|
|
vp = w->viewport;
|
|
|
|
vp->zoom = _saved_scrollpos_zoom;
|
|
|
|
vp->virtual_width = vp->width << vp->zoom;
|
|
|
|
vp->virtual_height = vp->height << vp->zoom;
|
|
|
|
|
|
|
|
|
|
|
|
// in version 4.0 of the savegame, is_active was introduced to determine
|
|
|
|
// if a player does exist, rather then checking name_1
|
|
|
|
if (version <= 0x400) {
|
|
|
|
CheckIsPlayerActive();
|
|
|
|
}
|
|
|
|
|
|
|
|
// the void tiles on the southern border used to belong to a wrong class.
|
|
|
|
if (version <= 0x402)
|
|
|
|
UpdateVoidTiles();
|
|
|
|
|
|
|
|
// If Load Scenario / New (Scenario) Game is used,
|
|
|
|
// a player does not exist yet. So create one here.
|
|
|
|
// 1 exeption: network-games. Those can have 0 players
|
|
|
|
// But this exeption is not true for network_servers!
|
|
|
|
if (!_players[0].is_active && (!_networking || (_networking && _network_server)))
|
|
|
|
DoStartupNewPlayer(false);
|
|
|
|
|
|
|
|
DoZoomInOutWindow(ZOOM_NONE, w); // update button status
|
|
|
|
MarkWholeScreenDirty();
|
|
|
|
|
|
|
|
//In 5.1, Oilrigs have been moved (again)
|
|
|
|
if (version <= 0x500) {
|
|
|
|
UpdateOilRig();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (version <= 0x600) {
|
|
|
|
BEGIN_TILE_LOOP(tile, MapSizeX(), MapSizeY(), 0) {
|
|
|
|
if (IsTileType(tile, MP_HOUSE)) {
|
|
|
|
_map3_hi[tile] = _map2[tile];
|
|
|
|
//XXX magic
|
|
|
|
SetTileType(tile, MP_VOID);
|
|
|
|
_map2[tile] = ClosestTownFromTile(tile,(uint)-1)->index;
|
|
|
|
SetTileType(tile, MP_HOUSE);
|
|
|
|
} else if (IsTileType(tile, MP_STREET)) {
|
|
|
|
//XXX magic
|
|
|
|
SetTileType(tile, MP_VOID);
|
|
|
|
_map3_hi[tile] |= (_map2[tile] << 4);
|
|
|
|
if ( _map_owner[tile] == OWNER_TOWN)
|
|
|
|
_map2[tile] = ClosestTownFromTile(tile,(uint)-1)->index;
|
|
|
|
else
|
|
|
|
_map2[tile] = 0;
|
|
|
|
SetTileType(tile, MP_STREET);
|
|
|
|
}
|
|
|
|
} END_TILE_LOOP(tile, MapSizeX(), MapSizeY(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (version < 0x900) {
|
|
|
|
Town *t;
|
|
|
|
FOR_ALL_TOWNS(t) {
|
|
|
|
UpdateTownMaxPass(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DebugProc(int i)
|
|
|
|
{
|
|
|
|
switch(i) {
|
|
|
|
case 0:
|
|
|
|
*(byte*)0 = 0;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
/* Server can not cheat in advertise mode either! */
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
if (!_networking || !_network_server || !_network_advertise)
|
|
|
|
#endif /* ENABLE_NETWORK */
|
|
|
|
DoCommandP(0, -10000000, 0, NULL, CMD_MONEY_CHEAT);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
UpdateAllStationVirtCoord();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|