diff --git a/README.md b/README.md index 32a5e73678..c02de6ed84 100644 --- a/README.md +++ b/README.md @@ -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 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 @@ -325,11 +326,11 @@ through the file called 'known-bugs.txt' which is distributed with OpenTTD like this readme. 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 -through the already reported bugs there to see if the bug is already known. -The 'known-bugs.txt' file might be a bit outdated at the moment you are -reading it as only bugs known before the release are documented there. Also -look through the recently closed bugs. +the tracker at https://github.com/OpenTTD/OpenTTD/issues. Before actually +reporting take a look through the already reported bugs there to see if +the bug is already known. The 'known-bugs.txt' file might be a bit outdated +at the moment you are reading it as only bugs known before the release +are documented there. Also look through the recently closed bugs. 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 - Platform (Windows, Linux, FreeBSD, …) and compiler (including version) if you compiled OpenTTD yourself. -- The processor architecture of your OS (32 bits Windows, 64 bits Windows, - Linux on an ARM, Mac OS X on a PowerPC, …) +- The processor architecture of your OS (32-bit Windows, 64-bit Windows, + Linux on an ARM, Mac OS X on a PowerPC, etc.) - Attach a saved game **and** a screenshot if possible - 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 @@ -426,7 +427,7 @@ platforms are: - DOS (Allegro) - FreeBSD (SDL) - Linux (SDL or Allegro) -- MacOS X (universal) (Cocoa video and sound drivers) +- macOS (universal) (Cocoa video and sound drivers) - MorphOS (SDL) - OpenBSD (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 OpenMSX for music can be found at: -- [http://www.openttd.org/download-opengfx](http://www.openttd.org/download-opengfx) for OpenGFX -- [http://www.openttd.org/download-opensfx](http://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-opengfx for OpenGFX +- https://www.openttd.org/download-opensfx for OpenSFX +- https://www.openttd.org/download-openmsx for OpenMSX Please follow the readme of these packages about the installation procedure. The Windows installer can optionally download and install these packages. @@ -496,20 +497,20 @@ have to copy the data files from the CD-ROM into the baseset/ directory. It does not matter whether you copy them from the DOS or Windows version of Transport Tycoon Deluxe. The Windows install can optionally copy these files. You need to copy the following files: - - sample.cat - - trg1r.grf or TRG1.GRF - - trgcr.grf or TRGC.GRF - - trghr.grf or TRGH.GRF - - trgir.grf or TRGI.GRF - - trgtr.grf or TRGT.GRF +- sample.cat +- trg1r.grf or TRG1.GRF +- trgcr.grf or TRGC.GRF +- trghr.grf or TRGH.GRF +- trgir.grf or TRGI.GRF +- trgtr.grf or TRGT.GRF #### 4.1.3) Original Transport Tycoon Deluxe music If you want the Transport Tycoon Deluxe music, copy the appropriate files from the original game into the baseset folder. - - TTD for Windows: All files in the gm/ folder (gm_tt00.gm up to gm_tt21.gm) - - TTD for DOS: The GM.CAT file - - Transport Tycoon Original: The GM.CAT file, but rename it to GM-TTO.CAT +- TTD for Windows: All files in the gm/ folder (gm_tt00.gm up to gm_tt21.gm) +- TTD for DOS: The GM.CAT file +- Transport Tycoon Original: The GM.CAT file, but rename it to GM-TTO.CAT #### 4.1.4) AIs @@ -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 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 -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. -OpenTTD can read inside tar files but it does not extract .tar.gz files by -itself. +OpenTTD can read inside tar files but it does not extract .tar.gz files by itself. 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 @@ -550,48 +550,49 @@ your operating system: 1. The current working directory (from where you started OpenTTD) - 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 - root directory, i.e. '/'. + 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 + root directory, i.e. '/'. + 2. Your personal directory - Windows: - `C:\My Documents\OpenTTD` (95, 98, ME) - `C:\Documents and Settings\\My Documents\OpenTTD` (2000, XP) - - `C:\Users\\Documents\OpenTTD` (Vista, 7) - - Mac OSX: `~/Documents/OpenTTD` + - `C:\Users\\Documents\OpenTTD` (Vista, 7, 8.1, 10) + - macOS: `~/Documents/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 - Windows: - `C:\Documents and Settings\All Users\Shared Documents\OpenTTD` (2000, XP) - - `C:\Users\Public\Documents\OpenTTD` (Vista, 7) - - Mac OSX: `/Library/Application Support/OpenTTD` + - `C:\Users\Public\Documents\OpenTTD` (Vista, 7, 8.1, 10) + - macOS: `/Library/Application Support/OpenTTD` - Linux: not available 4. The binary directory (where the OpenTTD executable is) - Windows: `C:\Program Files\OpenTTD` - Linux: `/usr/games` 5. The installation directory (Linux only) - 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 - are not touched + It includes the OpenTTD files (grf+lng) and it will work as long as they + are not touched Different types of data or extensions go into different subdirectories of the chosen main OpenTTD directory: -| data type | directory | additional info | +| data type | directory | additional info | | --- | --- | --- | | Config File | (no subdirectory) | | | Screenshots | screenshot | | -| Base Graphics | baseset| (or a subdirectory thereof) | -| Sound Sets | baseset| (or a subdirectory thereof) | -| NewGRFs | newgrf| (or a subdirectory thereof) | -| 32bpp Sets | newgrf| (or a subdirectory thereof) | -| Music Sets | baseset| (or a subdirectory thereof) | -| AIs | ai| (or a subdirectory thereof) | -| AI Libraries | ai/library| (or a subdirectory thereof) | -| Game Scripts (GS) | game| (or a subdirectory thereof) | +| Base Graphics | baseset | (or a subdirectory thereof) | +| Sound Sets | baseset | (or a subdirectory thereof) | +| NewGRFs | newgrf | (or a subdirectory thereof) | +| 32bpp Sets | newgrf | (or a subdirectory thereof) | +| Music Sets | baseset | (or a subdirectory thereof) | +| AIs | ai | (or a subdirectory thereof) | +| AI Libraries | ai/library | (or a subdirectory thereof) | +| Game Scripts (GS) | game | (or a subdirectory thereof) | | GS Libraries | game/library | (or a subdirectory thereof) | | Savegames | save | | | Automatic Savegames | save/autosave | | @@ -603,16 +604,16 @@ use and no files should be added to it or its subdirectories manually. #### Notes: - Linux in the previous list means .deb, but most paths should be similar for - others. + others. - The previous search order is also used for NewGRFs and openttd.cfg. - If openttd.cfg is not found, then it will be created using the 2, 4, 1, 3, - 5 order. When built with XDG base directory support, openttd.cfg will be - created in $XDG_CONFIG_HOME/openttd which is usually ~/.config/openttd. + 5 order. When built with XDG base directory support, openttd.cfg will be + created in $XDG_CONFIG_HOME/openttd which is usually ~/.config/openttd. - Savegames will be relative to the config file only if there is no save/ - directory in paths with higher priority than the config file path, but - autosaves and screenshots will always be relative to the config file. - Unless the configuration file is in $XDG_CONFIG_HOME/openttd, then all - other files will be saved under $XDG_DATA_HOME/openttd. + directory in paths with higher priority than the config file path, but + autosaves and screenshots will always be relative to the config file. + Unless the configuration file is in $XDG_CONFIG_HOME/openttd, then all + other files will be saved under $XDG_DATA_HOME/openttd. #### The preferred setup: @@ -662,19 +663,18 @@ OpenTTD has a lot of features going beyond the original Transport Tycoon Deluxe 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 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: - Ctrl modifies many commands and makes them more powerful. For example Ctrl - clicking on signals with the build signal tool changes their behaviour, holding - Ctrl while the track build tool is activated changes it to the track removal - tool, and so on. See [http://wiki.openttd.org/Hidden_features](http://wiki.openttd.org/Hidden_features) - for a non-comprehensive list or look at the tooltips. -- Ingame console. More information at - [http://wiki.openttd.org/index.php/Console](http://wiki.openttd.org/index.php/Console) + clicking on signals with the build signal tool changes their behaviour, + holding Ctrl while the track build tool is activated changes it to the track + removal tool, and so on. See https://wiki.openttd.org/Hidden_features + for a non-comprehensive list or look at the tooltips. +- Ingame console. More information at https://wiki.openttd.org/Console - Hovering over a GUI element shows tooltips. This can be changed to right click - via the advanced settings. + via the advanced settings. ### 5.1) Logging of potentially dangerous actions @@ -695,7 +695,7 @@ Information logged: - Changing NewGRF parameters, loading compatible NewGRF - Changing game mode (scenario editor <-> normal game) - Loading game saved in a different OpenTTD / TTDPatch / Transport Tycoon Deluxe / - original Transport Tycoon version + original Transport Tycoon version - Running a modified OpenTTD build - Changing settings affecting NewGRF behaviour (non-network-safe settings) - Triggering NewGRF bugs @@ -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 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 library (.lib) files into the lib/ directory. 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`'. 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) This will give you a binary for your CPU type (PPC/Intel) @@ -782,19 +782,19 @@ The following libraries are used by OpenTTD for: - libSDL/liballegro: hardware access (video, sound, mouse) - zlib: (de)compressing of old (0.3.0-1.0.5) savegames, content downloads, - heightmaps + heightmaps - liblzo2: (de)compressing of old (pre 0.3.0) savegames - liblzma: (de)compressing of savegames (1.1.0 and later) - libpng: making screenshots and loading heightmaps - libfreetype: loading generic fonts and rendering them - libfontconfig: searching for fonts, resolving font names to actual fonts - libicu: handling of right-to-left scripts (e.g. Arabic and Persian) and - natural sorting of strings. + natural sorting of strings. 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 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. ### 7.2) Supported compilers @@ -802,25 +802,25 @@ no graphical user interface; you would be building a dedicated server. The following compilers are known to compile OpenTTD: - Microsoft Visual C++ (MSVC) 2005, 2008 and 2010. - Version 2005 gives bogus warnings about scoping issues. + Version 2005 gives bogus warnings about scoping issues. - GNU Compiler Collection (GCC) 3.3 - 4.4, 4.6 - 4.8. - Versions 4.1 and earlier give bogus warnings about uninitialised variables. - Versions 4.4, 4.6 give bogus warnings about freeing non-heap objects. - Versions 4.6 and later give invalid warnings when lto is enabled. - Intel C++ Compiler (ICC) 12.0. - Clang/LLVM 2.9 - 3.0 - Version 2.9 gives bogus warnings about code nonconformity. + Version 2.9 gives bogus warnings about code nonconformity. The following compilers are known not to compile OpenTTD: - Microsoft Visual C++ (MSVC) 2003 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. - 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. - Version 10.0 and earlier fail a configure check and fail with recent - system headers. + system headers. - Version 10.1 fails to compile station_gui.cpp. - Version 11.1 fails with an internal error when compiling network.cpp. - Clang/LLVM 2.8 and earlier. @@ -833,7 +833,7 @@ Patches to support more compilers are welcome. To recompile the extra graphics needed to play with the original Transport 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 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 -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 -[http://translator.openttd.org/](http://translator.openttd.org/), is highly -encouraged. For getting an account simply follow the guidelines in the FAQ of -the translator website. +https://translator.openttd.org, is highly encouraged. For getting an account +simply follow the guidelines in the FAQ of the translator website. If for some reason the website is down for a longer period of time, the 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 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) - 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 - the file +- Lines beginning with ## (such as ##id), other than the first two lines + of the file ### 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 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 -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 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. -Any NewGRF file used in a game is stored inside the savegame and will refuse -to load if you do not have that NewGRF file available. A list of missing files -can be viewed in the NewGRF window accessible from the file load dialogue window. +Any NewGRF file used in a game is stored inside the savegame and will refuse to +load if you do not have that NewGRF file available. A list of missing files can +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 -are not available online – you can search manually through our [forum's graphics -development section](http://www.tt-forums.net/viewforum.php?f=66) or GrfCrawler -(http://grfcrawler.tt-forums.net/). Put the NewGRF files in OpenTTD's newgrf folder -(see section 4.2 'OpenTTD directories') and rescan the list of available NewGRFs. -Once you have all missing files, you are set to go. +are not available online – you can search manually through our +[forum's graphics development section](https://www.tt-forums.net/viewforum.php?f=66) +or [GRFCrawler](https://grfcrawler.tt-forums.net). Put the NewGRF files in +OpenTTD's newgrf folder (see section 4.2 'OpenTTD directories') and rescan the +list of available NewGRFs. Once you have all missing files, you are set to go. ## 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 custom binary-only license that prohibits modification. The exact licensing terms can be found in os/dos/cwsdpmi/cwsdpmi.txt. The sources -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) +for these files can be downloaded at its author site, at +http://homer.rice.edu/~sandmann/cwsdpmi/csdpmi5s.zip. -CONTRIBUTING.md is adapted from [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) +CONTRIBUTING.md is adapted from +[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. ## X.X) Credits @@ -998,7 +999,7 @@ terms for Bootstrap documentation. ### Inactive Developers: - 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) - 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) @@ -1012,10 +1013,10 @@ terms for Bootstrap documentation. - Tamás Faragó (Darkvater) - Ex-Lead coder (0.3 - 0.5) - 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) - 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) - Petr Baudiš (pasky) - Many patches, newgrf support, etc. (0.3 - 0.3) - Benedikt Brüggemeier (skidd13) - Bug fixer and code reworker (0.6 - 0.7) - Serge Paquet (vurlix) - 2nd contributor after ludde (0.1 - 0.3) @@ -1037,4 +1038,3 @@ terms for Bootstrap documentation. - All Translators - For their support to make OpenTTD a truly international game - Bug Reporters - Thanks for all bug reports - Chris Sawyer - For an amazing game! - diff --git a/bin/baseset/orig_win.obm b/bin/baseset/orig_win.obm index 8e2053e043..c8c4923e0e 100644 --- a/bin/baseset/orig_win.obm +++ b/bin/baseset/orig_win.obm @@ -142,5 +142,17 @@ GM_TT19.GM = Funk Central GM_TT20.GM = Jammit 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] default = You can find it on your Transport Tycoon Deluxe CD-ROM. diff --git a/changelog.txt b/changelog.txt index 8edba15fb4..5e1540a1e0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -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) ------------------------------------------------------------------------ (None) diff --git a/config.lib b/config.lib index 2ccd3c6ce3..a6fe1f6f07 100644 --- a/config.lib +++ b/config.lib @@ -2987,6 +2987,11 @@ detect_library() { eval "res=\$$2" if [ -z "$res" ]; then 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\$\"`" fi eval "res=\$$2" @@ -3035,6 +3040,11 @@ detect_library() { eval "res=\$$2" if [ -z "$res" ]; then 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: you requested a static link, but I can't find $3" diff --git a/media/baseset/orig_win.obm b/media/baseset/orig_win.obm index 8b35b6de9d..b5d4b024ed 100644 --- a/media/baseset/orig_win.obm +++ b/media/baseset/orig_win.obm @@ -90,5 +90,17 @@ GM_TT19.GM = Funk Central GM_TT20.GM = Jammit 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] default = You can find it on your Transport Tycoon Deluxe CD-ROM. diff --git a/src/3rdparty/squirrel/squirrel/sqdebug.cpp b/src/3rdparty/squirrel/squirrel/sqdebug.cpp index b163fae52e..2f24e83b66 100644 --- a/src/3rdparty/squirrel/squirrel/sqdebug.cpp +++ b/src/3rdparty/squirrel/squirrel/sqdebug.cpp @@ -115,7 +115,7 @@ void SQVM::Raise_ParamTypeError(SQInteger nparam,SQInteger typemask,SQInteger ty SQInteger found = 0; for(SQInteger i=0; i<16; i++) { - SQInteger mask = 0x00000001 << i; + SQInteger mask = 0x00000001LL << i; if(typemask & (mask)) { if(found>0) StringCat(exptypes,SQString::Create(_ss(this), "|", -1), exptypes); found ++; diff --git a/src/base_media_base.h b/src/base_media_base.h index 407dcda86a..b040abcf9d 100644 --- a/src/base_media_base.h +++ b/src/base_media_base.h @@ -26,6 +26,7 @@ struct ContentInfo; struct MD5File { /** The result of a checksum check */ enum ChecksumResult { + CR_UNKNOWN, ///< The file has not been checked yet CR_MATCH, ///< The file did exist and the md5 checksum did match CR_MISMATCH, ///< The file did exist, just the md5 checksum did not match CR_NO_FILE, ///< The file did not exist @@ -34,6 +35,7 @@ struct MD5File { const char *filename; ///< filename uint8 hash[16]; ///< md5 sum of the file const char *missing_warning; ///< warning when this file is missing + ChecksumResult check_result; ///< cached result of md5 check ChecksumResult CheckMD5(Subdirectory subdir, size_t max_size) const; }; @@ -301,6 +303,9 @@ struct MusicSongInfo { const char *filename; ///< file on disk containing song (when used in MusicSet class, this pointer is owned by MD5File object for the file) MusicTrackType filetype; ///< decoder required for song file int cat_index; ///< entry index in CAT file, for filetype==MTT_MPSMIDI + bool loop; ///< song should play in a tight loop if possible, never ending + int override_start; ///< MIDI ticks to skip over in beginning + int override_end; ///< MIDI tick to end the song at (0 if no override) }; /** All data of a music set. */ diff --git a/src/base_media_func.h b/src/base_media_func.h index f45956f765..2cb751fe0c 100644 --- a/src/base_media_func.h +++ b/src/base_media_func.h @@ -129,7 +129,11 @@ bool BaseSet::FillSetDetails(IniFile *ini, const file->missing_warning = stredup(item->value); } - switch (T::CheckMD5(file, BASESET_DIR)) { + file->check_result = T::CheckMD5(file, BASESET_DIR); + switch (file->check_result) { + case MD5File::CR_UNKNOWN: + break; + case MD5File::CR_MATCH: this->valid_files++; this->found_files++; diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 6d7a475c11..848b056d10 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -207,6 +207,7 @@ void UpdateFontHeightCache() class FreeTypeFontCache : public FontCache { private: FT_Face face; ///< The font face associated with this font. + int req_size; ///< Requested font size. typedef SmallMap > FontTable; ///< Table with font table cache FontTable font_tables; ///< Cached font tables. @@ -235,6 +236,7 @@ private: GlyphEntry *GetGlyphPtr(GlyphID key); void SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate = false); + void SetFontSize(FontSize fs, FT_Face face, int pixels); public: FreeTypeFontCache(FontSize fs, FT_Face face, int pixels); @@ -271,20 +273,26 @@ static const byte SHADOW_COLOUR = 2; * @param face The font that has to be loaded. * @param pixels The number of pixels this font should be high. */ -FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : FontCache(fs), face(face), glyph_to_sprite(NULL) +FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : FontCache(fs), face(face), req_size(pixels), glyph_to_sprite(NULL) { assert(face != NULL); + this->SetFontSize(fs, face, pixels); +} + +void FreeTypeFontCache::SetFontSize(FontSize fs, FT_Face face, int pixels) +{ if (pixels == 0) { /* Try to determine a good height based on the minimal height recommended by the font. */ - pixels = _default_font_height[this->fs]; + int scaled_height = ScaleGUITrad(_default_font_height[this->fs]); + pixels = scaled_height; TT_Header *head = (TT_Header *)FT_Get_Sfnt_Table(this->face, ft_sfnt_head); if (head != NULL) { /* Font height is minimum height plus the difference between the default * height for this font size and the small size. */ - int diff = _default_font_height[this->fs] - _default_font_height[FS_SMALL]; - pixels = Clamp(min(head->Lowest_Rec_PPEM, 20) + diff, _default_font_height[this->fs], MAX_FONT_SIZE); + int diff = scaled_height - ScaleGUITrad(_default_font_height[FS_SMALL]); + pixels = Clamp(min(head->Lowest_Rec_PPEM, 20) + diff, scaled_height, MAX_FONT_SIZE); } } @@ -401,6 +409,7 @@ found_face: FreeTypeFontCache::~FreeTypeFontCache() { FT_Done_Face(this->face); + this->face = NULL; this->ClearFontCache(); for (FontTable::iterator iter = this->font_tables.Begin(); iter != this->font_tables.End(); iter++) { @@ -430,6 +439,9 @@ void FreeTypeFontCache::ClearFontCache() this->glyph_to_sprite = NULL; Layouter::ResetFontCache(this->fs); + + /* GUI scaling might have changed, determine font size anew if it was automatically selected. */ + if (this->face != NULL && this->req_size == 0) this->SetFontSize(this->fs, this->face, this->req_size); } FreeTypeFontCache::GlyphEntry *FreeTypeFontCache::GetGlyphPtr(GlyphID key) diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index 896ecd9210..534d89c0bd 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -314,7 +314,7 @@ static DropDownList *BuildMapsizeDropDown(int other_dimension) for (uint i = MIN_MAP_SIZE_BITS; i <= MAX_MAP_SIZE_BITS; i++) { DropDownListParamStringItem *item = new DropDownListParamStringItem((i + other_dimension > MAX_MAP_TILES_BITS) ? STR_RED_INT : STR_JUST_INT, i, false); - item->SetParam(0, 1 << i); + item->SetParam(0, 1LL << i); *list->Append() = item; } @@ -372,8 +372,8 @@ struct GenerateLandscapeWindow : public Window { { switch (widget) { case WID_GL_START_DATE_TEXT: SetDParam(0, ConvertYMDToDate(_settings_newgame.game_creation.starting_year, 0, 1)); break; - case WID_GL_MAPSIZE_X_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_x); break; - case WID_GL_MAPSIZE_Y_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_y); break; + case WID_GL_MAPSIZE_X_PULLDOWN: SetDParam(0, 1LL << _settings_newgame.game_creation.map_x); break; + case WID_GL_MAPSIZE_Y_PULLDOWN: SetDParam(0, 1LL << _settings_newgame.game_creation.map_y); break; case WID_GL_MAX_HEIGHTLEVEL_TEXT: SetDParam(0, _settings_newgame.construction.max_heightlevel); break; case WID_GL_SNOW_LEVEL_TEXT: SetDParam(0, _settings_newgame.game_creation.snow_line_height); break; @@ -948,11 +948,11 @@ struct CreateScenarioWindow : public Window break; case WID_CS_MAPSIZE_X_PULLDOWN: - SetDParam(0, 1 << _settings_newgame.game_creation.map_x); + SetDParam(0, 1LL << _settings_newgame.game_creation.map_x); break; case WID_CS_MAPSIZE_Y_PULLDOWN: - SetDParam(0, 1 << _settings_newgame.game_creation.map_y); + SetDParam(0, 1LL << _settings_newgame.game_creation.map_y); break; case WID_CS_FLAT_LAND_HEIGHT_TEXT: diff --git a/src/gfx.cpp b/src/gfx.cpp index 2c9339adbc..2af17d3bc2 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -1111,6 +1111,7 @@ void DoPaletteAnimations() /** * Determine a contrasty text colour for a coloured background. * @param background Background colour. + * @param threshold Background colour brightness threshold below which the background is considered dark and TC_WHITE is returned, range: 0 - 255, default 128. * @return TC_BLACK or TC_WHITE depending on what gives a better contrast. */ TextColour GetContrastColour(uint8 background, uint8 threshold) @@ -1120,7 +1121,7 @@ TextColour GetContrastColour(uint8 background, uint8 threshold) * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */ uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114; /* Compare with threshold brightness which defaults to 128 (50%) */ - return sq1000_brightness < threshold * 128 * 1000 ? TC_WHITE : TC_BLACK; + return sq1000_brightness < ((uint) threshold) * ((uint) threshold) * 1000 ? TC_WHITE : TC_BLACK; } /** diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index 1d9fc7c313..0a9f5a0b13 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -597,6 +597,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, const char *&str, Font *f = Layouter::GetFont(state.fontsize, state.cur_colour); line.buffer = buff_begin; + fontMapping.Clear(); /* * Go through the whole string while adding Font instances to the font map diff --git a/src/lang/croatian.txt b/src/lang/croatian.txt index f22507d77e..8d2bd41749 100644 --- a/src/lang/croatian.txt +++ b/src/lang/croatian.txt @@ -3074,6 +3074,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Unesi im STR_TOWN_DIRECTORY_CAPTION :{WHITE}Gradovi STR_TOWN_DIRECTORY_NONE :{ORANGE}- Ništa - STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA}) +STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (Grad){BLACK} ({COMMA}) STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Imena gradova - klikni na ime kako bi centrirao pogled na grad. Ctrl+klik otvara novi prozor sa lokacijom grada STR_TOWN_POPULATION :{BLACK}Svjetsko stanovništvo: {COMMA} @@ -3081,6 +3082,7 @@ STR_TOWN_POPULATION :{BLACK}Svjetsko STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN} STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Metropola) STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Stanovništvo: {ORANGE}{COMMA}{BLACK} Kuće: {ORANGE}{COMMA} +STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} prošli mjesec: {ORANGE}{COMMA}{BLACK} maks: {ORANGE}{COMMA} STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Potrebno tereta za rast grada: STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} potrebno STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} potrebno zimi @@ -4329,6 +4331,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... ovo STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... cesta je orijentirana u krivom smjeru STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... prolazne postaje ne mogu imati zavoje STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... prolazne postaje ne mogu imati raskrižja +STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... cesta je jednosmjerna ili je blokirana # Station destruction related errors STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Nije moguće ukloniti dio postaje... @@ -4580,6 +4583,8 @@ STR_BASESOUNDS_DOS_DESCRIPTION :Originalni zvuk STR_BASESOUNDS_WIN_DESCRIPTION :Originalni zvukovi za Transport Tycoon Deluxe Windows izdanje. STR_BASESOUNDS_NONE_DESCRIPTION :Zvučni paket bez ikakvih zvukova. STR_BASEMUSIC_WIN_DESCRIPTION :Originalna glazba za Transport Tycoon Deluxe Windows izdanje. +STR_BASEMUSIC_DOS_DESCRIPTION :Originalna glazba za Transport Tycoon Deluxe DOS izdanje. +STR_BASEMUSIC_TTO_DESCRIPTION :Originalna glazba za Transport Tycoon (original/editor svijeta) DOS izdanje. STR_BASEMUSIC_NONE_DESCRIPTION :Glazbeni paket bez ikakve glazbe. ##id 0x2000 diff --git a/src/lang/english.txt b/src/lang/english.txt index 08197be86d..68f4daa329 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -889,10 +889,10 @@ STR_NEWS_EXCLUSIVE_RIGHTS_DESCRIPTION :{BIG_FONT}{BLAC # Extra view window STR_EXTRA_VIEW_PORT_TITLE :{WHITE}Viewport {COMMA} -STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN :{BLACK}Copy to viewport +STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN :{BLACK}Change viewport STR_EXTRA_VIEW_MOVE_VIEW_TO_MAIN_TT :{BLACK}Copy the location of the main view to this viewport -STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW :{BLACK}Paste from viewport -STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW_TT :{BLACK}Paste the location of this viewport to the main view +STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW :{BLACK}Change main view +STR_EXTRA_VIEW_MOVE_MAIN_TO_VIEW_TT :{BLACK}Copy the location of this viewport to the main view # Game options window STR_GAME_OPTIONS_CAPTION :{WHITE}Game Options @@ -3465,6 +3465,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Enter a STR_TOWN_DIRECTORY_CAPTION :{WHITE}Towns STR_TOWN_DIRECTORY_NONE :{ORANGE}- None - STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA}) +STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (City){BLACK} ({COMMA}) STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Town names - click on name to centre main view on town. Ctrl+Click opens a new viewport on town location STR_TOWN_POPULATION :{BLACK}World population: {COMMA} diff --git a/src/lang/french.txt b/src/lang/french.txt index c130c4beea..93998105de 100644 --- a/src/lang/french.txt +++ b/src/lang/french.txt @@ -2979,6 +2979,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Entrer u STR_TOWN_DIRECTORY_CAPTION :{WHITE}Villes STR_TOWN_DIRECTORY_NONE :{ORANGE}− Aucune − STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA}) +STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (Métropole){BLACK} ({COMMA}) STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Noms des villes - Cliquer sur un nom pour centrer la vue principale sur la ville. Ctrl-clic pour ouvrir une nouvelle vue sur la ville. STR_TOWN_POPULATION :{BLACK}Population mondiale{NBSP}: {COMMA} @@ -4235,6 +4236,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... cett STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... mauvaise orientation de la route STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... les arrêts ne peuvent pas avoir de virages STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... les arrêts ne peuvent pas avoir de jonctions +STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... la route est à sens unique ou bloquée # Station destruction related errors STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Impossible de supprimer une partie de la gare... @@ -4486,6 +4488,8 @@ STR_BASESOUNDS_DOS_DESCRIPTION :Sons originaux STR_BASESOUNDS_WIN_DESCRIPTION :Sons originaux de Transport Tycoon Deluxe (version Windows). STR_BASESOUNDS_NONE_DESCRIPTION :Un pack de sons sans sons. STR_BASEMUSIC_WIN_DESCRIPTION :Musiques originales de Transport Tycoon Deluxe (version Windows). +STR_BASEMUSIC_DOS_DESCRIPTION :Musiques originales de Transport Tycoon Deluxe (version DOS). +STR_BASEMUSIC_TTO_DESCRIPTION :Musiques originales de Transport Tycoon (version Originale/World Editor). STR_BASEMUSIC_NONE_DESCRIPTION :Un pack de musiques sans musiques. ##id 0x2000 diff --git a/src/lang/greek.txt b/src/lang/greek.txt index c4854e0735..c31e0ac8f5 100644 --- a/src/lang/greek.txt +++ b/src/lang/greek.txt @@ -3090,6 +3090,7 @@ STR_TOWN_POPULATION :{BLACK}Παγκ STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN} STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Πόλη) STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Πληθυσμός: {ORANGE}{COMMA}{BLACK} Σπίτια: {ORANGE}{COMMA} +STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} προηγούμενος μήνας: {ORANGE}{COMMA}{BLACK} μέγιστο: {ORANGE}{COMMA} STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Εμπορεύματα που χρειάζονται για την επέκταση της πόλης: STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} απαιτείται STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} απαιτείται τον χειμώνα @@ -4344,6 +4345,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... αυ STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... ο δρόμος βλέπει σε λάθος κατεύθυνση STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... οι μη τερματικοί σταθμοί δε μπορούν να έχουν στροφές STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... οι μη τερματικοί σταθμοί δε μπορούν να έχουν διασταυρώσεις +STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... ο δρόμος είναι μονόδρομος η μπλοκαρισμένος # Station destruction related errors STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Δεν μπορεί να αφαιρεθεί μέρος του σταθμού... @@ -4595,6 +4597,8 @@ STR_BASESOUNDS_DOS_DESCRIPTION :Αρχικοί STR_BASESOUNDS_WIN_DESCRIPTION :Αρχικοί ήχοι από το Transport Tycoon Deluxe έκδοση Windows. STR_BASESOUNDS_NONE_DESCRIPTION :Ένα πάκετο ήχων χώρις ήχους. STR_BASEMUSIC_WIN_DESCRIPTION :Αρχική μουσική από το Transport Tycoon Deluxe έκδοση Windows. +STR_BASEMUSIC_DOS_DESCRIPTION :Αρχική μουσική από το Transport Tycoon Deluxe έκδοση DOS. +STR_BASEMUSIC_TTO_DESCRIPTION :Αρχική μουσική από το Transport Tycoon (Αρχικός Επεξεργαστής Κόσμου) έκδοση DOS. STR_BASEMUSIC_NONE_DESCRIPTION :Ένα πάκετο μουσικής χωρίς πραγματική μουσική. ##id 0x2000 diff --git a/src/lang/italian.txt b/src/lang/italian.txt index 825ba668f3..8a2ad95be9 100644 --- a/src/lang/italian.txt +++ b/src/lang/italian.txt @@ -3008,6 +3008,7 @@ STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Inserire STR_TOWN_DIRECTORY_CAPTION :{WHITE}Città STR_TOWN_DIRECTORY_NONE :{ORANGE}- Nessuna - STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA}) +STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (Metropoli){BLACK} ({COMMA}) STR_TOWN_DIRECTORY_LIST_TOOLTIP :{BLACK}Nomi delle città - fare clic su un nome per centrare la visuale principale sulla città. CTRL+clic la mostra in una mini visuale. STR_TOWN_POPULATION :{BLACK}Popolazione mondiale: {COMMA} diff --git a/src/lang/korean.txt b/src/lang/korean.txt index 8ce21dae6b..fd12d18d8e 100644 --- a/src/lang/korean.txt +++ b/src/lang/korean.txt @@ -1400,7 +1400,7 @@ STR_CONFIG_SETTING_LIVERIES_ALL :모든 회사 STR_CONFIG_SETTING_PREFER_TEAMCHAT :엔터(ENTER) 키로 같은 팀끼리 채팅: {STRING} STR_CONFIG_SETTING_PREFER_TEAMCHAT_HELPTEXT :이 설정을 켜면, 멀티 플레이시 같은 회사 간의 채팅을 키 대신 키로 할 수 있게 됩니다. STR_CONFIG_SETTING_SCROLLWHEEL_SCROLLING :마우스 휠 동작: {STRING} -STR_CONFIG_SETTING_SCROLLWHEEL_SCROLLING_HELPTEXT :상하좌우로 회전 가능한 마우스 휠(2차원 마우스휠)로 지도를 스크롤할 수 있게 허용합니다. +STR_CONFIG_SETTING_SCROLLWHEEL_SCROLLING_HELPTEXT :상하좌우로 회전 가능한 마우스 휠(2차원 마우스 휠)로 지도를 스크롤할 수 있게 허용합니다. STR_CONFIG_SETTING_SCROLLWHEEL_ZOOM :화면 확대/축소 STR_CONFIG_SETTING_SCROLLWHEEL_SCROLL :지도 스크롤 STR_CONFIG_SETTING_SCROLLWHEEL_OFF :끄기 diff --git a/src/lang/polish.txt b/src/lang/polish.txt index 001347fe72..2b9b9d2d2b 100644 --- a/src/lang/polish.txt +++ b/src/lang/polish.txt @@ -3360,6 +3360,7 @@ STR_TOWN_POPULATION :{BLACK}Populacj STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN} STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Miasto) STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Populacja: {ORANGE}{COMMA}{BLACK} Domów: {ORANGE}{COMMA} +STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} w ostatnim miesiącu: {ORANGE}{COMMA}{BLACK} najwięcej: {ORANGE}{COMMA} STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Towar potrzebny do rozwoju miasta: STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{RED}Wymagana {ORANGE}{STRING} STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} wymagane zimą @@ -4614,6 +4615,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... ta d STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... droga jest zorientowana w złym kierunku STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... przystanki przelotowe nie mogą mieć zakrętów STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... przystanki przelotowe nie mogą mieć skrzyżowań +STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... droga jest jednokierunkowa lub zablokowana # Station destruction related errors STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Nie można usunąć części stacji... diff --git a/src/lang/russian.txt b/src/lang/russian.txt index 6de4768fe4..7737034602 100644 --- a/src/lang/russian.txt +++ b/src/lang/russian.txt @@ -3171,6 +3171,7 @@ STR_TOWN_POPULATION :{BLACK}Насе STR_TOWN_VIEW_TOWN_CAPTION :{WHITE}{TOWN} STR_TOWN_VIEW_CITY_CAPTION :{WHITE}{TOWN} (Мегаполис) STR_TOWN_VIEW_POPULATION_HOUSES :{BLACK}Население: {ORANGE}{COMMA}{BLACK} Зданий: {ORANGE}{COMMA} +STR_TOWN_VIEW_CARGO_LAST_MONTH_MAX :{BLACK}{CARGO_LIST} в прошлом месяце: {ORANGE}{COMMA}{BLACK} Макс.: {ORANGE}{COMMA} STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH :{BLACK}Груз, необходимый для роста города: STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_GENERAL :{ORANGE}{STRING}{RED} требу{G 0 е е е ю}тся STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING}{BLACK} требу{G 0 е е е ю}тся зимой @@ -4442,6 +4443,7 @@ STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... эт STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... неверное направление дороги STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... на проходных остановках нельзя делать повороты STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... на проходных остановках нельзя делать перекрёстки +STR_ERROR_DRIVE_THROUGH_ON_ONEWAY_ROAD :{WHITE}... дорога односторонняя или заблокирована # Station destruction related errors STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Невозможно удалить часть станции... @@ -4510,7 +4512,7 @@ STR_ERROR_IMPOSSIBLE_TRACK_COMBINATION :{WHITE}Недо STR_ERROR_MUST_REMOVE_SIGNALS_FIRST :{WHITE}Сначала удалите сигналы STR_ERROR_NO_SUITABLE_RAILROAD_TRACK :{WHITE}Нет подходящих рельсов STR_ERROR_MUST_REMOVE_RAILROAD_TRACK :{WHITE}Сначала удалите рельсы -STR_ERROR_CROSSING_ON_ONEWAY_ROAD :{WHITE}Дорога односторонняя или блокирована +STR_ERROR_CROSSING_ON_ONEWAY_ROAD :{WHITE}Дорога односторонняя или заблокирована STR_ERROR_CROSSING_DISALLOWED :{WHITE}Через этот вид рельсов запрещено строить переезды STR_ERROR_CAN_T_BUILD_SIGNALS_HERE :{WHITE}Здесь невозможно поставить сигнал... STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK :{WHITE}Здесь невозможно проложить рельсы... @@ -4686,13 +4688,15 @@ STR_ERROR_CAN_T_DELETE_SIGN :{WHITE}Не у STR_DESKTOP_SHORTCUT_COMMENT :Экономический симулятор на основе игры «Transport Tycoon Deluxe» # Translatable descriptions in media/baseset/*.ob* files -STR_BASEGRAPHICS_DOS_DESCRIPTION :Оригинальная графика из Transport Tycoon Deluxe для DOS. -STR_BASEGRAPHICS_DOS_DE_DESCRIPTION :Оригинальная графика из немецкой версии Transport Tycoon Deluxe для DOS. -STR_BASEGRAPHICS_WIN_DESCRIPTION :Оригинальная графика из Transport Tycoon Deluxe для Windows. -STR_BASESOUNDS_DOS_DESCRIPTION :Оригинальный набор звукового оформления из игры Transport Tycoon Deluxe для DOS. -STR_BASESOUNDS_WIN_DESCRIPTION :Оригинальный набор звукового оформления из игры Transport Tycoon Deluxe для Windows. +STR_BASEGRAPHICS_DOS_DESCRIPTION :Графика из Transport Tycoon Deluxe для DOS. +STR_BASEGRAPHICS_DOS_DE_DESCRIPTION :Графика из немецкой версии Transport Tycoon Deluxe для DOS. +STR_BASEGRAPHICS_WIN_DESCRIPTION :Графика из Transport Tycoon Deluxe для Windows. +STR_BASESOUNDS_DOS_DESCRIPTION :Набор звукового оформления из игры Transport Tycoon Deluxe для DOS. +STR_BASESOUNDS_WIN_DESCRIPTION :Набор звукового оформления из игры Transport Tycoon Deluxe для Windows. STR_BASESOUNDS_NONE_DESCRIPTION :"Пустой" набор звукового оформления, не содержащий никаких звуков. -STR_BASEMUSIC_WIN_DESCRIPTION :Оригинальный набор музыкального оформления из игры Transport Tycoon Deluxe для Windows. +STR_BASEMUSIC_WIN_DESCRIPTION :Набор музыкального оформления из игры Transport Tycoon Deluxe для Windows. +STR_BASEMUSIC_DOS_DESCRIPTION :Набор музыкального оформления из игры Transport Tycoon Deluxe для DOS. +STR_BASEMUSIC_TTO_DESCRIPTION :Набор музыкального оформления из игры Transport Tycoon Deluxe для DOS. STR_BASEMUSIC_NONE_DESCRIPTION :"Пустой" набор музыкального оформления, не содержащий никакой музыки. ##id 0x2000 diff --git a/src/lang/simplified_chinese.txt b/src/lang/simplified_chinese.txt index 0ab95b0f82..1730f3903b 100644 --- a/src/lang/simplified_chinese.txt +++ b/src/lang/simplified_chinese.txt @@ -674,7 +674,9 @@ STR_PLAYLIST_TRACK_NAME :{TINY_FONT}{LTB STR_PLAYLIST_TRACK_INDEX :{TINY_FONT}{BLACK}全部音轨列表 STR_PLAYLIST_PROGRAM :{TINY_FONT}{BLACK}当前选用'{STRING}'列表 STR_PLAYLIST_CLEAR :{TINY_FONT}{BLACK}清除 +STR_PLAYLIST_CHANGE_SET :更改设置 STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1 :{BLACK}清除当前列表中曲目{}(仅限自定义1或自定义2) +STR_PLAYLIST_TOOLTIP_CHANGE_SET :{BLACK}选择另一种已安装的音乐 STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK :{BLACK}点击音乐曲目以加入当前播放列表{}(仅限自定义1或自定义2) STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK :{BLACK}点击音乐曲目以从当前播放列表中删除{}(仅限自定义1或自定义2) @@ -1334,6 +1336,7 @@ STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_HELPTEXT :设置缩略地 STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_GREEN :绿色 STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_DARK_GREEN :深绿色 STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_VIOLET :紫色 +STR_CONFIG_SETTING_SCROLLMODE_RMB :鼠标右键移动地图 STR_CONFIG_SETTING_SMOOTH_SCROLLING :平滑视角滚动: {STRING} STR_CONFIG_SETTING_SMOOTH_SCROLLING_HELPTEXT :设置在缩略图上点击或者发出转到特定目标的命令时主视角的转换方式,如果“打开”本选项,视角平缓滚动,“关闭”时直接跳转到目标位置 STR_CONFIG_SETTING_MEASURE_TOOLTIP :建设时显示测量数据:{STRING} @@ -3647,6 +3650,7 @@ STR_VEHICLE_INFO_AGE :{COMMA} 年 ({C STR_VEHICLE_INFO_AGE_RED :{RED}{COMMA} 年 ({COMMA}) STR_VEHICLE_INFO_MAX_SPEED :{BLACK}最大速度:{LTBLUE}{VELOCITY} +STR_VEHICLE_INFO_MAX_SPEED_TYPE :{BLACK}最高速度: {LTBLUE}{VELOCITY} {BLACK}飞机种类: {LTBLUE}{STRING} STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}重量:{LTBLUE}{WEIGHT_SHORT} {BLACK}功率:{LTBLUE}{POWER}{BLACK} 最大速度:{LTBLUE}{VELOCITY} STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}重量:{LTBLUE}{WEIGHT_SHORT} {BLACK}功率:{LTBLUE}{POWER}{BLACK} 最大速度:{LTBLUE}{VELOCITY} {BLACK}最大牵引力:{LTBLUE}{FORCE} @@ -4464,6 +4468,7 @@ STR_BASESOUNDS_DOS_DESCRIPTION :运输大亨DOS STR_BASESOUNDS_WIN_DESCRIPTION :Transport Tycoon Deluxe Windows (运输大亨Windows豪华版)的原版音效包. STR_BASESOUNDS_NONE_DESCRIPTION :一个空的音效包. STR_BASEMUSIC_WIN_DESCRIPTION :Transport Tycoon Deluxe(运输大亨Windows豪华版)的原版音乐包 +STR_BASEMUSIC_DOS_DESCRIPTION :运输大亨DOS豪华版原版音乐。 STR_BASEMUSIC_NONE_DESCRIPTION :一个没有实际内容的音乐包. ##id 0x2000 diff --git a/src/linkgraph/linkgraph_gui.cpp b/src/linkgraph/linkgraph_gui.cpp index bcd6373528..17833615ae 100644 --- a/src/linkgraph/linkgraph_gui.cpp +++ b/src/linkgraph/linkgraph_gui.cpp @@ -475,7 +475,7 @@ void LinkGraphOverlay::SetCompanyMask(uint32 company_mask) /** Make a number of rows with buttons for each company for the linkgraph legend window. */ NWidgetBase *MakeCompanyButtonRowsLinkGraphGUI(int *biggest_index) { - return MakeCompanyButtonRows(biggest_index, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST, 3, 0); + return MakeCompanyButtonRows(biggest_index, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST, 3, STR_NULL); } NWidgetBase *MakeSaturationLegendLinkGraphGUI(int *biggest_index) @@ -652,30 +652,46 @@ void LinkGraphLegendWindow::DrawWidget(const Rect &r, int widget) const if (this->IsWidgetDisabled(widget)) return; CargoSpec *cargo = CargoSpec::Get(widget - WID_LGL_CARGO_FIRST); GfxFillRect(r.left + 2, r.top + 2, r.right - 2, r.bottom - 2, cargo->legend_colour); - DrawString(r.left, r.right, (r.top + r.bottom + 1 - FONT_HEIGHT_SMALL) / 2, cargo->abbrev, GetContrastColour(cargo->legend_colour, 42), SA_HOR_CENTER); + DrawString(r.left, r.right, (r.top + r.bottom + 1 - FONT_HEIGHT_SMALL) / 2, cargo->abbrev, GetContrastColour(cargo->legend_colour, 73), SA_HOR_CENTER); } } +bool LinkGraphLegendWindow::OnHoverCommon(Point pt, int widget, TooltipCloseCondition close_cond) +{ + if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) { + if (this->IsWidgetDisabled(widget)) { + GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_SELECT_COMPANIES, 0, NULL, close_cond); + } else { + uint64 params[2]; + CompanyID cid = (CompanyID)(widget - WID_LGL_COMPANY_FIRST); + params[0] = STR_LINKGRAPH_LEGEND_SELECT_COMPANIES; + params[1] = cid; + GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_COMPANY_TOOLTIP, 2, params, close_cond); + } + return true; + } + if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) { + if (this->IsWidgetDisabled(widget)) return false; + CargoSpec *cargo = CargoSpec::Get(widget - WID_LGL_CARGO_FIRST); + uint64 params[1]; + params[0] = cargo->name; + GuiShowTooltips(this, STR_BLACK_STRING, 1, params, close_cond); + return true; + } + return false; +} + void LinkGraphLegendWindow::OnHover(Point pt, int widget) { - if (IsInsideMM(widget, WID_LGL_COMPANY_FIRST, WID_LGL_COMPANY_LAST + 1)) { - uint64 params[5]; - if (this->IsWidgetDisabled(widget)) { - GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_SELECT_COMPANIES, 0, params, TCC_HOVER); - } else { - CompanyID cid = (CompanyID)(widget - WID_LGL_COMPANY_FIRST); - params[0] = STR_LINKGRAPH_LEGEND_SELECT_COMPANIES; - params[1] = cid; - GuiShowTooltips(this, STR_LINKGRAPH_LEGEND_COMPANY_TOOLTIP, 2, params, TCC_HOVER); - } - } - if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) { - if (this->IsWidgetDisabled(widget)) return; - CargoSpec *cargo = CargoSpec::Get(widget - WID_LGL_CARGO_FIRST); - uint64 params[5]; - params[0] = cargo->name; - GuiShowTooltips(this, STR_BLACK_STRING, 1, params, TCC_HOVER); + this->OnHoverCommon(pt, widget, TCC_HOVER); +} + +bool LinkGraphLegendWindow::OnRightClick(Point pt, int widget) +{ + if (_settings_client.gui.hover_delay_ms == 0) { + return this->OnHoverCommon(pt, widget, TCC_RIGHT_CLICK); } + return false; } /** diff --git a/src/linkgraph/linkgraph_gui.h b/src/linkgraph/linkgraph_gui.h index bffcc1b913..f213a3245e 100644 --- a/src/linkgraph/linkgraph_gui.h +++ b/src/linkgraph/linkgraph_gui.h @@ -15,6 +15,7 @@ #include "../company_func.h" #include "../station_base.h" #include "../widget_type.h" +#include "../window_gui.h" #include "linkgraph_base.h" #include #include @@ -118,6 +119,7 @@ public: virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize); virtual void DrawWidget(const Rect &r, int widget) const; virtual void OnHover(Point pt, int widget) override; + virtual bool OnRightClick(Point pt, int widget) override; virtual void OnClick(Point pt, int widget, int click_count); virtual void OnInvalidateData(int data = 0, bool gui_scope = true); @@ -126,6 +128,7 @@ private: void UpdateOverlayCompanies(); void UpdateOverlayCargoes(); + bool OnHoverCommon(Point pt, int widget, TooltipCloseCondition close_cond); }; #endif /* LINKGRAPH_GUI_H */ diff --git a/src/music.cpp b/src/music.cpp index 1975425269..fbcd465062 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -125,9 +125,11 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f this->num_available = 0; IniGroup *names = ini->GetGroup("names"); IniGroup *catindex = ini->GetGroup("catindex"); - for (uint i = 0, j = 1; i < lengthof(this->songinfo); i++) { + IniGroup *timingtrim = ini->GetGroup("timingtrim"); + uint tracknr = 1; + for (uint i = 0; i < lengthof(this->songinfo); i++) { const char *filename = this->files[i].filename; - if (names == NULL || StrEmpty(filename)) { + if (names == NULL || StrEmpty(filename) || this->files[i].check_result == MD5File::CR_NO_FILE) { this->songinfo[i].songname[0] = '\0'; continue; } @@ -142,7 +144,8 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f char *songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index); if (songname == NULL) { DEBUG(grf, 1, "Base music set song missing from CAT file: %s/%d", filename, this->songinfo[i].cat_index); - return false; + this->songinfo[i].songname[0] = '\0'; + continue; } strecpy(this->songinfo[i].songname, songname, lastof(this->songinfo[i].songname)); free(songname); @@ -150,15 +153,16 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f this->songinfo[i].filetype = MTT_STANDARDMIDI; } + const char *trimmed_filename = filename; /* As we possibly add a path to the filename and we compare * on the filename with the path as in the .obm, we need to * keep stripping path elements until we find a match. */ - for (const char *p = filename; p != NULL; p = strchr(p, PATHSEPCHAR)) { + for (; trimmed_filename != NULL; trimmed_filename = strchr(trimmed_filename, PATHSEPCHAR)) { /* Remove possible double path separator characters from * the beginning, so we don't start reading e.g. root. */ - while (*p == PATHSEPCHAR) p++; + while (*trimmed_filename == PATHSEPCHAR) trimmed_filename++; - item = names->GetItem(p, false); + item = names->GetItem(trimmed_filename, false); if (item != NULL && !StrEmpty(item->value)) break; } @@ -172,7 +176,21 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f } this->num_available++; - this->songinfo[i].tracknr = j++; + /* Number the theme song (if any) track 0, rest are normal */ + if (i == 0) { + this->songinfo[i].tracknr = 0; + } else { + this->songinfo[i].tracknr = tracknr++; + } + + item = timingtrim->GetItem(trimmed_filename, false); + if (item != NULL && !StrEmpty(item->value)) { + const char *endpos = strchr(item->value, ':'); + if (endpos != NULL) { + this->songinfo[i].override_start = atoi(item->value); + this->songinfo[i].override_end = atoi(endpos + 1); + } + } } } return ret; diff --git a/src/music/dmusic.cpp b/src/music/dmusic.cpp index ce76d22ef4..d48f51e85f 100644 --- a/src/music/dmusic.cpp +++ b/src/music/dmusic.cpp @@ -693,6 +693,9 @@ static void MidiThreadProc(void *) current_segment.start_block = bl; break; } else { + /* Skip the transmission delay compensation performed in the Win32 MIDI driver. + * The DMusic driver will most likely be used with the MS softsynth, which is not subject to transmission delays. + */ DEBUG(driver, 2, "DMusic: timer: start from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime) / 1000.0, (int)preload_bytes); playback_start_time -= block.realtime * MIDITIME_TO_REFTIME; break; @@ -723,14 +726,6 @@ static void MidiThreadProc(void *) while (current_block < current_file.blocks.size()) { MidiFile::DataBlock &block = current_file.blocks[current_block]; - /* check that block is not in the future */ - REFERENCE_TIME playback_time = current_time - playback_start_time; - if (block.realtime * MIDITIME_TO_REFTIME > playback_time + 3 *_playback.preload_time * MS_TO_REFTIME) { - /* Stop the thread loop until we are at the preload time of the next block. */ - next_timeout = Clamp(((int64)block.realtime * MIDITIME_TO_REFTIME - playback_time) / MS_TO_REFTIME - _playback.preload_time, 0, 1000); - DEBUG(driver, 9, "DMusic thread: Next event in %u ms (music %u, ref %lld)", next_timeout, block.realtime * MIDITIME_TO_REFTIME, playback_time); - break; - } /* check that block isn't at end-of-song override */ if (current_segment.end > 0 && block.ticktime >= current_segment.end) { if (current_segment.loop) { @@ -743,6 +738,14 @@ static void MidiThreadProc(void *) next_timeout = 0; break; } + /* check that block is not in the future */ + REFERENCE_TIME playback_time = current_time - playback_start_time; + if (block.realtime * MIDITIME_TO_REFTIME > playback_time + 3 *_playback.preload_time * MS_TO_REFTIME) { + /* Stop the thread loop until we are at the preload time of the next block. */ + next_timeout = Clamp(((int64)block.realtime * MIDITIME_TO_REFTIME - playback_time) / MS_TO_REFTIME - _playback.preload_time, 0, 1000); + DEBUG(driver, 9, "DMusic thread: Next event in %u ms (music %u, ref %lld)", next_timeout, block.realtime * MIDITIME_TO_REFTIME, playback_time); + break; + } /* Timestamp of the current block. */ block_time = playback_start_time + block.realtime * MIDITIME_TO_REFTIME; @@ -829,8 +832,8 @@ static void MidiThreadProc(void *) /* end? */ if (current_block == current_file.blocks.size()) { if (current_segment.loop) { - current_block = 0; - clock->GetTime(&playback_start_time); + current_block = current_segment.start_block; + playback_start_time = block_time - current_file.blocks[current_block].realtime * MIDITIME_TO_REFTIME; } else { _playback.do_stop = true; } @@ -1232,9 +1235,9 @@ void MusicDriver_DMusic::PlaySong(const MusicSongInfo &song) if (!_playback.next_file.LoadSong(song)) return; - _playback.next_segment.start = 0; - _playback.next_segment.end = 0; - _playback.next_segment.loop = false; + _playback.next_segment.start = song.override_start; + _playback.next_segment.end = song.override_end; + _playback.next_segment.loop = song.loop; _playback.do_start = true; SetEvent(_thread_event); diff --git a/src/music/win32_m.cpp b/src/music/win32_m.cpp index 51528133b6..a32318db12 100644 --- a/src/music/win32_m.cpp +++ b/src/music/win32_m.cpp @@ -184,7 +184,7 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW /* find first block after start time and pretend playback started earlier * this is to allow all blocks prior to the actual start to still affect playback, * as they may contain important controller and program changes */ - size_t preload_bytes = 0; + uint preload_bytes = 0; for (size_t bl = 0; bl < _midi.current_file.blocks.size(); bl++) { MidiFile::DataBlock &block = _midi.current_file.blocks[bl]; preload_bytes += block.data.Length(); @@ -194,8 +194,13 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW _midi.current_segment.start_block = bl; break; } else { + /* Calculate offset start time for playback. + * The preload_bytes are used to compensate for delay in transmission over traditional serial MIDI interfaces, + * which have a bitrate of 31,250 bits/sec, and transmit 1+8+1 start/data/stop bits per byte. + * The delay compensation is needed to avoid time-compression of following messages. + */ DEBUG(driver, 2, "Win32-MIDI: timer: start from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime) / 1000.0, (int)preload_bytes); - _midi.playback_start_time -= block.realtime / 1000; + _midi.playback_start_time -= block.realtime / 1000 - preload_bytes * 1000 / 3125; break; } } @@ -209,10 +214,6 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW while (_midi.current_block < _midi.current_file.blocks.size()) { MidiFile::DataBlock &block = _midi.current_file.blocks[_midi.current_block]; - /* check that block is not in the future */ - if (block.realtime / 1000 > playback_time) { - break; - } /* check that block isn't at end-of-song override */ if (_midi.current_segment.end > 0 && block.ticktime >= _midi.current_segment.end) { if (_midi.current_segment.loop) { @@ -223,6 +224,10 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW } break; } + /* check that block is not in the future */ + if (block.realtime / 1000 > playback_time) { + break; + } byte *data = block.data.Begin(); size_t remaining = block.data.Length(); @@ -297,8 +302,8 @@ void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DW /* end? */ if (_midi.current_block == _midi.current_file.blocks.size()) { if (_midi.current_segment.loop) { - _midi.current_block = 0; - _midi.playback_start_time = timeGetTime(); + _midi.current_block = _midi.current_segment.start_block; + _midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000; } else { _midi.do_stop = true; } @@ -315,9 +320,9 @@ void MusicDriver_Win32::PlaySong(const MusicSongInfo &song) return; } - _midi.next_segment.start = 0; - _midi.next_segment.end = 0; - _midi.next_segment.loop = false; + _midi.next_segment.start = song.override_start; + _midi.next_segment.end = song.override_end; + _midi.next_segment.loop = song.loop; DEBUG(driver, 2, "Win32-MIDI: PlaySong: setting flag"); _midi.do_stop = _midi.playing; diff --git a/src/music_gui.cpp b/src/music_gui.cpp index 4333e889c7..e7552fe02f 100644 --- a/src/music_gui.cpp +++ b/src/music_gui.cpp @@ -10,6 +10,7 @@ /** @file music_gui.cpp GUI for the music playback. */ #include "stdafx.h" +#include #include "openttd.h" #include "base_media_base.h" #include "music/music_driver.hpp" @@ -35,246 +36,385 @@ #include "safeguards.h" -/** - * Get the name of the song. - * @param index of the song. - * @return the name of the song. - */ -static const char *GetSongName(int index) -{ - return BaseMusic::GetUsedSet()->songinfo[index].songname; -} -/** - * Get the track number of the song. - * @param index of the song. - * @return the track number of the song. - */ -static int GetTrackNumber(int index) -{ - return BaseMusic::GetUsedSet()->songinfo[index].tracknr; -} +struct MusicSystem { + struct PlaylistEntry : MusicSongInfo { + const MusicSet *set; ///< music set the song comes from + uint set_index; ///< index of song in set -/** The currently played song */ -static byte _music_wnd_cursong = 1; -/** Whether a song is currently played */ -static bool _song_is_active = false; + PlaylistEntry(const MusicSet *set, uint set_index) : MusicSongInfo(set->songinfo[set_index]), set(set), set_index(set_index) { } + bool IsValid() const { return !StrEmpty(this->songname); } + }; + typedef std::vector Playlist; -/** Indices of the songs in the current playlist */ -static byte _cur_playlist[NUM_SONGS_PLAYLIST + 1]; + enum PlaylistChoices { + PLCH_ALLMUSIC, + PLCH_OLDSTYLE, + PLCH_NEWSTYLE, + PLCH_EZYSTREET, + PLCH_CUSTOM1, + PLCH_CUSTOM2, + PLCH_THEMEONLY, + PLCH_MAX, + }; -/** Indices of all songs */ -static byte _playlist_all[NUM_SONGS_AVAILABLE + 1]; -/** Indices of all old style songs */ -static byte _playlist_old_style[NUM_SONGS_CLASS + 1]; -/** Indices of all new style songs */ -static byte _playlist_new_style[NUM_SONGS_CLASS + 1]; -/** Indices of all ezy street songs */ -static byte _playlist_ezy_street[NUM_SONGS_CLASS + 1]; + Playlist active_playlist; ///< current play order of songs, including any shuffle + Playlist displayed_playlist; ///< current playlist as displayed in GUI, never in shuffled order + Playlist music_set; ///< all songs in current music set, in set order -assert_compile(lengthof(_settings_client.music.custom_1) == NUM_SONGS_PLAYLIST + 1); -assert_compile(lengthof(_settings_client.music.custom_2) == NUM_SONGS_PLAYLIST + 1); + PlaylistChoices selected_playlist; -/** The different playlists that can be played. */ -static byte * const _playlists[] = { - _playlist_all, - _playlist_old_style, - _playlist_new_style, - _playlist_ezy_street, - _settings_client.music.custom_1, - _settings_client.music.custom_2, + void BuildPlaylists(); + + void ChangePlaylist(PlaylistChoices pl); + void ChangeMusicSet(const char *set_name); + void Shuffle(); + void Unshuffle(); + + void Play(); + void Stop(); + void Next(); + void Prev(); + void CheckStatus(); + + bool IsPlaying() const; + bool IsShuffle() const; + PlaylistEntry GetCurrentSong() const; + + bool IsCustomPlaylist() const; + void PlaylistAdd(size_t song_index); + void PlaylistRemove(size_t song_index); + void PlaylistClear(); + +private: + void ChangePlaylistPosition(int ofs); + int playlist_position; + + void SaveCustomPlaylist(PlaylistChoices pl); + + Playlist standard_playlists[PLCH_MAX]; }; -/** - * Validate a playlist. - * @param playlist The playlist to validate. - * @param last The last location in the list. - */ -void ValidatePlaylist(byte *playlist, byte *last) -{ - while (*playlist != 0 && playlist <= last) { - /* Song indices are saved off-by-one so 0 is "nothing". */ - if (*playlist <= NUM_SONGS_AVAILABLE && !StrEmpty(GetSongName(*playlist - 1))) { - playlist++; - continue; - } - for (byte *p = playlist; *p != 0 && p <= last; p++) { - p[0] = p[1]; - } - } +MusicSystem _music; - /* Make sure the list is null terminated. */ - *last = 0; -} -/** Prepare the playlists */ -void InitializeMusic() +/** Rebuild all playlists for the current music set */ +void MusicSystem::BuildPlaylists() { - uint j = 0; + const MusicSet *set = BaseMusic::GetUsedSet(); + + /* Clear current playlists */ + for (size_t i = 0; i < lengthof(this->standard_playlists); ++i) this->standard_playlists[i].clear(); + this->music_set.clear(); + + /* Build standard playlists, and a list of available music */ for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) { - if (StrEmpty(GetSongName(i))) continue; - _playlist_all[j++] = i + 1; - } - /* Terminate the list */ - _playlist_all[j] = 0; + PlaylistEntry entry(set, i); + if (!entry.IsValid()) continue; - /* Now make the 'styled' playlists */ - for (uint k = 0; k < NUM_SONG_CLASSES; k++) { - j = 0; - for (uint i = 0; i < NUM_SONGS_CLASS; i++) { - int id = k * NUM_SONGS_CLASS + i + 1; - if (StrEmpty(GetSongName(id))) continue; - _playlists[k + 1][j++] = id + 1; + this->music_set.push_back(entry); + + /* Add theme song to theme-only playlist */ + if (i == 0) this->standard_playlists[PLCH_THEMEONLY].push_back(entry); + + /* Don't add the theme song to standard playlists */ + if (i > 0) { + this->standard_playlists[PLCH_ALLMUSIC].push_back(entry); + uint theme = (i - 1) / NUM_SONGS_CLASS; + this->standard_playlists[PLCH_OLDSTYLE + theme].push_back(entry); } - /* Terminate the list */ - _playlists[k + 1][j] = 0; } - ValidatePlaylist(_settings_client.music.custom_1, lastof(_settings_client.music.custom_1)); - ValidatePlaylist(_settings_client.music.custom_2, lastof(_settings_client.music.custom_2)); - - if (BaseMusic::GetUsedSet()->num_available < _music_wnd_cursong) { - /* If there are less songs than the currently played song, - * just pause and reset to no song. */ - _music_wnd_cursong = 0; - _song_is_active = false; - } -} - -static void SkipToPrevSong() -{ - byte *b = _cur_playlist; - byte *p = b; - byte t; - - if (b[0] == 0) return; // empty playlist - - do p++; while (p[0] != 0); // find the end - - t = *--p; // and copy the bytes - while (p != b) { - p--; - p[1] = p[0]; - } - *b = t; - - _song_is_active = false; -} - -static void SkipToNextSong() -{ - byte *b = _cur_playlist; - byte t; - - t = b[0]; - if (t != 0) { - while (b[1] != 0) { - b[0] = b[1]; - b++; + /* Load custom playlists + * Song index offsets are 1-based, zero indicates invalid/end-of-list value */ + for (uint i = 0; i < NUM_SONGS_PLAYLIST; i++) { + if (_settings_client.music.custom_1[i] > 0) { + PlaylistEntry entry(set, _settings_client.music.custom_1[i] - 1); + if (entry.IsValid()) this->standard_playlists[PLCH_CUSTOM1].push_back(entry); } - b[0] = t; + if (_settings_client.music.custom_2[i] > 0) { + PlaylistEntry entry(set, _settings_client.music.custom_2[i] - 1); + if (entry.IsValid()) this->standard_playlists[PLCH_CUSTOM2].push_back(entry); + } + } +} + +/** + * Switch to another playlist, or reload the current one. + * @param pl Playlist to select + */ +void MusicSystem::ChangePlaylist(PlaylistChoices pl) +{ + assert(pl < PLCH_MAX && pl >= PLCH_ALLMUSIC); + + this->displayed_playlist = this->standard_playlists[pl]; + this->active_playlist = this->displayed_playlist; + this->selected_playlist = pl; + this->playlist_position = 0; + + if (this->selected_playlist != PLCH_THEMEONLY) _settings_client.music.playlist = this->selected_playlist; + + if (_settings_client.music.shuffle) { + this->Shuffle(); + /* Shuffle() will also Play() if necessary, only start once */ + } else if (_settings_client.music.playing) { + this->Play(); } - _song_is_active = false; + InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0); + InvalidateWindowData(WC_MUSIC_WINDOW, 0); } -static void MusicVolumeChanged(byte new_vol) +/** + * Change to named music set, and reset playback. + * @param set_name Name of music set to select + */ +void MusicSystem::ChangeMusicSet(const char *set_name) { - MusicDriver::GetInstance()->SetVolume(new_vol); + BaseMusic::SetSet(set_name); + + this->BuildPlaylists(); + this->ChangePlaylist(this->selected_playlist); + + InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS, 0, true); } -static void DoPlaySong() +/** Enable shuffle mode and restart playback */ +void MusicSystem::Shuffle() { - char filename[MAX_PATH]; - MusicSongInfo songinfo = BaseMusic::GetUsedSet()->songinfo[_music_wnd_cursong - 1]; // copy - if (FioFindFullPath(filename, lastof(filename), BASESET_DIR, songinfo.filename) == NULL) { - FioFindFullPath(filename, lastof(filename), OLD_GM_DIR, songinfo.filename); + _settings_client.music.shuffle = true; + + this->active_playlist = this->displayed_playlist; + for (size_t i = 0; i < this->active_playlist.size(); i++) { + size_t shuffle_index = InteractiveRandom() % (this->active_playlist.size() - i); + std::swap(this->active_playlist[i], this->active_playlist[i + shuffle_index]); } - songinfo.filename = filename; // non-owned pointer - MusicDriver::GetInstance()->PlaySong(songinfo); - SetWindowDirty(WC_MUSIC_WINDOW, 0); + + if (_settings_client.music.playing) this->Play(); + + InvalidateWindowData(WC_MUSIC_WINDOW, 0); } -static void DoStopMusic() +/** Disable shuffle and restart playback */ +void MusicSystem::Unshuffle() +{ + _settings_client.music.shuffle = false; + this->active_playlist = this->displayed_playlist; + + if (_settings_client.music.playing) this->Play(); + + InvalidateWindowData(WC_MUSIC_WINDOW, 0); +} + +/** Start/restart playback at current song */ +void MusicSystem::Play() +{ + /* Always set the playing flag, even if there is no music */ + _settings_client.music.playing = true; + MusicDriver::GetInstance()->StopSong(); + /* Make sure playlist_position is a valid index, if playlist has changed etc. */ + this->ChangePlaylistPosition(0); + + /* If there is no music, don't try to play it */ + if (this->active_playlist.empty()) return; + + MusicSongInfo song = this->active_playlist[this->playlist_position]; + if (_game_mode == GM_MENU && this->selected_playlist == PLCH_THEMEONLY) song.loop = true; + MusicDriver::GetInstance()->PlaySong(song); + + InvalidateWindowData(WC_MUSIC_WINDOW, 0); +} + +/** Stop playback and set flag that we don't intend to play music */ +void MusicSystem::Stop() { MusicDriver::GetInstance()->StopSong(); - SetWindowDirty(WC_MUSIC_WINDOW, 0); + _settings_client.music.playing = false; + + InvalidateWindowData(WC_MUSIC_WINDOW, 0); } -/** Reload the active playlist data from playlist selection and shuffle setting */ -static void ResetPlaylist() +/** Skip to next track */ +void MusicSystem::Next() { - uint i = 0; - uint j = 0; + this->ChangePlaylistPosition(+1); + if (_settings_client.music.playing) this->Play(); - memset(_cur_playlist, 0, sizeof(_cur_playlist)); - do { - /* File is the index into the file table of the music set. The play list uses 0 as 'no entry', - * so we need to subtract 1. In case of 'no entry' (file = -1), just skip adding it outright. */ - int file = _playlists[_settings_client.music.playlist][i] - 1; - if (file >= 0) { - const char *filename = BaseMusic::GetUsedSet()->files[file].filename; - /* We are now checking for the existence of that file prior - * to add it to the list of available songs */ - if (!StrEmpty(filename) && FioCheckFileExists(filename, BASESET_DIR)) { - _cur_playlist[j] = _playlists[_settings_client.music.playlist][i]; - j++; - } + InvalidateWindowData(WC_MUSIC_WINDOW, 0); +} + +/** Skip to previous track */ +void MusicSystem::Prev() +{ + this->ChangePlaylistPosition(-1); + if (_settings_client.music.playing) this->Play(); + + InvalidateWindowData(WC_MUSIC_WINDOW, 0); +} + +/** Check that music is playing if it should, and that appropriate playlist is active for game/main menu */ +void MusicSystem::CheckStatus() +{ + if ((_game_mode == GM_MENU) != (this->selected_playlist == PLCH_THEMEONLY)) { + /* Make sure the theme-only playlist is active when on the title screen, and not during gameplay */ + this->ChangePlaylist((_game_mode == GM_MENU) ? PLCH_THEMEONLY : (PlaylistChoices)_settings_client.music.playlist); + } + if (this->active_playlist.empty()) return; + /* If we were supposed to be playing, but music has stopped, move to next song */ + if (this->IsPlaying() && !MusicDriver::GetInstance()->IsSongPlaying()) this->Next(); +} + +/** Is the player getting music right now? */ +bool MusicSystem::IsPlaying() const +{ + return _settings_client.music.playing && !this->active_playlist.empty(); +} + +/** Is shuffle mode enabled? */ +bool MusicSystem::IsShuffle() const +{ + return _settings_client.music.shuffle; +} + +/** Return the current song, or a dummy if none */ +MusicSystem::PlaylistEntry MusicSystem::GetCurrentSong() const +{ + if (!this->IsPlaying()) return PlaylistEntry(BaseMusic::GetUsedSet(), 0); + return this->active_playlist[this->playlist_position]; +} + +/** Is one of the custom playlists selected? */ +bool MusicSystem::IsCustomPlaylist() const +{ + return (this->selected_playlist == PLCH_CUSTOM1) || (this->selected_playlist == PLCH_CUSTOM2); +} + +/** + * Append a song to a custom playlist. + * Always adds to the currently active playlist. + * @param song_index Index of song in the current music set to add + */ +void MusicSystem::PlaylistAdd(size_t song_index) +{ + if (!this->IsCustomPlaylist()) return; + + /* Pick out song from the music set */ + if (song_index >= this->music_set.size()) return; + PlaylistEntry entry = this->music_set[song_index]; + + /* Check for maximum length */ + if (this->standard_playlists[this->selected_playlist].size() >= NUM_SONGS_PLAYLIST) return; + + /* Add it to the appropriate playlist, and the display */ + this->standard_playlists[this->selected_playlist].push_back(entry); + this->displayed_playlist.push_back(entry); + + /* Add it to the active playlist, if playback is shuffled select a random position to add at */ + if (this->active_playlist.empty()) { + this->active_playlist.push_back(entry); + if (this->IsPlaying()) this->Play(); + } else if (this->IsShuffle()) { + /* Generate a random position between 0 and n (inclusive, new length) to insert at */ + size_t maxpos = this->displayed_playlist.size(); + size_t newpos = InteractiveRandom() % maxpos; + this->active_playlist.insert(this->active_playlist.begin() + newpos, entry); + /* Make sure to shift up the current playback position if the song was inserted before it */ + if ((int)newpos <= this->playlist_position) this->playlist_position++; + } else { + this->active_playlist.push_back(entry); + } + + this->SaveCustomPlaylist(this->selected_playlist); + + InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0); +} + +/** + * Remove a song from a custom playlist. + * @param song_index Index in the custom playlist to remove. + */ +void MusicSystem::PlaylistRemove(size_t song_index) +{ + if (!this->IsCustomPlaylist()) return; + + Playlist &pl = this->standard_playlists[this->selected_playlist]; + if (song_index >= pl.size()) return; + + /* Remove from "simple" playlists */ + PlaylistEntry song = pl[song_index]; + pl.erase(pl.begin() + song_index); + this->displayed_playlist.erase(this->displayed_playlist.begin() + song_index); + + /* Find in actual active playlist (may be shuffled) and remove, + * if it's the current song restart playback */ + for (size_t i = 0; i < this->active_playlist.size(); i++) { + Playlist::iterator s2 = this->active_playlist.begin() + i; + if (s2->filename == song.filename && s2->cat_index == song.cat_index) { + this->active_playlist.erase(s2); + if ((int)i == this->playlist_position && this->IsPlaying()) this->Play(); + break; } - } while (_playlists[_settings_client.music.playlist][++i] != 0 && j < lengthof(_cur_playlist) - 1); + } - /* Do not shuffle when on the intro-start window, as the song to play has to be the original TTD Theme*/ - if (_settings_client.music.shuffle && _game_mode != GM_MENU) { - i = 500; - do { - uint32 r = InteractiveRandom(); - byte *a = &_cur_playlist[GB(r, 0, 5)]; - byte *b = &_cur_playlist[GB(r, 8, 5)]; + this->SaveCustomPlaylist(this->selected_playlist); - if (*a != 0 && *b != 0) { - byte t = *a; - *a = *b; - *b = t; - } - } while (--i); + InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0); +} + +/** + * Remove all songs from the current custom playlist. + * Effectively stops playback too. + */ +void MusicSystem::PlaylistClear() +{ + if (!this->IsCustomPlaylist()) return; + + this->standard_playlists[this->selected_playlist].clear(); + this->ChangePlaylist(this->selected_playlist); + + this->SaveCustomPlaylist(this->selected_playlist); +} + +/** + * Change playlist position pointer by the given offset, making sure to keep it within valid range. + * If the playlist is empty, position is always set to 0. + * @param ofs Amount to move playlist position by. + */ +void MusicSystem::ChangePlaylistPosition(int ofs) +{ + if (this->active_playlist.empty()) { + this->playlist_position = 0; + } else { + this->playlist_position += ofs; + while (this->playlist_position >= (int)this->active_playlist.size()) this->playlist_position -= (int)this->active_playlist.size(); + while (this->playlist_position < 0) this->playlist_position += (int)this->active_playlist.size(); } } -static void StopMusic() +/** + * Save a custom playlist to settings after modification. + * @param pl Playlist to store back + */ +void MusicSystem::SaveCustomPlaylist(PlaylistChoices pl) { - _music_wnd_cursong = 0; - DoStopMusic(); - _song_is_active = false; - SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9); -} - -/** Begin playing the next song on the playlist */ -static void PlayPlaylistSong() -{ - if (_cur_playlist[0] == 0) { - ResetPlaylist(); - /* if there is not songs in the playlist, it may indicate - * no file on the gm folder, or even no gm folder. - * Stop the playback, then */ - if (_cur_playlist[0] == 0) { - _song_is_active = false; - _music_wnd_cursong = 0; - _settings_client.music.playing = false; - return; - } + byte *settings_pl; + if (pl == PLCH_CUSTOM1) { + settings_pl = _settings_client.music.custom_1; + } else if (pl == PLCH_CUSTOM2) { + settings_pl = _settings_client.music.custom_2; + } else { + return; } - _music_wnd_cursong = _cur_playlist[0]; - DoPlaySong(); - _song_is_active = true; - SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9); + size_t num = 0; + MemSetT(settings_pl, 0, NUM_SONGS_PLAYLIST); + + for (Playlist::const_iterator song = this->standard_playlists[pl].begin(); song != this->standard_playlists[pl].end(); ++song) { + /* Music set indices in the settings playlist are 1-based, 0 means unused slot */ + settings_pl[num++] = (byte)song->set_index + 1; + } } -void ResetMusic() -{ - _music_wnd_cursong = 1; - DoPlaySong(); -} /** * Check music playback status and start/stop/song-finished. @@ -282,30 +422,7 @@ void ResetMusic() */ void MusicLoop() { - if (!_settings_client.music.playing && _song_is_active) { - StopMusic(); - } else if (_settings_client.music.playing && !_song_is_active) { - PlayPlaylistSong(); - } - - if (!_song_is_active) return; - - if (!MusicDriver::GetInstance()->IsSongPlaying()) { - if (_game_mode != GM_MENU) { - StopMusic(); - SkipToNextSong(); - PlayPlaylistSong(); - } else { - ResetMusic(); - } - } -} - -static void SelectPlaylist(byte list) -{ - _settings_client.music.playlist = list; - InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0); - InvalidateWindowData(WC_MUSIC_WINDOW, 0); + _music.CheckStatus(); } /** @@ -315,29 +432,20 @@ static void SelectPlaylist(byte list) void ChangeMusicSet(int index) { if (BaseMusic::GetIndexOfUsedSet() == index) return; - - /* Resume playback after switching? - * Always if music is already playing, and also if the user is switching - * away from an empty music set. - * If the user switches away from an empty set, assume it's because they - * want to hear music now. */ - bool shouldplay = _song_is_active || (BaseMusic::GetUsedSet()->num_available == 0); - StopMusic(); - const char *name = BaseMusic::GetSet(index)->name; - BaseMusic::SetSet(name); - free(BaseMusic::ini_set); - BaseMusic::ini_set = stredup(name); - - InitializeMusic(); - ResetPlaylist(); - _settings_client.music.playing = shouldplay; - - InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0); - InvalidateWindowData(WC_MUSIC_WINDOW, 0); - InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS, 0, true); + _music.ChangeMusicSet(name); } +/** + * Prepare the music system for use. + * Called from \c InitializeGame + */ +void InitializeMusic() +{ + _music.BuildPlaylists(); +} + + struct MusicTrackSelectionWindow : public Window { MusicTrackSelectionWindow(WindowDesc *desc, WindowNumber number) : Window(desc) { @@ -394,13 +502,10 @@ struct MusicTrackSelectionWindow : public Window { case WID_MTS_LIST_LEFT: case WID_MTS_LIST_RIGHT: { Dimension d = {0, 0}; - for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) { - const char *song_name = GetSongName(i); - if (StrEmpty(song_name)) continue; - - SetDParam(0, GetTrackNumber(i)); + for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) { + SetDParam(0, song->tracknr); SetDParam(1, 2); - SetDParamStr(2, GetSongName(i)); + SetDParamStr(2, song->songname); Dimension d2 = GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME); d.width = max(d.width, d2.width); d.height += d2.height; @@ -420,13 +525,10 @@ struct MusicTrackSelectionWindow : public Window { GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK); int y = r.top + WD_FRAMERECT_TOP; - for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) { - const char *song_name = GetSongName(i); - if (StrEmpty(song_name)) continue; - - SetDParam(0, GetTrackNumber(i)); + for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) { + SetDParam(0, song->tracknr); SetDParam(1, 2); - SetDParamStr(2, song_name); + SetDParamStr(2, song->songname); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME); y += FONT_HEIGHT_SMALL; } @@ -437,11 +539,10 @@ struct MusicTrackSelectionWindow : public Window { GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK); int y = r.top + WD_FRAMERECT_TOP; - for (const byte *p = _playlists[_settings_client.music.playlist]; *p != 0; p++) { - uint i = *p - 1; - SetDParam(0, GetTrackNumber(i)); + for (MusicSystem::Playlist::const_iterator song = _music.active_playlist.begin(); song != _music.active_playlist.end(); ++song) { + SetDParam(0, song->tracknr); SetDParam(1, 2); - SetDParamStr(2, GetSongName(i)); + SetDParamStr(2, song->songname); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME); y += FONT_HEIGHT_SMALL; } @@ -455,42 +556,13 @@ struct MusicTrackSelectionWindow : public Window { switch (widget) { case WID_MTS_LIST_LEFT: { // add to playlist int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL); - - if (_settings_client.music.playlist < 4) return; - if (!IsInsideMM(y, 0, BaseMusic::GetUsedSet()->num_available)) return; - - byte *p = _playlists[_settings_client.music.playlist]; - for (uint i = 0; i != NUM_SONGS_PLAYLIST - 1; i++) { - if (p[i] == 0) { - /* Find the actual song number */ - for (uint j = 0; j < NUM_SONGS_AVAILABLE; j++) { - if (GetTrackNumber(j) == y + 1) { - p[i] = j + 1; - break; - } - } - p[i + 1] = 0; - this->SetDirty(); - ResetPlaylist(); - break; - } - } + _music.PlaylistAdd(y); break; } case WID_MTS_LIST_RIGHT: { // remove from playlist int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL); - - if (_settings_client.music.playlist < 4) return; - if (!IsInsideMM(y, 0, NUM_SONGS_PLAYLIST)) return; - - byte *p = _playlists[_settings_client.music.playlist]; - for (uint i = y; i != NUM_SONGS_PLAYLIST - 1; i++) { - p[i] = p[i + 1]; - } - - this->SetDirty(); - ResetPlaylist(); + _music.PlaylistRemove(y); break; } @@ -502,17 +574,12 @@ struct MusicTrackSelectionWindow : public Window { } case WID_MTS_CLEAR: // clear - for (uint i = 0; _playlists[_settings_client.music.playlist][i] != 0; i++) _playlists[_settings_client.music.playlist][i] = 0; - this->SetDirty(); - StopMusic(); - ResetPlaylist(); + _music.PlaylistClear(); break; case WID_MTS_ALL: case WID_MTS_OLD: case WID_MTS_NEW: case WID_MTS_EZY: case WID_MTS_CUSTOM1: case WID_MTS_CUSTOM2: // set playlist - SelectPlaylist(widget - WID_MTS_ALL); - StopMusic(); - ResetPlaylist(); + _music.ChangePlaylist((MusicSystem::PlaylistChoices)(widget - WID_MTS_ALL)); break; } } @@ -627,8 +694,8 @@ struct MusicWindow : public Window { case WID_M_TRACK_NAME: { Dimension d = GetStringBoundingBox(STR_MUSIC_TITLE_NONE); - for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) { - SetDParamStr(0, GetSongName(i)); + for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) { + SetDParamStr(0, song->songname); d = maxdim(d, GetStringBoundingBox(STR_MUSIC_TITLE_NAME)); } d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT; @@ -654,8 +721,8 @@ struct MusicWindow : public Window { break; } StringID str = STR_MUSIC_TRACK_NONE; - if (_song_is_active != 0 && _music_wnd_cursong != 0) { - SetDParam(0, GetTrackNumber(_music_wnd_cursong - 1)); + if (_music.IsPlaying()) { + SetDParam(0, _music.GetCurrentSong().tracknr); SetDParam(1, 2); str = STR_MUSIC_TRACK_DIGIT; } @@ -668,9 +735,9 @@ struct MusicWindow : public Window { StringID str = STR_MUSIC_TITLE_NONE; if (BaseMusic::GetUsedSet()->num_available == 0) { str = STR_MUSIC_TITLE_NOMUSIC; - } else if (_song_is_active != 0 && _music_wnd_cursong != 0) { + } else if (_music.IsPlaying()) { str = STR_MUSIC_TITLE_NAME; - SetDParamStr(0, GetSongName(_music_wnd_cursong - 1)); + SetDParamStr(0, _music.GetCurrentSong().songname); } DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str, TC_FROMSTRING, SA_HOR_CENTER); break; @@ -710,23 +777,19 @@ struct MusicWindow : public Window { { switch (widget) { case WID_M_PREV: // skip to prev - if (!_song_is_active) return; - SkipToPrevSong(); - this->SetDirty(); + _music.Prev(); break; case WID_M_NEXT: // skip to next - if (!_song_is_active) return; - SkipToNextSong(); - this->SetDirty(); + _music.Next(); break; case WID_M_STOP: // stop playing - _settings_client.music.playing = false; + _music.Stop(); break; case WID_M_PLAY: // start playing - _settings_client.music.playing = true; + _music.Play(); break; case WID_M_MUSIC_VOL: case WID_M_EFFECT_VOL: { // volume sliders @@ -741,7 +804,7 @@ struct MusicWindow : public Window { if (new_vol < 3) new_vol = 0; if (new_vol != *vol) { *vol = new_vol; - if (widget == WID_M_MUSIC_VOL) MusicVolumeChanged(new_vol); + if (widget == WID_M_MUSIC_VOL) MusicDriver::GetInstance()->SetVolume(new_vol); this->SetDirty(); } @@ -750,12 +813,13 @@ struct MusicWindow : public Window { } case WID_M_SHUFFLE: // toggle shuffle - _settings_client.music.shuffle ^= 1; - this->SetWidgetLoweredState(WID_M_SHUFFLE, _settings_client.music.shuffle); + if (_music.IsShuffle()) { + _music.Unshuffle(); + } else { + _music.Shuffle(); + } + this->SetWidgetLoweredState(WID_M_SHUFFLE, _music.IsShuffle()); this->SetWidgetDirty(WID_M_SHUFFLE); - StopMusic(); - ResetPlaylist(); - this->SetDirty(); break; case WID_M_PROGRAMME: // show track selection @@ -764,10 +828,7 @@ struct MusicWindow : public Window { case WID_M_ALL: case WID_M_OLD: case WID_M_NEW: case WID_M_EZY: case WID_M_CUSTOM1: case WID_M_CUSTOM2: // playlist - SelectPlaylist(widget - WID_M_ALL); - StopMusic(); - ResetPlaylist(); - this->SetDirty(); + _music.ChangePlaylist((MusicSystem::PlaylistChoices)(widget - WID_M_ALL)); break; } } diff --git a/src/newgrf.cpp b/src/newgrf.cpp index f512289473..4fc1813546 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -4749,7 +4749,7 @@ static void NewSpriteGroup(ByteReader *buf) } } - group->num_ranges = optimised.size(); + group->num_ranges = (uint)optimised.size(); // cast is safe, there should never be 2**31 elements here if (group->num_ranges > 0) { group->ranges = MallocT(group->num_ranges); MemCpyT(group->ranges, &optimised.front(), group->num_ranges); diff --git a/src/openttd.cpp b/src/openttd.cpp index 3204e48105..11ed7929cf 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -393,8 +393,7 @@ static void LoadIntroGame(bool load_newgrfs = true) CheckForMissingGlyphs(); - /* Play main theme */ - if (MusicDriver::GetInstance()->IsSongPlaying()) ResetMusic(); + MusicLoop(); // ensure music is correct } void MakeNewgameSettingsLive() diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index b75c030645..1e8a93659f 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -688,6 +688,10 @@ static void ShipController(Ship *v) if (v->current_order.IsType(OT_LEAVESTATION)) { v->current_order.Free(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + /* Test if continuing forward would lead to a dead-end, moving into the dock. */ + DiagDirection exitdir = VehicleExitDir(v->direction, v->state); + TileIndex tile = TileAddByDiagDir(v->tile, exitdir); + if (TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0, exitdir)) == TRACK_BIT_NONE) goto reverse_direction; } else if (v->dest_tile != 0) { /* We have a target, let's see if we reached it... */ if (v->current_order.IsType(OT_GOTO_WAYPOINT) && diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 00e05625a9..ccc9b004ce 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -47,6 +47,7 @@ #include "game/game.hpp" #include "zoom_func.h" #include "zoning.h" +#include "scope.h" #include "table/strings.h" #include "table/town_land.h" @@ -1736,6 +1737,9 @@ void UpdateTownMaxPass(Town *t) t->supplied[CT_MAIL].old_max = t->cache.population >> 4; } +static void UpdateTownGrowthRate(Town *t); +static void UpdateTownGrowth(Town *t); + /** * Does the actual town creation. * @@ -1815,6 +1819,7 @@ static void DoCreateTown(Town *t, TileIndex tile, uint32 townnameparts, TownSize t->cache.num_houses -= x; UpdateTownRadius(t); + UpdateTownGrowthRate(t); UpdateTownMaxPass(t); UpdateAirportsNoise(); } @@ -2523,6 +2528,7 @@ static void DoBuildHouse(Town *t, TileIndex tile, HouseID house, byte random_bit MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits); UpdateTownRadius(t); + UpdateTownGrowthRate(t); UpdateTownCargoes(t, tile); } @@ -2781,8 +2787,6 @@ const CargoSpec *FindFirstCargoWithTownEffect(TownEffect effect) return NULL; } -static void UpdateTownGrowRate(Town *t); - /** * Change the cargo goal of a town. * @param tile Unused. @@ -2811,7 +2815,7 @@ CommandCost CmdTownCargoGoal(TileIndex tile, DoCommandFlag flags, uint32 p1, uin if (flags & DC_EXEC) { t->goal[te] = p2; - UpdateTownGrowRate(t); + UpdateTownGrowth(t); InvalidateWindowData(WC_TOWN_VIEW, index); } @@ -2861,7 +2865,7 @@ CommandCost CmdTownGrowthRate(TileIndex tile, DoCommandFlag flags, uint32 p1, ui if (flags & DC_EXEC) { if (p2 == 0) { - /* Just clear the flag, UpdateTownGrowRate will determine a proper growth rate */ + /* Just clear the flag, UpdateTownGrowth will determine a proper growth rate */ ClrBit(t->flags, TOWN_CUSTOM_GROWTH); } else { uint old_rate = t->growth_rate; @@ -2875,7 +2879,7 @@ CommandCost CmdTownGrowthRate(TileIndex tile, DoCommandFlag flags, uint32 p1, ui t->growth_rate = p2; SetBit(t->flags, TOWN_CUSTOM_GROWTH); } - UpdateTownGrowRate(t); + UpdateTownGrowth(t); InvalidateWindowData(WC_TOWN_VIEW, p1); } @@ -3162,7 +3166,7 @@ static CommandCost TownActionFundBuildings(Town *t, DoCommandFlag flags) t->fund_buildings_months = 3; /* Enable growth (also checking GameScript's opinion) */ - UpdateTownGrowRate(t); + UpdateTownGrowth(t); /* Build a new house, but add a small delay to make sure * that spamming funding doesn't let town grow any faster @@ -3368,10 +3372,121 @@ static void UpdateTownRating(Town *t) SetWindowDirty(WC_TOWN_AUTHORITY, t->index); } -static void UpdateTownGrowRate(Town *t) + +/** + * Updates town grow counter after growth rate change. + * Preserves relative house builting progress whenever it can. + * @param town The town to calculate grow counter for + * @param prev_growth_rate Town growth rate before it changed (one that was used with grow counter to be updated) + */ +static void UpdateTownGrowCounter(Town *t, uint16 prev_growth_rate) { - ClrBit(t->flags, TOWN_IS_GROWING); + if (t->growth_rate == TOWN_GROWTH_RATE_NONE) return; + if (prev_growth_rate == TOWN_GROWTH_RATE_NONE) { + t->grow_counter = min(t->growth_rate, t->grow_counter); + return; + } + t->grow_counter = RoundDivSU((uint32)t->grow_counter * (t->growth_rate + 1), prev_growth_rate + 1); +} + +/** + * Calculates amount of active stations in the range of town (HZB_TOWN_EDGE). + * @param town The town to calculate stations for + * @returns Amount of active stations + */ +static int CountActiveStations(Town *t) +{ + int n = 0; + const Station *st; + FOR_ALL_STATIONS(st) { + if (DistanceSquare(st->xy, t->xy) <= t->cache.squared_town_zone_radius[0]) { + if (st->time_since_load <= 20 || st->time_since_unload <= 20) { + n++; + } + } + } + return n; +} + +/** + * Calculates town growth rate in normal conditions (custom growth rate not set). + * If town growth speed is set to None(0) returns the same rate as if it was Normal(2). + * @param town The town to calculate growth rate for + * @returns Calculated growth rate + */ +static uint GetNormalGrowthRate(Town *t) +{ + static const uint16 _grow_count_values[2][6] = { + { 120, 120, 120, 100, 80, 60 }, // Fund new buildings has been activated + { 320, 420, 300, 220, 160, 100 } // Normal values + }; + + int n = CountActiveStations(t); + uint16 m = _grow_count_values[t->fund_buildings_months != 0 ? 0 : 1][min(n, 5)]; + + int growth_multiplier; + if (_settings_game.economy.town_growth_rate == 0) { + growth_multiplier = 1; + } else if (_settings_game.economy.town_growth_rate > 0) { + growth_multiplier = _settings_game.economy.town_growth_rate - 1; + } else { + growth_multiplier = _settings_game.economy.town_growth_rate; + } + + if (growth_multiplier < 0) { + m <<= (-growth_multiplier); + } else { + m >>= growth_multiplier; + } + if (t->larger_town) m /= 2; + + if (_settings_game.economy.town_growth_cargo_transported > 0) { + uint32 inverse_m = UINT32_MAX / m; + auto calculate_cargo_ratio_fix15 = [](const TransportedCargoStat &stat) -> uint32 { + return stat.old_max ? ((uint64) (stat.old_act << 15)) / stat.old_max : 1 << 15; + }; + uint32 cargo_ratio_fix16 = calculate_cargo_ratio_fix15(t->supplied[CT_PASSENGERS]) + calculate_cargo_ratio_fix15(t->supplied[CT_MAIL]); + uint32 cargo_dependant_part = (((uint64) cargo_ratio_fix16) * ((uint64) inverse_m) * _settings_game.economy.town_growth_cargo_transported) >> 16; + uint32 non_cargo_dependant_part = ((uint64) inverse_m) * (100 - _settings_game.economy.town_growth_cargo_transported); + uint32 total = (cargo_dependant_part + non_cargo_dependant_part); + if (total == 0) { + ClrBit(t->flags, TOWN_IS_GROWING); + return UINT16_MAX; + } + m = ((uint64) UINT32_MAX * 100) / total; + } + + return TownTicksToGameTicks(m / (t->cache.num_houses / 50 + 1)); +} + +/** + * Updates town growth rate. + * @param town The town to update growth rate for + */ +static void UpdateTownGrowthRate(Town *t) +{ + if (HasBit(t->flags, TOWN_CUSTOM_GROWTH)) return; + uint old_rate = t->growth_rate; + t->growth_rate = GetNormalGrowthRate(t); + UpdateTownGrowCounter(t, old_rate); SetWindowDirty(WC_TOWN_VIEW, t->index); +} + +/** + * Updates town growth state (whether it is growing or not). + * @param town The town to update growth for + */ +static void UpdateTownGrowth(Town *t) +{ + auto guard = scope_guard([t]() { + SetWindowDirty(WC_TOWN_VIEW, t->index); + }); + + SetBit(t->flags, TOWN_IS_GROWING); + UpdateTownGrowthRate(t); + if (!HasBit(t->flags, TOWN_IS_GROWING)) return; + + ClrBit(t->flags, TOWN_IS_GROWING); if (_settings_game.economy.town_growth_rate == 0 && t->fund_buildings_months == 0) return; @@ -3398,71 +3513,9 @@ static void UpdateTownGrowRate(Town *t) return; } - /** - * Towns are processed every TOWN_GROWTH_TICKS ticks, and this is the - * number of times towns are processed before a new building is built. - */ - static const uint16 _grow_count_values[2][6] = { - { 120, 120, 120, 100, 80, 60 }, // Fund new buildings has been activated - { 320, 420, 300, 220, 160, 100 } // Normal values - }; - - int n = 0; - - const Station *st; - FOR_ALL_STATIONS(st) { - if (DistanceSquare(st->xy, t->xy) <= t->cache.squared_town_zone_radius[0]) { - if (st->time_since_load <= 20 || st->time_since_unload <= 20) { - n++; - } - } - } - - uint16 m; - - if (t->fund_buildings_months != 0) { - m = _grow_count_values[0][min(n, 5)]; - } else { - m = _grow_count_values[1][min(n, 5)]; - if (n == 0 && !Chance16(1, 12)) return; - } - - /* Use the normal growth rate values if new buildings have been funded in - * this town and the growth rate is set to none. */ - int growth_multiplier; - if (_settings_game.economy.town_growth_rate == 0) { - growth_multiplier = 1; - } else if (_settings_game.economy.town_growth_rate > 0) { - growth_multiplier = _settings_game.economy.town_growth_rate - 1; - } else { - growth_multiplier = _settings_game.economy.town_growth_rate; - } - - if (growth_multiplier < 0) { - m <<= (-growth_multiplier); - } else { - m >>= growth_multiplier; - } - if (t->larger_town) m /= 2; - - if (_settings_game.economy.town_growth_cargo_transported > 0) { - uint32 inverse_m = UINT32_MAX / m; - auto calculate_cargo_ratio_fix15 = [](const TransportedCargoStat &stat) -> uint32 { - return stat.old_max ? ((uint64) (stat.old_act << 15)) / stat.old_max : 1 << 15; - }; - uint32 cargo_ratio_fix16 = calculate_cargo_ratio_fix15(t->supplied[CT_PASSENGERS]) + calculate_cargo_ratio_fix15(t->supplied[CT_MAIL]); - uint32 cargo_dependant_part = (((uint64) cargo_ratio_fix16) * ((uint64) inverse_m) * _settings_game.economy.town_growth_cargo_transported) >> 16; - uint32 non_cargo_dependant_part = ((uint64) inverse_m) * (100 - _settings_game.economy.town_growth_cargo_transported); - uint32 total = (cargo_dependant_part + non_cargo_dependant_part); - if (total == 0) return; - m = ((uint64) UINT32_MAX * 100) / total; - } - - t->growth_rate = TownTicksToGameTicks(m / (t->cache.num_houses / 50 + 1)); - t->grow_counter = min(t->growth_rate, t->grow_counter); + if (t->fund_buildings_months == 0 && CountActiveStations(t) == 0 && !Chance16(1, 12)) return; SetBit(t->flags, TOWN_IS_GROWING); - SetWindowDirty(WC_TOWN_VIEW, t->index); } static void UpdateTownAmounts(Town *t) @@ -3698,8 +3751,8 @@ void TownsMonthlyLoop() } UpdateTownAmounts(t); + UpdateTownGrowth(t); UpdateTownRating(t); - UpdateTownGrowRate(t); UpdateTownUnwanted(t); UpdateTownCargoes(t); } diff --git a/src/town_gui.cpp b/src/town_gui.cpp index 1bd6cec6c0..381aeb1d23 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -753,6 +753,16 @@ public: } } + /** + * Get the string to draw the town name. + * @param t Town to draw. + * @return The string to use. + */ + static StringID GetTownString(const Town *t) + { + return t->larger_town ? STR_TOWN_DIRECTORY_CITY : STR_TOWN_DIRECTORY_TOWN; + } + virtual void DrawWidget(const Rect &r, int widget) const { switch (widget) { @@ -791,7 +801,7 @@ public: SetDParam(0, t->index); SetDParam(1, t->cache.population); - DrawString(text_left, text_right, y + (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2, STR_TOWN_DIRECTORY_TOWN); + DrawString(text_left, text_right, y + (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2, GetTownString(t)); y += this->resize.step_height; if (++n == this->vscroll->GetCapacity()) break; // max number of towns in 1 window @@ -830,7 +840,7 @@ public: SetDParam(0, t->index); SetDParamMaxDigits(1, 8); - d = maxdim(d, GetStringBoundingBox(STR_TOWN_DIRECTORY_TOWN)); + d = maxdim(d, GetStringBoundingBox(GetTownString(t))); } Dimension icon_size = GetSpriteSize(SPR_TOWN_RATING_GOOD); d.width += icon_size.width + 2; diff --git a/src/track_func.h b/src/track_func.h index 20bcc3c238..aa5336447a 100644 --- a/src/track_func.h +++ b/src/track_func.h @@ -692,4 +692,25 @@ static inline bool IsUphillTrackdir(Slope slope, Trackdir dir) return HasBit(_uphill_trackdirs[RemoveHalftileSlope(slope)], dir); } +/** + * Determine the side in which the vehicle will leave the tile + * + * @param direction vehicle direction + * @param track vehicle track bits + * @return side of tile the vehicle will leave + */ +static inline DiagDirection VehicleExitDir(Direction direction, TrackBits track) +{ + static const TrackBits state_dir_table[DIAGDIR_END] = { TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER }; + + DiagDirection diagdir = DirToDiagDir(direction); + + /* Determine the diagonal direction in which we will exit this tile */ + if (!HasBit(direction, 0) && track != state_dir_table[diagdir]) { + diagdir = ChangeDiagDir(diagdir, DIAGDIRDIFF_90LEFT); + } + + return diagdir; +} + #endif /* TRACK_FUNC_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index bf4d8be7fb..b9f71ac7d0 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -69,27 +69,6 @@ bool IsValidImageIndex(uint8 image_index) return image_index < lengthof(_engine_sprite_base); } -/** - * Determine the side in which the train will leave the tile - * - * @param direction vehicle direction - * @param track vehicle track bits - * @return side of tile the train will leave - */ -static inline DiagDirection TrainExitDir(Direction direction, TrackBits track) -{ - static const TrackBits state_dir_table[DIAGDIR_END] = { TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER }; - - DiagDirection diagdir = DirToDiagDir(direction); - - /* Determine the diagonal direction in which we will exit this tile */ - if (!HasBit(direction, 0) && track != state_dir_table[diagdir]) { - diagdir = ChangeDiagDir(diagdir, DIAGDIRDIFF_90LEFT); - } - - return diagdir; -} - /** * Return the cargo weight multiplier to use for a rail vehicle @@ -469,7 +448,7 @@ int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *st if (TrainCanLeaveTile(front)) { /* Determine the non-diagonal direction in which we will exit this tile */ - DiagDirection dir = TrainExitDir(front->direction, front->track); + DiagDirection dir = VehicleExitDir(front->direction, front->track); /* Calculate next tile */ TileIndex tile = front->tile + TileOffsByDiagDir(dir); @@ -2213,9 +2192,9 @@ void ReverseTrainDirection(Train *v) return; } - /* TrainExitDir does not always produce the desired dir for depots and + /* VehicleExitDir does not always produce the desired dir for depots and * tunnels/bridges that is needed for UpdateSignalsOnSegment. */ - DiagDirection dir = TrainExitDir(v->direction, v->track); + DiagDirection dir = VehicleExitDir(v->direction, v->track); if (IsRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)) dir = INVALID_DIAGDIR; if (UpdateSignalsOnSegment(v->tile, dir, v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) { @@ -3634,7 +3613,7 @@ static Vehicle *CheckTrainAtSignal(Vehicle *v, void *data) /* not front engine of a train, inside wormhole or depot, crashed */ if (!t->IsFrontEngine() || !(t->track & TRACK_BIT_MASK)) return NULL; - if (t->cur_speed > 5 || TrainExitDir(t->direction, t->track) != exitdir) return NULL; + if (t->cur_speed > 5 || VehicleExitDir(t->direction, t->track) != exitdir) return NULL; return t; } @@ -4573,7 +4552,7 @@ static TileIndex TrainApproachingCrossingTile(const Train *v) if (!TrainCanLeaveTile(v)) return INVALID_TILE; - DiagDirection dir = TrainExitDir(v->direction, v->track); + DiagDirection dir = VehicleExitDir(v->direction, v->track); TileIndex tile = v->tile + TileOffsByDiagDir(dir); /* not a crossing || wrong axis || unusable rail (wrong type or owner) */ @@ -4606,7 +4585,7 @@ static bool TrainCheckIfLineEnds(Train *v, bool reverse) if (!TrainCanLeaveTile(v)) return true; /* Determine the non-diagonal direction in which we will exit this tile */ - DiagDirection dir = TrainExitDir(v->direction, v->track); + DiagDirection dir = VehicleExitDir(v->direction, v->track); /* Calculate next tile */ TileIndex tile = v->tile + TileOffsByDiagDir(dir); @@ -4689,7 +4668,7 @@ static bool TrainLocoHandler(Train *v, bool mode) /* Try to reserve a path when leaving the station as we * might not be marked as wanting a reservation, e.g. * when an overlength train gets turned around in a station. */ - DiagDirection dir = TrainExitDir(v->direction, v->track); + DiagDirection dir = VehicleExitDir(v->direction, v->track); if (IsRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)) dir = INVALID_DIAGDIR; if (UpdateSignalsOnSegment(v->tile, dir, v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) {