Merge branch 'master' into jgrpp

# Conflicts:
#	src/string.cpp
This commit is contained in:
Jonathan G Rennison
2018-06-07 06:49:18 +01:00
122 changed files with 2335 additions and 397 deletions

View File

@@ -285,11 +285,29 @@ static const uint NUM_SONGS_AVAILABLE = 1 + NUM_SONG_CLASSES * NUM_SONGS_CLASS;
/** Maximum number of songs in the (custom) playlist */
static const uint NUM_SONGS_PLAYLIST = 32;
/* Functions to read DOS music CAT files, similar to but not quite the same as sound effect CAT files */
char *GetMusicCatEntryName(const char *filename, size_t entrynum);
byte *GetMusicCatEntryData(const char *filename, size_t entrynum, size_t &entrylen);
enum MusicTrackType {
MTT_STANDARDMIDI, ///< Standard MIDI file
MTT_MPSMIDI, ///< MPS GM driver MIDI format (contained in a CAT file)
};
/** Metadata about a music track. */
struct MusicSongInfo {
char songname[32]; ///< name of song displayed in UI
byte tracknr; ///< track number of song displayed in UI
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
};
/** All data of a music set. */
struct MusicSet : BaseSet<MusicSet, NUM_SONGS_AVAILABLE, false> {
/** The name of the different songs. */
char song_name[NUM_SONGS_AVAILABLE][32];
byte track_nr[NUM_SONGS_AVAILABLE];
/** Data about individual songs in set. */
MusicSongInfo songinfo[NUM_SONGS_AVAILABLE];
/** Number of valid songs in set. */
byte num_available;
bool FillSetDetails(struct IniFile *ini, const char *path, const char *full_filename);

View File

@@ -532,7 +532,7 @@ FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir,
* Create a directory with the given name
* @param name the new name of the directory
*/
static void FioCreateDirectory(const char *name)
void FioCreateDirectory(const char *name)
{
/* Ignore directory creation errors; they'll surface later on, and most
* of the time they are 'directory already exists' errors anyhow. */

View File

@@ -55,6 +55,7 @@ char *FioGetFullPath(char *buf, const char *last, Searchpath sp, Subdirectory su
char *FioFindFullPath(char *buf, const char *last, Subdirectory subdir, const char *filename);
char *FioAppendDirectory(char *buf, const char *last, Searchpath sp, Subdirectory subdir);
char *FioGetDirectory(char *buf, const char *last, Subdirectory subdir);
void FioCreateDirectory(const char *name);
const char *FiosGetScreenshotDir();

View File

@@ -75,6 +75,7 @@ public:
virtual GlyphID MapCharToGlyph(WChar key) { assert(IsPrintable(key)); return SPRITE_GLYPH | key; }
virtual const void *GetFontTable(uint32 tag, size_t &length) { length = 0; return NULL; }
virtual const char *GetFontName() { return "sprite"; }
virtual bool IsBuiltInFont() { return true; }
};
/**
@@ -254,6 +255,7 @@ public:
virtual GlyphID MapCharToGlyph(WChar key);
virtual const void *GetFontTable(uint32 tag, size_t &length);
virtual const char *GetFontName() { return face->family_name; }
virtual bool IsBuiltInFont() { return false; }
};
FT_Library _library = NULL;

View File

@@ -147,6 +147,11 @@ public:
{
return this->parent != NULL;
}
/**
* Is this a built-in sprite font?
*/
virtual bool IsBuiltInFont() = 0;
};
/** Get the SpriteID mapped to the given font size and key */

View File

@@ -21,6 +21,10 @@
#include <unicode/ustring.h>
#endif /* WITH_ICU_LAYOUT */
#ifdef WITH_UNISCRIBE
#include "os/windows/string_uniscribe.h"
#endif /* WITH_UNISCRIBE */
#include "safeguards.h"
@@ -113,26 +117,12 @@ le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &poin
return FALSE;
}
static size_t AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c)
{
/* Transform from UTF-32 to internal ICU format of UTF-16. */
int32 length = 0;
UErrorCode err = U_ZERO_ERROR;
u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err);
return length;
}
/**
* Wrapper for doing layouts with ICU.
*/
class ICUParagraphLayout : public AutoDeleteSmallVector<ParagraphLayouter::Line *, 4>, public ParagraphLayouter {
ParagraphLayout *p; ///< The actual ICU paragraph layout.
public:
/** Helper for GetLayouter, to get the right type. */
typedef UChar CharType;
/** Helper for GetLayouter, to get whether the layouter supports RTL. */
static const bool SUPPORTS_RTL = true;
/** Visual run contains data about the bit of text with the same font. */
class ICUVisualRun : public ParagraphLayouter::VisualRun {
const ParagraphLayout::VisualRun *vr; ///< The actual ICU vr.
@@ -184,35 +174,54 @@ public:
}
};
static ParagraphLayouter *GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping)
{
int32 length = buff_end - buff;
/**
* Helper class to construct a new #ICUParagraphLayout.
*/
class ICUParagraphLayoutFactory {
public:
/** Helper for GetLayouter, to get the right type. */
typedef UChar CharType;
/** Helper for GetLayouter, to get whether the layouter supports RTL. */
static const bool SUPPORTS_RTL = true;
if (length == 0) {
/* ICU's ParagraphLayout cannot handle empty strings, so fake one. */
buff[0] = ' ';
length = 1;
fontMapping.End()[-1].first++;
static ParagraphLayouter *GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping)
{
int32 length = buff_end - buff;
if (length == 0) {
/* ICU's ParagraphLayout cannot handle empty strings, so fake one. */
buff[0] = ' ';
length = 1;
fontMapping.End()[-1].first++;
}
/* Fill ICU's FontRuns with the right data. */
FontRuns runs(fontMapping.Length());
for (FontMap::iterator iter = fontMapping.Begin(); iter != fontMapping.End(); iter++) {
runs.add(iter->second, iter->first);
}
LEErrorCode status = LE_NO_ERROR;
/* ParagraphLayout does not copy "buff", so it must stay valid.
* "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */
ParagraphLayout *p = new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
if (status != LE_NO_ERROR) {
delete p;
return NULL;
}
return new ICUParagraphLayout(p);
}
/* Fill ICU's FontRuns with the right data. */
FontRuns runs(fontMapping.Length());
for (FontMap::iterator iter = fontMapping.Begin(); iter != fontMapping.End(); iter++) {
runs.add(iter->second, iter->first);
static size_t AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c)
{
/* Transform from UTF-32 to internal ICU format of UTF-16. */
int32 length = 0;
UErrorCode err = U_ZERO_ERROR;
u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err);
return length;
}
LEErrorCode status = LE_NO_ERROR;
/* ParagraphLayout does not copy "buff", so it must stay valid.
* "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */
ParagraphLayout *p = new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
if (status != LE_NO_ERROR) {
delete p;
return NULL;
}
return new ICUParagraphLayout(p);
}
};
#endif /* WITH_ICU_LAYOUT */
/*** Paragraph layout ***/
@@ -236,11 +245,6 @@ static ParagraphLayouter *GetParagraphLayout(UChar *buff, UChar *buff_end, FontM
*/
class FallbackParagraphLayout : public ParagraphLayouter {
public:
/** Helper for GetLayouter, to get the right type. */
typedef WChar CharType;
/** Helper for GetLayouter, to get whether the layouter supports RTL. */
static const bool SUPPORTS_RTL = false;
/** Visual run contains data about the bit of text with the same font. */
class FallbackVisualRun : public ParagraphLayouter::VisualRun {
Font *font; ///< The font used to layout these.
@@ -280,6 +284,42 @@ public:
const ParagraphLayouter::Line *NextLine(int max_width);
};
/**
* Helper class to construct a new #FallbackParagraphLayout.
*/
class FallbackParagraphLayoutFactory {
public:
/** Helper for GetLayouter, to get the right type. */
typedef WChar CharType;
/** Helper for GetLayouter, to get whether the layouter supports RTL. */
static const bool SUPPORTS_RTL = false;
/**
* Get the actual ParagraphLayout for the given buffer.
* @param buff The begin of the buffer.
* @param buff_end The location after the last element in the buffer.
* @param fontMapping THe mapping of the fonts.
* @return The ParagraphLayout instance.
*/
static ParagraphLayouter *GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping)
{
return new FallbackParagraphLayout(buff, buff_end - buff, fontMapping);
}
/**
* Append a wide character to the internal buffer.
* @param buff The buffer to append to.
* @param buffer_last The end of the buffer.
* @param c The character to add.
* @return The number of buffer spaces that were used.
*/
static size_t AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c)
{
*buff = c;
return 1;
}
};
/**
* Create the visual run.
* @param font The font to use for this run.
@@ -536,31 +576,6 @@ const ParagraphLayouter::Line *FallbackParagraphLayout::NextLine(int max_width)
return l;
}
/**
* Appand a wide character to the internal buffer.
* @param buff The buffer to append to.
* @param buffer_last The end of the buffer.
* @param c The character to add.
* @return The number of buffer spaces that were used.
*/
static size_t AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c)
{
*buff = c;
return 1;
}
/**
* Get the actual ParagraphLayout for the given buffer.
* @param buff The begin of the buffer.
* @param buff_end The location after the last element in the buffer.
* @param fontMapping THe mapping of the fonts.
* @return The ParagraphLayout instance.
*/
static FallbackParagraphLayout *GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping)
{
return new FallbackParagraphLayout(buff, buff_end - buff, fontMapping);
}
/**
* Helper for getting a ParagraphLayouter of the given type.
*
@@ -605,7 +620,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, const char *&str,
* will not be handled in the fallback non ICU case because they are
* mostly needed for RTL languages which need more ICU support. */
if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue;
buff += AppendToBuffer(buff, buffer_last, c);
buff += T::AppendToBuffer(buff, buffer_last, c);
continue;
}
@@ -621,7 +636,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, const char *&str,
if (!fontMapping.Contains(buff - buff_begin)) {
fontMapping.Insert(buff - buff_begin, f);
}
line.layout = GetParagraphLayout(buff_begin, buff, fontMapping);
line.layout = T::GetParagraphLayout(buff_begin, buff, fontMapping);
line.state_after = state;
}
@@ -654,11 +669,11 @@ Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsi
line.layout->Reflow();
} else {
/* Line is new, layout it */
#ifdef WITH_ICU_LAYOUT
FontState old_state = state;
const char *old_str = str;
GetLayouter<ICUParagraphLayout>(line, str, state);
#ifdef WITH_ICU_LAYOUT
GetLayouter<ICUParagraphLayoutFactory>(line, str, state);
if (line.layout == NULL) {
static bool warned = false;
if (!warned) {
@@ -668,11 +683,22 @@ Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsi
state = old_state;
str = old_str;
GetLayouter<FallbackParagraphLayout>(line, str, state);
}
#else
GetLayouter<FallbackParagraphLayout>(line, str, state);
#endif
#ifdef WITH_UNISCRIBE
if (line.layout == NULL) {
GetLayouter<UniscribeParagraphLayoutFactory>(line, str, state);
if (line.layout == NULL) {
state = old_state;
str = old_str;
}
}
#endif
if (line.layout == NULL) {
GetLayouter<FallbackParagraphLayoutFactory>(line, str, state);
}
}
/* Copy all lines into a local cache so we can reuse them later on more easily. */
@@ -809,6 +835,10 @@ void Layouter::ResetFontCache(FontSize size)
/* We must reset the linecache since it references the just freed fonts */
ResetLineCache();
#if defined(WITH_UNISCRIBE)
UniscribeResetScriptCache(size);
#endif
}
/**

View File

@@ -2972,8 +2972,6 @@ STR_TOWN_POPULATION :{BLACK}Wêreldb
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (City)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Bevolking: {ORANGE}{COMMA}{BLACK} Huise: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passasiers verlede maand: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Pos verlede maand: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Vrag nodig om dorp te laat groei:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} vereis
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} vereis in winter

View File

@@ -2587,8 +2587,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}الركاب الشهر الماضي: {ORANGE}{COMMA}{BLACK} الأقصى: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}طرود البريد الشهر الماضي: {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}مطلوب في الشتاء

View File

@@ -2861,8 +2861,6 @@ STR_TOWN_POPULATION :{BLACK}Munduko
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Hiria)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Biztanleria: {ORANGE}{COMMA}{BLACK} Etxe kopurua: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Bidaiari kopurua aurreko hilabetean: {ORANGE}{COMMA}{BLACK} gehienez: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Posta kopurua aurreko hilabetean: {ORANGE}{COMMA}{BLACK} gehienez: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Beharrezko zama herri hazkunderako:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} beharrezkoa
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} Neguan beharrezkoa

View File

@@ -3318,8 +3318,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}Пасажыраў за мінулы месяц: {ORANGE}{COMMA}{BLACK} Макс.: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Пошты за мінулы месяц: {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 е e e ю}цца
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} патрабу{G 0 е e e ю}цца ўзімку

View File

@@ -2976,8 +2976,6 @@ STR_TOWN_POPULATION :{BLACK}Populaç
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Cidade)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}População: {ORANGE}{COMMA}{BLACK} Casas: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passageiros no mês passado: {ORANGE}{COMMA}{BLACK} máx: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Cartas no mês passado: {ORANGE}{COMMA}{BLACK} máx: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Carga necessária para prover o crescimento:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} necessário(a)
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} necessário no inverno

View File

@@ -2909,8 +2909,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}Пътници през последния месец: {ORANGE}{COMMA}{BLACK} максимум: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Поща през последния месец: {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} необходим през зимата

View File

@@ -2976,8 +2976,6 @@ STR_TOWN_POPULATION :{BLACK}Poblaci
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Ciutat)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Població: {ORANGE}{COMMA}{BLACK} Cases: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passatgers el darrer mes: {ORANGE}{COMMA}{BLACK} màx: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Correu el darrer mes: {ORANGE}{COMMA}{BLACK} màx: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Càrrega requerida per tal que la població creixi:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} requerides
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} requerit a l'hivern

View File

@@ -3081,8 +3081,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}Putnika prošli mjesec: {ORANGE}{COMMA}{BLACK} najviše: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Pošte prošli mjesec: {ORANGE}{COMMA}{BLACK} najviše: {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

View File

@@ -3068,8 +3068,6 @@ STR_TOWN_POPULATION :{BLACK}Populace
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (velkoměsto)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Populace: {ORANGE}{COMMA}{BLACK} Domů: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Cestujících minulý měsíc: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Pošta za minulý měsíc: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Množství doručeného nákladu potřebného pro rozvoj města:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED}Je potřeba {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} vyžadováno v zimě

View File

@@ -2976,8 +2976,6 @@ STR_TOWN_POPULATION :{BLACK}Verdens
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (by)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Indbyggere: {ORANGE}{COMMA}{BLACK} Huse: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passagerer sidste måned: {ORANGE}{COMMA}{BLACK} maks.: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post sidste måned: {ORANGE}{COMMA}{BLACK} maks.: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Nødvendig godsmængde for at byen kan vokse:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} krævet
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} kræves om vinteren

View File

@@ -2975,8 +2975,6 @@ STR_TOWN_POPULATION :{BLACK}Wereldbe
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Groeistad)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Bevolking: {ORANGE}{COMMA}{BLACK} Huizen: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passagiers afgelopen maand: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post afgelopen maand: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Vracht nodig voor groei van stad:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} noodzakelijk
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} noodzakelijk in de winter

View File

@@ -3457,8 +3457,7 @@ STR_TOWN_POPULATION :{BLACK}World po
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (City)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Population: {ORANGE}{COMMA}{BLACK} Houses: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passengers last month: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Mail last month: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} last month: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Cargo needed for town growth:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} required
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} required in winter
@@ -4998,6 +4997,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... this
STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... road facing in the wrong direction
STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... drive through stops can't have corners
STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... drive through stops can't have junctions
STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... road is one way or blocked
# Station destruction related errors
STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Can't remove part of station...
@@ -5262,6 +5262,8 @@ STR_BASESOUNDS_DOS_DESCRIPTION :Original Transp
STR_BASESOUNDS_WIN_DESCRIPTION :Original Transport Tycoon Deluxe Windows edition sounds.
STR_BASESOUNDS_NONE_DESCRIPTION :A sound pack without any sounds.
STR_BASEMUSIC_WIN_DESCRIPTION :Original Transport Tycoon Deluxe Windows edition music.
STR_BASEMUSIC_DOS_DESCRIPTION :Original Transport Tycoon Deluxe DOS edition music.
STR_BASEMUSIC_TTO_DESCRIPTION :Original Transport Tycoon (Original/World Editor) DOS edition music.
STR_BASEMUSIC_NONE_DESCRIPTION :A music pack without actual music.
##id 0x2000

View File

@@ -2938,8 +2938,6 @@ STR_TOWN_POPULATION :{BLACK}World po
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (City)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Population: {ORANGE}{COMMA}{BLACK} Houses: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passengers last month: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Mail last month: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Cargo needed for town growth:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} required
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} required in winter

View File

@@ -2975,8 +2975,6 @@ STR_TOWN_POPULATION :{BLACK}World po
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (City)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Population: {ORANGE}{COMMA}{BLACK} Houses: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passengers last month: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Mail last month: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Cargo needed for town growth:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} required
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} required in winter

View File

@@ -2507,8 +2507,6 @@ STR_TOWN_POPULATION :{BLACK}Monda en
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Urbo)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Enloĝantoj: {ORANGE}{COMMA}{BLACK} Domoj: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Pasaĝeroj lastmonate: {ORANGE}{COMMA}{BLACK} maksimume: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Poŝto lastmonate: {ORANGE}{COMMA}{BLACK} maksimume: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Kargo bezonata por kreskigi urbon:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} bezonatas
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} bezonatas en vintro

View File

@@ -3029,8 +3029,6 @@ STR_TOWN_POPULATION :{BLACK}Maailma
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Linn)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Rahvaarv: {ORANGE}{COMMA}{BLACK} Ehitisi: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Reisijaid eelmisel kuul: {ORANGE}{COMMA}{BLACK} Enim: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Posti eelmisel kuul: {ORANGE}{COMMA}{BLACK} Enim: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Veoseid linna kasvamiseks:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} vajalik
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} on talvel vajalik

View File

@@ -2641,8 +2641,6 @@ STR_TOWN_POPULATION :{BLACK}Heims f
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Býur)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Fólkatal: {ORANGE}{COMMA}{BLACK} Hús: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Ferðafólk síðsta mánað: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Postur síðsta mánað: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Farmur ið tørvast fyri bygda menning:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} krevst
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} krevst um veturin

View File

@@ -2975,8 +2975,6 @@ STR_TOWN_POPULATION :{BLACK}Maailman
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (City)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Asukasluku: {ORANGE}{COMMA}{BLACK} Taloja: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Matkustajia viime kuussa: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Postia viime kuussa: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Kaupungin kasvuun tarvittava rahti:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} vaadittu
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} vaaditaan talvella

View File

@@ -2986,8 +2986,7 @@ STR_TOWN_POPULATION :{BLACK}Populati
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Métropole)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Population{NBSP}: {ORANGE}{COMMA}{BLACK} Maisons{NBSP}: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passagers le mois dernier{NBSP}: {ORANGE}{COMMA}{BLACK} max.{NBSP}: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Courrier le mois dernier{NBSP}: {ORANGE}{COMMA}{BLACK} max.{NBSP}: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} le mois dernier{NBSP}: {ORANGE}{COMMA}{BLACK} - max.{NBSP}: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Cargaison nécessaire à la croissance de la ville{NBSP}:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} requis
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} requis en hiver

View File

@@ -3216,8 +3216,6 @@ STR_TOWN_POPULATION :{BLACK}Sluagh a
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Mòr-bhaile)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Sluagh: {ORANGE}{COMMA}{BLACK} Taighean: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Taistealaich am mìos mu dheireadh: {ORANGE}{COMMA}{BLACK} as motha: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post am mìos mu dheireadh: {ORANGE}{COMMA}{BLACK} as motha: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Carago a tha a dhìth ach am fàs am baile:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED}Feum air {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{BLACK}Feum air {ORANGE}{STRING}{BLACK} sa gheamhradh

View File

@@ -2976,8 +2976,6 @@ STR_TOWN_POPULATION :{BLACK}Poboaci
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Cidade)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Poboación: {ORANGE}{COMMA}{BLACK} Casas: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Pasaxeiros último mes: {ORANGE}{COMMA}{BLACK} máx: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Correo último mes: {ORANGE}{COMMA}{BLACK} máx: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Carga necesaria para o crecemento da cidade:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} necesario
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} necesarios en inverno

View File

@@ -3431,8 +3431,6 @@ STR_TOWN_POPULATION :{BLACK}Weltbev
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Großstadt)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Einwohner: {ORANGE}{COMMA}{BLACK} Häuser: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passagiere im letzten Monat: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Postsäcke im letzten Monat: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Für Stadtwachstum benötigte Fracht:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} benötigt
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} im Winter benötigt

View File

@@ -3090,8 +3090,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}Επιβάτες τον προηγούμενο μήνα: {ORANGE}{COMMA}{BLACK} μεγ: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Αλληλογραφία τον προηγούμενο μήνα: {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} απαιτείται τον χειμώνα

View File

@@ -2989,8 +2989,6 @@ STR_TOWN_POPULATION :{BLACK}אוכל
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (עיר)
STR_TOWN_VIEW_POPULATION_HOUSES :{ORANGE}{1:COMMA}{BLACK} :בתים {ORANGE}{0:COMMA}{BLACK} :אוכלוסיה
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{ORANGE}{1:COMMA}{BLACK} :מספר מירבי {ORANGE}{0:COMMA}{BLACK} :נוסעים בחודש שעבר
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{ORANGE}{1:COMMA}{BLACK} :מספר מירבי {ORANGE}{0:COMMA}{BLACK} :שקי דואר בחודש שעבר
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} דרוש בחורף

View File

@@ -3039,8 +3039,6 @@ STR_TOWN_POPULATION :{BLACK}Világn
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Város)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Lakosság: {ORANGE}{COMMA}{BLACK} Házak: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Utasok az előző hónapban: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Levélcsomagok az előző hónapban: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}A város növekedéséhez szükséges rakomány:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} szükséges
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} szükséges télen

View File

@@ -2799,8 +2799,6 @@ STR_TOWN_POPULATION :{BLACK}Heildar
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Borg)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Íbúafjöldi: {ORANGE}{COMMA}{BLACK} Hús: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Farþegar síðasta mánaðar: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Póstur síðasta mánaðar: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Nauðsynlegur farmur fyrir stækkun bæjarins:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} nauðsynlegt
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} nauðsynlegt á veturnar

View File

@@ -2969,8 +2969,6 @@ STR_TOWN_POPULATION :{BLACK}Populasi
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (City)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Populasi: {ORANGE}{COMMA}{BLACK} Rumah: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Penumpang bulan lalu: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Surat bulan lalu: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Kargo untuk pertumbuhan kota:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED} Butuh {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} dibutuhkan saat musim dingin

View File

@@ -2971,8 +2971,6 @@ STR_TOWN_POPULATION :{BLACK}Daonra d
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Cathair)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Daonra: {ORANGE}{COMMA}{BLACK} Tithe: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Paisinéirí an mhí seo caite: {ORANGE}{COMMA}{BLACK} Uasmhéid: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post an mhí seo caite: {ORANGE}{COMMA}{BLACK} uasmhéid: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Lastas atá ag teastáil le go bhfásfaidh an baile:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} ag teastáil
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} ag teastáil sa Gheimhreadh

View File

@@ -3015,8 +3015,7 @@ STR_TOWN_POPULATION :{BLACK}Popolazi
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Metropoli)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Popolazione: {ORANGE}{COMMA}{BLACK} Case: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passeggeri il mese scorso: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Posta il mese scorso: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} il mese scorso: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Carichi richiesti per la crescita della città:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} richiest{G 0 o o a}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} richiest{G 0 o o a} in inverno

View File

@@ -2972,8 +2972,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}旅客数(先月): {ORANGE}{COMMA}人{BLACK} 最大: {ORANGE}{COMMA}人
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}郵便袋(先月): {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}冬に必要です

View File

@@ -3438,8 +3438,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}지난 달 승객 수: {ORANGE}{COMMA}{BLACK} 최대: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}지난달 우편수: {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 "이" "가"} 필요함

View File

@@ -3177,8 +3177,6 @@ STR_TOWN_POPULATION :{BLACK}Incolae
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Urbs)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Incolae: {ORANGE}{COMMA}{BLACK} Aedificia: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Vectores mensis prioris: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Epistulae mensis prioris: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Onera mandata ad oppidum crescendum:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} mandatur
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} hieme mandatur

View File

@@ -2906,8 +2906,6 @@ STR_TOWN_POPULATION :{BLACK}Pasaules
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (lielpilsēta)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Iedzīvotāji: {ORANGE}{COMMA}{BLACK} Mājas: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Pasažieri pagājušajā mēnesī: {ORANGE}{COMMA}{BLACK} maksimāli: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Pasts pagājušajā mēnesī: {ORANGE}{COMMA}{BLACK} maksimāli: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Krava nepieciešama pilsētas attīstībai:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} nepieciešams
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} nepieciešams ziemā

View File

@@ -3190,8 +3190,6 @@ STR_TOWN_POPULATION :{BLACK}Pasaulio
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Miestas)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Populiacija: {ORANGE}{COMMA}{BLACK} Namų skaičius: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Praėjusio mėnesio keleivių sk.: {ORANGE}{COMMA}{BLACK} Daugiausia: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Praėjusio mėnesio pašto siuntų sk.: {ORANGE}{COMMA}{BLACK} Daugiausia: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Kad miestas augtų reikalingi kroviniai:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} reikia
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} reikalingas žiemą

View File

@@ -2975,8 +2975,6 @@ STR_TOWN_POPULATION :{BLACK}Weltbev
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Stad)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Awunner: {ORANGE}{COMMA}{BLACK} Haiser: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passagéier leschte Mount: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post leschte Mount: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Gidder gebraucht fir Stadwuesstem:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} gebraucht
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} gebraucht am Wanter

View File

@@ -2679,8 +2679,6 @@ STR_TOWN_POPULATION :{BLACK}Jumlah p
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Bandaraya)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Jumlah penduduk: {ORANGE}{COMMA}{BLACK} Rumah: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Penumpang bulan lalu: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Beg surat bulan lepas: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Kargi yang diperlukan untuk pembesaran bandar:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} diperlukan
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} diperlukan sewaktu musim sejuk

View File

@@ -2979,8 +2979,6 @@ STR_TOWN_POPULATION :{BLACK}Verdensb
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (By)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Innbyggertall: {ORANGE}{COMMA}{BLACK} Antall hus: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passasjerer forrige måned: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post forrige måned: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Varebehov for byvekst:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} påkrevd
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} nødvendig om vinteren

View File

@@ -2890,8 +2890,6 @@ STR_TOWN_POPULATION :{BLACK}Verdsinn
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (By)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Innbyggjartal: {ORANGE}{COMMA}{BLACK} Antal hus: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passasjerar førre månad: {ORANGE}{COMMA}{BLACK} maks.: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post førre månad: {ORANGE}{COMMA}{BLACK} maks.: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Varer naudsynt for folketalsauke:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} naudsynt
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} naudsynt om vinteren

View File

@@ -3360,8 +3360,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}Pasażerów w zeszłym miesiącu: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Poczta w zeszłym miesiącu: {ORANGE}{COMMA}{BLACK} maks: {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ą

View File

@@ -2976,8 +2976,6 @@ STR_TOWN_POPULATION :{BLACK}Populaç
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Metrópole)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}População: {ORANGE}{COMMA}{BLACK} Casas: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passageiros no último mês: {ORANGE}{COMMA}{BLACK} máx: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Correio no último mês: {ORANGE}{COMMA}{BLACK} máx: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Mercadoria necessária para o seu desenvolvimento:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED}É necessário {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{BLACK}No inverno, é necessário {ORANGE}{STRING}

View File

@@ -2930,8 +2930,6 @@ STR_TOWN_POPULATION :{BLACK}Populaţ
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Metropolă)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Populaţia: {ORANGE}{COMMA}{BLACK} Locuinţe: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Călători luna trecută: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Colete poştale luna trecută: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Transporturi necesare dezvoltării oraşului:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} necesare
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} necesare iarna

View File

@@ -3171,8 +3171,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}Пассажиров в прошлом месяце: {ORANGE}{COMMA}{BLACK}; макс.: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Почты в прошлом месяце: {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 е е е ю}тся зимой

View File

@@ -3166,8 +3166,6 @@ STR_TOWN_POPULATION :{BLACK}Svetska
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Grad)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Populacija: {ORANGE}{COMMA}{BLACK} Zgrada: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Broj putnika tokom meseca: {ORANGE}{COMMA}{BLACK} najviše: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Količina pošte tokom meseca: {ORANGE}{COMMA}{BLACK} najviše: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Tovar potreban za razvoj naselja:
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

View File

@@ -2972,8 +2972,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}上月旅客数量:{ORANGE}{COMMA}{BLACK} 最大值:{ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}上月邮包数量:{ORANGE}{COMMA}{BLACK} 最大值:{ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}城镇发展所必需的货物:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED} 需要: {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} 冬季的需求

View File

@@ -3039,8 +3039,6 @@ STR_TOWN_POPULATION :{BLACK}Svetová
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Mesto)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Obyvateľstvo: {ORANGE}{COMMA}{BLACK} Domov: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Cestujúci posledný mesiac: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Pošta posledný mesiac: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Tovar potrebný k rozrastu mesta:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} potrebný
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} potrebné v zime

View File

@@ -3125,8 +3125,6 @@ STR_TOWN_POPULATION :{BLACK}Svetovno
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Mesto)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Prebivalstvo: {ORANGE}{COMMA}{BLACK} Število stavb: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Potnikov prejšnji mesec: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Pošte prejšnji mesec: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Količina potrebnega tovora za rast mesta:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} potrebno
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} je potreben pozimi

View File

@@ -2976,8 +2976,6 @@ STR_TOWN_POPULATION :{BLACK}Poblaci
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Ciudad)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Habitantes: {ORANGE}{COMMA}{BLACK} Casas: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Pasajeros último mes: {ORANGE}{COMMA}{BLACK} Máx.: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Correo último mes: {ORANGE}{COMMA}{BLACK} Máx.: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Carga necesaria para crecimiento del municipio:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} requeridos
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} requerido en invierno

View File

@@ -2986,8 +2986,6 @@ STR_TOWN_POPULATION :{BLACK}Poblaci
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (ciudad)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Habitantes: {ORANGE}{COMMA}{BLACK} Casas: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Pasajeros mes pasado: {ORANGE}{COMMA}{BLACK} Máx.: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Correo mes pasado: {ORANGE}{COMMA}{BLACK} Máx.: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Cargamento necesario para crecimiento:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} requeridos
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} requerido en invierno

View File

@@ -2975,8 +2975,6 @@ STR_TOWN_POPULATION :{BLACK}Global f
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Stad)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Invånare: {ORANGE}{COMMA}{BLACK} Hus: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passagerare förra månaden: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post förra månaden: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Fraktgods behövs för ortens tillväxt:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} krävs
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} krävs under vintern

View File

@@ -2618,8 +2618,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}சென்ற மாத பயணிகள்: {ORANGE}{COMMA}{BLACK} அதிகம்: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}சென்ற மாத அஞ்சல்கள்: {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} குளிர்காலத்தில் தேவை

View File

@@ -2903,8 +2903,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}ผู้โดยสารเมื่อเดือนที่แล้ว: {ORANGE}{COMMA}{BLACK} จำนวนสูงสุด: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}ไปรษณีย์ภัณฑ์เมื่อเดือนที่แล้ว: {ORANGE}{COMMA}{BLACK} จำนวนสูงสุด: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}ความต้องการสินค้าสำหรับการขยายตัวของเมือง:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} required
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} ต้องการหิมะ

View File

@@ -2971,8 +2971,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}上月乘客數字:{ORANGE}{COMMA}{BLACK} 紀錄最高:{ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}上月郵件數量:{ORANGE}{COMMA}{BLACK} 紀錄最高:{ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}市鎮成長所需貨物:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED}需要 {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} 必須是冬天

View File

@@ -2976,8 +2976,6 @@ STR_TOWN_POPULATION :{BLACK}Dünya n
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Şehir)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Nüfus: {ORANGE}{COMMA}{BLACK} Ev: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Geçen ayki yolcu: {ORANGE}{COMMA}{BLACK} azami: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Geçen ayki posta: {ORANGE}{COMMA}{BLACK} azami: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Kasaba büyümesi için gerekli kargo:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} gerekli
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} kışın gerekir

View File

@@ -3102,8 +3102,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}Пасажирів за місяць: {ORANGE}{COMMA}{BLACK} найбільше: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Пошти за місяць: {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} потрібно взимку

View File

@@ -2747,8 +2747,6 @@ STR_TOWN_POPULATION :{BLACK}Wrâldpo
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (City)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Ynwenners: {ORANGE}{COMMA}{BLACK} Hûzen: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Passazjiers lêste moanne: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post lêste moanne: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Guod nedich foar stêdsgroei:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} ferplichte
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} ferplichte yn de winter

View File

@@ -2656,8 +2656,6 @@ 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_PASSENGERS_LAST_MONTH_MAX :{BLACK}مسافران در ماه گذشته: {ORANGE}{COMMA}{BLACK} حداکثر: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}نامه ها در ماه گذشته: {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} در زمستان ضروری است

View File

@@ -2975,8 +2975,6 @@ STR_TOWN_POPULATION :{BLACK}Dân s
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Thành Phố)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Dân số: {ORANGE}{COMMA}{BLACK} Toà nhà: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Du khách tháng trước: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Bưu kiện tháng trước: {ORANGE}{COMMA}{BLACK} max: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Hàng hoá cần để đô thị tăng trưởng:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} được yêu cầu
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} Yêu cầu trong mùa đông

View File

@@ -2975,8 +2975,6 @@ STR_TOWN_POPULATION :{BLACK}Poblogae
STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN}
STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Dinas)
STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Poblogaeth: {ORANGE}{COMMA}{BLACK} Tai: {ORANGE}{COMMA}
STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX :{BLACK}Teithwyr mis diwethaf: {ORANGE}{COMMA}{BLACK} uchafswm: {ORANGE}{COMMA}
STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX :{BLACK}Post mis diwethaf: {ORANGE}{COMMA}{BLACK} uchafswm: {ORANGE}{COMMA}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Nwyddau angenrheidiol ar gyfer tyfiant y dref:
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED}Angen {ORANGE}{STRING}
STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} ei angen yn y gaeaf

View File

@@ -323,10 +323,7 @@ public:
for (CargoID i = 0; i < NUM_CARGO; ++i) {
if (acceptance[i] > 0) {
/* Add a comma between each item. */
if (found) {
*strp++ = ',';
*strp++ = ' ';
}
if (found) strp = strecpy(strp, ", ", lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE]));
found = true;
/* If the accepted value is less than 8, show it in 1/8:ths */

View File

@@ -11,11 +11,69 @@
#include "stdafx.h"
/** The type of set we're replacing */
#define SET_TYPE "music"
#include "base_media_func.h"
#include "safeguards.h"
#include "fios.h"
/**
* Read the name of a music CAT file entry.
* @param filename Name of CAT file to read from
* @param entrynum Index of entry whose name to read
* @return Pointer to string, caller is responsible for freeing memory,
* NULL if entrynum does not exist.
*/
char *GetMusicCatEntryName(const char *filename, size_t entrynum)
{
if (!FioCheckFileExists(filename, BASESET_DIR)) return NULL;
FioOpenFile(CONFIG_SLOT, filename, BASESET_DIR);
uint32 ofs = FioReadDword();
size_t entry_count = ofs / 8;
if (entrynum < entry_count) {
FioSeekTo(entrynum * 8, SEEK_SET);
FioSeekTo(FioReadDword(), SEEK_SET);
byte namelen = FioReadByte();
char *name = MallocT<char>(namelen + 1);
FioReadBlock(name, namelen);
name[namelen] = '\0';
return name;
}
return NULL;
}
/**
* Read the full data of a music CAT file entry.
* @param filename Name of CAT file to read from.
* @param entrynum Index of entry to read
* @param[out] entrylen Receives length of data read
* @return Pointer to buffer with data read, caller is responsible for freeind memory,
* NULL if entrynum does not exist.
*/
byte *GetMusicCatEntryData(const char *filename, size_t entrynum, size_t &entrylen)
{
entrylen = 0;
if (!FioCheckFileExists(filename, BASESET_DIR)) return NULL;
FioOpenFile(CONFIG_SLOT, filename, BASESET_DIR);
uint32 ofs = FioReadDword();
size_t entry_count = ofs / 8;
if (entrynum < entry_count) {
FioSeekTo(entrynum * 8, SEEK_SET);
size_t entrypos = FioReadDword();
entrylen = FioReadDword();
FioSeekTo(entrypos, SEEK_SET);
FioSkipBytes(FioReadByte());
byte *data = MallocT<byte>(entrylen);
FioReadBlock(data, entrylen);
return data;
}
return NULL;
}
INSTANTIATE_BASE_MEDIA_METHODS(BaseMedia<MusicSet>, MusicSet)
@@ -66,14 +124,32 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
if (ret) {
this->num_available = 0;
IniGroup *names = ini->GetGroup("names");
for (uint i = 0, j = 1; i < lengthof(this->song_name); i++) {
IniGroup *catindex = ini->GetGroup("catindex");
for (uint i = 0, j = 1; i < lengthof(this->songinfo); i++) {
const char *filename = this->files[i].filename;
if (names == NULL || StrEmpty(filename)) {
this->song_name[i][0] = '\0';
this->songinfo[i].songname[0] = '\0';
continue;
}
IniItem *item = NULL;
this->songinfo[i].filename = filename; // non-owned pointer
IniItem *item = catindex->GetItem(_music_file_names[i], false);
if (item != NULL && !StrEmpty(item->value)) {
/* Song has a CAT file index, assume it's MPS MIDI format */
this->songinfo[i].filetype = MTT_MPSMIDI;
this->songinfo[i].cat_index = atoi(item->value);
char *songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index);
if (songname == NULL) {
DEBUG(grf, 0, "Base music set song missing from CAT file: %s/%d", filename, this->songinfo[i].cat_index);
return false;
}
strecpy(this->songinfo[i].songname, songname, lastof(this->songinfo[i].songname));
free(songname);
} else {
this->songinfo[i].filetype = MTT_STANDARDMIDI;
}
/* 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. */
@@ -86,14 +162,17 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
if (item != NULL && !StrEmpty(item->value)) break;
}
if (item == NULL || StrEmpty(item->value)) {
DEBUG(grf, 0, "Base music set song name missing: %s", filename);
return false;
if (this->songinfo[i].filetype == MTT_STANDARDMIDI) {
if (item != NULL && !StrEmpty(item->value)) {
strecpy(this->songinfo[i].songname, item->value, lastof(this->songinfo[i].songname));
} else {
DEBUG(grf, 0, "Base music set song name missing: %s", filename);
return false;
}
}
strecpy(this->song_name[i], item->value, lastof(this->song_name[i]));
this->track_nr[i] = j++;
this->num_available++;
this->songinfo[i].tracknr = j++;
}
}
return ret;

View File

@@ -14,6 +14,7 @@
#include "../stdafx.h"
#include "../debug.h"
#include "allegro_m.h"
#include "midifile.hpp"
#include <allegro.h>
#include "../safeguards.h"
@@ -58,11 +59,17 @@ void MusicDriver_Allegro::Stop()
if (--_allegro_instance_count == 0) allegro_exit();
}
void MusicDriver_Allegro::PlaySong(const char *filename)
void MusicDriver_Allegro::PlaySong(const MusicSongInfo &song)
{
std::string filename = MidiFile::GetSMFFile(song);
if (_midi != NULL) destroy_midi(_midi);
_midi = load_midi(filename);
play_midi(_midi, false);
if (!filename.empty()) {
_midi = load_midi(filename.c_str());
play_midi(_midi, false);
} else {
_midi = NULL;
}
}
void MusicDriver_Allegro::StopSong()

View File

@@ -21,7 +21,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -12,6 +12,8 @@
#include "../stdafx.h"
#include "../openttd.h"
#include "bemidi.h"
#include "../base_media_base.h"
#include "midifile.hpp"
/* BeOS System Includes */
#include <MidiSynthFile.h>
@@ -34,13 +36,17 @@ void MusicDriver_BeMidi::Stop()
midiSynthFile.UnloadFile();
}
void MusicDriver_BeMidi::PlaySong(const char *filename)
void MusicDriver_BeMidi::PlaySong(const MusicSongInfo &song)
{
std::string filename = MidiFile::GetSMFFile(song);
this->Stop();
entry_ref midiRef;
get_ref_for_path(filename, &midiRef);
midiSynthFile.LoadFile(&midiRef);
midiSynthFile.Start();
if (!filename.empty()) {
entry_ref midiRef;
get_ref_for_path(filename.c_str(), &midiRef);
midiSynthFile.LoadFile(&midiRef);
midiSynthFile.Start();
}
}
void MusicDriver_BeMidi::StopSong()

View File

@@ -21,7 +21,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -18,7 +18,9 @@
#include "../stdafx.h"
#include "../os/macosx/macos.h"
#include "cocoa_m.h"
#include "midifile.hpp"
#include "../debug.h"
#include "../base_media_base.h"
#define Rect OTTDRect
#define Point OTTDPoint
@@ -141,11 +143,13 @@ void MusicDriver_Cocoa::Stop()
/**
* Starts playing a new song.
*
* @param filename Path to a MIDI file.
* @param song Description of music to load and play
*/
void MusicDriver_Cocoa::PlaySong(const char *filename)
void MusicDriver_Cocoa::PlaySong(const MusicSongInfo &song)
{
DEBUG(driver, 2, "cocoa_m: trying to play '%s'", filename);
std::string filename = MidiFile::GetSMFFile(song);
DEBUG(driver, 2, "cocoa_m: trying to play '%s'", filename.c_str());
this->StopSong();
if (_sequence != NULL) {
@@ -153,12 +157,14 @@ void MusicDriver_Cocoa::PlaySong(const char *filename)
_sequence = NULL;
}
if (filename.empty()) return;
if (NewMusicSequence(&_sequence) != noErr) {
DEBUG(driver, 0, "cocoa_m: Failed to create music sequence");
return;
}
const char *os_file = OTTD2FS(filename);
const char *os_file = OTTD2FS(filename.c_str());
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)os_file, strlen(os_file), false);
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
@@ -218,7 +224,7 @@ void MusicDriver_Cocoa::PlaySong(const char *filename)
if (MusicPlayerStart(_player) != noErr) return;
_playing = true;
DEBUG(driver, 3, "cocoa_m: playing '%s'", filename);
DEBUG(driver, 3, "cocoa_m: playing '%s'", filename.c_str());
}

View File

@@ -20,7 +20,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -21,6 +21,7 @@
#include "../core/mem_func.hpp"
#include "../thread/thread.h"
#include "../fileio_func.h"
#include "../base_media_base.h"
#include "dmusic.h"
#include "midifile.hpp"
#include "midi.h"
@@ -1225,11 +1226,12 @@ void MusicDriver_DMusic::Stop()
}
void MusicDriver_DMusic::PlaySong(const char *filename)
void MusicDriver_DMusic::PlaySong(const MusicSongInfo &song)
{
ThreadMutexLocker lock(_thread_mutex);
_playback.next_file.LoadFile(filename);
if (!_playback.next_file.LoadSong(song)) return;
_playback.next_segment.start = 0;
_playback.next_segment.end = 0;
_playback.next_segment.loop = false;

View File

@@ -23,7 +23,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -17,6 +17,8 @@
#include "../video/video_driver.hpp"
#include "../gfx_func.h"
#include "extmidi.h"
#include "../base_media_base.h"
#include "midifile.hpp"
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -83,10 +85,13 @@ void MusicDriver_ExtMidi::Stop()
this->DoStop();
}
void MusicDriver_ExtMidi::PlaySong(const char *filename)
void MusicDriver_ExtMidi::PlaySong(const MusicSongInfo &song)
{
strecpy(this->song, filename, lastof(this->song));
this->DoStop();
std::string filename = MidiFile::GetSMFFile(song);
if (!filename.empty()) {
strecpy(this->song, filename.c_str(), lastof(this->song));
this->DoStop();
}
}
void MusicDriver_ExtMidi::StopSong()

View File

@@ -28,7 +28,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -14,6 +14,8 @@
#include "../sound_type.h"
#include "../debug.h"
#include "libtimidity.h"
#include "midifile.hpp"
#include "../base_media_base.h"
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -73,11 +75,14 @@ void MusicDriver_LibTimidity::Stop()
mid_exit();
}
void MusicDriver_LibTimidity::PlaySong(const char *filename)
void MusicDriver_LibTimidity::PlaySong(const MusicSongInfo &song)
{
this->StopSong();
std::string filename = MidiFile::GetSMFFile(song);
_midi.stream = mid_istream_open_file(filename);
this->StopSong();
if (filename.empty()) return;
_midi.stream = mid_istream_open_file(filename.c_str());
if (_midi.stream == NULL) {
DEBUG(driver, 0, "Could not open music file");
return;

View File

@@ -21,7 +21,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -12,13 +12,20 @@
#include "midifile.hpp"
#include "../fileio_func.h"
#include "../fileio_type.h"
#include "../string_func.h"
#include "../core/endian_func.hpp"
#include "../base_media_base.h"
#include "midi.h"
#include <algorithm>
#include "../console_func.h"
#include "../console_internal.h"
/* implementation based on description at: http://www.somascape.org/midi/tech/mfile.html */
/* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
static MidiFile *_midifile_instance = NULL;
/**
* Owning byte buffer readable as a stream.
@@ -158,7 +165,7 @@ static bool ReadTrackChunk(FILE *file, MidiFile &target)
return false;
}
/* read chunk length and then the whole chunk */
/* Read chunk length and then the whole chunk */
uint32 chunk_length;
if (fread(&chunk_length, 1, 4, file) != 4) {
return false;
@@ -176,7 +183,7 @@ static bool ReadTrackChunk(FILE *file, MidiFile &target)
byte last_status = 0;
bool running_sysex = false;
while (!chunk.IsEnd()) {
/* read deltatime for event, start new block */
/* Read deltatime for event, start new block */
uint32 deltatime = 0;
if (!chunk.ReadVariableLength(deltatime)) {
return false;
@@ -186,14 +193,14 @@ static bool ReadTrackChunk(FILE *file, MidiFile &target)
block = &target.blocks.back();
}
/* read status byte */
/* Read status byte */
byte status;
if (!chunk.ReadByte(status)) {
return false;
}
if ((status & 0x80) == 0) {
/* high bit not set means running status message, status is same as last
/* High bit not set means running status message, status is same as last
* convert to explicit status */
chunk.Rewind(1);
status = last_status;
@@ -266,7 +273,7 @@ static bool ReadTrackChunk(FILE *file, MidiFile &target)
return false;
}
if (data[length] != 0xF7) {
/* engage Casio weirdo mode - convert to normal sysex */
/* Engage Casio weirdo mode - convert to normal sysex */
running_sysex = true;
*block->data.Append() = 0xF7;
} else {
@@ -312,18 +319,20 @@ static bool FixupMidiData(MidiFile &target)
std::sort(target.blocks.begin(), target.blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
if (target.tempos.size() == 0) {
/* no tempo information, assume 120 bpm (500,000 microseconds per beat */
/* No tempo information, assume 120 bpm (500,000 microseconds per beat */
target.tempos.push_back(MidiFile::TempoChange(0, 500000));
}
/* add sentinel tempo at end */
/* Add sentinel tempo at end */
target.tempos.push_back(MidiFile::TempoChange(UINT32_MAX, 0));
/* merge blocks with identical tick times */
/* Merge blocks with identical tick times */
std::vector<MidiFile::DataBlock> merged_blocks;
uint32 last_ticktime = 0;
for (size_t i = 0; i < target.blocks.size(); i++) {
MidiFile::DataBlock &block = target.blocks[i];
if (block.ticktime > last_ticktime || merged_blocks.size() == 0) {
if (block.data.Length() == 0) {
continue;
} else if (block.ticktime > last_ticktime || merged_blocks.size() == 0) {
merged_blocks.push_back(block);
last_ticktime = block.ticktime;
} else {
@@ -333,7 +342,7 @@ static bool FixupMidiData(MidiFile &target)
}
std::swap(merged_blocks, target.blocks);
/* annotate blocks with real time */
/* Annotate blocks with real time */
last_ticktime = 0;
uint32 last_realtime = 0;
size_t cur_tempo = 0, cur_block = 0;
@@ -390,13 +399,13 @@ bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
return false;
}
/* check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
/* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
const byte magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
return false;
}
/* read the parameters of the file */
/* Read the parameters of the file */
header.format = (buffer[8] << 8) | buffer[9];
header.tracks = (buffer[10] << 8) | buffer[11];
header.tickdiv = (buffer[12] << 8) | buffer[13];
@@ -410,12 +419,15 @@ bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
*/
bool MidiFile::LoadFile(const char *filename)
{
_midifile_instance = this;
this->blocks.clear();
this->tempos.clear();
this->tickdiv = 0;
bool success = false;
FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
if (file == NULL) return false;
SMFHeader header;
if (!ReadSMFHeader(file, header)) goto cleanup;
@@ -440,6 +452,383 @@ cleanup:
return success;
}
/**
* Decoder for "MPS MIDI" format data.
* This format for MIDI music is also used in a few other Microprose games contemporary with Transport Tycoon.
*
* The song data are usually packed inside a CAT file, with one CAT chunk per song. The song titles are used as names for the CAT chunks.
*
* Unlike the Standard MIDI File format, which is based on the IFF structure, the MPS MIDI format is best described as two linked lists of sub-tracks,
* the first list contains a number of reusable "segments", and the second list contains the "master tracks". Each list is prefixed with a byte
* giving the number of elements in the list, and the actual list is just a byte count (BE16 format) for the segment/track followed by the actual data,
* there is no index as such, so the entire data must be seeked through to build an index.
*
* The actual MIDI data inside each track is almost standard MIDI, prefixing every event with a delay, encoded using the same variable-length format
* used in SMF. A few status codes have changed meaning in MPS MIDI: 0xFE changes control from master track to a segment, 0xFD returns from a segment
* to the master track, and 0xFF is used to end the song. (In Standard MIDI all those values must only occur in real-time data.)
*
* As implemented in the original decoder, there is no support for recursively calling segments from segments, i.e. code 0xFE must only occur in
* a master track, and code 0xFD must only occur in a segment. There are no checks made for this, it's assumed that the only input data will ever
* be the original game music, not music from other games, or new productions.
*
* Additionally, some program change and controller events are given special meaning, see comments in the code.
*/
struct MpsMachine {
/** Starting parameter and playback status for one channel/track */
struct Channel {
byte cur_program; ///< program selected, used for velocity scaling (lookup into programvelocities array)
byte running_status; ///< last midi status code seen
uint16 delay; ///< frames until next command
uint32 playpos; ///< next byte to play this channel from
uint32 startpos; ///< start position of master track
uint32 returnpos; ///< next return position after playing a segment
Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
};
Channel channels[16]; ///< playback status for each MIDI channel
std::vector<uint32> segments; ///< pointers into songdata to repeatable data segments
int16 tempo_ticks; ///< ticker that increments when playing a frame, decrements before playing a frame
int16 current_tempo; ///< threshold for actually playing a frame
int16 initial_tempo; ///< starting tempo of song
bool shouldplayflag; ///< not-end-of-song flag
static const int TEMPO_RATE;
static const byte programvelocities[128];
const byte *songdata; ///< raw data array
size_t songdatalen; ///< length of song data
MidiFile &target; ///< recipient of data
/** Overridden MIDI status codes used in the data format */
enum MpsMidiStatus {
MPSMIDIST_SEGMENT_RETURN = 0xFD, ///< resume playing master track from stored position
MPSMIDIST_SEGMENT_CALL = 0xFE, ///< store current position of master track playback, and begin playback of a segment
MPSMIDIST_ENDSONG = 0xFF, ///< immediately end the song
};
static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2)
{
*block.data.Append() = b1;
*block.data.Append() = b2;
}
static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2, byte b3)
{
*block.data.Append() = b1;
*block.data.Append() = b2;
*block.data.Append() = b3;
}
/**
* Construct a TTD DOS music format decoder.
* @param songdata Buffer of song data from CAT file, ownership remains with caller
* @param songdatalen Length of the data buffer in bytes
* @param target MidiFile object to add decoded data to
*/
MpsMachine(const byte *data, size_t length, MidiFile &target)
: songdata(data), songdatalen(length), target(target)
{
uint32 pos = 0;
int loopmax;
int loopidx;
/* First byte is the initial "tempo" */
this->initial_tempo = this->songdata[pos++];
/* Next byte is a count of callable segments */
loopmax = this->songdata[pos++];
for (loopidx = 0; loopidx < loopmax; loopidx++) {
/* Segments form a linked list in the stream,
* first two bytes in each is an offset to the next.
* Two bytes between offset to next and start of data
* are unaccounted for. */
this->segments.push_back(pos + 4);
pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
}
/* After segments follows list of master tracks for each channel,
* also prefixed with a byte counting actual tracks. */
loopmax = this->songdata[pos++];
for (loopidx = 0; loopidx < loopmax; loopidx++) {
/* Similar structure to segments list, but also has
* the MIDI channel number as a byte before the offset
* to next track. */
byte ch = this->songdata[pos++];
this->channels[ch].startpos = pos + 4;
pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
}
}
/**
* Read an SMF-style variable length value (note duration) from songdata.
* @param pos Position to read from, updated to point to next byte after the value read
* @return Value read from data stream
*/
uint16 ReadVariableLength(uint32 &pos)
{
byte b = 0;
uint16 res = 0;
do {
b = this->songdata[pos++];
res = (res << 7) + (b & 0x7F);
} while (b & 0x80);
return res;
}
/**
* Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
*/
void RestartSong()
{
for (int ch = 0; ch < 16; ch++) {
Channel &chandata = this->channels[ch];
if (chandata.startpos != 0) {
/* Active track, set position to beginning */
chandata.playpos = chandata.startpos;
chandata.delay = this->ReadVariableLength(chandata.playpos);
} else {
/* Inactive track, mark as such */
chandata.playpos = 0;
chandata.delay = 0;
}
}
}
/**
* Play one frame of data from one channel
*/
uint16 PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
{
uint16 newdelay = 0;
byte b1, b2;
Channel &chandata = this->channels[channel];
do {
/* Read command/status byte */
b1 = this->songdata[chandata.playpos++];
/* Command 0xFE, call segment from master track */
if (b1 == MPSMIDIST_SEGMENT_CALL) {
b1 = this->songdata[chandata.playpos++];
chandata.returnpos = chandata.playpos;
chandata.playpos = this->segments[b1];
newdelay = this->ReadVariableLength(chandata.playpos);
if (newdelay == 0) {
continue;
}
return newdelay;
}
/* Command 0xFD, return from segment to master track */
if (b1 == MPSMIDIST_SEGMENT_RETURN) {
chandata.playpos = chandata.returnpos;
chandata.returnpos = 0;
newdelay = this->ReadVariableLength(chandata.playpos);
if (newdelay == 0) {
continue;
}
return newdelay;
}
/* Command 0xFF, end of song */
if (b1 == MPSMIDIST_ENDSONG) {
this->shouldplayflag = false;
return 0;
}
/* Regular MIDI channel message status byte */
if (b1 >= 0x80) {
/* Save the status byte as running status for the channel
* and read another byte for first parameter to command */
chandata.running_status = b1;
b1 = this->songdata[chandata.playpos++];
}
switch (chandata.running_status & 0xF0) {
case MIDIST_NOTEOFF:
case MIDIST_NOTEON:
b2 = this->songdata[chandata.playpos++];
if (b2 != 0) {
/* Note on, read velocity and scale according to rules */
int16 velocity;
if (channel == 9) {
/* Percussion channel, fixed velocity scaling not in the table */
velocity = (int16)b2 * 0x50;
} else {
/* Regular channel, use scaling from table */
velocity = b2 * programvelocities[chandata.cur_program];
}
b2 = (velocity / 128) & 0x00FF;
AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
} else {
/* Note off */
AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
}
break;
case MIDIST_CONTROLLER:
b2 = this->songdata[chandata.playpos++];
if (b1 == MIDICT_MODE_MONO) {
/* Unknown what the purpose of this is.
* Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
* Possibly intended to give hints to other (non-GM) music drivers decoding the song.
*/
break;
} else if (b1 == 0) {
/* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
* This is not actually used in any of the original songs. */
if (b2 != 0) {
this->current_tempo = ((int)b2) * 48 / 60;
}
break;
} else if (b1 == MIDICT_EFFECTS1) {
/* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
* Unknown what the purpose of this particular value is. */
b2 = 30;
}
AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
break;
case MIDIST_PROGCHG:
if (b1 == 0x7E) {
/* Program change to "Applause" is originally used
* to cause the song to loop, but that gets handled
* separately in the output driver here.
* Just end the song. */
this->shouldplayflag = false;
break;
}
/* Used for note velocity scaling lookup */
chandata.cur_program = b1;
/* Two programs translated to a third, this is likely to
* provide three different velocity scalings of "brass". */
if (b1 == 0x57 || b1 == 0x3F) {
b1 = 0x3E;
}
AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
break;
case MIDIST_PITCHBEND:
b2 = this->songdata[chandata.playpos++];
AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
break;
default:
break;
}
newdelay = this->ReadVariableLength(chandata.playpos);
} while (newdelay == 0);
return newdelay;
}
/**
* Play one frame of data into a block.
*/
bool PlayFrame(MidiFile::DataBlock &block)
{
/* Update tempo/ticks counter */
this->tempo_ticks -= this->current_tempo;
if (this->tempo_ticks > 0) {
return true;
}
this->tempo_ticks += TEMPO_RATE;
/* Look over all channels, play those active */
for (int ch = 0; ch < 16; ch++) {
Channel &chandata = this->channels[ch];
if (chandata.playpos != 0) {
if (chandata.delay == 0) {
chandata.delay = this->PlayChannelFrame(block, ch);
}
chandata.delay--;
}
}
return this->shouldplayflag;
}
/**
* Perform playback of whole song.
*/
bool PlayInto()
{
/* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
* Use this as the tickdiv, and define the tempo to be one second (1M microseconds) per tickdiv.
* MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
this->target.tickdiv = TEMPO_RATE;
this->target.tempos.push_back(MidiFile::TempoChange(0, 1000000));
/* Initialize playback simulation */
this->RestartSong();
this->shouldplayflag = true;
this->current_tempo = (int32)this->initial_tempo * 24 / 60;
this->tempo_ticks = this->current_tempo;
/* Always reset percussion channel to program 0 */
this->target.blocks.push_back(MidiFile::DataBlock());
AddMidiData(this->target.blocks.back(), MIDIST_PROGCHG+9, 0x00);
/* Technically should be an endless loop, but having
* a maximum (about 10 minutes) avoids getting stuck,
* in case of corrupted data. */
for (uint32 tick = 0; tick < 100000; tick+=1) {
this->target.blocks.push_back(MidiFile::DataBlock());
auto &block = this->target.blocks.back();
block.ticktime = tick;
if (!this->PlayFrame(block)) {
break;
}
}
return true;
}
};
/** Frames/ticks per second for music playback */
const int MpsMachine::TEMPO_RATE = 148;
/** Base note velocities for various GM programs */
const byte MpsMachine::programvelocities[128] = {
100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
};
/**
* Create MIDI data from song data for the original Microprose music drivers.
* @param data pointer to block of data
* @param length size of data in bytes
* @return true if the data could be loaded
*/
bool MidiFile::LoadMpsData(const byte *data, size_t length)
{
_midifile_instance = this;
MpsMachine machine(data, length, *this);
return machine.PlayInto() && FixupMidiData(*this);
}
bool MidiFile::LoadSong(const MusicSongInfo &song)
{
switch (song.filetype) {
case MTT_STANDARDMIDI:
return this->LoadFile(song.filename);
case MTT_MPSMIDI:
{
size_t songdatalen = 0;
byte *songdata = GetMusicCatEntryData(song.filename, song.cat_index, songdatalen);
if (songdata != NULL) {
bool result = this->LoadMpsData(songdata, songdatalen);
free(songdata);
return result;
} else {
return false;
}
}
default:
NOT_REACHED();
}
}
/**
* Move data from other to this, and clears other.
* @param other object containing loaded data to take over
@@ -450,8 +839,280 @@ void MidiFile::MoveFrom(MidiFile &other)
std::swap(this->tempos, other.tempos);
this->tickdiv = other.tickdiv;
_midifile_instance = this;
other.blocks.clear();
other.tempos.clear();
other.tickdiv = 0;
}
static void WriteVariableLen(FILE *f, uint32 value)
{
if (value < 0x7F) {
byte tb = value;
fwrite(&tb, 1, 1, f);
} else if (value < 0x3FFF) {
byte tb[2];
tb[1] = value & 0x7F; value >>= 7;
tb[0] = (value & 0x7F) | 0x80; value >>= 7;
fwrite(tb, 1, sizeof(tb), f);
} else if (value < 0x1FFFFF) {
byte tb[3];
tb[2] = value & 0x7F; value >>= 7;
tb[1] = (value & 0x7F) | 0x80; value >>= 7;
tb[0] = (value & 0x7F) | 0x80; value >>= 7;
fwrite(tb, 1, sizeof(tb), f);
} else if (value < 0x0FFFFFFF) {
byte tb[4];
tb[3] = value & 0x7F; value >>= 7;
tb[2] = (value & 0x7F) | 0x80; value >>= 7;
tb[1] = (value & 0x7F) | 0x80; value >>= 7;
tb[0] = (value & 0x7F) | 0x80; value >>= 7;
fwrite(tb, 1, sizeof(tb), f);
}
}
/**
* Write a Standard MIDI File containing the decoded music.
* @param filename Name of file to write to
* @return True if the file was written to completion
*/
bool MidiFile::WriteSMF(const char *filename)
{
FILE *f = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
if (!f) {
return false;
}
/* SMF header */
const byte fileheader[] = {
'M', 'T', 'h', 'd', // block name
0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
0x00, 0x00, // writing format 0 (all in one track)
0x00, 0x01, // containing 1 track (BE16)
(byte)(this->tickdiv >> 8), (byte)this->tickdiv, // tickdiv in BE16
};
fwrite(fileheader, sizeof(fileheader), 1, f);
/* Track header */
const byte trackheader[] = {
'M', 'T', 'r', 'k', // block name
0, 0, 0, 0, // BE32 block length, unknown at this time
};
fwrite(trackheader, sizeof(trackheader), 1, f);
/* Determine position to write the actual track block length at */
size_t tracksizepos = ftell(f) - 4;
/* Write blocks in sequence */
uint32 lasttime = 0;
size_t nexttempoindex = 0;
for (size_t bi = 0; bi < this->blocks.size(); bi++) {
DataBlock &block = this->blocks[bi];
TempoChange &nexttempo = this->tempos[nexttempoindex];
uint32 timediff = block.ticktime - lasttime;
/* Check if there is a tempo change before this block */
if (nexttempo.ticktime < block.ticktime) {
timediff = nexttempo.ticktime - lasttime;
}
/* Write delta time for block */
lasttime += timediff;
bool needtime = false;
WriteVariableLen(f, timediff);
/* Write tempo change if there is one */
if (nexttempo.ticktime <= block.ticktime) {
byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
tempobuf[5] = (nexttempo.tempo & 0x000000FF);
fwrite(tempobuf, sizeof(tempobuf), 1, f);
nexttempoindex++;
needtime = true;
}
/* If a tempo change occurred between two blocks, rather than
* at start of this one, start over with delta time for the block. */
if (nexttempo.ticktime < block.ticktime) {
/* Start loop over at same index */
bi--;
continue;
}
/* Write each block data command */
byte *dp = block.data.Begin();
while (dp < block.data.End()) {
/* Always zero delta time inside blocks */
if (needtime) {
fputc(0, f);
}
needtime = true;
/* Check message type and write appropriate number of bytes */
switch (*dp & 0xF0) {
case MIDIST_NOTEOFF:
case MIDIST_NOTEON:
case MIDIST_POLYPRESS:
case MIDIST_CONTROLLER:
case MIDIST_PITCHBEND:
fwrite(dp, 1, 3, f);
dp += 3;
continue;
case MIDIST_PROGCHG:
case MIDIST_CHANPRESS:
fwrite(dp, 1, 2, f);
dp += 2;
continue;
}
/* Sysex needs to measure length and write that as well */
if (*dp == MIDIST_SYSEX) {
fwrite(dp, 1, 1, f);
dp++;
byte *sysexend = dp;
while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
ptrdiff_t sysexlen = sysexend - dp;
WriteVariableLen(f, sysexlen);
fwrite(dp, 1, sysexend - dp, f);
dp = sysexend;
continue;
}
/* Fail for any other commands */
fclose(f);
return false;
}
}
/* End of track marker */
static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
/* Fill out the RIFF block length */
size_t trackendpos = ftell(f);
fseek(f, tracksizepos, SEEK_SET);
uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
tracksize = TO_BE32(tracksize);
fwrite(&tracksize, 4, 1, f);
fclose(f);
return true;
}
/**
* Get the name of a Standard MIDI File for a given song.
* For songs already in SMF format, just returns the original.
* Otherwise the song is converted, written to a temporary-ish file, and the written filename is returned.
* @param song Song definition to query
* @return Full filename string, empty string if failed
*/
std::string MidiFile::GetSMFFile(const MusicSongInfo &song)
{
if (song.filetype == MTT_STANDARDMIDI) {
return std::string(song.filename);
}
if (song.filetype != MTT_MPSMIDI) return std::string();
const char *lastpathsep = strrchr(song.filename, PATHSEPCHAR);
if (lastpathsep == NULL) {
lastpathsep = song.filename;
}
char basename[MAX_PATH];
{
/* Remove all '.' characters from filename */
char *wp = basename;
for (const char *rp = lastpathsep + 1; *rp != '\0'; rp++) {
if (*rp != '.') *wp++ = *rp;
}
*wp++ = '\0';
}
char tempdirname[MAX_PATH];
FioGetFullPath(tempdirname, lastof(tempdirname), Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR, basename);
if (!AppendPathSeparator(tempdirname, lastof(tempdirname))) return std::string();
FioCreateDirectory(tempdirname);
char output_filename[MAX_PATH];
seprintf(output_filename, lastof(output_filename), "%s%d.mid", tempdirname, song.cat_index);
if (FileExists(output_filename)) {
/* If the file already exists, assume it's the correct decoded data */
return std::string(output_filename);
}
byte *data;
size_t datalen;
data = GetMusicCatEntryData(song.filename, song.cat_index, datalen);
if (data == NULL) return std::string();
MidiFile midifile;
if (!midifile.LoadMpsData(data, datalen)) {
free(data);
return std::string();
}
free(data);
if (midifile.WriteSMF(output_filename)) {
return std::string(output_filename);
} else {
return std::string();
}
}
static bool CmdDumpSMF(byte argc, char *argv[])
{
if (argc == 0) {
IConsolePrint(CC_WARNING, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'");
return true;
}
if (argc != 2) {
IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
return false;
}
if (_midifile_instance == NULL) {
IConsolePrint(CC_ERROR, "There is no MIDI file loaded currently, make sure music is playing, and you're using a driver that works with raw MIDI.");
return false;
}
char fnbuf[MAX_PATH] = { 0 };
if (seprintf(fnbuf, lastof(fnbuf), "%s%s", FiosGetScreenshotDir(), argv[1]) >= (int)lengthof(fnbuf)) {
IConsolePrint(CC_ERROR, "Filename too long.");
return false;
}
IConsolePrintF(CC_INFO, "Dumping MIDI to: %s", fnbuf);
if (_midifile_instance->WriteSMF(fnbuf)) {
IConsolePrint(CC_INFO, "File written successfully.");
return true;
} else {
IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
return false;
}
}
static void RegisterConsoleMidiCommands()
{
static bool registered = false;
if (!registered) {
IConsoleCmdRegister("dumpsmf", CmdDumpSMF);
registered = true;
}
}
MidiFile::MidiFile()
{
RegisterConsoleMidiCommands();
}
MidiFile::~MidiFile()
{
if (_midifile_instance == this) {
_midifile_instance = NULL;
}
}

View File

@@ -16,6 +16,9 @@
#include "../core/smallvec_type.hpp"
#include "midi.h"
#include <vector>
#include <string>
struct MusicSongInfo;
struct MidiFile {
struct DataBlock {
@@ -34,9 +37,17 @@ struct MidiFile {
std::vector<TempoChange> tempos; ///< list of tempo changes in file
uint16 tickdiv; ///< ticks per quarter note
MidiFile();
~MidiFile();
bool LoadFile(const char *filename);
bool LoadMpsData(const byte *data, size_t length);
bool LoadSong(const MusicSongInfo &song);
void MoveFrom(MidiFile &other);
bool WriteSMF(const char *filename);
static std::string GetSMFFile(const MusicSongInfo &song);
static bool ReadSMFHeader(const char *filename, SMFHeader &header);
static bool ReadSMFHeader(FILE *file, SMFHeader &header);
};

View File

@@ -14,6 +14,8 @@
#include "../driver.h"
struct MusicSongInfo;
/** Driver for all music playback. */
class MusicDriver : public Driver {
public:
@@ -21,7 +23,7 @@ public:
* Play a particular song.
* @param filename The name of file with the song to play.
*/
virtual void PlaySong(const char *filename) = 0;
virtual void PlaySong(const MusicSongInfo &song) = 0;
/**
* Stop playing the current song.

View File

@@ -21,7 +21,7 @@ public:
/* virtual */ void Stop() { }
/* virtual */ void PlaySong(const char *filename) { }
/* virtual */ void PlaySong(const MusicSongInfo &song) { }
/* virtual */ void StopSong() { }

View File

@@ -12,6 +12,8 @@
#include "../stdafx.h"
#include "../openttd.h"
#include "os2_m.h"
#include "midifile.hpp"
#include "../base_media_base.h"
#define INCL_DOS
#define INCL_OS2MM
@@ -49,11 +51,14 @@ static long CDECL MidiSendCommand(const char *cmd, ...)
/** OS/2's music player's factory. */
static FMusicDriver_OS2 iFMusicDriver_OS2;
void MusicDriver_OS2::PlaySong(const char *filename)
void MusicDriver_OS2::PlaySong(const MusicSongInfo &song)
{
MidiSendCommand("close all");
std::string filename = MidiFile::GetSMFFile(song);
if (MidiSendCommand("open %s type sequencer alias song", filename) != 0) {
MidiSendCommand("close all");
if (filename.empty()) return;
if (MidiSendCommand("open %s type sequencer alias song", filename.c_str()) != 0) {
return;
}

View File

@@ -21,7 +21,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -30,7 +30,9 @@
#include "../stdafx.h"
#include "qtmidi.h"
#include "midifile.hpp"
#include "../debug.h"
#include "../base_media_base.h"
#define Rect OTTDRect
#define Point OTTDPoint
@@ -258,11 +260,14 @@ void MusicDriver_QtMidi::Stop()
*
* @param filename Path to a MIDI file.
*/
void MusicDriver_QtMidi::PlaySong(const char *filename)
void MusicDriver_QtMidi::PlaySong(const MusicSongInfo &song)
{
if (!_quicktime_started) return;
DEBUG(driver, 2, "qtmidi: trying to play '%s'", filename);
std::string filename = MidiFile::GetSMFFile(song);
if (filename.empty()) return;
DEBUG(driver, 2, "qtmidi: trying to play '%s'", filename.c_str());
switch (_quicktime_state) {
case QT_STATE_PLAY:
StopSong();
@@ -276,12 +281,12 @@ void MusicDriver_QtMidi::PlaySong(const char *filename)
FALLTHROUGH;
case QT_STATE_IDLE:
LoadMovieForMIDIFile(filename, &_quicktime_movie);
LoadMovieForMIDIFile(filename.c_str(), &_quicktime_movie);
SetMovieVolume(_quicktime_movie, VOLUME);
StartMovie(_quicktime_movie);
_quicktime_state = QT_STATE_PLAY;
}
DEBUG(driver, 3, "qtmidi: playing '%s'", filename);
DEBUG(driver, 3, "qtmidi: playing '%s'", filename.c_str());
}

View File

@@ -20,7 +20,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -18,6 +18,7 @@
#include "../debug.h"
#include "midifile.hpp"
#include "midi.h"
#include "../base_media_base.h"
#include "../safeguards.h"
@@ -304,12 +305,16 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW
}
}
void MusicDriver_Win32::PlaySong(const char *filename)
void MusicDriver_Win32::PlaySong(const MusicSongInfo &song)
{
DEBUG(driver, 2, "Win32-MIDI: PlaySong: entry");
EnterCriticalSection(&_midi.lock);
_midi.next_file.LoadFile(filename);
if (!_midi.next_file.LoadSong(song)) {
LeaveCriticalSection(&_midi.lock);
return;
}
_midi.next_segment.start = 0;
_midi.next_segment.end = 0;
_midi.next_segment.loop = false;

View File

@@ -21,7 +21,7 @@ public:
/* virtual */ void Stop();
/* virtual */ void PlaySong(const char *filename);
/* virtual */ void PlaySong(const MusicSongInfo &song);
/* virtual */ void StopSong();

View File

@@ -42,7 +42,7 @@
*/
static const char *GetSongName(int index)
{
return BaseMusic::GetUsedSet()->song_name[index];
return BaseMusic::GetUsedSet()->songinfo[index].songname;
}
/**
@@ -52,7 +52,7 @@ static const char *GetSongName(int index)
*/
static int GetTrackNumber(int index)
{
return BaseMusic::GetUsedSet()->track_nr[index];
return BaseMusic::GetUsedSet()->songinfo[index].tracknr;
}
/** The currently played song */
@@ -186,10 +186,12 @@ static void MusicVolumeChanged(byte new_vol)
static void DoPlaySong()
{
char filename[MAX_PATH];
if (FioFindFullPath(filename, lastof(filename), BASESET_DIR, BaseMusic::GetUsedSet()->files[_music_wnd_cursong - 1].filename) == NULL) {
FioFindFullPath(filename, lastof(filename), OLD_GM_DIR, BaseMusic::GetUsedSet()->files[_music_wnd_cursong - 1].filename);
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);
}
MusicDriver::GetInstance()->PlaySong(filename);
songinfo.filename = filename; // non-owned pointer
MusicDriver::GetInstance()->PlaySong(songinfo);
SetWindowDirty(WC_MUSIC_WINDOW, 0);
}

View File

@@ -0,0 +1,610 @@
/* $Id$ */
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file string_uniscribe.cpp Functions related to laying out text on Win32. */
#if defined(WITH_UNISCRIBE)
#include "../../stdafx.h"
#include "../../debug.h"
#include "string_uniscribe.h"
#include "../../language.h"
#include "../../strings_func.h"
#include "../../string_func.h"
#include "../../table/control_codes.h"
#include "win32.h"
#include <vector>
#include <windows.h>
#include <usp10.h>
#include "../../safeguards.h"
#ifdef _MSC_VER
# pragma comment(lib, "usp10")
#endif
/** Uniscribe cache for internal font information, cleared when OTTD changes fonts. */
static SCRIPT_CACHE _script_cache[FS_END];
/**
* Contains all information about a run of characters. A run are consecutive
* characters that share a single font and language.
*/
struct UniscribeRun {
int pos;
int len;
Font *font;
std::vector<GlyphID> ft_glyphs;
SCRIPT_ANALYSIS sa;
std::vector<WORD> char_to_glyph;
std::vector<SCRIPT_VISATTR> vis_attribs;
std::vector<WORD> glyphs;
std::vector<int> advances;
std::vector<GOFFSET> offsets;
int total_advance;
UniscribeRun(int pos, int len, Font *font, SCRIPT_ANALYSIS &sa) : pos(pos), len(len), font(font), sa(sa) {}
};
/** Break a string into language formatting ranges. */
static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutFactory::CharType *buff, int32 length);
/** Generate and place glyphs for a run of characters. */
static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range);
/**
* Wrapper for doing layouts with Uniscribe.
*/
class UniscribeParagraphLayout : public ParagraphLayouter {
private:
const UniscribeParagraphLayoutFactory::CharType *text_buffer;
std::vector<UniscribeRun> ranges; ///< All runs of the text.
std::vector<UniscribeRun>::iterator cur_range; ///< The next run to be output.
int cur_range_offset = 0; ///< Offset from the start of the current run from where to output.
public:
/** Visual run contains data about the bit of text with the same font. */
class UniscribeVisualRun : public ParagraphLayouter::VisualRun {
private:
std::vector<GlyphID> glyphs;
std::vector<float> positions;
std::vector<WORD> char_to_glyph;
int start_pos;
int total_advance;
int num_glyphs;
Font *font;
mutable int *glyph_to_char = NULL;
public:
UniscribeVisualRun(const UniscribeRun &range, int x);
virtual ~UniscribeVisualRun()
{
free(this->glyph_to_char);
}
virtual const GlyphID *GetGlyphs() const { return &this->glyphs[0]; }
virtual const float *GetPositions() const { return &this->positions[0]; }
virtual const int *GetGlyphToCharMap() const;
virtual const Font *GetFont() const { return this->font; }
virtual int GetLeading() const { return this->font->fc->GetHeight(); }
virtual int GetGlyphCount() const { return this->num_glyphs; }
int GetAdvance() const { return this->total_advance; }
};
/** A single line worth of VisualRuns. */
class UniscribeLine : public AutoDeleteSmallVector<UniscribeVisualRun *, 4>, public ParagraphLayouter::Line {
public:
virtual int GetLeading() const;
virtual int GetWidth() const;
virtual int CountRuns() const { return this->Length(); }
virtual const VisualRun *GetVisualRun(int run) const { return *this->Get(run); }
int GetInternalCharLength(WChar c) const
{
/* Uniscribe uses UTF-16 internally which means we need to account for surrogate pairs. */
return c >= 0x010000U ? 2 : 1;
}
};
UniscribeParagraphLayout(std::vector<UniscribeRun> &ranges, const UniscribeParagraphLayoutFactory::CharType *buffer) : text_buffer(buffer), ranges(ranges)
{
this->Reflow();
}
virtual ~UniscribeParagraphLayout() {}
virtual void Reflow()
{
this->cur_range = this->ranges.begin();
this->cur_range_offset = 0;
}
virtual const Line *NextLine(int max_width);
};
void UniscribeResetScriptCache(FontSize size)
{
if (_script_cache[size] != NULL) {
ScriptFreeCache(&_script_cache[size]);
_script_cache[size] = NULL;
}
}
/** Load the matching native Windows font. */
static HFONT HFontFromFont(Font *font)
{
LOGFONT logfont;
ZeroMemory(&logfont, sizeof(LOGFONT));
logfont.lfHeight = font->fc->GetHeight();
logfont.lfWeight = FW_NORMAL;
logfont.lfCharSet = DEFAULT_CHARSET;
convert_to_fs(font->fc->GetFontName(), logfont.lfFaceName, lengthof(logfont.lfFaceName));
return CreateFontIndirect(&logfont);
}
/** Determine the glyph positions for a run. */
static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range)
{
/* Initial size guess for the number of glyphs recommended by Uniscribe. */
range.glyphs.resize(range.len * 3 / 2 + 16);
range.vis_attribs.resize(range.glyphs.size());
/* The char-to-glyph array is the same size as the input. */
range.char_to_glyph.resize(range.len);
HDC temp_dc = NULL;
HFONT old_font = NULL;
HFONT cur_font = NULL;
while (true) {
/* Shape the text run by determining the glyphs needed for display. */
int glyphs_used = 0;
HRESULT hr = ScriptShape(temp_dc, &_script_cache[range.font->fc->GetSize()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used);
if (SUCCEEDED(hr)) {
range.glyphs.resize(glyphs_used);
range.vis_attribs.resize(glyphs_used);
/* Calculate the glyph positions. */
ABC abc;
range.advances.resize(range.glyphs.size());
range.offsets.resize(range.glyphs.size());
hr = ScriptPlace(temp_dc, &_script_cache[range.font->fc->GetSize()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc);
if (SUCCEEDED(hr)) {
/* We map our special sprite chars to values that don't fit into a WORD. Copy the glyphs
* into a new vector and query the real glyph to use for these special chars. */
range.ft_glyphs.resize(range.glyphs.size());
for (size_t g_id = 0; g_id < range.glyphs.size(); g_id++) {
range.ft_glyphs[g_id] = range.glyphs[g_id];
}
for (int i = 0; i < range.len; i++) {
if (buff[range.pos + i] >= SCC_SPRITE_START && buff[range.pos + i] <= SCC_SPRITE_END) {
range.ft_glyphs[range.char_to_glyph[i]] = range.font->fc->MapCharToGlyph(buff[range.pos + i]);
}
}
/* FreeType and GDI/Uniscribe seems to occasionally disagree over the width of a glyph. */
range.total_advance = 0;
for (size_t i = 0; i < range.advances.size(); i++) {
if (range.advances[i] > 0 && range.ft_glyphs[i] != 0xFFFF) range.advances[i] = range.font->fc->GetGlyphWidth(range.ft_glyphs[i]);
range.total_advance += range.advances[i];
}
break;
}
}
if (hr == E_OUTOFMEMORY) {
/* The glyph buffer needs to be larger. Just double it every time. */
range.glyphs.resize(range.glyphs.size() * 2);
range.vis_attribs.resize(range.vis_attribs.size() * 2);
} else if (hr == E_PENDING) {
/* Glyph data is not in cache, load native font. */
cur_font = HFontFromFont(range.font);
if (cur_font == NULL) return false; // Sorry, no dice.
temp_dc = CreateCompatibleDC(NULL);
SetMapMode(temp_dc, MM_TEXT);
old_font = (HFONT)SelectObject(temp_dc, cur_font);
} else if (hr == USP_E_SCRIPT_NOT_IN_FONT && range.sa.eScript != SCRIPT_UNDEFINED) {
/* Try again with the generic shaping engine. */
range.sa.eScript = SCRIPT_UNDEFINED;
} else {
/* Some unknown other error. */
if (temp_dc != NULL) {
SelectObject(temp_dc, old_font);
DeleteObject(cur_font);
ReleaseDC(NULL, temp_dc);
}
return false;
}
}
if (temp_dc != NULL) {
SelectObject(temp_dc, old_font);
DeleteObject(cur_font);
ReleaseDC(NULL, temp_dc);
}
return true;
}
static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutFactory::CharType *buff, int32 length)
{
/* Itemize text. */
SCRIPT_CONTROL control;
ZeroMemory(&control, sizeof(SCRIPT_CONTROL));
control.uDefaultLanguage = _current_language->winlangid;
SCRIPT_STATE state;
ZeroMemory(&state, sizeof(SCRIPT_STATE));
state.uBidiLevel = _current_text_dir == TD_RTL ? 1 : 0;
std::vector<SCRIPT_ITEM> items(16);
while (true) {
/* We subtract one from max_items to work around a buffer overflow on some older versions of Windows. */
int generated = 0;
HRESULT hr = ScriptItemize(buff, length, (int)items.size() - 1, &control, &state, &items[0], &generated);
if (SUCCEEDED(hr)) {
/* Resize the item buffer. Note that Uniscribe will always add an additional end sentinel item. */
items.resize(generated + 1);
break;
}
/* Some kind of error except item buffer too small. */
if (hr != E_OUTOFMEMORY) return std::vector<SCRIPT_ITEM>();
items.resize(items.size() * 2);
}
return items;
}
/* static */ ParagraphLayouter *UniscribeParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping)
{
int32 length = buff_end - buff;
/* Can't layout an empty string. */
if (length == 0) return NULL;
/* Can't layout our in-built sprite fonts. */
for (FontMap::const_iterator i = fontMapping.Begin(); i != fontMapping.End(); i++) {
if (i->second->fc->IsBuiltInFont()) return NULL;
}
/* Itemize text. */
std::vector<SCRIPT_ITEM> items = UniscribeItemizeString(buff, length);
if (items.size() == 0) return NULL;
/* Build ranges from the items and the font map. A range is a run of text
* that is part of a single item and formatted using a single font style. */
std::vector<UniscribeRun> ranges;
int cur_pos = 0;
std::vector<SCRIPT_ITEM>::iterator cur_item = items.begin();
for (FontMap::const_iterator i = fontMapping.Begin(); i != fontMapping.End(); i++) {
while (cur_pos < i->first && cur_item != items.end() - 1) {
/* Add a range that spans the intersection of the remaining item and font run. */
int stop_pos = min(i->first, (cur_item + 1)->iCharPos);
assert(stop_pos - cur_pos > 0);
ranges.push_back(UniscribeRun(cur_pos, stop_pos - cur_pos, i->second, cur_item->a));
/* Shape the range. */
if (!UniscribeShapeRun(buff, ranges.back())) {
return NULL;
}
/* If we are at the end of the current item, advance to the next item. */
if (stop_pos == (cur_item + 1)->iCharPos) cur_item++;
cur_pos = stop_pos;
}
}
return new UniscribeParagraphLayout(ranges, buff);
}
/* virtual */ const ParagraphLayouter::Line *UniscribeParagraphLayout::NextLine(int max_width)
{
std::vector<UniscribeRun>::iterator start_run = this->cur_range;
std::vector<UniscribeRun>::iterator last_run = this->cur_range;
if (start_run == this->ranges.end()) return NULL;
/* Add remaining width of the first run if it is a broken run. */
int cur_width = 0;
if (this->cur_range_offset != 0) {
std::vector<int> dx(start_run->len);
ScriptGetLogicalWidths(&start_run->sa, start_run->len, (int)start_run->glyphs.size(), &start_run->advances[0], &start_run->char_to_glyph[0], &start_run->vis_attribs[0], &dx[0]);
for (std::vector<int>::const_iterator c = dx.begin() + this->cur_range_offset; c != dx.end(); c++) {
cur_width += *c;
}
++last_run;
}
/* Gather runs until the line is full. */
while (last_run != this->ranges.end() && cur_width < max_width) {
cur_width += last_run->total_advance;
++last_run;
}
/* If the text does not fit into the available width, find a suitable breaking point. */
int remaing_offset = (last_run - 1)->len;
if (cur_width > max_width) {
std::vector<SCRIPT_LOGATTR> log_attribs;
/* Get word break information. */
int width_avail = max_width;
int num_chars = this->cur_range_offset;
int start_offs = this->cur_range_offset;
int last_cluster = this->cur_range_offset + 1;
for (std::vector<UniscribeRun>::iterator r = start_run; r != last_run; r++) {
log_attribs.resize(r->pos - start_run->pos + r->len);
if (FAILED(ScriptBreak(this->text_buffer + r->pos + start_offs, r->len - start_offs, &r->sa, &log_attribs[r->pos - start_run->pos + start_offs]))) return NULL;
std::vector<int> dx(r->len);
ScriptGetLogicalWidths(&r->sa, r->len, (int)r->glyphs.size(), &r->advances[0], &r->char_to_glyph[0], &r->vis_attribs[0], &dx[0]);
/* Count absolute max character count on the line. */
for (int c = start_offs; c < r->len && width_avail > 0; c++, num_chars++) {
if (c > start_offs && log_attribs[num_chars].fCharStop) last_cluster = num_chars;
width_avail -= dx[c];
}
start_offs = 0;
}
/* Walk backwards to find the last suitable breaking point. */
while (--num_chars > this->cur_range_offset && !log_attribs[num_chars].fSoftBreak && !log_attribs[num_chars].fWhiteSpace) {}
if (num_chars == this->cur_range_offset) {
/* Didn't find any suitable word break point, just break on the last cluster boundary. */
num_chars = last_cluster;
}
/* Include whitespace characters after the breaking point. */
while (num_chars < (int)log_attribs.size() && log_attribs[num_chars].fWhiteSpace) {
num_chars++;
}
/* Get last run that corresponds to the number of characters to show. */
for (std::vector<UniscribeRun>::iterator run = start_run; run != last_run; run++) {
num_chars -= run->len;
if (num_chars <= 0) {
remaing_offset = num_chars + run->len + 1;
last_run = run + 1;
assert(remaing_offset - 1 > 0);
break;
}
}
}
/* Build display order from the runs. */
std::vector<BYTE> bidi_level;
for (std::vector<UniscribeRun>::iterator r = start_run; r != last_run; r++) {
bidi_level.push_back(r->sa.s.uBidiLevel);
}
std::vector<INT> vis_to_log(bidi_level.size());
if (FAILED(ScriptLayout((int)bidi_level.size(), &bidi_level[0], &vis_to_log[0], NULL))) return NULL;
/* Create line. */
UniscribeLine *line = new UniscribeLine();
int cur_pos = 0;
for (std::vector<INT>::iterator l = vis_to_log.begin(); l != vis_to_log.end(); l++) {
std::vector<UniscribeRun>::iterator i_run = start_run + *l;
UniscribeRun run = *i_run;
/* Partial run after line break (either start or end)? Reshape run to get the first/last glyphs right. */
if (i_run == last_run - 1 && remaing_offset < (last_run - 1)->len) {
run.len = remaing_offset - 1;
if (!UniscribeShapeRun(this->text_buffer, run)) return NULL;
}
if (i_run == start_run && this->cur_range_offset > 0) {
assert(run.len - this->cur_range_offset > 0);
run.pos += this->cur_range_offset;
run.len -= this->cur_range_offset;
if (!UniscribeShapeRun(this->text_buffer, run)) return NULL;
}
*line->Append() = new UniscribeVisualRun(run, cur_pos);
cur_pos += run.total_advance;
}
if (remaing_offset < (last_run - 1)->len) {
/* We didn't use up all of the last run, store remainder for the next line. */
this->cur_range_offset = remaing_offset - 1;
this->cur_range = last_run - 1;
assert(this->cur_range->len > this->cur_range_offset);
} else {
this->cur_range_offset = 0;
this->cur_range = last_run;
}
return line;
}
/**
* Get the height of the line.
* @return The maximum height of the line.
*/
int UniscribeParagraphLayout::UniscribeLine::GetLeading() const
{
int leading = 0;
for (const UniscribeVisualRun * const *run = this->Begin(); run != this->End(); run++) {
leading = max(leading, (*run)->GetLeading());
}
return leading;
}
/**
* Get the width of this line.
* @return The width of the line.
*/
int UniscribeParagraphLayout::UniscribeLine::GetWidth() const
{
int length = 0;
for (const UniscribeVisualRun * const *run = this->Begin(); run != this->End(); run++) {
length += (*run)->GetAdvance();
}
return length;
}
UniscribeParagraphLayout::UniscribeVisualRun::UniscribeVisualRun(const UniscribeRun &range, int x) : glyphs(range.ft_glyphs), char_to_glyph(range.char_to_glyph), start_pos(range.pos), total_advance(range.total_advance), font(range.font)
{
this->num_glyphs = (int)glyphs.size();
this->positions.resize(this->num_glyphs * 2 + 2);
int advance = 0;
for (int i = 0; i < this->num_glyphs; i++) {
this->positions[i * 2 + 0] = range.offsets[i].du + advance + x;
this->positions[i * 2 + 1] = range.offsets[i].dv;
advance += range.advances[i];
}
this->positions[this->num_glyphs * 2] = advance + x;
}
const int *UniscribeParagraphLayout::UniscribeVisualRun::GetGlyphToCharMap() const
{
if (this->glyph_to_char == NULL) {
this->glyph_to_char = CallocT<int>(this->GetGlyphCount());
/* The char to glyph array contains the first glyph index of the cluster that is associated
* with each character. It is possible for a cluster to be formed of several chars. */
for (int c = 0; c < (int)this->char_to_glyph.size(); c++) {
/* If multiple chars map to one glyph, only refer back to the first character. */
if (this->glyph_to_char[this->char_to_glyph[c]] == 0) this->glyph_to_char[this->char_to_glyph[c]] = c + this->start_pos;
}
/* We only marked the first glyph of each cluster in the loop above. Fill the gaps. */
int last_char = this->glyph_to_char[0];
for (int g = 0; g < this->GetGlyphCount(); g++) {
if (this->glyph_to_char[g] != 0) last_char = this->glyph_to_char[g];
this->glyph_to_char[g] = last_char;
}
}
return this->glyph_to_char;
}
/* virtual */ void UniscribeStringIterator::SetString(const char *s)
{
const char *string_base = s;
this->utf16_to_utf8.clear();
this->str_info.clear();
this->cur_pos = 0;
/* Uniscribe operates on UTF-16, thus we have to convert the input string.
* To be able to return proper offsets, we have to create a mapping at the same time. */
std::vector<wchar_t> utf16_str; ///< UTF-16 copy of the string.
while (*s != '\0') {
size_t idx = s - string_base;
WChar c = Utf8Consume(&s);
if (c < 0x10000) {
utf16_str.push_back((wchar_t)c);
} else {
/* Make a surrogate pair. */
utf16_str.push_back((wchar_t)(0xD800 + ((c - 0x10000) >> 10)));
utf16_str.push_back((wchar_t)(0xDC00 + ((c - 0x10000) & 0x3FF)));
this->utf16_to_utf8.push_back(idx);
}
this->utf16_to_utf8.push_back(idx);
}
this->utf16_to_utf8.push_back(s - string_base);
/* Query Uniscribe for word and cluster break information. */
this->str_info.resize(utf16_to_utf8.size());
if (utf16_str.size() > 0) {
/* Itemize string into language runs. */
std::vector<SCRIPT_ITEM> runs = UniscribeItemizeString(&utf16_str[0], (int32)utf16_str.size());
for (std::vector<SCRIPT_ITEM>::const_iterator run = runs.begin(); runs.size() > 0 && run != runs.end() - 1; run++) {
/* Get information on valid word and character break.s */
int len = (run + 1)->iCharPos - run->iCharPos;
std::vector<SCRIPT_LOGATTR> attr(len);
ScriptBreak(&utf16_str[run->iCharPos], len, &run->a, &attr[0]);
/* Extract the information we're interested in. */
for (size_t c = 0; c < attr.size(); c++) {
/* First character of a run is always a valid word break. */
this->str_info[c + run->iCharPos].word_stop = attr[c].fWordStop || c == 0;
this->str_info[c + run->iCharPos].char_stop = attr[c].fCharStop;
}
}
}
/* End-of-string is always a valid stopping point. */
this->str_info.back().char_stop = true;
this->str_info.back().word_stop = true;
}
/* virtual */ size_t UniscribeStringIterator::SetCurPosition(size_t pos)
{
/* Convert incoming position to an UTF-16 string index. */
size_t utf16_pos = 0;
for (size_t i = 0; i < this->utf16_to_utf8.size(); i++) {
if (this->utf16_to_utf8[i] == pos) {
utf16_pos = i;
break;
}
}
/* Sanitize in case we get a position inside a grapheme cluster. */
while (utf16_pos > 0 && !this->str_info[utf16_pos].char_stop) utf16_pos--;
this->cur_pos = utf16_pos;
return this->utf16_to_utf8[this->cur_pos];
}
/* virtual */ size_t UniscribeStringIterator::Next(IterType what)
{
assert(this->cur_pos <= this->utf16_to_utf8.size());
assert(what == StringIterator::ITER_CHARACTER || what == StringIterator::ITER_WORD);
if (this->cur_pos == this->utf16_to_utf8.size()) return END;
do {
this->cur_pos++;
} while (this->cur_pos < this->utf16_to_utf8.size() && (what == ITER_WORD ? !this->str_info[this->cur_pos].word_stop : !this->str_info[this->cur_pos].char_stop));
return this->cur_pos == this->utf16_to_utf8.size() ? END : this->utf16_to_utf8[this->cur_pos];
}
/*virtual */ size_t UniscribeStringIterator::Prev(IterType what)
{
assert(this->cur_pos <= this->utf16_to_utf8.size());
assert(what == StringIterator::ITER_CHARACTER || what == StringIterator::ITER_WORD);
if (this->cur_pos == 0) return END;
do {
this->cur_pos--;
} while (this->cur_pos > 0 && (what == ITER_WORD ? !this->str_info[this->cur_pos].word_stop : !this->str_info[this->cur_pos].char_stop));
return this->utf16_to_utf8[this->cur_pos];
}
#endif /* defined(WITH_UNISCRIBE) */

View File

@@ -0,0 +1,92 @@
/* $Id$ */
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file string_uniscribe.h Functions related to laying out text on Win32. */
#ifndef STRING_UNISCRIBE_H
#define STRING_UNISCRIBE_H
#if defined(WITH_UNISCRIBE)
#include "../../gfx_layout.h"
#include "../../string_base.h"
#include <vector>
void UniscribeResetScriptCache(FontSize size);
/**
* Helper class to construct a new #UniscribeParagraphLayout.
*/
class UniscribeParagraphLayoutFactory {
public:
/** Helper for GetLayouter, to get the right type. */
typedef wchar_t CharType;
/** Helper for GetLayouter, to get whether the layouter supports RTL. */
static const bool SUPPORTS_RTL = true;
/**
* Get the actual ParagraphLayout for the given buffer.
* @param buff The begin of the buffer.
* @param buff_end The location after the last element in the buffer.
* @param fontMapping THe mapping of the fonts.
* @return The ParagraphLayout instance.
*/
static ParagraphLayouter *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping);
/**
* Append a wide character to the internal buffer.
* @param buff The buffer to append to.
* @param buffer_last The end of the buffer.
* @param c The character to add.
* @return The number of buffer spaces that were used.
*/
static size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c)
{
if (c >= 0x010000U) {
/* Character is encoded using surrogates in UTF-16. */
if (buff + 1 <= buffer_last) {
buff[0] = (CharType)(((c - 0x010000U) >> 10) + 0xD800);
buff[1] = (CharType)(((c - 0x010000U) & 0x3FF) + 0xDC00);
} else {
/* Not enough space in buffer. */
*buff = 0;
}
return 2;
} else {
*buff = (CharType)(c & 0xFFFF);
return 1;
}
}
};
/** String iterator using Uniscribe as a backend. */
class UniscribeStringIterator : public StringIterator {
/** */
struct CharInfo {
bool word_stop : 1; ///< Code point is suitable as a word break.
bool char_stop : 1; ///< Code point is the start of a grapheme cluster, i.e. a "character".
};
std::vector<CharInfo> str_info; ///< Break information for each code point.
std::vector<size_t> utf16_to_utf8; ///< Mapping from UTF-16 code point position to index in the UTF-8 source string.
size_t cur_pos; ///< Current iteration position.
public:
virtual void SetString(const char *s);
virtual size_t SetCurPosition(size_t pos);
virtual size_t Next(IterType what);
virtual size_t Prev(IterType what);
};
#endif /* defined(WITH_UNISCRIBE) */
#endif /* STRING_UNISCRIBE_H */

View File

@@ -28,6 +28,7 @@
#include "../../crashlog.h"
#include <errno.h>
#include <sys/stat.h>
#include "../../language.h"
/* Due to TCHAR, strncat and strncpy have to remain (for a while). */
#include "../../safeguards.h"
@@ -742,6 +743,70 @@ uint GetCPUCoreCount()
return info.dwNumberOfProcessors;
}
static WCHAR _cur_iso_locale[16] = L"";
void Win32SetCurrentLocaleName(const char *iso_code)
{
/* Convert the iso code into the format that windows expects. */
char iso[16];
if (strcmp(iso_code, "zh_TW") == 0) {
strecpy(iso, "zh-Hant", lastof(iso));
} else if (strcmp(iso_code, "zh_CN") == 0) {
strecpy(iso, "zh-Hans", lastof(iso));
} else {
/* Windows expects a '-' between language and country code, but we use a '_'. */
strecpy(iso, iso_code, lastof(iso));
for (char *c = iso; *c != '\0'; c++) {
if (*c == '_') *c = '-';
}
}
MultiByteToWideChar(CP_UTF8, 0, iso, -1, _cur_iso_locale, lengthof(_cur_iso_locale));
}
int OTTDStringCompare(const char *s1, const char *s2)
{
typedef int (WINAPI *PFNCOMPARESTRINGEX)(LPCWSTR, DWORD, LPCWCH, int, LPCWCH, int, LPVOID, LPVOID, LPARAM);
static PFNCOMPARESTRINGEX _CompareStringEx = NULL;
static bool first_time = true;
#ifndef SORT_DIGITSASNUMBERS
# define SORT_DIGITSASNUMBERS 0x00000008 // use digits as numbers sort method
#endif
#ifndef LINGUISTIC_IGNORECASE
# define LINGUISTIC_IGNORECASE 0x00000010 // linguistically appropriate 'ignore case'
#endif
if (first_time) {
_CompareStringEx = (PFNCOMPARESTRINGEX)GetProcAddress(GetModuleHandle(_T("Kernel32")), "CompareStringEx");
first_time = false;
}
if (_CompareStringEx != NULL) {
/* CompareStringEx takes UTF-16 strings, even in ANSI-builds. */
int len_s1 = MultiByteToWideChar(CP_UTF8, 0, s1, -1, NULL, 0);
int len_s2 = MultiByteToWideChar(CP_UTF8, 0, s2, -1, NULL, 0);
if (len_s1 != 0 && len_s2 != 0) {
LPWSTR str_s1 = AllocaM(WCHAR, len_s1);
LPWSTR str_s2 = AllocaM(WCHAR, len_s2);
MultiByteToWideChar(CP_UTF8, 0, s1, -1, str_s1, len_s1);
MultiByteToWideChar(CP_UTF8, 0, s2, -1, str_s2, len_s2);
int result = _CompareStringEx(_cur_iso_locale, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, str_s1, -1, str_s2, -1, NULL, NULL, 0);
if (result != 0) return result;
}
}
TCHAR s1_buf[512], s2_buf[512];
convert_to_fs(s1, s1_buf, lengthof(s1_buf));
convert_to_fs(s2, s2_buf, lengthof(s2_buf));
return CompareString(MAKELCID(_current_language->winlangid, SORT_DEFAULT), NORM_IGNORECASE, s1_buf, -1, s2_buf, -1);
}
#ifdef _MSC_VER
/* Code from MSDN: https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx */
const DWORD MS_VC_EXCEPTION = 0x406D1388;

View File

@@ -45,4 +45,7 @@ void SetWin32ThreadName(DWORD dwThreadID, const char* threadName);
static inline void SetWin32ThreadName(DWORD dwThreadID, const char* threadName) {}
#endif
void Win32SetCurrentLocaleName(const char *iso_code);
int OTTDStringCompare(const char *s1, const char *s2);
#endif /* WIN32_H */

View File

@@ -633,24 +633,28 @@ struct BuildRoadToolbarWindow : Window {
break;
case DDSP_BUILD_BUSSTOP:
PlaceRoadStop(start_tile, end_tile, (_ctrl_pressed << 5) | RoadTypeToRoadTypes(_cur_roadtype) << 2 | ROADSTOP_BUS, CMD_BUILD_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_build_station[ROADSTOP_BUS]));
case DDSP_REMOVE_BUSSTOP:
if (this->IsWidgetLowered(WID_ROT_BUS_STATION)) {
if (_remove_button_clicked) {
TileArea ta(start_tile, end_tile);
DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_BUS, CMD_REMOVE_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_remove_station[ROADSTOP_BUS]), CcPlaySound_SPLAT_OTHER);
} else {
PlaceRoadStop(start_tile, end_tile, (_ctrl_pressed << 5) | RoadTypeToRoadTypes(_cur_roadtype) << 2 | ROADSTOP_BUS, CMD_BUILD_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_build_station[ROADSTOP_BUS]));
}
}
break;
case DDSP_BUILD_TRUCKSTOP:
PlaceRoadStop(start_tile, end_tile, (_ctrl_pressed << 5) | RoadTypeToRoadTypes(_cur_roadtype) << 2 | ROADSTOP_TRUCK, CMD_BUILD_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_build_station[ROADSTOP_TRUCK]));
case DDSP_REMOVE_TRUCKSTOP:
if (this->IsWidgetLowered(WID_ROT_TRUCK_STATION)) {
if (_remove_button_clicked) {
TileArea ta(start_tile, end_tile);
DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_TRUCK, CMD_REMOVE_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_remove_station[ROADSTOP_TRUCK]), CcPlaySound_SPLAT_OTHER);
} else {
PlaceRoadStop(start_tile, end_tile, (_ctrl_pressed << 5) | RoadTypeToRoadTypes(_cur_roadtype) << 2 | ROADSTOP_TRUCK, CMD_BUILD_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_build_station[ROADSTOP_TRUCK]));
}
}
break;
case DDSP_REMOVE_BUSSTOP: {
TileArea ta(start_tile, end_tile);
DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_BUS, CMD_REMOVE_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_remove_station[ROADSTOP_BUS]), CcPlaySound_SPLAT_OTHER);
break;
}
case DDSP_REMOVE_TRUCKSTOP: {
TileArea ta(start_tile, end_tile);
DoCommandP(ta.tile, ta.w | ta.h << 8, (_ctrl_pressed << 1) | ROADSTOP_TRUCK, CMD_REMOVE_ROAD_STOP | CMD_MSG(_road_type_infos[_cur_roadtype].err_remove_station[ROADSTOP_TRUCK]), CcPlaySound_SPLAT_OTHER);
break;
}
}
}
}

View File

@@ -965,6 +965,8 @@ static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags
num_roadbits += CountBits(GetRoadBits(cur_tile, ROADTYPE_ROAD));
}
if (GetDisallowedRoadDirections(cur_tile) != DRD_NONE) return_cmd_error(STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD);
/* There is a tram, check if we can build road+tram stop over it. */
if (HasBit(cur_rts, ROADTYPE_TRAM)) {
Owner tram_owner = GetRoadOwner(cur_tile, ROADTYPE_TRAM);

View File

@@ -25,6 +25,14 @@
#include <errno.h> // required by vsnprintf implementation for MSVC
#endif
#ifdef WIN32
#include "os/windows/win32.h"
#endif
#ifdef WITH_UNISCRIBE
#include "os/windows/string_uniscribe.h"
#endif
#ifdef WITH_ICU_SORT
/* Required by strnatcmp. */
#include <unicode/ustring.h>
@@ -658,20 +666,32 @@ int strnatcmp(const char *s1, const char *s2, bool ignore_garbage_at_front)
s1 = SkipGarbage(s1);
s2 = SkipGarbage(s2);
}
#ifdef WITH_ICU_SORT
if (_current_collator != NULL) {
UErrorCode status = U_ZERO_ERROR;
int result = _current_collator->compareUTF8(s1, s2, status);
if (U_SUCCESS(status)) return result;
}
#endif /* WITH_ICU_SORT */
#if defined(WIN32) && !defined(STRGEN) && !defined(SETTINGSGEN)
int res = OTTDStringCompare(s1, s2);
if (res != 0) return res - 2; // Convert to normal C return values.
#endif
/* Do a manual natural sort comparison if ICU is missing or if we cannot create a collator. */
return _strnatcmpIntl(s1, s2);
}
#ifdef WITH_ICU_SORT
#ifdef WITH_UNISCRIBE
/* static */ StringIterator *StringIterator::Create()
{
return new UniscribeStringIterator();
}
#elif defined(WITH_ICU_SORT)
#include <unicode/utext.h>
#include <unicode/brkiter.h>

View File

@@ -2048,6 +2048,11 @@ bool ReadLanguagePack(const LanguageMetadata *lang)
strecpy(_config_language_file, c_file, lastof(_config_language_file));
SetCurrentGrfLangID(_current_language->newgrflangid);
#ifdef WIN32
extern void Win32SetCurrentLocaleName(const char *iso_code);
Win32SetCurrentLocaleName(_current_language->isocode);
#endif
#ifdef WITH_ICU_SORT
/* Delete previous collator. */
if (_current_collator != NULL) {
@@ -2390,7 +2395,7 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
/* Update the font with cache */
LoadStringWidthTable(searcher->Monospace());
#if !defined(WITH_ICU_LAYOUT)
#if !defined(WITH_ICU_LAYOUT) && !defined(WITH_UNISCRIBE)
/*
* For right-to-left languages we need the ICU library. If
* we do not have support for that library we warn the user

View File

@@ -343,13 +343,15 @@ public:
SetDParam(1, this->town->cache.num_houses);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y, STR_TOWN_VIEW_POPULATION_HOUSES);
SetDParam(0, this->town->supplied[CT_PASSENGERS].old_act);
SetDParam(1, this->town->supplied[CT_PASSENGERS].old_max);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX);
SetDParam(0, 1 << CT_PASSENGERS);
SetDParam(1, this->town->supplied[CT_PASSENGERS].old_act);
SetDParam(2, this->town->supplied[CT_PASSENGERS].old_max);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX);
SetDParam(0, this->town->supplied[CT_MAIL].old_act);
SetDParam(1, this->town->supplied[CT_MAIL].old_max);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX);
SetDParam(0, 1 << CT_MAIL);
SetDParam(1, this->town->supplied[CT_MAIL].old_act);
SetDParam(2, this->town->supplied[CT_MAIL].old_max);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX);
bool first = true;
for (int i = TE_BEGIN; i < TE_END; i++) {