Merge branch 'master' into jgrpp

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

110
README.md
View File

@@ -314,9 +314,10 @@ The easiest way to contact the OpenTTD team is by submitting bug reports or
posting comments in our forums. You can also chat with us on IRC (#openttd posting comments in our forums. You can also chat with us on IRC (#openttd
on irc.oftc.net). on irc.oftc.net).
The OpenTTD homepage is [http://www.openttd.org/](http://www.openttd.org/). The OpenTTD homepage is https://www.openttd.org.
You can also find the OpenTTD forums at [http://forum.openttd.org/](http://forum.openttd.org/). You can also find the OpenTTD forums at
[https://www.tt-forums.net](https://www.tt-forums.net/viewforum.php?f=55).
### 2.1) Reporting bugs ### 2.1) Reporting bugs
@@ -325,11 +326,11 @@ through the file called 'known-bugs.txt' which is distributed with OpenTTD
like this readme. like this readme.
For tracking our bugs we are using GitHub's issue tracker. You can find For tracking our bugs we are using GitHub's issue tracker. You can find
the tracker at [https://github.com/OpenTTD/OpenTTD/issues](https://github.com/OpenTTD/OpenTTD/issues). Before actually reporting take a look the tracker at https://github.com/OpenTTD/OpenTTD/issues. Before actually
through the already reported bugs there to see if the bug is already known. reporting take a look through the already reported bugs there to see if
The 'known-bugs.txt' file might be a bit outdated at the moment you are the bug is already known. The 'known-bugs.txt' file might be a bit outdated
reading it as only bugs known before the release are documented there. Also at the moment you are reading it as only bugs known before the release
look through the recently closed bugs. are documented there. Also look through the recently closed bugs.
When you are sure it is not already reported you should: When you are sure it is not already reported you should:
@@ -352,8 +353,8 @@ following information in your bug report:
- Bug details, including instructions how to reproduce it - Bug details, including instructions how to reproduce it
- Platform (Windows, Linux, FreeBSD, …) and compiler (including version) if - Platform (Windows, Linux, FreeBSD, …) and compiler (including version) if
you compiled OpenTTD yourself. you compiled OpenTTD yourself.
- The processor architecture of your OS (32 bits Windows, 64 bits Windows, - The processor architecture of your OS (32-bit Windows, 64-bit Windows,
Linux on an ARM, Mac OS X on a PowerPC, ) Linux on an ARM, Mac OS X on a PowerPC, etc.)
- Attach a saved game **and** a screenshot if possible - Attach a saved game **and** a screenshot if possible
- If this bug only occurred recently please note the last version without - If this bug only occurred recently please note the last version without
the bug and the first version including the bug. That way we can fix it the bug and the first version including the bug. That way we can fix it
@@ -426,7 +427,7 @@ platforms are:
- DOS (Allegro) - DOS (Allegro)
- FreeBSD (SDL) - FreeBSD (SDL)
- Linux (SDL or Allegro) - Linux (SDL or Allegro)
- MacOS X (universal) (Cocoa video and sound drivers) - macOS (universal) (Cocoa video and sound drivers)
- MorphOS (SDL) - MorphOS (SDL)
- OpenBSD (SDL) - OpenBSD (SDL)
- OS/2 (SDL) - OS/2 (SDL)
@@ -482,9 +483,9 @@ when using other versions of the game.
The free data files, split into OpenGFX for graphics, OpenSFX for sounds and The free data files, split into OpenGFX for graphics, OpenSFX for sounds and
OpenMSX for music can be found at: OpenMSX for music can be found at:
- [http://www.openttd.org/download-opengfx](http://www.openttd.org/download-opengfx) for OpenGFX - https://www.openttd.org/download-opengfx for OpenGFX
- [http://www.openttd.org/download-opensfx](http://www.openttd.org/download-opensfx) for OpenSFX - https://www.openttd.org/download-opensfx for OpenSFX
- [http://www.openttd.org/download-openmsx](http://www.openttd.org/download-openmsx) for OpenMSX - https://www.openttd.org/download-openmsx for OpenMSX
Please follow the readme of these packages about the installation procedure. Please follow the readme of these packages about the installation procedure.
The Windows installer can optionally download and install these packages. The Windows installer can optionally download and install these packages.
@@ -518,10 +519,9 @@ not possible or you want to use an AI that has not been uploaded to the content
download system download the tar file and place it in the ai/ directory. If the download system download the tar file and place it in the ai/ directory. If the
AI needs libraries you will have to download those too and put them in the AI needs libraries you will have to download those too and put them in the
ai/library/ directory. All AIs and AI Libraries that have been uploaded to ai/library/ directory. All AIs and AI Libraries that have been uploaded to
the content download system can be found at http://noai.openttd.org/downloads/ the content download system can be found at https://noai.openttd.org/downloads.
The AIs and libraries can be found their in the form of .tar.gz packages. The AIs and libraries can be found their in the form of .tar.gz packages.
OpenTTD can read inside tar files but it does not extract .tar.gz files by OpenTTD can read inside tar files but it does not extract .tar.gz files by itself.
itself.
To figure out which libraries you need for an AI you have to start the AI and To figure out which libraries you need for an AI you have to start the AI and
wait for an error message to pop up. The error message will tell you wait for an error message to pop up. The error message will tell you
@@ -553,26 +553,27 @@ your operating system:
For non-Windows operating systems OpenTTD will not scan for files in this For non-Windows operating systems OpenTTD will not scan for files in this
directory if it is your personal directory, i.e. '~/', or when it is the directory if it is your personal directory, i.e. '~/', or when it is the
root directory, i.e. '/'. root directory, i.e. '/'.
2. Your personal directory 2. Your personal directory
- Windows: - Windows:
- `C:\My Documents\OpenTTD` (95, 98, ME) - `C:\My Documents\OpenTTD` (95, 98, ME)
- `C:\Documents and Settings\<username>\My Documents\OpenTTD` (2000, XP) - `C:\Documents and Settings\<username>\My Documents\OpenTTD` (2000, XP)
- `C:\Users\<username>\Documents\OpenTTD` (Vista, 7) - `C:\Users\<username>\Documents\OpenTTD` (Vista, 7, 8.1, 10)
- Mac OSX: `~/Documents/OpenTTD` - macOS: `~/Documents/OpenTTD`
- Linux: `$XDG_DATA_HOME/openttd` which is usually `~/.local/share/openttd` - Linux: `$XDG_DATA_HOME/openttd` which is usually `~/.local/share/openttd`
when built with XDG base directory support, otherwise `~/.openttd` when built with XDG base directory support, otherwise `~/.openttd`
3. The shared directory 3. The shared directory
- Windows: - Windows:
- `C:\Documents and Settings\All Users\Shared Documents\OpenTTD` (2000, XP) - `C:\Documents and Settings\All Users\Shared Documents\OpenTTD` (2000, XP)
- `C:\Users\Public\Documents\OpenTTD` (Vista, 7) - `C:\Users\Public\Documents\OpenTTD` (Vista, 7, 8.1, 10)
- Mac OSX: `/Library/Application Support/OpenTTD` - macOS: `/Library/Application Support/OpenTTD`
- Linux: not available - Linux: not available
4. The binary directory (where the OpenTTD executable is) 4. The binary directory (where the OpenTTD executable is)
- Windows: `C:\Program Files\OpenTTD` - Windows: `C:\Program Files\OpenTTD`
- Linux: `/usr/games` - Linux: `/usr/games`
5. The installation directory (Linux only) 5. The installation directory (Linux only)
- Linux: `/usr/share/games/openttd` - Linux: `/usr/share/games/openttd`
6. The application bundle (Mac OSX only) 6. The application bundle (macOS only)
It includes the OpenTTD files (grf+lng) and it will work as long as they It includes the OpenTTD files (grf+lng) and it will work as long as they
are not touched are not touched
@@ -662,17 +663,16 @@ OpenTTD has a lot of features going beyond the original Transport Tycoon Deluxe
emulation. Unfortunately, there is currently no comprehensive list of features, emulation. Unfortunately, there is currently no comprehensive list of features,
but there is a basic features list on the web, and some optional features can be but there is a basic features list on the web, and some optional features can be
controlled through the Advanced Settings dialog. We also implement some controlled through the Advanced Settings dialog. We also implement some
features known from [TTDPatch](http://www.ttdpatch.net/). features known from [TTDPatch](https://www.ttdpatch.net).
Several important non-standard controls: Several important non-standard controls:
- Ctrl modifies many commands and makes them more powerful. For example Ctrl - Ctrl modifies many commands and makes them more powerful. For example Ctrl
clicking on signals with the build signal tool changes their behaviour, holding clicking on signals with the build signal tool changes their behaviour,
Ctrl while the track build tool is activated changes it to the track removal holding Ctrl while the track build tool is activated changes it to the track
tool, and so on. See [http://wiki.openttd.org/Hidden_features](http://wiki.openttd.org/Hidden_features) removal tool, and so on. See https://wiki.openttd.org/Hidden_features
for a non-comprehensive list or look at the tooltips. for a non-comprehensive list or look at the tooltips.
- Ingame console. More information at - Ingame console. More information at https://wiki.openttd.org/Console
[http://wiki.openttd.org/index.php/Console](http://wiki.openttd.org/index.php/Console)
- Hovering over a GUI element shows tooltips. This can be changed to right click - Hovering over a GUI element shows tooltips. This can be changed to right click
via the advanced settings. via the advanced settings.
@@ -724,7 +724,7 @@ you need to add WITH_SDL to the project settings.
PNG (WITH_PNG) and ZLIB (WITH_ZLIB) support is enabled by default. For these PNG (WITH_PNG) and ZLIB (WITH_ZLIB) support is enabled by default. For these
to work you need their development files. For best results, download the to work you need their development files. For best results, download the
openttd-useful.zip file from [http://www.openttd.org/download-openttd-useful](http://www.openttd.org/download-openttd-useful) openttd-useful.zip file from https://www.openttd.org/download-openttd-useful.
Put the header files into your compiler's include/ directory and the Put the header files into your compiler's include/ directory and the
library (.lib) files into the lib/ directory. library (.lib) files into the lib/ directory.
For more help with VS see docs/Readme_Windows_MSVC.txt. For more help with VS see docs/Readme_Windows_MSVC.txt.
@@ -741,7 +741,7 @@ Use '`gmake`', but do a '`./configure`' before the first build.
OpenTTD can be built with GNU '`make`'. On non-GNU systems it is called '`gmake`'. OpenTTD can be built with GNU '`make`'. On non-GNU systems it is called '`gmake`'.
However, for the first build one has to do a '`./configure`' first. However, for the first build one has to do a '`./configure`' first.
### MacOS X: ### macOS:
Use '`make`' or Xcode (which will then call make for you) Use '`make`' or Xcode (which will then call make for you)
This will give you a binary for your CPU type (PPC/Intel) This will give you a binary for your CPU type (PPC/Intel)
@@ -794,7 +794,7 @@ The following libraries are used by OpenTTD for:
OpenTTD does not require any of the libraries to be present, but without OpenTTD does not require any of the libraries to be present, but without
liblzma you cannot open most recent savegames and without zlib you cannot liblzma you cannot open most recent savegames and without zlib you cannot
open most older savegames or use the content downloading system. open most older savegames or use the content downloading system.
Without libSDL/liballegro on non-Windows and non-MacOS X machines you have Without libSDL/liballegro on non-Windows and non-macOS machines you have
no graphical user interface; you would be building a dedicated server. no graphical user interface; you would be building a dedicated server.
### 7.2) Supported compilers ### 7.2) Supported compilers
@@ -817,7 +817,7 @@ The following compilers are known not to compile OpenTTD:
- GNU Compiler Collection (GCC) 3.2 and earlier. - GNU Compiler Collection (GCC) 3.2 and earlier.
These old versions fail due to OpenTTD's template usage. These old versions fail due to OpenTTD's template usage.
- GNU Compiler Collection (GCC) 4.5. It optimizes enums too aggressively. - GNU Compiler Collection (GCC) 4.5. It optimizes enums too aggressively.
See http://bugs.openttd.org/task/5513 and references therein. See https://github.com/OpenTTD/OpenTTD/issues/5513 and references therein.
- Intel C++ Compiler (ICC) 11.1 and earlier. - Intel C++ Compiler (ICC) 11.1 and earlier.
- Version 10.0 and earlier fail a configure check and fail with recent - Version 10.0 and earlier fail a configure check and fail with recent
system headers. system headers.
@@ -833,7 +833,7 @@ Patches to support more compilers are welcome.
To recompile the extra graphics needed to play with the original Transport To recompile the extra graphics needed to play with the original Transport
Tycoon Deluxe graphics you need GRFCodec (which includes NFORenum) as well. Tycoon Deluxe graphics you need GRFCodec (which includes NFORenum) as well.
GRFCodec can be found at: [http://www.openttd.org/download-grfcodec](http://www.openttd.org/download-grfcodec) GRFCodec can be found at https://www.openttd.org/download-grfcodec.
The compilation of these extra graphics does generally not happen, unless The compilation of these extra graphics does generally not happen, unless
you remove the graphics file using '`make maintainer-clean`'. you remove the graphics file using '`make maintainer-clean`'.
@@ -846,17 +846,16 @@ modification of the base set files by the build process.
## 8.0) Translating ## 8.0) Translating
See [http://www.openttd.org/development](http://www.openttd.org/development) for up-to-date information. See https://www.openttd.org/development for up-to-date information.
The use of the online Translator service, located at The use of the online Translator service, located at
[http://translator.openttd.org/](http://translator.openttd.org/), is highly https://translator.openttd.org, is highly encouraged. For getting an account
encouraged. For getting an account simply follow the guidelines in the FAQ of simply follow the guidelines in the FAQ of the translator website.
the translator website.
If for some reason the website is down for a longer period of time, the If for some reason the website is down for a longer period of time, the
information below might be of help. information below might be of help.
Please contact the translations manager ([http://www.openttd.org/contact](http://www.openttd.org/contact)) Please contact the translations manager (https://www.openttd.org/contact)
before beginning the translation process! This avoids double work, as before beginning the translation process! This avoids double work, as
someone else may have already started translating to the same language. someone else may have already started translating to the same language.
@@ -875,8 +874,8 @@ Note: Do not alter the following parts of the file:
- String identifiers (the first word on each line) - String identifiers (the first word on each line)
- Parts of the strings which are in curly braces (such as {STRING}) - Parts of the strings which are in curly braces (such as {STRING})
- Lines beginning with ## (such as ##id), other than the first two lines of - Lines beginning with ## (such as ##id), other than the first two lines
the file of the file
### 8.2) Previewing ### 8.2) Previewing
@@ -908,7 +907,7 @@ If the game is acting strange and you feel adventurous you can try the
debugging output. The 'name' variable can help you to display only some type of debugging output. The 'name' variable can help you to display only some type of
debugging messages. This is mostly undocumented so best is to look in the debugging messages. This is mostly undocumented so best is to look in the
source code file debug.c for the various debugging types. For more information source code file debug.c for the various debugging types. For more information
look at [http://wiki.openttd.org/index.php/Command_line](http://wiki.openttd.org/index.php/Command_line). look at https://wiki.openttd.org/Command_line.
The most frequent problem is missing data files. Please install OpenGFX and The most frequent problem is missing data files. Please install OpenGFX and
possibly OpenSFX and OpenMSX. See section 4.1.1 for more information. possibly OpenSFX and OpenMSX. See section 4.1.1 for more information.
@@ -932,16 +931,16 @@ and add a suitable font for the small, medium and / or large font, e.g.:
You should use a font name like 'Tahoma' or a path to the desired font. You should use a font name like 'Tahoma' or a path to the desired font.
Any NewGRF file used in a game is stored inside the savegame and will refuse Any NewGRF file used in a game is stored inside the savegame and will refuse to
to load if you do not have that NewGRF file available. A list of missing files load if you do not have that NewGRF file available. A list of missing files can
can be viewed in the NewGRF window accessible from the file load dialogue window. be viewed in the NewGRF window accessible from the file load dialogue window.
You can try to obtain the missing files from that NewGRF dialogue or if they You can try to obtain the missing files from that NewGRF dialogue or if they
are not available online you can search manually through our [forum's graphics are not available online you can search manually through our
development section](http://www.tt-forums.net/viewforum.php?f=66) or GrfCrawler [forum's graphics development section](https://www.tt-forums.net/viewforum.php?f=66)
(http://grfcrawler.tt-forums.net/). Put the NewGRF files in OpenTTD's newgrf folder or [GRFCrawler](https://grfcrawler.tt-forums.net). Put the NewGRF files in
(see section 4.2 'OpenTTD directories') and rescan the list of available NewGRFs. OpenTTD's newgrf folder (see section 4.2 'OpenTTD directories') and rescan the
Once you have all missing files, you are set to go. list of available NewGRFs. Once you have all missing files, you are set to go.
## 10.0) Licensing ## 10.0) Licensing
@@ -970,11 +969,13 @@ os/dos/exe2coff/copying.dj for the exact licensing terms.
The CWSDPMI implementation in os/dos/cwsdpmi is distributed under a The CWSDPMI implementation in os/dos/cwsdpmi is distributed under a
custom binary-only license that prohibits modification. The exact custom binary-only license that prohibits modification. The exact
licensing terms can be found in os/dos/cwsdpmi/cwsdpmi.txt. The sources licensing terms can be found in os/dos/cwsdpmi/cwsdpmi.txt. The sources
for these files can be downloaded at its author site, at: for these files can be downloaded at its author site, at
[http://homer.rice.edu/~sandmann/cwsdpmi/csdpmi5s.zip](http://homer.rice.edu/~sandmann/cwsdpmi/csdpmi5s.zip) http://homer.rice.edu/~sandmann/cwsdpmi/csdpmi5s.zip.
CONTRIBUTING.md is adapted from [Bootstrap](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md) CONTRIBUTING.md is adapted from
under the [Creative Commons Attribution 3.0 Unported License](https://github.com/twbs/bootstrap/blob/master/docs/LICENSE) [Bootstrap](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md)
under the [Creative Commons Attribution 3.0 Unported
License](https://github.com/twbs/bootstrap/blob/master/docs/LICENSE)
terms for Bootstrap documentation. terms for Bootstrap documentation.
## X.X) Credits ## X.X) Credits
@@ -998,7 +999,7 @@ terms for Bootstrap documentation.
### Inactive Developers: ### Inactive Developers:
- Jean-François Claeys (Belugas) - GUI, newindustries and more (0.4.5 - 1.0) - Jean-François Claeys (Belugas) - GUI, newindustries and more (0.4.5 - 1.0)
- Bjarni Corfitzen (Bjarni) - MacOSX port, coder and vehicles (0.3 - 0.7) - Bjarni Corfitzen (Bjarni) - macOS port, coder and vehicles (0.3 - 0.7)
- Victor Fischer (Celestar) - Programming everywhere you need him to (0.3 - 0.6) - Victor Fischer (Celestar) - Programming everywhere you need him to (0.3 - 0.6)
- Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;) (0.4.5 - 0.6) - Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;) (0.4.5 - 0.6)
- Jonathan Coome (Maedhros) - High priest of the NewGRF Temple (0.5 - 0.6) - Jonathan Coome (Maedhros) - High priest of the NewGRF Temple (0.5 - 0.6)
@@ -1012,7 +1013,7 @@ terms for Bootstrap documentation.
- Tamás Faragó (Darkvater) - Ex-Lead coder (0.3 - 0.5) - Tamás Faragó (Darkvater) - Ex-Lead coder (0.3 - 0.5)
- Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3 - 0.3) - Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3 - 0.3)
- Emil Djupfeld (egladil) - MacOSX port (0.4 - 0.6) - Emil Djupfeld (egladil) - macOS port (0.4 - 0.6)
- Simon Sasburg (HackyKid) - Bug fixer (0.4 - 0.4.5) - Simon Sasburg (HackyKid) - Bug fixer (0.4 - 0.4.5)
- Ludvig Strigeus (ludde) - Original author of OpenTTD, main coder (0.1 - 0.3) - Ludvig Strigeus (ludde) - Original author of OpenTTD, main coder (0.1 - 0.3)
- Cian Duffy (MYOB) - BeOS port / manual writing (0.1 - 0.3) - Cian Duffy (MYOB) - BeOS port / manual writing (0.1 - 0.3)
@@ -1037,4 +1038,3 @@ terms for Bootstrap documentation.
- All Translators - For their support to make OpenTTD a truly international game - All Translators - For their support to make OpenTTD a truly international game
- Bug Reporters - Thanks for all bug reports - Bug Reporters - Thanks for all bug reports
- Chris Sawyer - For an amazing game! - Chris Sawyer - For an amazing game!

View File

@@ -142,5 +142,17 @@ GM_TT19.GM = Funk Central
GM_TT20.GM = Jammit GM_TT20.GM = Jammit
GM_TT21.GM = Movin' On GM_TT21.GM = Movin' On
; MIDI timecodes where the playback should attemp to start and stop short.
; This is to allow fixing undesired silences in original MIDI files.
; However not all music drivers may support this.
[timingtrim]
; Theme has two beats silence at the beginning which prevents clean looping.
GM_TT00.GM = 768:53760
; Can't Get There From Here from the Windows version has a long silence at the end,
; followed by a solo repeat. This isn't in the original DOS version music and is likely
; unintentional from the people who converted the music from the DOS version.
; Actual song ends after measure 152.
GM_TT10.GM = 0:235008
[origin] [origin]
default = You can find it on your Transport Tycoon Deluxe CD-ROM. default = You can find it on your Transport Tycoon Deluxe CD-ROM.

View File

@@ -1,3 +1,44 @@
1.8.0 (2018-04-01)
------------------------------------------------------------------------
(None)
1.8.0-RC1 (2018-03-21)
------------------------------------------------------------------------
- Feature: [GFX] Climate-specific Action5 extra airport sprites [FS#6664] (r27976)
- Feature: Draw vertical separators at tile distance in the train depot GUI (r27938, r27899)
- Feature: [Build] MSVC 2017 project file generator. Most noticeable, std:c++latest is enabled (r27920, r27919, r27918, r27917)
- Feature: [Build] Project file generator for kdevelop 4/5 [FS#6577] (r27897)
- Feature: Add option to close windows with right click [FS#4950] (r27826, r27825)
- Feature: Vehicle Group Info: Add profits and occupancy display to group vehicle list (r27822)
- Feature: Display aircraft type in vehicle preview/purchase/detail windows (r27802, r27799, r27797)
- Change: [NewGRF] Various performance improvements to resolving VA2 (r27989, r27985, r27984, r27983, r27982)
- Change: [NewGRF] Increase maximum allowed vehicle sprite size to reduce clipping of ships (r27987)
- Change: Check companies for bankruptcy before subtracting reoccuring monthly costs [FS#6679] (r27981)
- Change: [GFX] Replace the office building sprite on various toyland airports with a better fitting sprite [FS#6664] (r27977)
- Change: [GFX] The switch-toolbar icon contained pixels from the fire cycle. Replace the whole icon with a new version [FS#6654] (r27961)
- Change: Reword texts in industry view, when stockpiling is used (r27952)
- Change: Remove the gap between windows when positioning them after opening [FS#6568] (r27934, r27900)
- Change: [Build] Enable usage of static_assert for MSVC (r27916)
- Change: [Build] Preserve PKG_CONFIG_PATH and PKG_CONFIG_LIBDIR environment variables in config.cache file [FS#6614] (r27902)
- Change: Do not cancel headquarter construction and engine-preview-query when shift-clicking (r27889)
- Change: Parse extmidi command string for parameters to pass on (r27834)
- Change: Draw images in centre of buttons (r27829, r27821)
- Fix: Store the map variety setting in the savegame like the other mapgen settings, so restarting maps considers it [FS#6673] (r27978)
- Fix: Hair selection was missing one option [FS#6642] (r27975)
- Fix: Avoid tile operations outside map border when building lock [FS#6662] (r27973)
- Fix: Catenary sprites got mixed up for depots [FS#6670] (r27972)
- Fix: Make automatic window-positioning RTL-aware (r27934, r27900)
- Fix: Automatic window-positioning now uses GUI-scale/style dependent sizes/distances instead of fixed pixel values (r27934, r27900)
- Fix: [NewGRF] While executing random triggers, var 5F should include the new triggers (r27928)
- Fix: [NewGRF] Reset used random triggers only after all A123 chains have been resolved, so that all RA2 in all chains can test the shared triggers (r27928)
- Fix: [NewGRF] Industry random triggers are stored per tile, even when randomising the shared random bits of the parent industry (r27928)
- Fix: [NPF] Reserved track bits were not accounted for when trying to find any safe position (r27912)
- Fix: Do not modify argv[0] [FS#6575] (r27886)
- Fix: Do not search directories when opening ini files as we already have their full path [FS#6421] (r27816)
- Fix: Road tunnel/bridge heads have no trackbits wrt. catenary drawing (r27812)
1.7.2 (2017-12-24) 1.7.2 (2017-12-24)
------------------------------------------------------------------------ ------------------------------------------------------------------------
(None) (None)

View File

@@ -2987,6 +2987,11 @@ detect_library() {
eval "res=\$$2" eval "res=\$$2"
if [ -z "$res" ]; then if [ -z "$res" ]; then
log 2 " trying /mingw/include/$4$5... no" log 2 " trying /mingw/include/$4$5... no"
eval "$2=`ls -1 /mingw$cpu_type/include/$4*.h 2>/dev/null | egrep \"\/$5\$\"`"
fi
eval "res=\$$2"
if [ -z "$res" ]; then
log 2 " trying /mingw$cpu_type/include/$4$5... no"
eval "$2=`ls -1 /opt/local/include/$4*.h 2>/dev/null | egrep \"\/$5\$\"`" eval "$2=`ls -1 /opt/local/include/$4*.h 2>/dev/null | egrep \"\/$5\$\"`"
fi fi
eval "res=\$$2" eval "res=\$$2"
@@ -3035,6 +3040,11 @@ detect_library() {
eval "res=\$$2" eval "res=\$$2"
if [ -z "$res" ]; then if [ -z "$res" ]; then
log 2 " trying /mingw/lib/$3... no" log 2 " trying /mingw/lib/$3... no"
eval "$2=`ls /mingw$cpu_type/lib/*.a 2>/dev/null | egrep \"\/$3\$\"`"
fi
eval "res=\$$2"
if [ -z "$res" ]; then
log 2 " trying /mingw$cpu_type/lib/$3... no"
log 1 "configure: error: $2 couldn't be found" log 1 "configure: error: $2 couldn't be found"
log 1 "configure: error: you requested a static link, but I can't find $3" log 1 "configure: error: you requested a static link, but I can't find $3"

View File

@@ -90,5 +90,17 @@ GM_TT19.GM = Funk Central
GM_TT20.GM = Jammit GM_TT20.GM = Jammit
GM_TT21.GM = Movin' On GM_TT21.GM = Movin' On
; MIDI timecodes where the playback should attemp to start and stop short.
; This is to allow fixing undesired silences in original MIDI files.
; However not all music drivers may support this.
[timingtrim]
; Theme has two beats silence at the beginning which prevents clean looping.
GM_TT00.GM = 768:53760
; Can't Get There From Here from the Windows version has a long silence at the end,
; followed by a solo repeat. This isn't in the original DOS version music and is likely
; unintentional from the people who converted the music from the DOS version.
; Actual song ends after measure 152.
GM_TT10.GM = 0:235008
[origin] [origin]
default = You can find it on your Transport Tycoon Deluxe CD-ROM. default = You can find it on your Transport Tycoon Deluxe CD-ROM.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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