Merge branch 'master' into jgrpp

# Conflicts:
#	src/genworld_gui.cpp
#	src/gfx.cpp
#	src/lang/korean.txt
#	src/linkgraph/linkgraph_gui.cpp
#	src/linkgraph/linkgraph_gui.h
#	src/music.cpp
#	src/table/settings.ini
#	src/town_cmd.cpp
#	src/train_cmd.cpp
This commit is contained in:
Jonathan G Rennison
2018-06-25 18:57:48 +01:00
34 changed files with 887 additions and 591 deletions

View File

@@ -115,7 +115,7 @@ void SQVM::Raise_ParamTypeError(SQInteger nparam,SQInteger typemask,SQInteger ty
SQInteger found = 0;
for(SQInteger i=0; i<16; i++)
{
SQInteger mask = 0x00000001 << i;
SQInteger mask = 0x00000001LL << i;
if(typemask & (mask)) {
if(found>0) StringCat(exptypes,SQString::Create(_ss(this), "|", -1), exptypes);
found ++;

View File

@@ -26,6 +26,7 @@ struct ContentInfo;
struct MD5File {
/** The result of a checksum check */
enum ChecksumResult {
CR_UNKNOWN, ///< The file has not been checked yet
CR_MATCH, ///< The file did exist and the md5 checksum did match
CR_MISMATCH, ///< The file did exist, just the md5 checksum did not match
CR_NO_FILE, ///< The file did not exist
@@ -34,6 +35,7 @@ struct MD5File {
const char *filename; ///< filename
uint8 hash[16]; ///< md5 sum of the file
const char *missing_warning; ///< warning when this file is missing
ChecksumResult check_result; ///< cached result of md5 check
ChecksumResult CheckMD5(Subdirectory subdir, size_t max_size) const;
};
@@ -301,6 +303,9 @@ struct MusicSongInfo {
const char *filename; ///< file on disk containing song (when used in MusicSet class, this pointer is owned by MD5File object for the file)
MusicTrackType filetype; ///< decoder required for song file
int cat_index; ///< entry index in CAT file, for filetype==MTT_MPSMIDI
bool loop; ///< song should play in a tight loop if possible, never ending
int override_start; ///< MIDI ticks to skip over in beginning
int override_end; ///< MIDI tick to end the song at (0 if no override)
};
/** All data of a music set. */

View File

@@ -129,7 +129,11 @@ bool BaseSet<T, Tnum_files, Tsearch_in_tars>::FillSetDetails(IniFile *ini, const
file->missing_warning = stredup(item->value);
}
switch (T::CheckMD5(file, BASESET_DIR)) {
file->check_result = T::CheckMD5(file, BASESET_DIR);
switch (file->check_result) {
case MD5File::CR_UNKNOWN:
break;
case MD5File::CR_MATCH:
this->valid_files++;
this->found_files++;

View File

@@ -207,6 +207,7 @@ void UpdateFontHeightCache()
class FreeTypeFontCache : public FontCache {
private:
FT_Face face; ///< The font face associated with this font.
int req_size; ///< Requested font size.
typedef SmallMap<uint32, SmallPair<size_t, const void*> > FontTable; ///< Table with font table cache
FontTable font_tables; ///< Cached font tables.
@@ -235,6 +236,7 @@ private:
GlyphEntry *GetGlyphPtr(GlyphID key);
void SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate = false);
void SetFontSize(FontSize fs, FT_Face face, int pixels);
public:
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
@@ -271,20 +273,26 @@ static const byte SHADOW_COLOUR = 2;
* @param face The font that has to be loaded.
* @param pixels The number of pixels this font should be high.
*/
FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : FontCache(fs), face(face), glyph_to_sprite(NULL)
FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : FontCache(fs), face(face), req_size(pixels), glyph_to_sprite(NULL)
{
assert(face != NULL);
this->SetFontSize(fs, face, pixels);
}
void FreeTypeFontCache::SetFontSize(FontSize fs, FT_Face face, int pixels)
{
if (pixels == 0) {
/* Try to determine a good height based on the minimal height recommended by the font. */
pixels = _default_font_height[this->fs];
int scaled_height = ScaleGUITrad(_default_font_height[this->fs]);
pixels = scaled_height;
TT_Header *head = (TT_Header *)FT_Get_Sfnt_Table(this->face, ft_sfnt_head);
if (head != NULL) {
/* Font height is minimum height plus the difference between the default
* height for this font size and the small size. */
int diff = _default_font_height[this->fs] - _default_font_height[FS_SMALL];
pixels = Clamp(min(head->Lowest_Rec_PPEM, 20) + diff, _default_font_height[this->fs], MAX_FONT_SIZE);
int diff = scaled_height - ScaleGUITrad(_default_font_height[FS_SMALL]);
pixels = Clamp(min(head->Lowest_Rec_PPEM, 20) + diff, scaled_height, MAX_FONT_SIZE);
}
}
@@ -401,6 +409,7 @@ found_face:
FreeTypeFontCache::~FreeTypeFontCache()
{
FT_Done_Face(this->face);
this->face = NULL;
this->ClearFontCache();
for (FontTable::iterator iter = this->font_tables.Begin(); iter != this->font_tables.End(); iter++) {
@@ -430,6 +439,9 @@ void FreeTypeFontCache::ClearFontCache()
this->glyph_to_sprite = NULL;
Layouter::ResetFontCache(this->fs);
/* GUI scaling might have changed, determine font size anew if it was automatically selected. */
if (this->face != NULL && this->req_size == 0) this->SetFontSize(this->fs, this->face, this->req_size);
}
FreeTypeFontCache::GlyphEntry *FreeTypeFontCache::GetGlyphPtr(GlyphID key)

View File

@@ -314,7 +314,7 @@ static DropDownList *BuildMapsizeDropDown(int other_dimension)
for (uint i = MIN_MAP_SIZE_BITS; i <= MAX_MAP_SIZE_BITS; i++) {
DropDownListParamStringItem *item = new DropDownListParamStringItem((i + other_dimension > MAX_MAP_TILES_BITS) ? STR_RED_INT : STR_JUST_INT, i, false);
item->SetParam(0, 1 << i);
item->SetParam(0, 1LL << i);
*list->Append() = item;
}
@@ -372,8 +372,8 @@ struct GenerateLandscapeWindow : public Window {
{
switch (widget) {
case WID_GL_START_DATE_TEXT: SetDParam(0, ConvertYMDToDate(_settings_newgame.game_creation.starting_year, 0, 1)); break;
case WID_GL_MAPSIZE_X_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_x); break;
case WID_GL_MAPSIZE_Y_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_y); break;
case WID_GL_MAPSIZE_X_PULLDOWN: SetDParam(0, 1LL << _settings_newgame.game_creation.map_x); break;
case WID_GL_MAPSIZE_Y_PULLDOWN: SetDParam(0, 1LL << _settings_newgame.game_creation.map_y); break;
case WID_GL_MAX_HEIGHTLEVEL_TEXT: SetDParam(0, _settings_newgame.construction.max_heightlevel); break;
case WID_GL_SNOW_LEVEL_TEXT: SetDParam(0, _settings_newgame.game_creation.snow_line_height); break;
@@ -948,11 +948,11 @@ struct CreateScenarioWindow : public Window
break;
case WID_CS_MAPSIZE_X_PULLDOWN:
SetDParam(0, 1 << _settings_newgame.game_creation.map_x);
SetDParam(0, 1LL << _settings_newgame.game_creation.map_x);
break;
case WID_CS_MAPSIZE_Y_PULLDOWN:
SetDParam(0, 1 << _settings_newgame.game_creation.map_y);
SetDParam(0, 1LL << _settings_newgame.game_creation.map_y);
break;
case WID_CS_FLAT_LAND_HEIGHT_TEXT:

View File

@@ -1111,6 +1111,7 @@ void DoPaletteAnimations()
/**
* Determine a contrasty text colour for a coloured background.
* @param background Background colour.
* @param threshold Background colour brightness threshold below which the background is considered dark and TC_WHITE is returned, range: 0 - 255, default 128.
* @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
*/
TextColour GetContrastColour(uint8 background, uint8 threshold)
@@ -1120,7 +1121,7 @@ TextColour GetContrastColour(uint8 background, uint8 threshold)
* The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
/* Compare with threshold brightness which defaults to 128 (50%) */
return sq1000_brightness < threshold * 128 * 1000 ? TC_WHITE : TC_BLACK;
return sq1000_brightness < ((uint) threshold) * ((uint) threshold) * 1000 ? TC_WHITE : TC_BLACK;
}
/**

View File

@@ -597,6 +597,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, const char *&str,
Font *f = Layouter::GetFont(state.fontsize, state.cur_colour);
line.buffer = buff_begin;
fontMapping.Clear();
/*
* Go through the whole string while adding Font instances to the font map

View File

@@ -3074,6 +3074,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Unesi im
STR_TOWN_DIRECTORY_CAPTION :{WHITE}Gradovi
STR_TOWN_DIRECTORY_NONE :{ORANGE}- Ništa -
STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA})
STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (Grad){BLACK} ({COMMA})
STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Imena gradova - klikni na ime kako bi centrirao pogled na grad. Ctrl+klik otvara novi prozor sa lokacijom grada
STR_TOWN_POPULATION :{BLACK}Svjetsko stanovništvo: {COMMA}
@@ -3081,6 +3082,7 @@ STR_TOWN_POPULATION :{BLACK}Svjetsko
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Metropola)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Stanovništvo: {ORANGE}{COMMA}{BLACK} Kuće: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} prošli mjesec: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Potrebno tereta za rast grada:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} potrebno
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} potrebno zimi
@@ -4329,6 +4331,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... ovo
STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... cesta je orijentirana u krivom smjeru
STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... prolazne postaje ne mogu imati zavoje
STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... prolazne postaje ne mogu imati raskrižja
STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... cesta je jednosmjerna ili je blokirana
# Station destruction related errors
STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Nije moguće ukloniti dio postaje...
@@ -4580,6 +4583,8 @@ STR_BASESOUNDS_DOS_DESCRIPTION :Originalni zvuk
STR_BASESOUNDS_WIN_DESCRIPTION :Originalni zvukovi za Transport Tycoon Deluxe Windows izdanje.
STR_BASESOUNDS_NONE_DESCRIPTION :Zvučni paket bez ikakvih zvukova.
STR_BASEMUSIC_WIN_DESCRIPTION :Originalna glazba za Transport Tycoon Deluxe Windows izdanje.
STR_BASEMUSIC_DOS_DESCRIPTION :Originalna glazba za Transport Tycoon Deluxe DOS izdanje.
STR_BASEMUSIC_TTO_DESCRIPTION :Originalna glazba za Transport Tycoon (original/editor svijeta) DOS izdanje.
STR_BASEMUSIC_NONE_DESCRIPTION :Glazbeni paket bez ikakve glazbe.
##id 0x2000

View File

@@ -889,10 +889,10 @@ STR_NEWS_EXCLUSIVE_RIGHTS_DESCRIPTION :{BIG_FONT}{BLAC
# Extra view window
STR_EXTRA_VIEW_PORT_TITLE :{WHITE}Viewport {COMMA}
STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN :{BLACK}Copy to viewport
STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN :{BLACK}Change viewport
STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN_TT :{BLACK}Copy the location of the main view to this viewport
STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW :{BLACK}Paste from viewport
STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW_TT :{BLACK}Paste the location of this viewport to the main view
STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW :{BLACK}Change main view
STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW_TT :{BLACK}Copy the location of this viewport to the main view
# Game options window
STR_GAME_OPTIONS_CAPTION :{WHITE}Game Options
@@ -3465,6 +3465,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Enter a
STR_TOWN_DIRECTORY_CAPTION :{WHITE}Towns
STR_TOWN_DIRECTORY_NONE :{ORANGE}- None -
STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA})
STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (City){BLACK} ({COMMA})
STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Town names - click on name to centre main view on town. Ctrl+Click opens a new viewport on town location
STR_TOWN_POPULATION :{BLACK}World population: {COMMA}

View File

@@ -2979,6 +2979,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Entrer u
STR_TOWN_DIRECTORY_CAPTION :{WHITE}Villes
STR_TOWN_DIRECTORY_NONE :{ORANGE} Aucune
STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA})
STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (Métropole){BLACK} ({COMMA})
STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Noms des villes - Cliquer sur un nom pour centrer la vue principale sur la ville. Ctrl-clic pour ouvrir une nouvelle vue sur la ville.
STR_TOWN_POPULATION :{BLACK}Population mondiale{NBSP}: {COMMA}
@@ -4235,6 +4236,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... cett
STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... mauvaise orientation de la route
STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... les arrêts ne peuvent pas avoir de virages
STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... les arrêts ne peuvent pas avoir de jonctions
STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... la route est à sens unique ou bloquée
# Station destruction related errors
STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Impossible de supprimer une partie de la gare...
@@ -4486,6 +4488,8 @@ STR_BASESOUNDS_DOS_DESCRIPTION :Sons originaux
STR_BASESOUNDS_WIN_DESCRIPTION :Sons originaux de Transport Tycoon Deluxe (version Windows).
STR_BASESOUNDS_NONE_DESCRIPTION :Un pack de sons sans sons.
STR_BASEMUSIC_WIN_DESCRIPTION :Musiques originales de Transport Tycoon Deluxe (version Windows).
STR_BASEMUSIC_DOS_DESCRIPTION :Musiques originales de Transport Tycoon Deluxe (version DOS).
STR_BASEMUSIC_TTO_DESCRIPTION :Musiques originales de Transport Tycoon (version Originale/World Editor).
STR_BASEMUSIC_NONE_DESCRIPTION :Un pack de musiques sans musiques.
##id 0x2000

View File

@@ -3090,6 +3090,7 @@ STR_TOWN_POPULATION :{BLACK}Παγκ
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Πόλη)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Πληθυσμός: {ORANGE}{COMMA}{BLACK} Σπίτια: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} προηγούμενος μήνας: {ORANGE}{COMMA}{BLACK} μέγιστο: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Εμπορεύματα που χρειάζονται για την επέκταση της πόλης:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} απαιτείται
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} απαιτείται τον χειμώνα
@@ -4344,6 +4345,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... αυ
STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... ο δρόμος βλέπει σε λάθος κατεύθυνση
STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... οι μη τερματικοί σταθμοί δε μπορούν να έχουν στροφές
STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... οι μη τερματικοί σταθμοί δε μπορούν να έχουν διασταυρώσεις
STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... ο δρόμος είναι μονόδρομος η μπλοκαρισμένος
# Station destruction related errors
STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Δεν μπορεί να αφαιρεθεί μέρος του σταθμού...
@@ -4595,6 +4597,8 @@ STR_BASESOUNDS_DOS_DESCRIPTION :Αρχικοί
STR_BASESOUNDS_WIN_DESCRIPTION :Αρχικοί ήχοι από το Transport Tycoon Deluxe έκδοση Windows.
STR_BASESOUNDS_NONE_DESCRIPTION :Ένα πάκετο ήχων χώρις ήχους.
STR_BASEMUSIC_WIN_DESCRIPTION :Αρχική μουσική από το Transport Tycoon Deluxe έκδοση Windows.
STR_BASEMUSIC_DOS_DESCRIPTION :Αρχική μουσική από το Transport Tycoon Deluxe έκδοση DOS.
STR_BASEMUSIC_TTO_DESCRIPTION :Αρχική μουσική από το Transport Tycoon (Αρχικός Επεξεργαστής Κόσμου) έκδοση DOS.
STR_BASEMUSIC_NONE_DESCRIPTION :Ένα πάκετο μουσικής χωρίς πραγματική μουσική.
##id 0x2000

View File

@@ -3008,6 +3008,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Inserire
STR_TOWN_DIRECTORY_CAPTION :{WHITE}Città
STR_TOWN_DIRECTORY_NONE :{ORANGE}- Nessuna -
STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA})
STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (Metropoli){BLACK} ({COMMA})
STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Nomi delle città - fare clic su un nome per centrare la visuale principale sulla città. CTRL+clic la mostra in una mini visuale.
STR_TOWN_POPULATION :{BLACK}Popolazione mondiale: {COMMA}

View File

@@ -1400,7 +1400,7 @@ STR_CONFIG_SETTING_LIVERIES_ALL :모든 회사
STR_CONFIG_SETTING_PREFER_TEAMCHAT :엔터(ENTER) 키로 같은 팀끼리 채팅: {STRING}
STR_CONFIG_SETTING_PREFER_TEAMCHAT_HELPTEXT :이 설정을 켜면, 멀티 플레이시 같은 회사 간의 채팅을 <CTRL+ENTER> 키 대신 <ENTER> 키로 할 수 있게 됩니다.
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLLING :마우스 휠 동작: {STRING}
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLLING_HELPTEXT :상하좌우로 회전 가능한 마우스 휠(2차원 마우스휠)로 지도를 스크롤할 수 있게 허용합니다.
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLLING_HELPTEXT :상하좌우로 회전 가능한 마우스 휠(2차원 마우스 휠)로 지도를 스크롤할 수 있게 허용합니다.
STR_CONFIG_SETTING_SCROLLWHEEL_ZOOM :화면 확대/축소
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLL :지도 스크롤
STR_CONFIG_SETTING_SCROLLWHEEL_OFF :끄기

View File

@@ -3360,6 +3360,7 @@ STR_TOWN_POPULATION :{BLACK}Populacj
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Miasto)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Populacja: {ORANGE}{COMMA}{BLACK} Domów: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} w ostatnim miesiącu: {ORANGE}{COMMA}{BLACK} najwięcej: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Towar potrzebny do rozwoju miasta:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED}Wymagana {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} wymagane zimą
@@ -4614,6 +4615,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... ta d
STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... droga jest zorientowana w złym kierunku
STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... przystanki przelotowe nie mogą mieć zakrętów
STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... przystanki przelotowe nie mogą mieć skrzyżowań
STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... droga jest jednokierunkowa lub zablokowana
# Station destruction related errors
STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Nie można usunąć części stacji...

View File

@@ -3171,6 +3171,7 @@ STR_TOWN_POPULATION :{BLACK}Насе
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Мегаполис)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Население: {ORANGE}{COMMA}{BLACK} Зданий: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} в прошлом месяце: {ORANGE}{COMMA}{BLACK} Макс.: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Груз, необходимый для роста города:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} требу{G 0 е е е ю}тся
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} требу{G 0 е е е ю}тся зимой
@@ -4442,6 +4443,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... эт
STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... неверное направление дороги
STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... на проходных остановках нельзя делать повороты
STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... на проходных остановках нельзя делать перекрёстки
STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... дорога односторонняя или заблокирована
# Station destruction related errors
STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Невозможно удалить часть станции...
@@ -4510,7 +4512,7 @@ STR_ERROR_IMPOSSIBLE_TRACK_COMBINATION :{WHITE}Недо
STR_ERROR_MUST_REMOVE_SIGNALS_FIRST :{WHITE}Сначала удалите сигналы
STR_ERROR_NO_SUITABLE_RAILROAD_TRACK :{WHITE}Нет подходящих рельсов
STR_ERROR_MUST_REMOVE_RAILROAD_TRACK :{WHITE}Сначала удалите рельсы
STR_ERROR_CROSSING_ON_ONEWAY_ROAD :{WHITE}Дорога односторонняя или блокирована
STR_ERROR_CROSSING_ON_ONEWAY_ROAD :{WHITE}Дорога односторонняя или заблокирована
STR_ERROR_CROSSING_DISALLOWED :{WHITE}Через этот вид рельсов запрещено строить переезды
STR_ERROR_CAN_T_BUILD_SIGNALS_HERE :{WHITE}Здесь невозможно поставить сигнал...
STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK :{WHITE}Здесь невозможно проложить рельсы...
@@ -4686,13 +4688,15 @@ STR_ERROR_CAN_T_DELETE_SIGN :{WHITE}Не у
STR_DESKTOP_SHORTCUT_COMMENT :Экономический симулятор на основе игры «Transport Tycoon Deluxe»
# Translatable descriptions in media/baseset/*.ob* files
STR_BASEGRAPHICS_DOS_DESCRIPTION :Оригинальная графика из Transport Tycoon Deluxe для DOS.
STR_BASEGRAPHICS_DOS_DE_DESCRIPTION :Оригинальная графика из немецкой версии Transport Tycoon Deluxe для DOS.
STR_BASEGRAPHICS_WIN_DESCRIPTION :Оригинальная графика из Transport Tycoon Deluxe для Windows.
STR_BASESOUNDS_DOS_DESCRIPTION :Оригинальный набор звукового оформления из игры Transport Tycoon Deluxe для DOS.
STR_BASESOUNDS_WIN_DESCRIPTION :Оригинальный набор звукового оформления из игры Transport Tycoon Deluxe для Windows.
STR_BASEGRAPHICS_DOS_DESCRIPTION :Графика из Transport Tycoon Deluxe для DOS.
STR_BASEGRAPHICS_DOS_DE_DESCRIPTION :Графика из немецкой версии Transport Tycoon Deluxe для DOS.
STR_BASEGRAPHICS_WIN_DESCRIPTION :Графика из Transport Tycoon Deluxe для Windows.
STR_BASESOUNDS_DOS_DESCRIPTION :Набор звукового оформления из игры Transport Tycoon Deluxe для DOS.
STR_BASESOUNDS_WIN_DESCRIPTION :Набор звукового оформления из игры Transport Tycoon Deluxe для Windows.
STR_BASESOUNDS_NONE_DESCRIPTION :"Пустой" набор звукового оформления, не содержащий никаких звуков.
STR_BASEMUSIC_WIN_DESCRIPTION :Оригинальный набор музыкального оформления из игры Transport Tycoon Deluxe для Windows.
STR_BASEMUSIC_WIN_DESCRIPTION :Набор музыкального оформления из игры Transport Tycoon Deluxe для Windows.
STR_BASEMUSIC_DOS_DESCRIPTION :Набор музыкального оформления из игры Transport Tycoon Deluxe для DOS.
STR_BASEMUSIC_TTO_DESCRIPTION :Набор музыкального оформления из игры Transport Tycoon Deluxe для DOS.
STR_BASEMUSIC_NONE_DESCRIPTION :"Пустой" набор музыкального оформления, не содержащий никакой музыки.
##id 0x2000

View File

@@ -674,7 +674,9 @@ STR_PLAYLIST_TRACK_NAME :{TINY_FONT}{LTB
STR_PLAYLIST_TRACK_INDEX :{TINY_FONT}{BLACK}全部音轨列表
STR_PLAYLIST_PROGRAM :{TINY_FONT}{BLACK}当前选用'{STRING}'列表
STR_PLAYLIST_CLEAR :{TINY_FONT}{BLACK}清除
STR_PLAYLIST_CHANGE_SET :更改设置
STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1 :{BLACK}清除当前列表中曲目{}仅限自定义1或自定义2
STR_PLAYLIST_TOOLTIP_CHANGE_SET :{BLACK}选择另一种已安装的音乐
STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK :{BLACK}点击音乐曲目以加入当前播放列表{}(仅限自定义1或自定义2)
STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK :{BLACK}点击音乐曲目以从当前播放列表中删除{}(仅限自定义1或自定义2)
@@ -1334,6 +1336,7 @@ STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_HELPTEXT :设置缩略地
STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_GREEN :绿色
STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_DARK_GREEN :深绿色
STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_VIOLET :紫色
STR_CONFIG_SETTING_SCROLLMODE_RMB :鼠标右键移动地图
STR_CONFIG_SETTING_SMOOTH_SCROLLING :平滑视角滚动: {STRING}
STR_CONFIG_SETTING_SMOOTH_SCROLLING_HELPTEXT :设置在缩略图上点击或者发出转到特定目标的命令时主视角的转换方式,如果“打开”本选项,视角平缓滚动,“关闭”时直接跳转到目标位置
STR_CONFIG_SETTING_MEASURE_TOOLTIP :建设时显示测量数据:{STRING}
@@ -3647,6 +3650,7 @@ STR_VEHICLE_INFO_AGE :{COMMA} 年 ({C
STR_VEHICLE_INFO_AGE_RED :{RED}{COMMA} 年 ({COMMA})
STR_VEHICLE_INFO_MAX_SPEED :{BLACK}最大速度:{LTBLUE}{VELOCITY}
STR_VEHICLE_INFO_MAX_SPEED_TYPE :{BLACK}最高速度: {LTBLUE}{VELOCITY} {BLACK}飞机种类: {LTBLUE}{STRING}
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}重量:{LTBLUE}{WEIGHT_SHORT} {BLACK}功率:{LTBLUE}{POWER}{BLACK} 最大速度:{LTBLUE}{VELOCITY}
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}重量:{LTBLUE}{WEIGHT_SHORT} {BLACK}功率:{LTBLUE}{POWER}{BLACK} 最大速度:{LTBLUE}{VELOCITY} {BLACK}最大牵引力:{LTBLUE}{FORCE}
@@ -4464,6 +4468,7 @@ STR_BASESOUNDS_DOS_DESCRIPTION :运输大亨DOS
STR_BASESOUNDS_WIN_DESCRIPTION :Transport Tycoon Deluxe Windows (运输大亨Windows豪华版)的原版音效包.
STR_BASESOUNDS_NONE_DESCRIPTION :一个空的音效包.
STR_BASEMUSIC_WIN_DESCRIPTION :Transport Tycoon Deluxe运输大亨Windows豪华版的原版音乐包
STR_BASEMUSIC_DOS_DESCRIPTION :运输大亨DOS豪华版原版音乐。
STR_BASEMUSIC_NONE_DESCRIPTION :一个没有实际内容的音乐包.
##id 0x2000

View File

@@ -475,7 +475,7 @@ void LinkGraphOverlay::SetCompanyMask(uint32 company_mask)
/** Make a number of rows with buttons for each company for the linkgraph legend window. */
NWidgetBase *MakeCompanyButtonRowsLinkGraphGUI(int *biggest_index)
{
return MakeCompanyButtonRows(biggest_index, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST, 3, 0);
return MakeCompanyButtonRows(biggest_index, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST, 3, STR_NULL);
}
NWidgetBase *MakeSaturationLegendLinkGraphGUI(int *biggest_index)
@@ -652,30 +652,46 @@ void LinkGraphLegendWindow::DrawWidget(const Rect &r, int widget) const
if (this->IsWidgetDisabled(widget)) return;
CargoSpec *cargo = CargoSpec::Get(widget - WID_LGL_CARGO_FIRST);
GfxFillRect(r.left + 2, r.top + 2, r.right - 2, r.bottom - 2, cargo->legend_colour);
DrawString(r.left, r.right, (r.top + r.bottom + 1 - FONT_HEIGHT_SMALL) / 2, cargo->abbrev, GetContrastColour(cargo->legend_colour, 42), SA_HOR_CENTER);
DrawString(r.left, r.right, (r.top + r.bottom + 1 - FONT_HEIGHT_SMALL) / 2, cargo->abbrev, GetContrastColour(cargo->legend_colour, 73), SA_HOR_CENTER);
}
}
bool LinkGraphLegendWindow::OnHoverCommon(Point pt, int widget, TooltipCloseCondition close_cond)
{
if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
if (this->IsWidgetDisabled(widget)) {
GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_SELECT_COMPANIES, 0, NULL, close_cond);
} else {
uint64 params[2];
CompanyID cid = (CompanyID)(widget - WID_LGL_COMPANY_FIRST);
params[0] = STR_LINKGRAPH_LEGEND_SELECT_COMPANIES;
params[1] = cid;
GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_COMPANY_TOOLTIP, 2, params, close_cond);
}
return true;
}
if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
if (this->IsWidgetDisabled(widget)) return false;
CargoSpec *cargo = CargoSpec::Get(widget - WID_LGL_CARGO_FIRST);
uint64 params[1];
params[0] = cargo->name;
GuiShowTooltips(this, STR_BLACK_STRING, 1, params, close_cond);
return true;
}
return false;
}
void LinkGraphLegendWindow::OnHover(Point pt, int widget)
{
if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) {
uint64 params[5];
if (this->IsWidgetDisabled(widget)) {
GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_SELECT_COMPANIES, 0, params, TCC_HOVER);
} else {
CompanyID cid = (CompanyID)(widget - WID_LGL_COMPANY_FIRST);
params[0] = STR_LINKGRAPH_LEGEND_SELECT_COMPANIES;
params[1] = cid;
GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_COMPANY_TOOLTIP, 2, params, TCC_HOVER);
}
}
if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) {
if (this->IsWidgetDisabled(widget)) return;
CargoSpec *cargo = CargoSpec::Get(widget - WID_LGL_CARGO_FIRST);
uint64 params[5];
params[0] = cargo->name;
GuiShowTooltips(this, STR_BLACK_STRING, 1, params, TCC_HOVER);
this->OnHoverCommon(pt, widget, TCC_HOVER);
}
bool LinkGraphLegendWindow::OnRightClick(Point pt, int widget)
{
if (_settings_client.gui.hover_delay_ms == 0) {
return this->OnHoverCommon(pt, widget, TCC_RIGHT_CLICK);
}
return false;
}
/**

View File

@@ -15,6 +15,7 @@
#include "../company_func.h"
#include "../station_base.h"
#include "../widget_type.h"
#include "../window_gui.h"
#include "linkgraph_base.h"
#include <map>
#include <vector>
@@ -118,6 +119,7 @@ public:
virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize);
virtual void DrawWidget(const Rect &r, int widget) const;
virtual void OnHover(Point pt, int widget) override;
virtual bool OnRightClick(Point pt, int widget) override;
virtual void OnClick(Point pt, int widget, int click_count);
virtual void OnInvalidateData(int data = 0, bool gui_scope = true);
@@ -126,6 +128,7 @@ private:
void UpdateOverlayCompanies();
void UpdateOverlayCargoes();
bool OnHoverCommon(Point pt, int widget, TooltipCloseCondition close_cond);
};
#endif /* LINKGRAPH_GUI_H */

View File

@@ -125,9 +125,11 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
this->num_available = 0;
IniGroup *names = ini->GetGroup("names");
IniGroup *catindex = ini->GetGroup("catindex");
for (uint i = 0, j = 1; i < lengthof(this->songinfo); i++) {
IniGroup *timingtrim = ini->GetGroup("timingtrim");
uint tracknr = 1;
for (uint i = 0; i < lengthof(this->songinfo); i++) {
const char *filename = this->files[i].filename;
if (names == NULL || StrEmpty(filename)) {
if (names == NULL || StrEmpty(filename) || this->files[i].check_result == MD5File::CR_NO_FILE) {
this->songinfo[i].songname[0] = '\0';
continue;
}
@@ -142,7 +144,8 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
char *songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index);
if (songname == NULL) {
DEBUG(grf, 1, "Base music set song missing from CAT file: %s/%d", filename, this->songinfo[i].cat_index);
return false;
this->songinfo[i].songname[0] = '\0';
continue;
}
strecpy(this->songinfo[i].songname, songname, lastof(this->songinfo[i].songname));
free(songname);
@@ -150,15 +153,16 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
this->songinfo[i].filetype = MTT_STANDARDMIDI;
}
const char *trimmed_filename = filename;
/* As we possibly add a path to the filename and we compare
* on the filename with the path as in the .obm, we need to
* keep stripping path elements until we find a match. */
for (const char *p = filename; p != NULL; p = strchr(p, PATHSEPCHAR)) {
for (; trimmed_filename != NULL; trimmed_filename = strchr(trimmed_filename, PATHSEPCHAR)) {
/* Remove possible double path separator characters from
* the beginning, so we don't start reading e.g. root. */
while (*p == PATHSEPCHAR) p++;
while (*trimmed_filename == PATHSEPCHAR) trimmed_filename++;
item = names->GetItem(p, false);
item = names->GetItem(trimmed_filename, false);
if (item != NULL && !StrEmpty(item->value)) break;
}
@@ -172,7 +176,21 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
}
this->num_available++;
this->songinfo[i].tracknr = j++;
/* Number the theme song (if any) track 0, rest are normal */
if (i == 0) {
this->songinfo[i].tracknr = 0;
} else {
this->songinfo[i].tracknr = tracknr++;
}
item = timingtrim->GetItem(trimmed_filename, false);
if (item != NULL && !StrEmpty(item->value)) {
const char *endpos = strchr(item->value, ':');
if (endpos != NULL) {
this->songinfo[i].override_start = atoi(item->value);
this->songinfo[i].override_end = atoi(endpos + 1);
}
}
}
}
return ret;

View File

@@ -693,6 +693,9 @@ static void MidiThreadProc(void *)
current_segment.start_block = bl;
break;
} else {
/* Skip the transmission delay compensation performed in the Win32 MIDI driver.
* The DMusic driver will most likely be used with the MS softsynth, which is not subject to transmission delays.
*/
DEBUG(driver, 2, "DMusic: timer: start from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime) / 1000.0, (int)preload_bytes);
playback_start_time -= block.realtime * MIDITIME_TO_REFTIME;
break;
@@ -723,14 +726,6 @@ static void MidiThreadProc(void *)
while (current_block < current_file.blocks.size()) {
MidiFile::DataBlock &block = current_file.blocks[current_block];
/* check that block is not in the future */
REFERENCE_TIME playback_time = current_time - playback_start_time;
if (block.realtime * MIDITIME_TO_REFTIME > playback_time + 3 *_playback.preload_time * MS_TO_REFTIME) {
/* Stop the thread loop until we are at the preload time of the next block. */
next_timeout = Clamp(((int64)block.realtime * MIDITIME_TO_REFTIME - playback_time) / MS_TO_REFTIME - _playback.preload_time, 0, 1000);
DEBUG(driver, 9, "DMusic thread: Next event in %u ms (music %u, ref %lld)", next_timeout, block.realtime * MIDITIME_TO_REFTIME, playback_time);
break;
}
/* check that block isn't at end-of-song override */
if (current_segment.end > 0 && block.ticktime >= current_segment.end) {
if (current_segment.loop) {
@@ -743,6 +738,14 @@ static void MidiThreadProc(void *)
next_timeout = 0;
break;
}
/* check that block is not in the future */
REFERENCE_TIME playback_time = current_time - playback_start_time;
if (block.realtime * MIDITIME_TO_REFTIME > playback_time + 3 *_playback.preload_time * MS_TO_REFTIME) {
/* Stop the thread loop until we are at the preload time of the next block. */
next_timeout = Clamp(((int64)block.realtime * MIDITIME_TO_REFTIME - playback_time) / MS_TO_REFTIME - _playback.preload_time, 0, 1000);
DEBUG(driver, 9, "DMusic thread: Next event in %u ms (music %u, ref %lld)", next_timeout, block.realtime * MIDITIME_TO_REFTIME, playback_time);
break;
}
/* Timestamp of the current block. */
block_time = playback_start_time + block.realtime * MIDITIME_TO_REFTIME;
@@ -829,8 +832,8 @@ static void MidiThreadProc(void *)
/* end? */
if (current_block == current_file.blocks.size()) {
if (current_segment.loop) {
current_block = 0;
clock->GetTime(&playback_start_time);
current_block = current_segment.start_block;
playback_start_time = block_time - current_file.blocks[current_block].realtime * MIDITIME_TO_REFTIME;
} else {
_playback.do_stop = true;
}
@@ -1232,9 +1235,9 @@ void MusicDriver_DMusic::PlaySong(const MusicSongInfo &song)
if (!_playback.next_file.LoadSong(song)) return;
_playback.next_segment.start = 0;
_playback.next_segment.end = 0;
_playback.next_segment.loop = false;
_playback.next_segment.start = song.override_start;
_playback.next_segment.end = song.override_end;
_playback.next_segment.loop = song.loop;
_playback.do_start = true;
SetEvent(_thread_event);

View File

@@ -184,7 +184,7 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW
/* find first block after start time and pretend playback started earlier
* this is to allow all blocks prior to the actual start to still affect playback,
* as they may contain important controller and program changes */
size_t preload_bytes = 0;
uint preload_bytes = 0;
for (size_t bl = 0; bl < _midi.current_file.blocks.size(); bl++) {
MidiFile::DataBlock &block = _midi.current_file.blocks[bl];
preload_bytes += block.data.Length();
@@ -194,8 +194,13 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW
_midi.current_segment.start_block = bl;
break;
} else {
/* Calculate offset start time for playback.
* The preload_bytes are used to compensate for delay in transmission over traditional serial MIDI interfaces,
* which have a bitrate of 31,250 bits/sec, and transmit 1+8+1 start/data/stop bits per byte.
* The delay compensation is needed to avoid time-compression of following messages.
*/
DEBUG(driver, 2, "Win32-MIDI: timer: start from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime) / 1000.0, (int)preload_bytes);
_midi.playback_start_time -= block.realtime / 1000;
_midi.playback_start_time -= block.realtime / 1000 - preload_bytes * 1000 / 3125;
break;
}
}
@@ -209,10 +214,6 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW
while (_midi.current_block < _midi.current_file.blocks.size()) {
MidiFile::DataBlock &block = _midi.current_file.blocks[_midi.current_block];
/* check that block is not in the future */
if (block.realtime / 1000 > playback_time) {
break;
}
/* check that block isn't at end-of-song override */
if (_midi.current_segment.end > 0 && block.ticktime >= _midi.current_segment.end) {
if (_midi.current_segment.loop) {
@@ -223,6 +224,10 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW
}
break;
}
/* check that block is not in the future */
if (block.realtime / 1000 > playback_time) {
break;
}
byte *data = block.data.Begin();
size_t remaining = block.data.Length();
@@ -297,8 +302,8 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW
/* end? */
if (_midi.current_block == _midi.current_file.blocks.size()) {
if (_midi.current_segment.loop) {
_midi.current_block = 0;
_midi.playback_start_time = timeGetTime();
_midi.current_block = _midi.current_segment.start_block;
_midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000;
} else {
_midi.do_stop = true;
}
@@ -315,9 +320,9 @@ void MusicDriver_Win32::PlaySong(const MusicSongInfo &song)
return;
}
_midi.next_segment.start = 0;
_midi.next_segment.end = 0;
_midi.next_segment.loop = false;
_midi.next_segment.start = song.override_start;
_midi.next_segment.end = song.override_end;
_midi.next_segment.loop = song.loop;
DEBUG(driver, 2, "Win32-MIDI: PlaySong: setting flag");
_midi.do_stop = _midi.playing;

View File

@@ -10,6 +10,7 @@
/** @file music_gui.cpp GUI for the music playback. */
#include "stdafx.h"
#include <vector>
#include "openttd.h"
#include "base_media_base.h"
#include "music/music_driver.hpp"
@@ -35,246 +36,385 @@
#include "safeguards.h"
/**
* Get the name of the song.
* @param index of the song.
* @return the name of the song.
*/
static const char *GetSongName(int index)
{
return BaseMusic::GetUsedSet()->songinfo[index].songname;
}
/**
* Get the track number of the song.
* @param index of the song.
* @return the track number of the song.
*/
static int GetTrackNumber(int index)
{
return BaseMusic::GetUsedSet()->songinfo[index].tracknr;
}
struct MusicSystem {
struct PlaylistEntry : MusicSongInfo {
const MusicSet *set; ///< music set the song comes from
uint set_index; ///< index of song in set
/** The currently played song */
static byte _music_wnd_cursong = 1;
/** Whether a song is currently played */
static bool _song_is_active = false;
PlaylistEntry(const MusicSet *set, uint set_index) : MusicSongInfo(set->songinfo[set_index]), set(set), set_index(set_index) { }
bool IsValid() const { return !StrEmpty(this->songname); }
};
typedef std::vector<PlaylistEntry> Playlist;
/** Indices of the songs in the current playlist */
static byte _cur_playlist[NUM_SONGS_PLAYLIST + 1];
enum PlaylistChoices {
PLCH_ALLMUSIC,
PLCH_OLDSTYLE,
PLCH_NEWSTYLE,
PLCH_EZYSTREET,
PLCH_CUSTOM1,
PLCH_CUSTOM2,
PLCH_THEMEONLY,
PLCH_MAX,
};
/** Indices of all songs */
static byte _playlist_all[NUM_SONGS_AVAILABLE + 1];
/** Indices of all old style songs */
static byte _playlist_old_style[NUM_SONGS_CLASS + 1];
/** Indices of all new style songs */
static byte _playlist_new_style[NUM_SONGS_CLASS + 1];
/** Indices of all ezy street songs */
static byte _playlist_ezy_street[NUM_SONGS_CLASS + 1];
Playlist active_playlist; ///< current play order of songs, including any shuffle
Playlist displayed_playlist; ///< current playlist as displayed in GUI, never in shuffled order
Playlist music_set; ///< all songs in current music set, in set order
assert_compile(lengthof(_settings_client.music.custom_1) == NUM_SONGS_PLAYLIST + 1);
assert_compile(lengthof(_settings_client.music.custom_2) == NUM_SONGS_PLAYLIST + 1);
PlaylistChoices selected_playlist;
/** The different playlists that can be played. */
static byte * const _playlists[] = {
_playlist_all,
_playlist_old_style,
_playlist_new_style,
_playlist_ezy_street,
_settings_client.music.custom_1,
_settings_client.music.custom_2,
void BuildPlaylists();
void ChangePlaylist(PlaylistChoices pl);
void ChangeMusicSet(const char *set_name);
void Shuffle();
void Unshuffle();
void Play();
void Stop();
void Next();
void Prev();
void CheckStatus();
bool IsPlaying() const;
bool IsShuffle() const;
PlaylistEntry GetCurrentSong() const;
bool IsCustomPlaylist() const;
void PlaylistAdd(size_t song_index);
void PlaylistRemove(size_t song_index);
void PlaylistClear();
private:
void ChangePlaylistPosition(int ofs);
int playlist_position;
void SaveCustomPlaylist(PlaylistChoices pl);
Playlist standard_playlists[PLCH_MAX];
};
/**
* Validate a playlist.
* @param playlist The playlist to validate.
* @param last The last location in the list.
*/
void ValidatePlaylist(byte *playlist, byte *last)
{
while (*playlist != 0 && playlist <= last) {
/* Song indices are saved off-by-one so 0 is "nothing". */
if (*playlist <= NUM_SONGS_AVAILABLE && !StrEmpty(GetSongName(*playlist - 1))) {
playlist++;
continue;
}
for (byte *p = playlist; *p != 0 && p <= last; p++) {
p[0] = p[1];
}
}
MusicSystem _music;
/* Make sure the list is null terminated. */
*last = 0;
}
/** Prepare the playlists */
void InitializeMusic()
/** Rebuild all playlists for the current music set */
void MusicSystem::BuildPlaylists()
{
uint j = 0;
const MusicSet *set = BaseMusic::GetUsedSet();
/* Clear current playlists */
for (size_t i = 0; i < lengthof(this->standard_playlists); ++i) this->standard_playlists[i].clear();
this->music_set.clear();
/* Build standard playlists, and a list of available music */
for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
if (StrEmpty(GetSongName(i))) continue;
_playlist_all[j++] = i + 1;
}
/* Terminate the list */
_playlist_all[j] = 0;
PlaylistEntry entry(set, i);
if (!entry.IsValid()) continue;
/* Now make the 'styled' playlists */
for (uint k = 0; k < NUM_SONG_CLASSES; k++) {
j = 0;
for (uint i = 0; i < NUM_SONGS_CLASS; i++) {
int id = k * NUM_SONGS_CLASS + i + 1;
if (StrEmpty(GetSongName(id))) continue;
_playlists[k + 1][j++] = id + 1;
this->music_set.push_back(entry);
/* Add theme song to theme-only playlist */
if (i == 0) this->standard_playlists[PLCH_THEMEONLY].push_back(entry);
/* Don't add the theme song to standard playlists */
if (i > 0) {
this->standard_playlists[PLCH_ALLMUSIC].push_back(entry);
uint theme = (i - 1) / NUM_SONGS_CLASS;
this->standard_playlists[PLCH_OLDSTYLE + theme].push_back(entry);
}
/* Terminate the list */
_playlists[k + 1][j] = 0;
}
ValidatePlaylist(_settings_client.music.custom_1, lastof(_settings_client.music.custom_1));
ValidatePlaylist(_settings_client.music.custom_2, lastof(_settings_client.music.custom_2));
if (BaseMusic::GetUsedSet()->num_available < _music_wnd_cursong) {
/* If there are less songs than the currently played song,
* just pause and reset to no song. */
_music_wnd_cursong = 0;
_song_is_active = false;
}
}
static void SkipToPrevSong()
{
byte *b = _cur_playlist;
byte *p = b;
byte t;
if (b[0] == 0) return; // empty playlist
do p++; while (p[0] != 0); // find the end
t = *--p; // and copy the bytes
while (p != b) {
p--;
p[1] = p[0];
}
*b = t;
_song_is_active = false;
}
static void SkipToNextSong()
{
byte *b = _cur_playlist;
byte t;
t = b[0];
if (t != 0) {
while (b[1] != 0) {
b[0] = b[1];
b++;
/* Load custom playlists
* Song index offsets are 1-based, zero indicates invalid/end-of-list value */
for (uint i = 0; i < NUM_SONGS_PLAYLIST; i++) {
if (_settings_client.music.custom_1[i] > 0) {
PlaylistEntry entry(set, _settings_client.music.custom_1[i] - 1);
if (entry.IsValid()) this->standard_playlists[PLCH_CUSTOM1].push_back(entry);
}
b[0] = t;
if (_settings_client.music.custom_2[i] > 0) {
PlaylistEntry entry(set, _settings_client.music.custom_2[i] - 1);
if (entry.IsValid()) this->standard_playlists[PLCH_CUSTOM2].push_back(entry);
}
}
}
/**
* Switch to another playlist, or reload the current one.
* @param pl Playlist to select
*/
void MusicSystem::ChangePlaylist(PlaylistChoices pl)
{
assert(pl < PLCH_MAX && pl >= PLCH_ALLMUSIC);
this->displayed_playlist = this->standard_playlists[pl];
this->active_playlist = this->displayed_playlist;
this->selected_playlist = pl;
this->playlist_position = 0;
if (this->selected_playlist != PLCH_THEMEONLY) _settings_client.music.playlist = this->selected_playlist;
if (_settings_client.music.shuffle) {
this->Shuffle();
/* Shuffle() will also Play() if necessary, only start once */
} else if (_settings_client.music.playing) {
this->Play();
}
_song_is_active = false;
InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
static void MusicVolumeChanged(byte new_vol)
/**
* Change to named music set, and reset playback.
* @param set_name Name of music set to select
*/
void MusicSystem::ChangeMusicSet(const char *set_name)
{
MusicDriver::GetInstance()->SetVolume(new_vol);
BaseMusic::SetSet(set_name);
this->BuildPlaylists();
this->ChangePlaylist(this->selected_playlist);
InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS, 0, true);
}
static void DoPlaySong()
/** Enable shuffle mode and restart playback */
void MusicSystem::Shuffle()
{
char filename[MAX_PATH];
MusicSongInfo songinfo = BaseMusic::GetUsedSet()->songinfo[_music_wnd_cursong - 1]; // copy
if (FioFindFullPath(filename, lastof(filename), BASESET_DIR, songinfo.filename) == NULL) {
FioFindFullPath(filename, lastof(filename), OLD_GM_DIR, songinfo.filename);
_settings_client.music.shuffle = true;
this->active_playlist = this->displayed_playlist;
for (size_t i = 0; i < this->active_playlist.size(); i++) {
size_t shuffle_index = InteractiveRandom() % (this->active_playlist.size() - i);
std::swap(this->active_playlist[i], this->active_playlist[i + shuffle_index]);
}
songinfo.filename = filename; // non-owned pointer
MusicDriver::GetInstance()->PlaySong(songinfo);
SetWindowDirty(WC_MUSIC_WINDOW, 0);
if (_settings_client.music.playing) this->Play();
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
static void DoStopMusic()
/** Disable shuffle and restart playback */
void MusicSystem::Unshuffle()
{
_settings_client.music.shuffle = false;
this->active_playlist = this->displayed_playlist;
if (_settings_client.music.playing) this->Play();
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
/** Start/restart playback at current song */
void MusicSystem::Play()
{
/* Always set the playing flag, even if there is no music */
_settings_client.music.playing = true;
MusicDriver::GetInstance()->StopSong();
/* Make sure playlist_position is a valid index, if playlist has changed etc. */
this->ChangePlaylistPosition(0);
/* If there is no music, don't try to play it */
if (this->active_playlist.empty()) return;
MusicSongInfo song = this->active_playlist[this->playlist_position];
if (_game_mode == GM_MENU && this->selected_playlist == PLCH_THEMEONLY) song.loop = true;
MusicDriver::GetInstance()->PlaySong(song);
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
/** Stop playback and set flag that we don't intend to play music */
void MusicSystem::Stop()
{
MusicDriver::GetInstance()->StopSong();
SetWindowDirty(WC_MUSIC_WINDOW, 0);
_settings_client.music.playing = false;
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
/** Reload the active playlist data from playlist selection and shuffle setting */
static void ResetPlaylist()
/** Skip to next track */
void MusicSystem::Next()
{
uint i = 0;
uint j = 0;
this->ChangePlaylistPosition(+1);
if (_settings_client.music.playing) this->Play();
memset(_cur_playlist, 0, sizeof(_cur_playlist));
do {
/* File is the index into the file table of the music set. The play list uses 0 as 'no entry',
* so we need to subtract 1. In case of 'no entry' (file = -1), just skip adding it outright. */
int file = _playlists[_settings_client.music.playlist][i] - 1;
if (file >= 0) {
const char *filename = BaseMusic::GetUsedSet()->files[file].filename;
/* We are now checking for the existence of that file prior
* to add it to the list of available songs */
if (!StrEmpty(filename) && FioCheckFileExists(filename, BASESET_DIR)) {
_cur_playlist[j] = _playlists[_settings_client.music.playlist][i];
j++;
}
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
/** Skip to previous track */
void MusicSystem::Prev()
{
this->ChangePlaylistPosition(-1);
if (_settings_client.music.playing) this->Play();
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
/** Check that music is playing if it should, and that appropriate playlist is active for game/main menu */
void MusicSystem::CheckStatus()
{
if ((_game_mode == GM_MENU) != (this->selected_playlist == PLCH_THEMEONLY)) {
/* Make sure the theme-only playlist is active when on the title screen, and not during gameplay */
this->ChangePlaylist((_game_mode == GM_MENU) ? PLCH_THEMEONLY : (PlaylistChoices)_settings_client.music.playlist);
}
if (this->active_playlist.empty()) return;
/* If we were supposed to be playing, but music has stopped, move to next song */
if (this->IsPlaying() && !MusicDriver::GetInstance()->IsSongPlaying()) this->Next();
}
/** Is the player getting music right now? */
bool MusicSystem::IsPlaying() const
{
return _settings_client.music.playing && !this->active_playlist.empty();
}
/** Is shuffle mode enabled? */
bool MusicSystem::IsShuffle() const
{
return _settings_client.music.shuffle;
}
/** Return the current song, or a dummy if none */
MusicSystem::PlaylistEntry MusicSystem::GetCurrentSong() const
{
if (!this->IsPlaying()) return PlaylistEntry(BaseMusic::GetUsedSet(), 0);
return this->active_playlist[this->playlist_position];
}
/** Is one of the custom playlists selected? */
bool MusicSystem::IsCustomPlaylist() const
{
return (this->selected_playlist == PLCH_CUSTOM1) || (this->selected_playlist == PLCH_CUSTOM2);
}
/**
* Append a song to a custom playlist.
* Always adds to the currently active playlist.
* @param song_index Index of song in the current music set to add
*/
void MusicSystem::PlaylistAdd(size_t song_index)
{
if (!this->IsCustomPlaylist()) return;
/* Pick out song from the music set */
if (song_index >= this->music_set.size()) return;
PlaylistEntry entry = this->music_set[song_index];
/* Check for maximum length */
if (this->standard_playlists[this->selected_playlist].size() >= NUM_SONGS_PLAYLIST) return;
/* Add it to the appropriate playlist, and the display */
this->standard_playlists[this->selected_playlist].push_back(entry);
this->displayed_playlist.push_back(entry);
/* Add it to the active playlist, if playback is shuffled select a random position to add at */
if (this->active_playlist.empty()) {
this->active_playlist.push_back(entry);
if (this->IsPlaying()) this->Play();
} else if (this->IsShuffle()) {
/* Generate a random position between 0 and n (inclusive, new length) to insert at */
size_t maxpos = this->displayed_playlist.size();
size_t newpos = InteractiveRandom() % maxpos;
this->active_playlist.insert(this->active_playlist.begin() + newpos, entry);
/* Make sure to shift up the current playback position if the song was inserted before it */
if ((int)newpos <= this->playlist_position) this->playlist_position++;
} else {
this->active_playlist.push_back(entry);
}
this->SaveCustomPlaylist(this->selected_playlist);
InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
}
/**
* Remove a song from a custom playlist.
* @param song_index Index in the custom playlist to remove.
*/
void MusicSystem::PlaylistRemove(size_t song_index)
{
if (!this->IsCustomPlaylist()) return;
Playlist &pl = this->standard_playlists[this->selected_playlist];
if (song_index >= pl.size()) return;
/* Remove from "simple" playlists */
PlaylistEntry song = pl[song_index];
pl.erase(pl.begin() + song_index);
this->displayed_playlist.erase(this->displayed_playlist.begin() + song_index);
/* Find in actual active playlist (may be shuffled) and remove,
* if it's the current song restart playback */
for (size_t i = 0; i < this->active_playlist.size(); i++) {
Playlist::iterator s2 = this->active_playlist.begin() + i;
if (s2->filename == song.filename && s2->cat_index == song.cat_index) {
this->active_playlist.erase(s2);
if ((int)i == this->playlist_position && this->IsPlaying()) this->Play();
break;
}
} while (_playlists[_settings_client.music.playlist][++i] != 0 && j < lengthof(_cur_playlist) - 1);
}
/* Do not shuffle when on the intro-start window, as the song to play has to be the original TTD Theme*/
if (_settings_client.music.shuffle && _game_mode != GM_MENU) {
i = 500;
do {
uint32 r = InteractiveRandom();
byte *a = &_cur_playlist[GB(r, 0, 5)];
byte *b = &_cur_playlist[GB(r, 8, 5)];
this->SaveCustomPlaylist(this->selected_playlist);
if (*a != 0 && *b != 0) {
byte t = *a;
*a = *b;
*b = t;
}
} while (--i);
InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
}
/**
* Remove all songs from the current custom playlist.
* Effectively stops playback too.
*/
void MusicSystem::PlaylistClear()
{
if (!this->IsCustomPlaylist()) return;
this->standard_playlists[this->selected_playlist].clear();
this->ChangePlaylist(this->selected_playlist);
this->SaveCustomPlaylist(this->selected_playlist);
}
/**
* Change playlist position pointer by the given offset, making sure to keep it within valid range.
* If the playlist is empty, position is always set to 0.
* @param ofs Amount to move playlist position by.
*/
void MusicSystem::ChangePlaylistPosition(int ofs)
{
if (this->active_playlist.empty()) {
this->playlist_position = 0;
} else {
this->playlist_position += ofs;
while (this->playlist_position >= (int)this->active_playlist.size()) this->playlist_position -= (int)this->active_playlist.size();
while (this->playlist_position < 0) this->playlist_position += (int)this->active_playlist.size();
}
}
static void StopMusic()
/**
* Save a custom playlist to settings after modification.
* @param pl Playlist to store back
*/
void MusicSystem::SaveCustomPlaylist(PlaylistChoices pl)
{
_music_wnd_cursong = 0;
DoStopMusic();
_song_is_active = false;
SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9);
}
/** Begin playing the next song on the playlist */
static void PlayPlaylistSong()
{
if (_cur_playlist[0] == 0) {
ResetPlaylist();
/* if there is not songs in the playlist, it may indicate
* no file on the gm folder, or even no gm folder.
* Stop the playback, then */
if (_cur_playlist[0] == 0) {
_song_is_active = false;
_music_wnd_cursong = 0;
_settings_client.music.playing = false;
return;
}
byte *settings_pl;
if (pl == PLCH_CUSTOM1) {
settings_pl = _settings_client.music.custom_1;
} else if (pl == PLCH_CUSTOM2) {
settings_pl = _settings_client.music.custom_2;
} else {
return;
}
_music_wnd_cursong = _cur_playlist[0];
DoPlaySong();
_song_is_active = true;
SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9);
size_t num = 0;
MemSetT(settings_pl, 0, NUM_SONGS_PLAYLIST);
for (Playlist::const_iterator song = this->standard_playlists[pl].begin(); song != this->standard_playlists[pl].end(); ++song) {
/* Music set indices in the settings playlist are 1-based, 0 means unused slot */
settings_pl[num++] = (byte)song->set_index + 1;
}
}
void ResetMusic()
{
_music_wnd_cursong = 1;
DoPlaySong();
}
/**
* Check music playback status and start/stop/song-finished.
@@ -282,30 +422,7 @@ void ResetMusic()
*/
void MusicLoop()
{
if (!_settings_client.music.playing && _song_is_active) {
StopMusic();
} else if (_settings_client.music.playing && !_song_is_active) {
PlayPlaylistSong();
}
if (!_song_is_active) return;
if (!MusicDriver::GetInstance()->IsSongPlaying()) {
if (_game_mode != GM_MENU) {
StopMusic();
SkipToNextSong();
PlayPlaylistSong();
} else {
ResetMusic();
}
}
}
static void SelectPlaylist(byte list)
{
_settings_client.music.playlist = list;
InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
_music.CheckStatus();
}
/**
@@ -315,29 +432,20 @@ static void SelectPlaylist(byte list)
void ChangeMusicSet(int index)
{
if (BaseMusic::GetIndexOfUsedSet() == index) return;
/* Resume playback after switching?
* Always if music is already playing, and also if the user is switching
* away from an empty music set.
* If the user switches away from an empty set, assume it's because they
* want to hear music now. */
bool shouldplay = _song_is_active || (BaseMusic::GetUsedSet()->num_available == 0);
StopMusic();
const char *name = BaseMusic::GetSet(index)->name;
BaseMusic::SetSet(name);
free(BaseMusic::ini_set);
BaseMusic::ini_set = stredup(name);
InitializeMusic();
ResetPlaylist();
_settings_client.music.playing = shouldplay;
InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
InvalidateWindowData(WC_MUSIC_WINDOW, 0);
InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS, 0, true);
_music.ChangeMusicSet(name);
}
/**
* Prepare the music system for use.
* Called from \c InitializeGame
*/
void InitializeMusic()
{
_music.BuildPlaylists();
}
struct MusicTrackSelectionWindow : public Window {
MusicTrackSelectionWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
{
@@ -394,13 +502,10 @@ struct MusicTrackSelectionWindow : public Window {
case WID_MTS_LIST_LEFT: case WID_MTS_LIST_RIGHT: {
Dimension d = {0, 0};
for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
const char *song_name = GetSongName(i);
if (StrEmpty(song_name)) continue;
SetDParam(0, GetTrackNumber(i));
for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) {
SetDParam(0, song->tracknr);
SetDParam(1, 2);
SetDParamStr(2, GetSongName(i));
SetDParamStr(2, song->songname);
Dimension d2 = GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME);
d.width = max(d.width, d2.width);
d.height += d2.height;
@@ -420,13 +525,10 @@ struct MusicTrackSelectionWindow : public Window {
GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
int y = r.top + WD_FRAMERECT_TOP;
for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
const char *song_name = GetSongName(i);
if (StrEmpty(song_name)) continue;
SetDParam(0, GetTrackNumber(i));
for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) {
SetDParam(0, song->tracknr);
SetDParam(1, 2);
SetDParamStr(2, song_name);
SetDParamStr(2, song->songname);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
y += FONT_HEIGHT_SMALL;
}
@@ -437,11 +539,10 @@ struct MusicTrackSelectionWindow : public Window {
GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
int y = r.top + WD_FRAMERECT_TOP;
for (const byte *p = _playlists[_settings_client.music.playlist]; *p != 0; p++) {
uint i = *p - 1;
SetDParam(0, GetTrackNumber(i));
for (MusicSystem::Playlist::const_iterator song = _music.active_playlist.begin(); song != _music.active_playlist.end(); ++song) {
SetDParam(0, song->tracknr);
SetDParam(1, 2);
SetDParamStr(2, GetSongName(i));
SetDParamStr(2, song->songname);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
y += FONT_HEIGHT_SMALL;
}
@@ -455,42 +556,13 @@ struct MusicTrackSelectionWindow : public Window {
switch (widget) {
case WID_MTS_LIST_LEFT: { // add to playlist
int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL);
if (_settings_client.music.playlist < 4) return;
if (!IsInsideMM(y, 0, BaseMusic::GetUsedSet()->num_available)) return;
byte *p = _playlists[_settings_client.music.playlist];
for (uint i = 0; i != NUM_SONGS_PLAYLIST - 1; i++) {
if (p[i] == 0) {
/* Find the actual song number */
for (uint j = 0; j < NUM_SONGS_AVAILABLE; j++) {
if (GetTrackNumber(j) == y + 1) {
p[i] = j + 1;
break;
}
}
p[i + 1] = 0;
this->SetDirty();
ResetPlaylist();
break;
}
}
_music.PlaylistAdd(y);
break;
}
case WID_MTS_LIST_RIGHT: { // remove from playlist
int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL);
if (_settings_client.music.playlist < 4) return;
if (!IsInsideMM(y, 0, NUM_SONGS_PLAYLIST)) return;
byte *p = _playlists[_settings_client.music.playlist];
for (uint i = y; i != NUM_SONGS_PLAYLIST - 1; i++) {
p[i] = p[i + 1];
}
this->SetDirty();
ResetPlaylist();
_music.PlaylistRemove(y);
break;
}
@@ -502,17 +574,12 @@ struct MusicTrackSelectionWindow : public Window {
}
case WID_MTS_CLEAR: // clear
for (uint i = 0; _playlists[_settings_client.music.playlist][i] != 0; i++) _playlists[_settings_client.music.playlist][i] = 0;
this->SetDirty();
StopMusic();
ResetPlaylist();
_music.PlaylistClear();
break;
case WID_MTS_ALL: case WID_MTS_OLD: case WID_MTS_NEW:
case WID_MTS_EZY: case WID_MTS_CUSTOM1: case WID_MTS_CUSTOM2: // set playlist
SelectPlaylist(widget - WID_MTS_ALL);
StopMusic();
ResetPlaylist();
_music.ChangePlaylist((MusicSystem::PlaylistChoices)(widget - WID_MTS_ALL));
break;
}
}
@@ -627,8 +694,8 @@ struct MusicWindow : public Window {
case WID_M_TRACK_NAME: {
Dimension d = GetStringBoundingBox(STR_MUSIC_TITLE_NONE);
for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
SetDParamStr(0, GetSongName(i));
for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) {
SetDParamStr(0, song->songname);
d = maxdim(d, GetStringBoundingBox(STR_MUSIC_TITLE_NAME));
}
d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
@@ -654,8 +721,8 @@ struct MusicWindow : public Window {
break;
}
StringID str = STR_MUSIC_TRACK_NONE;
if (_song_is_active != 0 && _music_wnd_cursong != 0) {
SetDParam(0, GetTrackNumber(_music_wnd_cursong - 1));
if (_music.IsPlaying()) {
SetDParam(0, _music.GetCurrentSong().tracknr);
SetDParam(1, 2);
str = STR_MUSIC_TRACK_DIGIT;
}
@@ -668,9 +735,9 @@ struct MusicWindow : public Window {
StringID str = STR_MUSIC_TITLE_NONE;
if (BaseMusic::GetUsedSet()->num_available == 0) {
str = STR_MUSIC_TITLE_NOMUSIC;
} else if (_song_is_active != 0 && _music_wnd_cursong != 0) {
} else if (_music.IsPlaying()) {
str = STR_MUSIC_TITLE_NAME;
SetDParamStr(0, GetSongName(_music_wnd_cursong - 1));
SetDParamStr(0, _music.GetCurrentSong().songname);
}
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str, TC_FROMSTRING, SA_HOR_CENTER);
break;
@@ -710,23 +777,19 @@ struct MusicWindow : public Window {
{
switch (widget) {
case WID_M_PREV: // skip to prev
if (!_song_is_active) return;
SkipToPrevSong();
this->SetDirty();
_music.Prev();
break;
case WID_M_NEXT: // skip to next
if (!_song_is_active) return;
SkipToNextSong();
this->SetDirty();
_music.Next();
break;
case WID_M_STOP: // stop playing
_settings_client.music.playing = false;
_music.Stop();
break;
case WID_M_PLAY: // start playing
_settings_client.music.playing = true;
_music.Play();
break;
case WID_M_MUSIC_VOL: case WID_M_EFFECT_VOL: { // volume sliders
@@ -741,7 +804,7 @@ struct MusicWindow : public Window {
if (new_vol < 3) new_vol = 0;
if (new_vol != *vol) {
*vol = new_vol;
if (widget == WID_M_MUSIC_VOL) MusicVolumeChanged(new_vol);
if (widget == WID_M_MUSIC_VOL) MusicDriver::GetInstance()->SetVolume(new_vol);
this->SetDirty();
}
@@ -750,12 +813,13 @@ struct MusicWindow : public Window {
}
case WID_M_SHUFFLE: // toggle shuffle
_settings_client.music.shuffle ^= 1;
this->SetWidgetLoweredState(WID_M_SHUFFLE, _settings_client.music.shuffle);
if (_music.IsShuffle()) {
_music.Unshuffle();
} else {
_music.Shuffle();
}
this->SetWidgetLoweredState(WID_M_SHUFFLE, _music.IsShuffle());
this->SetWidgetDirty(WID_M_SHUFFLE);
StopMusic();
ResetPlaylist();
this->SetDirty();
break;
case WID_M_PROGRAMME: // show track selection
@@ -764,10 +828,7 @@ struct MusicWindow : public Window {
case WID_M_ALL: case WID_M_OLD: case WID_M_NEW:
case WID_M_EZY: case WID_M_CUSTOM1: case WID_M_CUSTOM2: // playlist
SelectPlaylist(widget - WID_M_ALL);
StopMusic();
ResetPlaylist();
this->SetDirty();
_music.ChangePlaylist((MusicSystem::PlaylistChoices)(widget - WID_M_ALL));
break;
}
}

View File

@@ -4749,7 +4749,7 @@ static void NewSpriteGroup(ByteReader *buf)
}
}
group->num_ranges = optimised.size();
group->num_ranges = (uint)optimised.size(); // cast is safe, there should never be 2**31 elements here
if (group->num_ranges > 0) {
group->ranges = MallocT<DeterministicSpriteGroupRange>(group->num_ranges);
MemCpyT(group->ranges, &optimised.front(), group->num_ranges);

View File

@@ -393,8 +393,7 @@ static void LoadIntroGame(bool load_newgrfs = true)
CheckForMissingGlyphs();
/* Play main theme */
if (MusicDriver::GetInstance()->IsSongPlaying()) ResetMusic();
MusicLoop(); // ensure music is correct
}
void MakeNewgameSettingsLive()

View File

@@ -688,6 +688,10 @@ static void ShipController(Ship *v)
if (v->current_order.IsType(OT_LEAVESTATION)) {
v->current_order.Free();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
/* Test if continuing forward would lead to a dead-end, moving into the dock. */
DiagDirection exitdir = VehicleExitDir(v->direction, v->state);
TileIndex tile = TileAddByDiagDir(v->tile, exitdir);
if (TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, exitdir)) == TRACK_BIT_NONE) goto reverse_direction;
} else if (v->dest_tile != 0) {
/* We have a target, let's see if we reached it... */
if (v->current_order.IsType(OT_GOTO_WAYPOINT) &&

View File

@@ -47,6 +47,7 @@
#include "game/game.hpp"
#include "zoom_func.h"
#include "zoning.h"
#include "scope.h"
#include "table/strings.h"
#include "table/town_land.h"
@@ -1736,6 +1737,9 @@ void UpdateTownMaxPass(Town *t)
t->supplied[CT_MAIL].old_max = t->cache.population >> 4;
}
static void UpdateTownGrowthRate(Town *t);
static void UpdateTownGrowth(Town *t);
/**
* Does the actual town creation.
*
@@ -1815,6 +1819,7 @@ static void DoCreateTown(Town *t, TileIndex tile, uint32 townnameparts, TownSize
t->cache.num_houses -= x;
UpdateTownRadius(t);
UpdateTownGrowthRate(t);
UpdateTownMaxPass(t);
UpdateAirportsNoise();
}
@@ -2523,6 +2528,7 @@ static void DoBuildHouse(Town *t, TileIndex tile, HouseID house, byte random_bit
MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits);
UpdateTownRadius(t);
UpdateTownGrowthRate(t);
UpdateTownCargoes(t, tile);
}
@@ -2781,8 +2787,6 @@ const CargoSpec *FindFirstCargoWithTownEffect(TownEffect effect)
return NULL;
}
static void UpdateTownGrowRate(Town *t);
/**
* Change the cargo goal of a town.
* @param tile Unused.
@@ -2811,7 +2815,7 @@ CommandCost CmdTownCargoGoal(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
if (flags & DC_EXEC) {
t->goal[te] = p2;
UpdateTownGrowRate(t);
UpdateTownGrowth(t);
InvalidateWindowData(WC_TOWN_VIEW, index);
}
@@ -2861,7 +2865,7 @@ CommandCost CmdTownGrowthRate(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
if (flags & DC_EXEC) {
if (p2 == 0) {
/* Just clear the flag, UpdateTownGrowRate will determine a proper growth rate */
/* Just clear the flag, UpdateTownGrowth will determine a proper growth rate */
ClrBit(t->flags, TOWN_CUSTOM_GROWTH);
} else {
uint old_rate = t->growth_rate;
@@ -2875,7 +2879,7 @@ CommandCost CmdTownGrowthRate(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
t->growth_rate = p2;
SetBit(t->flags, TOWN_CUSTOM_GROWTH);
}
UpdateTownGrowRate(t);
UpdateTownGrowth(t);
InvalidateWindowData(WC_TOWN_VIEW, p1);
}
@@ -3162,7 +3166,7 @@ static CommandCost TownActionFundBuildings(Town *t, DoCommandFlag flags)
t->fund_buildings_months = 3;
/* Enable growth (also checking GameScript's opinion) */
UpdateTownGrowRate(t);
UpdateTownGrowth(t);
/* Build a new house, but add a small delay to make sure
* that spamming funding doesn't let town grow any faster
@@ -3368,10 +3372,121 @@ static void UpdateTownRating(Town *t)
SetWindowDirty(WC_TOWN_AUTHORITY, t->index);
}
static void UpdateTownGrowRate(Town *t)
/**
* Updates town grow counter after growth rate change.
* Preserves relative house builting progress whenever it can.
* @param town The town to calculate grow counter for
* @param prev_growth_rate Town growth rate before it changed (one that was used with grow counter to be updated)
*/
static void UpdateTownGrowCounter(Town *t, uint16 prev_growth_rate)
{
ClrBit(t->flags, TOWN_IS_GROWING);
if (t->growth_rate == TOWN_GROWTH_RATE_NONE) return;
if (prev_growth_rate == TOWN_GROWTH_RATE_NONE) {
t->grow_counter = min(t->growth_rate, t->grow_counter);
return;
}
t->grow_counter = RoundDivSU((uint32)t->grow_counter * (t->growth_rate + 1), prev_growth_rate + 1);
}
/**
* Calculates amount of active stations in the range of town (HZB_TOWN_EDGE).
* @param town The town to calculate stations for
* @returns Amount of active stations
*/
static int CountActiveStations(Town *t)
{
int n = 0;
const Station *st;
FOR_ALL_STATIONS(st) {
if (DistanceSquare(st->xy, t->xy) <= t->cache.squared_town_zone_radius[0]) {
if (st->time_since_load <= 20 || st->time_since_unload <= 20) {
n++;
}
}
}
return n;
}
/**
* Calculates town growth rate in normal conditions (custom growth rate not set).
* If town growth speed is set to None(0) returns the same rate as if it was Normal(2).
* @param town The town to calculate growth rate for
* @returns Calculated growth rate
*/
static uint GetNormalGrowthRate(Town *t)
{
static const uint16 _grow_count_values[2][6] = {
{ 120, 120, 120, 100, 80, 60 }, // Fund new buildings has been activated
{ 320, 420, 300, 220, 160, 100 } // Normal values
};
int n = CountActiveStations(t);
uint16 m = _grow_count_values[t->fund_buildings_months != 0 ? 0 : 1][min(n, 5)];
int growth_multiplier;
if (_settings_game.economy.town_growth_rate == 0) {
growth_multiplier = 1;
} else if (_settings_game.economy.town_growth_rate > 0) {
growth_multiplier = _settings_game.economy.town_growth_rate - 1;
} else {
growth_multiplier = _settings_game.economy.town_growth_rate;
}
if (growth_multiplier < 0) {
m <<= (-growth_multiplier);
} else {
m >>= growth_multiplier;
}
if (t->larger_town) m /= 2;
if (_settings_game.economy.town_growth_cargo_transported > 0) {
uint32 inverse_m = UINT32_MAX / m;
auto calculate_cargo_ratio_fix15 = [](const TransportedCargoStat<uint32> &stat) -> uint32 {
return stat.old_max ? ((uint64) (stat.old_act << 15)) / stat.old_max : 1 << 15;
};
uint32 cargo_ratio_fix16 = calculate_cargo_ratio_fix15(t->supplied[CT_PASSENGERS]) + calculate_cargo_ratio_fix15(t->supplied[CT_MAIL]);
uint32 cargo_dependant_part = (((uint64) cargo_ratio_fix16) * ((uint64) inverse_m) * _settings_game.economy.town_growth_cargo_transported) >> 16;
uint32 non_cargo_dependant_part = ((uint64) inverse_m) * (100 - _settings_game.economy.town_growth_cargo_transported);
uint32 total = (cargo_dependant_part + non_cargo_dependant_part);
if (total == 0) {
ClrBit(t->flags, TOWN_IS_GROWING);
return UINT16_MAX;
}
m = ((uint64) UINT32_MAX * 100) / total;
}
return TownTicksToGameTicks(m / (t->cache.num_houses / 50 + 1));
}
/**
* Updates town growth rate.
* @param town The town to update growth rate for
*/
static void UpdateTownGrowthRate(Town *t)
{
if (HasBit(t->flags, TOWN_CUSTOM_GROWTH)) return;
uint old_rate = t->growth_rate;
t->growth_rate = GetNormalGrowthRate(t);
UpdateTownGrowCounter(t, old_rate);
SetWindowDirty(WC_TOWN_VIEW, t->index);
}
/**
* Updates town growth state (whether it is growing or not).
* @param town The town to update growth for
*/
static void UpdateTownGrowth(Town *t)
{
auto guard = scope_guard([t]() {
SetWindowDirty(WC_TOWN_VIEW, t->index);
});
SetBit(t->flags, TOWN_IS_GROWING);
UpdateTownGrowthRate(t);
if (!HasBit(t->flags, TOWN_IS_GROWING)) return;
ClrBit(t->flags, TOWN_IS_GROWING);
if (_settings_game.economy.town_growth_rate == 0 && t->fund_buildings_months == 0) return;
@@ -3398,71 +3513,9 @@ static void UpdateTownGrowRate(Town *t)
return;
}
/**
* Towns are processed every TOWN_GROWTH_TICKS ticks, and this is the
* number of times towns are processed before a new building is built.
*/
static const uint16 _grow_count_values[2][6] = {
{ 120, 120, 120, 100, 80, 60 }, // Fund new buildings has been activated
{ 320, 420, 300, 220, 160, 100 } // Normal values
};
int n = 0;
const Station *st;
FOR_ALL_STATIONS(st) {
if (DistanceSquare(st->xy, t->xy) <= t->cache.squared_town_zone_radius[0]) {
if (st->time_since_load <= 20 || st->time_since_unload <= 20) {
n++;
}
}
}
uint16 m;
if (t->fund_buildings_months != 0) {
m = _grow_count_values[0][min(n, 5)];
} else {
m = _grow_count_values[1][min(n, 5)];
if (n == 0 && !Chance16(1, 12)) return;
}
/* Use the normal growth rate values if new buildings have been funded in
* this town and the growth rate is set to none. */
int growth_multiplier;
if (_settings_game.economy.town_growth_rate == 0) {
growth_multiplier = 1;
} else if (_settings_game.economy.town_growth_rate > 0) {
growth_multiplier = _settings_game.economy.town_growth_rate - 1;
} else {
growth_multiplier = _settings_game.economy.town_growth_rate;
}
if (growth_multiplier < 0) {
m <<= (-growth_multiplier);
} else {
m >>= growth_multiplier;
}
if (t->larger_town) m /= 2;
if (_settings_game.economy.town_growth_cargo_transported > 0) {
uint32 inverse_m = UINT32_MAX / m;
auto calculate_cargo_ratio_fix15 = [](const TransportedCargoStat<uint32> &stat) -> uint32 {
return stat.old_max ? ((uint64) (stat.old_act << 15)) / stat.old_max : 1 << 15;
};
uint32 cargo_ratio_fix16 = calculate_cargo_ratio_fix15(t->supplied[CT_PASSENGERS]) + calculate_cargo_ratio_fix15(t->supplied[CT_MAIL]);
uint32 cargo_dependant_part = (((uint64) cargo_ratio_fix16) * ((uint64) inverse_m) * _settings_game.economy.town_growth_cargo_transported) >> 16;
uint32 non_cargo_dependant_part = ((uint64) inverse_m) * (100 - _settings_game.economy.town_growth_cargo_transported);
uint32 total = (cargo_dependant_part + non_cargo_dependant_part);
if (total == 0) return;
m = ((uint64) UINT32_MAX * 100) / total;
}
t->growth_rate = TownTicksToGameTicks(m / (t->cache.num_houses / 50 + 1));
t->grow_counter = min(t->growth_rate, t->grow_counter);
if (t->fund_buildings_months == 0 && CountActiveStations(t) == 0 && !Chance16(1, 12)) return;
SetBit(t->flags, TOWN_IS_GROWING);
SetWindowDirty(WC_TOWN_VIEW, t->index);
}
static void UpdateTownAmounts(Town *t)
@@ -3698,8 +3751,8 @@ void TownsMonthlyLoop()
}
UpdateTownAmounts(t);
UpdateTownGrowth(t);
UpdateTownRating(t);
UpdateTownGrowRate(t);
UpdateTownUnwanted(t);
UpdateTownCargoes(t);
}

View File

@@ -753,6 +753,16 @@ public:
}
}
/**
* Get the string to draw the town name.
* @param t Town to draw.
* @return The string to use.
*/
static StringID GetTownString(const Town *t)
{
return t->larger_town ? STR_TOWN_DIRECTORY_CITY : STR_TOWN_DIRECTORY_TOWN;
}
virtual void DrawWidget(const Rect &r, int widget) const
{
switch (widget) {
@@ -791,7 +801,7 @@ public:
SetDParam(0, t->index);
SetDParam(1, t->cache.population);
DrawString(text_left, text_right, y + (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2, STR_TOWN_DIRECTORY_TOWN);
DrawString(text_left, text_right, y + (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2, GetTownString(t));
y += this->resize.step_height;
if (++n == this->vscroll->GetCapacity()) break; // max number of towns in 1 window
@@ -830,7 +840,7 @@ public:
SetDParam(0, t->index);
SetDParamMaxDigits(1, 8);
d = maxdim(d, GetStringBoundingBox(STR_TOWN_DIRECTORY_TOWN));
d = maxdim(d, GetStringBoundingBox(GetTownString(t)));
}
Dimension icon_size = GetSpriteSize(SPR_TOWN_RATING_GOOD);
d.width += icon_size.width + 2;

View File

@@ -692,4 +692,25 @@ static inline bool IsUphillTrackdir(Slope slope, Trackdir dir)
return HasBit(_uphill_trackdirs[RemoveHalftileSlope(slope)], dir);
}
/**
* Determine the side in which the vehicle will leave the tile
*
* @param direction vehicle direction
* @param track vehicle track bits
* @return side of tile the vehicle will leave
*/
static inline DiagDirection VehicleExitDir(Direction direction, TrackBits track)
{
static const TrackBits state_dir_table[DIAGDIR_END] = { TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER };
DiagDirection diagdir = DirToDiagDir(direction);
/* Determine the diagonal direction in which we will exit this tile */
if (!HasBit(direction, 0) && track != state_dir_table[diagdir]) {
diagdir = ChangeDiagDir(diagdir, DIAGDIRDIFF_90LEFT);
}
return diagdir;
}
#endif /* TRACK_FUNC_H */

View File

@@ -69,27 +69,6 @@ bool IsValidImageIndex<VEH_TRAIN>(uint8 image_index)
return image_index < lengthof(_engine_sprite_base);
}
/**
* Determine the side in which the train will leave the tile
*
* @param direction vehicle direction
* @param track vehicle track bits
* @return side of tile the train will leave
*/
static inline DiagDirection TrainExitDir(Direction direction, TrackBits track)
{
static const TrackBits state_dir_table[DIAGDIR_END] = { TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER };
DiagDirection diagdir = DirToDiagDir(direction);
/* Determine the diagonal direction in which we will exit this tile */
if (!HasBit(direction, 0) && track != state_dir_table[diagdir]) {
diagdir = ChangeDiagDir(diagdir, DIAGDIRDIFF_90LEFT);
}
return diagdir;
}
/**
* Return the cargo weight multiplier to use for a rail vehicle
@@ -469,7 +448,7 @@ int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *st
if (TrainCanLeaveTile(front)) {
/* Determine the non-diagonal direction in which we will exit this tile */
DiagDirection dir = TrainExitDir(front->direction, front->track);
DiagDirection dir = VehicleExitDir(front->direction, front->track);
/* Calculate next tile */
TileIndex tile = front->tile + TileOffsByDiagDir(dir);
@@ -2213,9 +2192,9 @@ void ReverseTrainDirection(Train *v)
return;
}
/* TrainExitDir does not always produce the desired dir for depots and
/* VehicleExitDir does not always produce the desired dir for depots and
* tunnels/bridges that is needed for UpdateSignalsOnSegment. */
DiagDirection dir = TrainExitDir(v->direction, v->track);
DiagDirection dir = VehicleExitDir(v->direction, v->track);
if (IsRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)) dir = INVALID_DIAGDIR;
if (UpdateSignalsOnSegment(v->tile, dir, v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) {
@@ -3634,7 +3613,7 @@ static Vehicle *CheckTrainAtSignal(Vehicle *v, void *data)
/* not front engine of a train, inside wormhole or depot, crashed */
if (!t->IsFrontEngine() || !(t->track & TRACK_BIT_MASK)) return NULL;
if (t->cur_speed > 5 || TrainExitDir(t->direction, t->track) != exitdir) return NULL;
if (t->cur_speed > 5 || VehicleExitDir(t->direction, t->track) != exitdir) return NULL;
return t;
}
@@ -4573,7 +4552,7 @@ static TileIndex TrainApproachingCrossingTile(const Train *v)
if (!TrainCanLeaveTile(v)) return INVALID_TILE;
DiagDirection dir = TrainExitDir(v->direction, v->track);
DiagDirection dir = VehicleExitDir(v->direction, v->track);
TileIndex tile = v->tile + TileOffsByDiagDir(dir);
/* not a crossing || wrong axis || unusable rail (wrong type or owner) */
@@ -4606,7 +4585,7 @@ static bool TrainCheckIfLineEnds(Train *v, bool reverse)
if (!TrainCanLeaveTile(v)) return true;
/* Determine the non-diagonal direction in which we will exit this tile */
DiagDirection dir = TrainExitDir(v->direction, v->track);
DiagDirection dir = VehicleExitDir(v->direction, v->track);
/* Calculate next tile */
TileIndex tile = v->tile + TileOffsByDiagDir(dir);
@@ -4689,7 +4668,7 @@ static bool TrainLocoHandler(Train *v, bool mode)
/* Try to reserve a path when leaving the station as we
* might not be marked as wanting a reservation, e.g.
* when an overlength train gets turned around in a station. */
DiagDirection dir = TrainExitDir(v->direction, v->track);
DiagDirection dir = VehicleExitDir(v->direction, v->track);
if (IsRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)) dir = INVALID_DIAGDIR;
if (UpdateSignalsOnSegment(v->tile, dir, v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) {