././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0914626 nml-0.7.6/0000755000175100001660000000000014754345610011726 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/LICENSE0000644000175100001660000004310414754345605012741 0ustar00runnerdocker GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/MANIFEST.in0000644000175100001660000000136414754345605013474 0ustar00runnerdocker# Exclude hidden files and directories global-exclude .* prune .* # Include documentation recursive-include docs *.html *.txt nmlc.1 nml.spec include LICENSE # Include regression tests recursive-include regression *.nml *.lng *.grf *.nfo *.png *.pcx include regression/Makefile include regression/beef.wav # But do not include files generated by regression tests prune regression/output prune regression/output2 prune regression/nml_output # Include (some) examples recursive-include examples *.nml *.lng *.png # include nml itself, including c-modules recursive-include nml *.py *.c # Building from a released tarball shouldn't try to update the version exclude nml/version_update.py # Include build files and main script file include Makefile nmlc ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/Makefile0000644000175100001660000000110614754345605013370 0ustar00runnerdockerMAKE?=make PYTHON?=/usr/bin/env python3 BLACK_OPTIONS=-l 120 --exclude 'action2var_variables.py|action3_callbacks.py|generated' .PHONY: regression test install extensions clean flake black regression: extensions $(MAKE) -C regression test: regression flake install: $(PYTHON) setup.py install extensions: $(PYTHON) setup.py build_ext --inplace clean: $(MAKE) -C regression clean # Clean extension put into root dir by --inplace rm -f *.so flake: $(PYTHON) -m black --check $(BLACK_OPTIONS) nml $(PYTHON) -m flake8 nml black: $(PYTHON) -m black $(BLACK_OPTIONS) nml ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0904627 nml-0.7.6/PKG-INFO0000644000175100001660000000230514754345610013023 0ustar00runnerdockerMetadata-Version: 2.2 Name: nml Version: 0.7.6 Summary: An OpenTTD NewGRF compiler for the nml language Home-page: https://github.com/OpenTTD/nml Author: NML Development Team Author-email: nml-team@openttdcoop.org License: GPL-2.0+ Classifier: Development Status :: 2 - Pre-Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Topic :: Software Development :: Compilers Requires-Python: >=3.5 License-File: LICENSE Requires-Dist: Pillow>=3.4 Requires-Dist: ply Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license Dynamic: requires-dist Dynamic: requires-python Dynamic: summary A tool to compile NewGRFs for OpenTTD from nml filesNML is a meta-language that aims to be a lot simpler to learn and use than nfo used traditionally to write NewGRFs. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/README.md0000644000175100001660000001434114754345605013214 0ustar00runnerdocker# NML NewGRF meta language NML is a a python-based compiler, capable of compiling NML files (along with their associated language, sound and graphic files) into grf and / or nfo files. The documentation about the language can be found on http://newgrf-specs.tt-wiki.net/wiki/NML:Main NML is licensed under the GNU General Public License version 2, or at your option, any later version. For more information, see [LICENSE](https://github.com/OpenTTD/nml/blob/master/LICENSE) (GPL version 2), or later versions at http://www.gnu.org/licenses/. ## Table of Contents 1. [Contact](#1-contact) 2. [Dependencies](#2-dependencies) * 2.1 [Required dependencies](#21-required-dependencies) * 2.2 [Optional dependencies](#22-optional-dependencies) 3. [Installation](#3-installation) 4. [Usage](#4-usage) 5. [Reporting bugs and contributing](#5-reporting-bugs-and-contributing) * 5.1 [Reporting bugs](#51-reporting-bugs) * 5.2 [Making an NML release](#52-making-an-nml-release) ## 1) Contact - [issue tracker / source repository](https://github.com/OpenTTD/nml) - IRC chat using #openttd on irc.oftc.net [more info about our irc channel](https://wiki.openttd.org/Irc) ## 2) Dependencies ### 2.1) Required dependencies NML requires the following 3rd party packages to run: - `python` Minimal version is 3.5. Python 2 is not supported. - `python image library` For install options see https://pillow.readthedocs.io/en/stable/installation.html Minimal version is 3.4. Older versions are not supported. - `ply` Downloadable from http://www.dabeaz.com/ply/ ### 2.2) Optional dependencies To install NML you'll need these 3rd party packages: - gcc (or possibly another c++ compiler). Needed to compile the cython version of the lz77 module for grf encoding. Running `make test` requires these code formatters and checkers. You don't need these to use NML, only to test or help improve it. - `flake8` Minimal version is 3.7. - `black` ## 3) Installation The easiest way to install NML is by using pip: ```bash pip3 install nml ``` In order to install NML from a source checkout run: ```bash python setup.py install ``` If you want to install the package manually copy 'nmlc' to any directory in your path and the directory 'nml' to any directory in your python path. ## 4) Usage Usage: nmlc [options] ``. Where `` is the nml file to parse. Options: ``` --version show program's version number and exit -h, --help show this help message and exit -d, --debug write the AST to stdout -s, --stack Dump stack when an error occurs --grf= write the resulting grf to --md5= Write an md5sum of the resulting grf to --nfo= write nfo output to -M output a rule suitable for make describing the graphics dependencies of the main grf file (requires input file or --grf) --MF= When used with -M, specifies a file to write the dependencies to --MT= target of the rule emitted by dependency generation (requires -M) -c crop extraneous transparent blue from real sprites -u save uncompressed data in the grf file --nml= write optimized nml to -o , --output= write output(nfo/grf) to -t , --custom-tags= Load custom tags from [default: custom_tags.txt] -l , --lang-dir= Load language files from directory [default: lang] --default-lang= The default language is stored in [default: english.lng] --start-sprite= Set the first sprite number to write (do not use except when you output nfo that you want to include in other files) -p , --palette= Force nml to use the palette [default: ANY]. Valid values are 'DOS', 'WIN', 'ANY' --quiet Disable all warnings. Errors will be printed normally. -n, --no-cache Disable caching of sprites in .cache[index] files, which may reduce compilation time. --cache-dir= Cache files are stored in directory [default: .nmlcache] --clear-orphaned Remove unused/orphaned items from cache files. --verbosity= Set the verbosity level for informational output. [default: 3, max: 4] ``` ## 5) Reporting bugs and contributing ### 5.1) Reporting bugs If you find any bugs with NML, please let us know via the [GitHub issue tracker](https://github.com/OpenTTD/nml/issues). Please make sure that you're using the latest available version before reporting a bug. You can check the [issue tracker](https://github.com/OpenTTD/nml/issues) to see if the bug you've found is already reported (or fixed!). If you have bug fixes or other patches for NML, please also share those with us via the [GitHub pull request page](https://github.com/OpenTTD/nml/pulls). ### 5.2) Making an NML release 1. Check that all relevant PRs are approved and merged. 2. Decide what the version number will be. 3. Update the changelog. This is done manually, and commits are grouped by type. The audience is NewGRF authors and downstream package maintainers, so don't list commits that don't affect those audiences. For large releases and/or if there are deprecations or nml syntax changes, provide more detailed release notes. Example: https://github.com/OpenTTD/nml/blob/master/docs/changelog.txt 4. Publish a new release using the release tool in the GitHub project: https://github.com/OpenTTD/nml/releases/new 5. GitHub Actions will build the release, publish to PyPI (the Python package index) and also to the GitHub release. 6. GitHub Actions will publish the Windows binary to the GitHub release. 7. (Optional) announce the release in places such as https://www.tt-forums.net/viewforum.php?f=68 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0424623 nml-0.7.6/docs/0000755000175100001660000000000014754345610012656 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/docs/changelog.txt0000644000175100001660000011065114754345605015356 0ustar00runnerdocker0.7.6 (2025-02-15) ------------------------------------------------------------------------ This release adds support for user defined constants, improves stations support, and reduces comsuption of limited ressources (registers and D0xx strings). Support for NewGRF additions of OpenTTD 15: - Add: Support for station property 1E (#333) - Change: Support for 8 bit station tile types. (#339) - Change: cargo class bits 13 and 14 now define potable and non-potable (OpenTTD #12979) (#343) - Change: add vehicle 'refit' callback (cb 0x163) and cargo class filter prop (0x28 for trains etc) (#344) - Fix: incorrect start values for roadstop var 43/44 (#349) - Add: Support for Action5 type 19 road waypoints (#351) Support for NewGRF additions of OpenTTD 14: - Add: Support for station variable 6B (#320) - Add: Constants and varact2 variable associated with road waypoints (#327) Other changes and fixes: - Add: 'const' keyword to allow users to define new constants (#302) - Change: reduce register consumption for spritelayouts (#306) - Codechange: Reduce register usage for BinOp (#308) - Change: Use extended Action1s (#309) - Fix #238: include information about relevant block in ScriptError message (#321) - Add: Basic range check for replace() (#324) - Change: Reduce D0xx usage where possible (#326) - Change: Unreachable range for default only switch should target CB_FAILED (#328) - Change: Remove road stops variable random_bits (#329) - Fix d4eb3ff: `nearby_tile_same_grf`, `nearby_tile_other_grf` and `nearby_tile_original_gfx` compared to wrong values (#330) - Fix 749aa73: station property 0A should be an extended byte (#334) - Add: Support for station properties 0E and 0F (station layouts) (#335) - Fix #337: Station 'availability' is a purchase callback (#338) - Fix #340: Always use raw strings for token regex (#341) - Add: --list-unused-strings to list the unused strings (#348) - Fix: Properly check number of varaction2 ranges (#352) - Fix #354: Improve length check for string literal properties (#355) 0.7.5 (2024-03-02) ------------------------------------------------------------------------ This release restores compatibility for stations in OpenTTD <14. Support for NewGRF additions of OpenTTD 14: - Change: add constants GROUNDSPRITE_ROAD_X and GROUNDSPRITE_ROAD_Y (#307) - Add: Support for build probability action CB 162 var10 0 (reverse rail vehicle). (#313) - Add: Support for faster ship speed 23 and acceleration 24 (#315) - Add: Support town_production_effect and town_production_multiplier. (#318) Other changes and fixes: - Fix: Register named parameters earlier (#301) - Fix a9a1a3e: Don't use station properties 1C/1D for IDs 00-FF (#305) - Change: Allow creating 32bpp-only NewGRFs (#314) - Add: --no-palette-validation option to skip palette validation for sprites if the grf author doesn't need it (#322) 0.7.4 (2023-06-28) ------------------------------------------------------------------------ This release adds support for more strings and sloped one-way road markers. Support for NewGRF additions of OpenTTD 14: - Change: Use station properties 1C and 1D for names - Change: extend DCxx string range up to FFFF Support for NewGRF additions of OpenTTD 13: - Add: Support for roadtype direction markings (#274) Other changes and fixes: - Fix dfb4499: incomplete read only checks (again) (#292) - Fix #295: Adjust version_openttd for OpenTTD >= 12.0. (#296) - Change: Use a single replacenew-type 'SIGNALS' instead of 3 different spellings of pre-signals, semaphores and path-signals. (#297) 0.7.3 (2023-05-21) ------------------------------------------------------------------------ This release adds support for more objects, stations and roadstops. Support for NewGRF additions of OpenTTD 14: - Change: Support extended object/station/roadstop limits. - Change: Write extended bytes in Action 3 if ID is >= 0xFF. - Change: Extend vehicle random bits to 16. (#288) - Add: Support for {FORCE} string command (#289) Other changes and fixes: - Add: STAT_ALL_TILES constant (for draw_pylon_tiles, hide_wire_tiles and non_traversable_tiles) (#283) - Add: --no-32bpp and --no-extra-zoom options to skip some alternative sprites (#286) 0.7.2 (2023-04-17) ------------------------------------------------------------------------ This release adds support for vehicle variants and the road stops. Support for NewGRF additions of OpenTTD 14: - Feature: Engine name callback - Add: Road stops (feature 0x14) (#279) Support for NewGRF additions of OpenTTD 13: - Add: 'tunnels' callback for road- and tramtypes (#273) - Feature: support extra_flags for vehicles - Feature: support for variant_group action 0 prop for vehicles Other changes and fixes: - Change: Optimise trivial right shift operations on variables - Change: Drop binary operations with constant 0 for more operators - Fix: By default there is no ELRL railtype. (#277) - Change: Don't output callback flag props that are zero. 0.7.1 (2022-12-03) ------------------------------------------------------------------------ While writing documentation for stations, we made some small changes to the syntax. Stations changes: - Codechange: always use intermediate registers for station sprite layouts - Change: allow any expression for station (purchase_)prepare_layout - Fix: Station animation triggers every 250 ticks, not 256. (#266) - Change: replace nearby_tile_platform_type with nearby_tile_tile_type. (#265) - Change: disabled_platforms/length is now bitmask(1-8) (#264) - Change: use an array of cargoes for cargo_random_triggers (#263) - Fix 16eb0035: disabled_platforms and disabled_length properties used a wrong number Other changes and fixes: - Fix #256: Objects have a 'colour' in var 47. (#267) - Fix: Versioning of NewGRF is supposed to start at 1. 0.7.0 (2022-08-29) ------------------------------------------------------------------------ This release adds the long awaited support for stations. Support for NewGRF additions of OpenTTD 13.0: - Add: Map seed in patch variable 0x17. Support for NewGRF additions of OpenTTD 1.11: - Change: provide nml vars for industry var 0x47 - GameScript control status Other changes and fixes: - Codechange: improve error reporting for procedure calls missing '()' (#257) - Add: show an error if a required property is not set for objects (feature 0F) (#236) - Fix: Reject empty arrays of expressions (#224) - Fix #209: Cargo 'profit' callback applied a unit conversion, when there was no unit. (#220) - Change: adjust sound name constants to match OpenTTD - Cleanup 8a03613: also remove now useless SHORTEN_TO_X_8 constants - Fix #253: Produce a more efficient NFO code for abs() builtin function (#255) - Add: Prevent side effects when ternary operator results are not read only (#247) - Change: Export keyword JSON for VSCode (#245) 0.6.1 (2021-09-15) ------------------------------------------------------------------------ Support for NewGRF additions of OpenTTD 12.0: - Update: Increase number of OTTD_GUI sprites to 191 (#253) 0.6.0 (2021-08-15) ------------------------------------------------------------------------ This release adds major enhancements to switches: - Switches can now be used as functions inside expressions - Switches can now define parameters, which are passed by callers for usage inside the switch - NML now applies optimisations to switches and chains of switches - optimisation and deprecation warnings can be suppressed with nmlc flags, see 'nmlc --help' (#230) Support for NewGRF additions of OpenTTD 12.0: - Add: rail vehicle property and callback 'curve_speed_mod' (rail vehicle property 0x2E) (#222) Support for NewGRF additions of OpenTTD 1.11: - Update: Increase number of OTTD_GUI sprites to 186 (#183) - Add: Industry spec_flag 'IND_FLAG_DO_NOT_CLAMP_PASSENGER_PRODUCTION' (#183) - Add: Vehicle variables 'tile_(supports|powers|is)_(rail|road|tram)type' (#183) - Add: Vehicle variable 'tile_has_catenary' (#183) - Add: General variable 'inflation', which reports the game setting (#183) - Add: Alternative string constants introduced in OpenTTD/OpenTTD#8392 (#176) Other changes and fixes: - Add: industry variable for 'town_index' (var 0x41) - Add: constants GROUNDSPRITE_GRASS, GROUNDSPRITE_DESERT_2_2, GROUNDSPRITE_GRASS_1_3, GROUNDSPRITE_GRASS_2_3, GROUNDSPRITE_GRASS_3_3 - Add: builtin functions for round() and sqrt() - Add: plural form 14 for Romanian - Change: Check that user code doesn't try to use reserved registers (#189) - Change: warn when a deprecated constant is used - Change: progress display shows input filename when reading files - Change: improve error message when invalid features are used - Add: More-obvious error for trailing '.' in a string id (#145) - Change: Clean up language definitions (#208) - Fix #184: Share townname bits when possible (#185) - Fix: Compatibility with Pillow 8.1.0 (#182) - Fix #180: No proper error message was given, if an unreferenced String was unable to allocate an id (#181) - Fix: Access to persistent-storage of towns (#173) - Fix: Don't suppress errors for incorrect `hide_sprite` values (#168) - Fix: Remove trailing whitespaces in NFO output (#164) - Fix: town_euclidean_dist was returning incorrect value (#206) - Fix: rename MAP_TYPE_RECTANGULAR to MAP_TYPE_SQUARE (#201) - Fix: LZ77 compatibility with Python 3.9+ (#215, #228) - Fix: access to persistent-storage of towns was broken (#173) - Fix: use most likely defined position when reporting error (#226) - Update: VS generation script syntax (#233) 0.5.3 (2020-09-15) ------------------------------------------------------------------------ This release primarily restores legacy industry properties and vars which were removed in NML 0.5.0. This is done for compatibility, so that older industry grfs can still be compiled with NML 0.5.3. See the NML 0.5.0 changelog entry for a list of the legacy industry properties and vars that were removed, and are restored in NML 0.5.3. A warning will be shown when these are used, and they may be removed again in some future version. - Change: Show more info in `nmlc --version - Change: Reintroduce industry property 0.4 syntax for compatibility - Fix: Rounding errors for some speed values (#147) 0.5.2 (2020-05-17) ------------------------------------------------------------------------ This release primarily fixes a spritelayout-related bug introduced in 0.5.1 that may affect many grfs. - Fix #140: non-advanced sprite layouts don't have flags - Fix: Make --cache-dir work - Fix #139: Don't create the cache directory if --no-cache is passed 0.5.1 (2020-05-10) ------------------------------------------------------------------------ This release fixes a number of issues reported in 0.5.0. Note: Although nmlc now permits up to 63 each of road and tramtypes, OpenTTD currently requires that the combined total not be more than 63 and that the same label not be used for both a roadtype and a tramtype. These limitations are not checked by nmlc. - Add: Builtin functions roadtype() and tramtype() - Fix: Allow calculations for palette in spritelayout - Fix #116: TownNames used bits could be the same for sub-parts (#117) - Fix #120: Value range of transported_last_month_pct should be 0..100 (#122) - Fix #121: STORE_PERM address limit should be 255, not 15 - Fix #134: Incorrect value of PALETTE_CC_RED - Fix #74: Update ottd_display_speed to match changes from OpenTTD r23945 (#75) - Fix: ANIM warnings in examples - Fix: Make the industry example GRF do something, rather than crashing - Fix: Correct limit of available road/tramtypes - Fix: Produce, TownNames & TracktypeTable debug_print (#126, #127, #129) - Fix: Don't attempt to determine version from git when building from a release tarball 0.5.0 (2020-04-26) ------------------------------------------------------------------------ NML 0.5.0 is a substantial release, covering multiple areas of the newgrf spec. Thanks to everyone who helped. Industries ---------- NOTE TO AUTHORS: NML 0.5.0 removes many industry properties and vars. Industry sets using these properties or vars will *not* compile with nml 0.5.0 and need to be updated. All industry vars removed in 0.5.0 are replaced by equivalent new vars. Production callback (produce block) format also changed so produce blocks will need to be updated. We don't break NML backwards compatibility lightly, but this change was necessary to support 16 accepted and 16 produced cargos. Thanks. - Support for up to 16 accepted cargos and 16 produced cargos (available in OpenTTD 1.9.0 onwards) - New industry properties and vars - cargo_types - incoming_cargo_waiting - produced_cargo_waiting - this_month_production - last_month_production - this_month_transported - last_month_transported - transported_last_month_pct - production_rate - Removed legacy industry properties and vars - accept_cargo_types - prod_cargo_types - prod_multiplier - input_multiplier_1 - input_multiplier_2 - input_multiplier_3 - production_rate_1 - production_rate_2 - waiting_cargo_1 - waiting_cargo_2 - waiting_cargo_3 - produced_cargo_waiting_1 - produced_cargo_waiting_2 - produced_this_month_1 - produced_this_month_2 - produced_last_month_1 - produced_last_month_2 - transported_this_month_1 - transported_this_month_2 - transported_last_month_1 - transported_last_month_2 - transported_last_month_pct_1 - transported_last_month_pct_2 - New approach to tile cargo acceptance - use INDTILE_FLAG_ACCEPT_ALL to accept all cargos industry accepts - or use up to 16 cargos with accepted_cargos property - Production callback format changed - see https://newgrf-specs.tt-wiki.net/wiki/NML:Produce Industry docs: https://newgrf-specs.tt-wiki.net/wiki/NML:Industries industry tile docs: https://newgrf-specs.tt-wiki.net/wiki/NML:IndustryTiles Houses ------ - Support for up to 16 accepted cargos at houses Houses docs: https://newgrf-specs.tt-wiki.net/wiki/NML:Houses Roadtypes and Tramtypes ----------------------- - Support for Roadtypes and Tramtypes (available in OpenTTD 1.10.0 onwards) Roadtypes docs; https://newgrf-specs.tt-wiki.net/wiki/NML:Roadtypes Tramtypes docs: https://newgrf-specs.tt-wiki.net/wiki/NML:Tramtypes Railtypes --------- - Increase railtype ID range from 16 to 64, to support 64 railtypes - Railtype flag RAILTYPE_FLAG_HIDDEN for hiding a railtype from player construction menu - Railtype flags RAILTYPE_FLAG_ALLOW_90DEG and RAILTYPE_FLAG_DISALLOW_90DEG for 90 degree curves - Railtype flags RAILTYPE_FLAG_HIDDEN and RAILTYPE_FLAG_PRECOMBINED for precombined spriteset type Railtypes docs: https://newgrf-specs.tt-wiki.net/wiki/NML:Railtypes Procedures ---------- - Support for use of switches and random switches as procedures, reducing repetition of switches There are no docs for this at the time of release. Other Changes ------------- - Feature: Add ANIM_TRIGGER_APT_AIRPLANE_LANDS (follows OpenTTD #7182) - Feature: Add station var nearby_tile_grfid (0x6A) - Feature: Add TTDPatchFlag 256_persistent_registers (0x80) - Feature: PUSH/POP_COLOUR codes for strings - Feature: Debug_print for GRF parameters - Feature: Allow PLY to generate parsing/lexing tables (improves performance); regeneration can be forced with nmlc -R - Feature: syntax highlighting support for Visual Studio Editor - Change: improved colouring of nmlc errors and warnings (where shell/terminal supports this) - Change: Make sprite encoder warnings more informative (#89) - Add: nmlc -D option to enable debug mode for parser - Fix #36: Global variable traffic_side was inconsistent with global parameter traffic_side - Fix #39: Add compatibility with >=pillow-7.0.0 - Fix #52: Compatibility with python 3.8 - Fix #57: GRF parameters could be given negative values - Fix: Add missing parameter in industry and airport tiles in RandomAction2 - Fix: Properly clean up resource handles (eliminates 'too many open files' error on macOS with larger GRFs) - Codechange: Update nml version detection to support Git using similar approach to OpenTTD - Doc: Update example train nml to demonstrate use of sprite stack and vehicle length - Doc/Fix: Update the stated version requirements 0.4.5 (2018-06-30) ------------------------------------------------------------------------ - Feature: increase cargo ID range from 32 to 64, to support 64 cargos in OpenTTD 11ab3c4ea2f6a6d29efda8c9ba2af04194621ea7 - Change: Support more DCxx strings for OpenTTD r27769. - Add: Properties for vehicle sprite stack. - Add: CB_FLAG_MORE_SPRITES, PALETTE_IDENTITY - Add: String command 9A 1E CARGO_NAME. - Add: CB_RESULT_IND_NO_TEXT_NO_AMOUNT - Codechange: Store textids without offset in string_ranges - Fix: String encoding with cases was non-deterministic and resulted in differing md5sums for the NewGRF. - Fix: Rearrange order of string id assignment to keep compatibility. - Fix: When some IDs depleted, an internal NML error was raised instead of the intended error message. - Fix: most_common_refit was reading the wrong bits in var 42; read the write bits and rename it to most_common_cargo_type as it's more accurate; also add most_common_cargo_subtype which also reads var 42 and was previously missing - Fix: Switch from :r format code to !r conversion flag to avoid crashes. - Update: Changelog 0.4.4 (2016-01-07) ------------------------------------------------------------------------ - Fix/Revert: Stripping one path level is not enough 0.4.3 (2016-01-07) ------------------------------------------------------------------------ - Add: prob_map_gen as alternative name for the mapgen industry probability property - Fix: Stripping one path level is enough - Fix: Compatibility with newer versions of pillow. - Fix: Vehicle 'sort' function caused internal error when an empty list was passed. - Fix: motion_counter is 24 bits, not 4. 0.4.2 (2015-09-13) ------------------------------------------------------------------------ -Add: New industry type limits of OpenTTD 1.6 -Fix: [CF] Build the version which is asked to be built instead of tip -Fix: Mark the cython acceleration module as optional. -Fix #7641: Sort gender and case translation tables deterministically (matthijs) -Fix #7640: Use dashes, not hyphens in manpage (matthijs) -Fix: getbits -Fix #7336: Action 6 offset was off by one for VA2 ranges when using a list of expressions in a switch. -Fix #7185: Incorrect Action6 offsets for Production Action2. -Doc: Be more verbose about MANIFEST.in and remove bootstrap from manifest as well 0.4.1 (2015-04-12) ------------------------------------------------------------------------ - Change: Try to improve packaging by applying some in-built automatisms via find_package() (oberhumer) (issue #7540) - Add: second_rocky_tileset - Add: Build-in function 'getbits' - Fix: Building source bundle was broken - Fix: Version identification for tags - Doc: Update readme with python version info - Cleanup: Remove pre-OpenTTD-1.1 wrappers for SHIFT_LEFT, SHIFT_RIGHT and SHIFTU_RIGHT. - Cleanup: Remove bootstrap 0.4.0 (2015-02-18) ------------------------------------------------------------------------ - Feature: [NewGRF] create_effect and effect_spawn_model - Feature: [NewGRF] EFFECT_SPRITE_NONE constant for create_effect callback - Feature: [NewGRF] support for OTTD_RECOLOUR action5 sprite(s) - Feature: [NewGRF] Support for Latin - Feature: [NewGRF] Variable to test for enabled wagon speed limits. (issue #6474) - Feature: Improve speed by caching position during parsing. - Feature: Warn about usage of animation and semi-transparent colours, and add spriteset flags to enable/disable the checks. (issue #1085) - Feature: Improved error position reporting with templated real sprites. Closes #7001 - Feature: Cython acceleration module for GRF compression. - Change: Convert from Python2 to Python 3.2+ - Change: At least check for isatty when using the funky colour codes in warning output (issue #5411) - Change: Do not store uncompressed sprites in the sprite cache. - Change: Use a separate spritecache file for each source image. - Change: Keep (possibly only temporarily) unused items in the spritecache until they are out-dated for sure. - Change: Reduce load on stdout by limiting incremental progress output to 1 message per second. - Change: Store amount of pure-white pixels in the spritecache instead of plain text warnings. - Change: Rewrite syntax file generators as python scripts - Change: [devzone] Don't run regressions in parallel to allow other tasks to run concurrently - Change: [devzone] Also update DevZone when we build tip but call it by its hash - Change: [devzone] Build the nml_lz77 extension for use by the CF - Add: Print progress information to interactive terminals. - Add: Progress output about lang files. - Add: Print statistics about used Action0 ids. - Add: Print statistics about used ActionF ids. - Add: Print statistics about used Spriteset ids. - Add: Print statistics about used Spritegroup ids. - Add: Print statistics about temporary Action2 registers. - Add: Print statistics about temporary ActionD registers and Action10 labels. - Add: Print statistics about GRF parameters. - Add: Print statistics about string ids. - Add: Print statistics about sound effect ids. - Add: Print statistics about cargo and railtype translation tables. - Add: Command line option to specify a cache directory. - Add: Command line option to set verbosity level of info output. - Add: Vebosity level 4 for printing CPU time on processing. - Add: Real sprites keep list of positions for improved error reporting with templates. - Add: Collect positions for real sprites through template instantiation. - Add: Build a position with an include stack from a list positions. - Fix: [NewGRF] Number of vehicles in var 41 is one-based, only var 40 is zero-based. - Fix: [NewGRF] Patch flags can only be accessed via action 7/9. (issue #6996) - Fix: [NewGRF] Action7/9 bit tests must use varsize 1. - Fix: Don't write parse tables. Closes #4091 - Fix: Printing Unicode characters in NFO was broken. Also added regression check for it. - Fix: CPP output line directive can have several flags, which caused matching failure. - Fix: Validate string names for being proper identifiers - Fix: Add the output palette to the spritecache key. (issue #6496) - Fix: Encode sprites sequentially per source image file. (issue #7004) - Fix: No proper error message was given, if an unreferenced Spritegroup was unable to allocate an id. - Fix: Don't fail if there's a .hg directory but no mercurial - Doc: main function, and instance variables of the parser and scanner. - Doc: RealSprite members - Doc: Unused return values. - Doc: instance variables of SpriteAction. 0.3.1 (2014-05-10) ------------------------------------------------------------------------ - Add: String commands CARGO_LONG, CARGO_SHORT and CARGO_TINY - Add: Object variable 'nearby_tile_object_view' - Add: Vehicle variable 'vehicle_is_unloading' - Add: House callback result 'CB_RESULT_HOUSE_NO_MORE_PRODUCTION' - Fix: Typo-fix in font_glpyh block name. - Fix: Error in raising an error in format_string - Fix: Don't catch more exception than expected, comment typo fixes, removed empty line - Fix: Report error from the spriteview position, instead of using a non-existing variable. - Fix: Do not report a position using a non-existing instance variable. - Fix: Don't crash on raising None after printing a warning. - Fix: Add also rpm spec file and syntax highlighting creation scripts to source distribution - Doc: Additional documentation, small fixes/improvments. - Change: Drop Python 2.5 support. 0.3.0 (2014-01-01) ------------------------------------------------------------------------ - Feature: use grf container format v2, nfo v32 and grf v8 - Feature: Support for alternate sprites, including 32bpp - Feature: Support for houses - Feature: Configurable volume for sound effects - Feature: Support pillow image library as well (Toshio Kuratomi) (issue #4799) - Feature: Remove the 64 kiB file size limit on sound effects - Feature: Vehicles: Deprecate property 'refittable_cargo_types' (issue #3583) - Feature: Vehicles: Add property 'default_cargo_type' for trains, RVs and ships (issue #3571) - Feature: Vehicle properties cargo_allow_refit and cargo_disallow_refit - Feature: Vehicles variables curvature info: 'curv_info', ' curv_info_prev_cur', 'curv_info_cur_next' and 'curv_info_prev_next' (variable 0x62) - Feature: Vehicles variables 'position_in_articulated_veh' and 'position_in_articulated_veh_from_end' (var 0x4D) - Feature: Canals variable 'dike_map' (variable 0x82) - Feature: Base-station variables for airports and stations - Feature: Station variables - Feature: Objects property 'count_per_map256' - Feature: General variables 'base_sprite_foundations' and 'base_sprite_shores' (variables 0x15 and 0x16) - Feature: Cargo property 'capacity multiplier' (property 0x1D) - Feature: Allow outputting multiple sprites per real sprite slot (issue #3712) - Feature: Labels for recolour sprites, as for real sprites - Feature: use language code instead of number as argument for ##grflangid pragma in language files (issue #3960) - Feature: Improved error output (issue #2929, #3814, #4736, #4299, #5411, #6209) - Feature: Unit conversions for non-constant values (issue #3828) - Feature: Builtin function format_string(format, args) (issue #4074) - Feature: Cache real sprites when building a GRF, to reduce compilation time - Feature: Allow using 'default: foo' in switch-blocks, or omitting the default altogether (issue #4186) - Feature: Check for identical language IDs in the different language files (issue #4997) - Feature: Verify whether translations use the plural form expected by their language - Feature: Consider file system paths case-insensitive at all systems (issue #5429) - Change: Vehicles: 'length' property and callback for trains and RVs (replaces 'shorten_vehicle') - Change: Vehicles: No longer set the default cargo automatically (issue #3571) - Change: Rework the RV speed property, so it works with non-constant values (issue #3828) - Change: Merge aircraft properties 'is_helicopter' and 'is_large' into a single common property 'aircraft_type' (issue #3700) - Change: Rename LOADINGSTAGE_XXX to LOADING_STAGE_XXX - Change: Canals: variable 'tile_height' now returns height in tiles - Change: Rename 'availability' callback to 'construction_probability' - Change: snowline_height is in tiles - Change: Output cropping debug output to stderr instead of stdout - Change: Unlink .grf file before write (issue #4165) - Change: Let item ID -1 mean 'use default ID' (as if no ID was specified) - Change: Don't always set the same property value for all tiles, only do so when appropriate - Change: Allocate parameters starting at 127 instead of 64 (issue #4222) - Change: improve optimization for expressions containing comparison operators by marking them as "return bool" - Change: Modify version output to always give the version and revision, for both, releases and nightlies - Change: delay changing not into xor with 1 to the last moment, it's not efficient when doing the computations via actionD, only for action2 - Change: use the location of the version_info.py file instead of the current repo's version (issue #5513) - Feature: Maintenance cost properties for airports and railtypes - Feature: New base costs for property maintenance - Add: max_height_level variable - Add: a few constants for generic groundsprites in the baseset - Add: new constant CB_RESULT_NO_TEXT for use in cargo_subtype_(text/display) - Add: Alternative railtype label list (issue #3459) - Add: New property 'value_function' for action 0 properties - Add: Command-line parameter '--no-cache' (-n) to disable caching of real sprites. Enable this parameter in the regression makefile for the NML-output to NFO/GRF compilation step - Add: Command-line parameter '--quiet' to disable all warnings (issue #3106) - Add: Command-line parameter '--md5' to write md5sum of grf to another file (issue #3732) - Add: NML output for item size and sprite layout parameters - Add: Language files: ##map_case pragma similar to ##map_gender - Add: TILE_CLASS_VOID to allow checking for map border and to have a constant when using nearby_tile_class on it - Add: Scripts to create syntax highlighting file for kate and notepad++ - Add: misc object flag 'OBJ_FLAG_SCALE_BY_WATER' to influence amount of objects placed on maps - Add: Support for Scottish Gaelic - Fix: hardcoded path in .devzone/build/files caused errors with new CF (issue #3267) - Fix: proper error message when input nml is not utf-8 encoded (issue #3233) - Fix: also use the filename in error messages if it isn't preprocessed by gcc but directly supplied as input file - Fix: provide proper error message when running out of parameter or label numbers - Fix: it was not possible to use constants in the -part of an item-block - Fix: Rename all occurences of 'base_sprites' to 'base_graphics', to avoid possible confusion such as wrong NML output - Fix: Real sprite lists may contain unexpanded templates, so comparing their lengths makes no sense - Fix: only reduce start_id argument to replace-block during pre-process stage (issue #3744) - Fix: with statement needs import from future for python 2.5 - Fix: Fix tile compression to remove some useless (for grfv2) code and add a missing check for the chunk length (issue #3785) - Fix: Sprites with long format (!= long chunks) tile compression had incorrect offsets - Fix: some string industry properties didn't accept a string as value - Fix: do palette conversion before putting the sprite data in a tuple - Fix: ind. tile 'autoslope' cb num was set to 0x3B instead of 0x3C - Fix: compatibility with python 2.5 (issue #3998) - Fix: provide file/line information when detecting an error in a string even when in a later stage (issue #3898) - Fix: Backslash-escapes in strings weren't properly validated. Also remove useless \n escape (issue #3636) - Fix: Proper handling of failed callbacks (issue #2933) - Fix: Provide a proper error message if a substring is missing, instead of an assertion error (also issue #3674) (issue #3932) - Fix: Use translations for statically included string parameters if possible, even if the base string is not translated (issue #3642) - Fix: Proper error message when running out of switch registers (issue #3082) - Fix: Don't use a string instead of a position object (issue #4063) - Fix: We should always round floats, but not try to round everything else - Fix: refit_cost callback may also be called from the purchase menu - Fix: Publishing one of the generated grfs is enough. Especially if they would overwrite eachother on publishing - Fix: Make ActionA work for more than 255 consecutive sprites - Fix: applying operator ! to a constant number didn't work (issue #4458) - Fix: Missing range check for callback results could cause assertions (issue #4769) - Fix: Cache white pixel messages (issue #4760) - Fix: Random switch interdependencies were messed up (issue #4742) - Fix: Rail type and snowline table action6 offsets weren't updated when changing ID to an extended byte (issue #4787) - Fix: Incorrect action 6 offset in random action 2 (issue #4730) - Fix: Use the same (decimal) numbering scheme for plurals in language files as OpenTTD (issue #4811) - Fix: cargo_age_period is property 2B, not 28. A typo caused CB36 to fail - Fix: Position information for errors regarding sound file includes was missing / broken (issue #4850) - Fix: Give the expected default lang file name in case it wasn't found - Fix: Road vehicle speed was incorrectly set for vehicles faster than 70mph (issue #5336) - Fix: Object variable 'company colour' returned faulty values (issue #5624) - Fix: Snowline code still generated GRFv7 output (issue #5609) - Fix: Town name parts could end up with more than 255 entries - Fix: Also add offset when skipping fixed rail types - Fix: Don't loop forever on / paths (issue #6209) - Fix: adjust_value had tendency to take the value higher than the wanted - Fix: aircraft speed property conversion didn't do the conversion from (issue #4667) - Fix: For houses without any callbacks a dummy VA2 with zero cases was created, which unintentionally returned a computed value (issue #5294) - Fix: P string command shall default to the previous parameter (issue #6503) - Doc: add abstract base class to document Action0Property and relatives - Doc: RPM build spec - Doc: why the default language is processed twice - Doc: Add some code documentation to the grfstrings.py file - Remove: Ability to save 32bpp sprites as pngs, and lots of other stuff that won't be needed anymore - Remove: Deprecated refittable_cargo_types property. Instead zero it upon writing a different refit property, to avoid possible conflicts with other grfs (issue #3583) - Remove: Unused command-line parameter 'sprites-dir.' - Remove: Cargo properties single_unit_text and multiple_units_text 0.2.5 (2014-01-01) ------------------------------------------------------------------------ - Feature: Support pillow image library as well (Toshio Kuratomi) (issue #4799) - Change: Modify version output to always give the version and revision, for both, releases and nightlies - Add: New GUI sprite in OpenTTD r24749, r25293, r25344 and r25916 - Fix: Use the correct version_info.py file instead of the current repo's version (issue #5513) - Fix: Town name parts could end up with more than 255 entries - Fix: Also add offset when skipping fixed rail types - Fix: adjust_value had tendency to take the value higher than the wanted value - Fix: aircraft speed property conversion didn't do the conversion from (issue #4667) 0.2.4 (2012-10-14) ------------------------------------------------------------------------ - Feature: Report NML line information as well as pixel position for pure white pixels. Also, report number of pixels in the sprite, instead of the whole image (issue #4029) - Feature: 'signals' callback for railtypes - Feature: Allow the 'nfo' unit to be used with non-constant values (issue #3828) - Feature: 'build_prod_change' callback for industries to set industry production level on construction - Feature: Constant CB_RESULT_REFIT_COST_MASK - Feature: Vehicle misc_flag VEHTYPE_FLAG_NO_BREAKDOWN_SMOKE - Feature: 'current_max_speed' variable for vehicles (issue #3979) - Feature: 'vehicle_is_in_depot' variable for aircraft - Feature: 'range' property and callback for aircraft - Feature: 'production_rate_1/2' variables for industries - Feature: 'town_zone' variable for railtypes - Feature: 'other_veh_(curv_info|is_hidden|x_offset|y_offset|z_offset)' variables for vehicles - Fix: Provide a proper error message when running out of action2 IDs - Fix: A '{' at the end of a string could cause a crash - Fix: Backslash-escapes in strings weren't properly validated. Also remove useless \n escape (issue #3636) - Fix: Provide a proper error message if a substring is missing, instead of an assertion error (issue #3932) - Fix: 'refit_cost' callback may also be called from the purchase menu - Fix: Allow using constants > 255 as variable parameter (issue #4086) - Fix: Sprite layout register code contained an unsorted iteration over dictionary keys, resulting in possible regression failures 0.2.3 (2012-02-19) ------------------------------------------------------------------------ - Feature: Action5 for tunnel portals - Fix: Properly catch out-of-bounds image reads (issue #3666) - Fix: Character code 0xA0 (NBSP) is used for an up arrow in TTD, so don't write it as ascii. Force unicode instead (issue #3643) 0.2.2 (2012-01-29) ------------------------------------------------------------------------ - Feature: support for (optional) url-information in the grf-block - Feature: names for newly defined cargo classes: CC_POWDERIZED and CC_NEO_BULK - Feature: clean target to Makefile in main dir and let make clean remove regression/parsetab.py - Fix: don't crash when a line in custom_tags.txt is missing a colon delimiter - Fix: each action4 is limited to 255 strings. Write multiple actions when we have more than that - Fix: groff warning about manpage - Fix: include buildout.cfg in src distribution and prune regression/nml_output/ (issue #3490) - Fix: Add NML output for replacenew-block (issue #3450) - Fix: include nmlc script so regression runs out-of-the-box for source package - Fix: several files from examples/ were missing in source distributions - Fix: 'make install' was broken - Doc: add notice about ZPL in readme.txt 0.2.1 (2011-12-18) ------------------------------------------------------------------------ - Feature: CB_RESULT_NO_MORE_ARTICULATED_PARTS, CB_RESULT_REVERSED_VEHICLE and CB_RESULT_NO_TEXT as constants to make porting projects to NML 0.3 easier. - Fix: Internal error when the ID in a replace-block was not a compile-time constant - Fix: Crash when referencing a SpriteSetCollection in a graphics-block that was inside an if-block - Fix: Text files in docs/ were not included in source package - Doc: Add GPL v2 header to all .py files 0.2.0 (2011-11-20) ------------------------------------------------------------------------ No changelog available for 0.2.0 and earlier ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/docs/index.html0000644000175100001660000000123414754345605014657 0ustar00runnerdocker NML documentation

The NML documentation has been moved to the following location: http://newgrf-specs.tt-wiki.net/wiki/NML:Main.

You will be redirected in a few seconds, click the link if this doesn't happen (most likely because meta refresh is disabled in your browser).

././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/docs/nml.spec0000644000175100001660000000433214754345605014326 0ustar00runnerdocker# # spec file for package nml # # Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed # upon. The license for this file, and modifications and additions to the # file, is the same license as for the pristine package itself (unless the # license for the pristine package is not an Open Source License, in which # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. # Please submit bugfixes or comments via http://bugs.opensuse.org/ # Name: nml Version: 0.4.3 Release: 0 Summary: NewGRF Meta Language License: GPL-2.0+ Group: Development/Tools/Building Url: http://dev.openttdcoop.org/projects/nml Source0: http://bundles.openttdcoop.org/nml/releases/%{version}/%{name}-%{version}.src.tar.gz BuildRequires: python-devel >= 3.2 BuildRequires: python-setuptools #We need for regression test the required packages also on building: BuildRequires: python-imaging BuildRequires: python-ply Requires: python-imaging Requires: python-ply Provides: nmlc = %{version} BuildRoot: %{_tmppath}/%{name}-%{version}-build %if 0%{?suse_version} && 0%{?suse_version} <= 1110 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %else BuildArch: noarch %endif %description A tool to compile nml files to grf or nfo files, making newgrf coding easier. %prep %setup -q %build python setup.py build %install python setup.py install --skip-build --root=%{buildroot} --prefix=%{_prefix} install -D -m0644 docs/nmlc.1 %{buildroot}%{_mandir}/man1/nmlc.1 #setuptools should not be a requirement on running, so we install the nmlc wrapper from source install -m0755 nmlc %{buildroot}%{_bindir}/nmlc %check cd regression PYTHONPATH=%{buildroot}%{python_sitelib} make _V= NMLC=%{buildroot}%{_bindir}/nmlc %files %defattr(-,root,root,-) %doc docs/*.txt %{_bindir}/nmlc %{_mandir}/man1/nmlc.1* %{python_sitelib}/* %changelog ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/docs/nmlc.10000644000175100001660000000536214754345605013703 0ustar00runnerdocker.Dd January 07, 2016 .Dt NMLC 1 .Sh NAME .Nm NMLC .Nd A compiler from NML code to NFO and/or GRF files. .Sh SYNOPSIS .Nm nmlc .Op options .Op file .Sh OPTIONS .Bl -tag .It Fl c Crop extraneous transparent blue from real sprites. .It Fl u Save real sprites uncompressed to GRF files. This saves a lot of time during encoding but it's not recommended when creating a file for distribution since it makes the output file substantially bigger. .It Fl \-grf Ns = Ns Ar file Write output in GRF format to . .It Fl \-nfo Ns = Ns Ar file Write output in NFO format to . .It Fl \-nml Ns = Ns Ar file Write output in NML format to . .It Fl \-output Ns = Ns Ar file | Fl o Ar file Write output to . The output type is detected from the extension of the filename. It must be one of nfo, nml or grf. .It Fl \-md5 Ns = Ns Ar file Write an md5sum of the resulting grf to . .It Fl \-debug | Fl d Print a dump of the AST to stdout. .It Fl \-version Print programme's version number and exit. .It Fl \-help | Fl h Print usage information. .It Fl \-stack | Fl s Dump stack when an error occurs. .It Fl M Output a rule suitable for make describing the graphics dependencies of the main grf file (requires input file or \-\-grf) .It Fl \-MF Ns = Ns Ar file When used with \-M, specifies a file to write the dependencies to .It Fl \-MT Ns = Ns Ar file Target of the rule emitted by dependency generation (requires \-M) .It Fl \-custom\-tags Ns = Ns Ar file | Fl t Ar file Load custom tags from [default: custom_tags.txt]. .It Fl \-lang-dir Ns = Ns Ar dir | Fl l Ar dir Load language files from directory [default: lang]. .It Fl \-default\-lang Ns = Ns Ar file The default language is stored in [default: english.lng]. .It Fl \-sprites\-dir Ns = Ns Ar dir | Fl a Ar dir Store 32bpp sprites in directory [default: sprites]. .It Fl \-start\-sprite Ns = Ns Ar num Set the first sprite number to write (do not use except when you output nfo that you want to include in other files). .It Fl \-palette Ns = Ns Ar palette | Fl p Ar palette Force nml to use the palette [default: ANY]. Valid values are 'DOS', 'WIN', 'ANY'. .It Fl \-quiet Disable all warnings. Errors will be printed normally. .It Fl \-cache\-dir Ns = Ns Ar dir Cache files are stored in directory [default: .nmlcache]. .It Fl \-clear\-orphaned Remove unused / orphaned items from cache files. .It Fl \-verbosity Ns = Ns Ar level Set the verbosity level for informational output [default: 3, max: 4]. .El .Sh SEE ALSO The language reference at .Pa http://newgrf\-specs.tt\-wiki.net/wiki/NML:Main .Sh AUTHOR NML was written by Albert Hofkamp, Christoph Elsenhans, Jasper Reichardt, Ingo von Borstel, José Soler and Thijs Marinussen. .Pp This manual page was originally written by Thijs Marinussen. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0394623 nml-0.7.6/examples/0000755000175100001660000000000014754345610013544 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0434623 nml-0.7.6/examples/industry/0000755000175100001660000000000014754345610015425 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/industry/example_industry.nml0000644000175100001660000001060514754345605021537 0ustar00runnerdocker/* * This file is aimed to provide an example on how to code a basic industry in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. * * This version shows only how to modify a built-in industry. * * Apart from this file, you will also need the following * - Language files, to be placed in the 'lang' folder. * Currently english.lng is supplied. */ /********************************************** * Header, containing some general stuff: **********************************************/ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the third real grf defined as part of * NML, therefore the last character is set to 2. Successive grfs will * have 3, 4, etc. there, to make sure each example grf has a unique GRFID. */ grfid : "NML\04"; name : string(STR_GRF_NAME); desc : string(STR_GRF_DESCRIPTION); /* This is the first version, start numbering at 1. */ version: 1; min_compatible_version: 1; } /* this example assumes we're just matching to the default temperate cargos, this wouldn't be the usual case */ cargotable { PASS, COAL, MAIL, OIL_, LVST, GOOD, GRAI, WOOD, IORE, STEL, VALU } /* * This example extends the cargos accepted and produce by the default temperate factory. */ produce(consume_all_prod, // Consume the full amount of all waiting cargos (stored by factory_production_switch below). [COAL: LOAD_TEMP(1); IORE: LOAD_TEMP(2); GRAI: LOAD_TEMP(3);], // Produce steel from coal and ore, goods from grain. [STEL: LOAD_TEMP(1) / 2 + LOAD_TEMP(2); GOOD: LOAD_TEMP(3) * 2;], 0 ) produce(do_nothing_prod, [], []) // Make the factory produce cargo only when coal is delivered. // Other cargos will be stockpiled until then. switch(FEAT_INDUSTRIES, SELF, factory_production_switch, [ STORE_TEMP(incoming_cargo_waiting("COAL"), 1), STORE_TEMP(incoming_cargo_waiting("IORE"), 2), STORE_TEMP(incoming_cargo_waiting("GRAI"), 3), incoming_cargo_waiting("COAL") ]) { // If no coal, don't produce anything. 0: do_nothing_prod; // Otherwise consume all waiting cargo. consume_all_prod; } switch(FEAT_INDUSTRIES, SELF, extra_text_switch, // Put the production amount on the textstack for use in a string parameter. // See https://newgrf-specs.tt-wiki.net/wiki/NML:Language_files#String_parameters [STORE_TEMP(this_month_production("GOOD"), 256)]) { return string(STR_INDUSTRY_EXTRA_TEXT); } item(FEAT_INDUSTRIES, factory) { property { substitute: INDUSTRYTYPE_TEMPERATE_FACTORY; override: INDUSTRYTYPE_TEMPERATE_FACTORY; cargo_types: [ accept_cargo("COAL"), accept_cargo("IORE"), accept_cargo("GRAI"), // Output production amount by callback above, but must be listed here. produce_cargo("STEL", 0), produce_cargo("GOOD", 0) ]; } graphics { produce_cargo_arrival: factory_production_switch; extra_text_industry: extra_text_switch; } } item(FEAT_INDUSTRYTILES, factory_tile_1) { property { substitute: 39; override: 39; special_flags: bitmask(INDTILE_FLAG_ACCEPT_ALL); } } item(FEAT_INDUSTRYTILES, factory_tile_2) { property { substitute: 40; override: 40; special_flags: bitmask(INDTILE_FLAG_ACCEPT_ALL); } } item(FEAT_INDUSTRYTILES, factory_tile_3) { property { substitute: 41; override: 41; special_flags: bitmask(INDTILE_FLAG_ACCEPT_ALL); } } item(FEAT_INDUSTRYTILES, factory_tile_4) { property { substitute: 42; override: 42; special_flags: bitmask(INDTILE_FLAG_ACCEPT_ALL); } } /* * This example causes the default farm to produce livestock, grain, and wood. */ item(FEAT_INDUSTRIES, farm) { property { substitute: INDUSTRYTYPE_TEMPERATE_ARCTIC_FARM; override: INDUSTRYTYPE_TEMPERATE_ARCTIC_FARM; cargo_types: [ produce_cargo("LVST", 8), produce_cargo("GRAI", 12), produce_cargo("WOOD", 4), ]; } } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0434623 nml-0.7.6/examples/industry/lang/0000755000175100001660000000000014754345610016346 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/industry/lang/english.lng0000644000175100001660000000066514754345605020514 0ustar00runnerdocker##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Industry STR_GRF_DESCRIPTION :{ORANGE}NML Example NewGRF: Industry{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML. STR_INDUSTRY_EXTRA_TEXT :{}Goods produced this month: {YELLOW}{SIGNED_WORD} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0434623 nml-0.7.6/examples/object/0000755000175100001660000000000014754345610015012 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/object/cc_grid.png0000644000175100001660000001276314754345605017127 0ustar00runnerdockerPNG  IHDR1KzTXtRaw profile type exifxڭm$)Ds98|YU=3f[QIpwObk^ .Z|=?ۣGP_mi0?nxFC{#}PɸSy/uQ{Ny{Ys9|(J[E< ^>y)ךSx;s_UO4_{F ƈ~?ypHc_A>gsQ*ַb@ r/ڞs8ₜW+$rB*iN"".YdI~Z6.1# .xșa%=VjZ;;3>Y{/XKoaγO fKk'z91) 2r[*E.s؅P!H܄.cPX<oA?MsR`.@ݯ}z{e4fo&ۇrWYϚl@3u %ٽE*'I6}3n \;>[ϵg̒!mvg20aߛaCSÜulrt[\h_-_SgoIpsWGf_=ԥv0i5Dž=טkٷy}UIeˬ$4bV'Mkȍ jx~Z6HQ@hxG3IcowNJpΖ`^L3S"2") >N ^M8K%MRfd$7u Br|_ֶ6d\);WPV.w{8O'/ bvEj3M*_-'2{ (ڗ: xEW6^q2;r*Urw,4KՄ!dK 밋8Ͱ8?mZEx~!{'fmINk\6 y hj6S<?Jr01HOc$pu3}fHYXxbԴ#7 48-DWZo=z%=!i RVH&9<:Ny/ OZժѾNg5dIA(:X&)vcG>ݖW-.gv3it]uIjD\;B 1n* N(C,AQ 9On PM||Q#N֯y`HzO;W@nګ58B'l)v \?I6н \\75e ٔ]OS3,{ tz5q pp ({ͻC{rThPLTE 000@@@PPPdddttt44!Mx }{&9z#m:3(A5q|5ztrmht}9,_h3 toQzU|5zP?ę]kjCM'9mv|=rf1ή]ӻV һQN]?|R3L̓/QE{ݛO%vA:t9Qhi4ՆL`%6?Ho{GdcT+ ,jG'׆Fk5 to{ޯa;03vT]|oN}m@|@斾V! \]N .i|}SmX|ho{H_w/ӻ~D [K; ?NZ}O=Q''bLE_7waFx Єp{A >{yn6<֓4פob|C /ȵwIhx ]'8|g=>As{&74P⼁˜EFwOAV;`(>4^]>J9 d!V]AzֹyZsy7 _TG%_mjjb{ Jd ~M}87^+GSYPDB({O|~'4S=LU'PwʣC{#-.LJ: Lf'P>]$ֳ .yd3ѻ . NB'6l( R? *wV~=:s&=㈔2jho2gz s-s&m~F_tdEW !އFoٲeMY_O{e˖oCp2BFIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/object/example_object.nml0000644000175100001660000001410514754345605020510 0ustar00runnerdocker/* * This file is aimed to provide an example on how to code an Object in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. * * The NewGRF implements a replacement for the company land as NewObject which does * not show a sign but blends in with the terrain naturally. In transparent view * it shows a company-coloured border around the tiles. * * Apart from this file, you will also need the following * - Graphics, found in cc_grid.png (in the same folder) * - Language files, to be placed in the 'lang' folder. * Currently english.lng is supplied. */ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the second real grf defined as part of * NML (the first is the train example), therefore the last character is * set to 1. Successive grfs will have 2, 3, etc. there, to make sure each * example grf has a unique GRFID. */ grfid: "NML\01"; /* GRF name and description strings are defined in the lang files */ name: string(STR_GRF_NAME); desc: string(STR_GRF_DESCRIPTION); /* This is the first version, start numbering at 1. */ version: 1; min_compatible_version: 1; /* This NewGRF has no parameters. See the train example NewGRF for parameter * usage */ } /* Using parametrized sprite layouts are only valid in OpenTTD r22723 or later. * Earlier versions will choke on those and otherwise disable the NewGRF. */ if (version_openttd(1,2,0,22723) > openttd_version) { error(FATAL, REQUIRES_OPENTTD, string(STR_VERSION_22723)); } // Template for 19 sprites: one for each possible tile slope template tmpl_groundsprites(x, y) { [ 0+x, y, 64, 31, -31, 0 ] [ 80+x, y, 64, 31, -31, 0 ] [ 160+x, y, 64, 23, -31, 0 ] [ 240+x, y, 64, 23, -31, 0 ] [ 320+x, y, 64, 31, -31, 0 ] [ 398+x, y, 64, 31, -31, 0 ] [ 478+x, y, 64, 23, -31, 0 ] [ 558+x, y, 64, 23, -31, 0 ] [ 638+x, y, 64, 39, -31, -8 ] [ 718+x, y, 64, 39, -31, -8 ] [ 798+x, y, 64, 31, -31, -8 ] [ 878+x, y, 64, 31, -31, -8 ] [ 958+x, y, 64, 39, -31, -8 ] [1038+x, y, 64, 39, -31, -8 ] [1118+x, y, 64, 31, -31, -8 ] [1196+x, y, 64, 47, -31,-16 ] [1276+x, y, 64, 15, -31, 0 ] [1356+x, y, 64, 31, -31, -8 ] [1436+x, y, 64, 31, -31, -8 ] } /* Spriteset of the 19 possible landslopes with company-coloured grid */ spriteset (cc_frame, "cc_grid.png") { tmpl_groundsprites(1, 1) } spritelayout company_land_layout { ground { /* normal ground sprite - always draw */ sprite: LOAD_TEMP(0) + LOAD_TEMP(1); } childsprite { /* company-coloured border - always draw */ sprite: cc_frame(LOAD_TEMP(0)); always_draw: 1; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } childsprite { /* again the normal ground sprite. Thus in non-transparent view * only the normal ground sprite is shown. In transparent view * this acts as sprite which darkens the other two sprites via * a translation to transparency. */ sprite: LOAD_TEMP(0) + LOAD_TEMP(1); } } /* A pseudo-switch which sets the temporary parameters for the sprite layout */ switch (FEAT_OBJECTS, SELF, company_land_terrain_switch, [ /* We store the offset into the spriteset due to the tile slope into the 1st temporary variable * (= storage register 0) */ STORE_TEMP(slope_to_sprite_offset(tile_slope), 0), /* We store the offset to the flat groundsprite we use into the 2nd temporary variable * (= storage register 1) */ STORE_TEMP(GROUNDSPRITE_NORMAL, 1), STORE_TEMP(terrain_type == TILETYPE_DESERT ? GROUNDSPRITE_DESERT : LOAD_TEMP(1), 1), STORE_TEMP(terrain_type == TILETYPE_SNOW ? GROUNDSPRITE_SNOW : LOAD_TEMP(1), 1), ]) { company_land_layout; } /* Pseudo switch for the purchase list branch: we want to display the flat ground tile */ switch (FEAT_OBJECTS, SELF, company_land_purchase_switch, [ STORE_TEMP(0, 0), STORE_TEMP(GROUNDSPRITE_NORMAL, 1), 1 ]) { company_land_layout; } /* Define the object itself */ item(FEAT_OBJECTS, company_land) { property { /* The class allows to sort objects into categories. This is 'infrastructure' */ class: "INFR"; /* If no other NewGRF provides this class before us, we have to name it */ classname: string(STR_NAME_OBJCLASS_INFRASTRUCTURE); /* Name of this particular object */ name: string(STR_NAME_COMPANY_LAND); climates_available: ALL_CLIMATES; size: [1, 1]; build_cost_multiplier: 1; remove_cost_multiplier: 1; introduction_date: date(1,1,1); // available from day 1 end_of_life_date: date(10000,1,1); // available till year 10000 /* Anything can overbuild the object, removing returns the money, we don't want foundations and we want to allow bridges */ object_flags: bitmask(OBJ_FLAG_ANYTHING_REMOVE, OBJ_FLAG_REMOVE_IS_INCOME, OBJ_FLAG_NO_FOUNDATIONS, OBJ_FLAG_ALLOW_BRIDGE); height: 0; // it's only a ground tile num_views: 1; } graphics { purchase: company_land_purchase_switch; // Allow placement on any land tile (the default prevents building on 'steep' slopes). // The object cannot be placed on water despite this, because OBJ_FLAG_ON_WATER isn't set. tile_check: return CB_RESULT_LOCATION_ALLOW; additional_text: return string(STR_NAME_COMPANY_LAND); company_land_terrain_switch; } } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0434623 nml-0.7.6/examples/object/lang/0000755000175100001660000000000014754345610015733 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/object/lang/english.lng0000644000175100001660000000131414754345605020071 0ustar00runnerdocker##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Object STR_GRF_DESCRIPTION :{ORANGE}NML Example NewGRF: Object{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}planetmaker, {BLACK}coding by {SILVER}planetmaker.{}{BLACK}This NewGRF defines a tile which can act as company-land replacement. STR_VERSION_22723 :1.2.0 (r22723) STR_NAME_OBJCLASS_INFRASTRUCTURE :Infrastructure STR_NAME_COMPANY_LAND :Company land ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0434623 nml-0.7.6/examples/railtype/0000755000175100001660000000000014754345610015375 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/example_railtype.nml0000644000175100001660000002710214754345605021457 0ustar00runnerdocker/* * This file is aimed to provide an example on how to code a railtype in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. * * The NewGRF implements a graphical replacement for the normal and electric * rails. Since almost all sprites (except caternary) are supplied, you can * use this grf to see in detail what sprites are needed and in what order. * * Essentially this is a cut-down version of the Swedish Rails grf, drawn by * Irwe and coded by planetmaker. Support for parameters, time-dependent * graphics and snow support has been removed to keep this example within * reasonable size. Due to the large quantity of sprites required for a * railtype grf, the number of lines of code is still relatively high. * * All real sprites have been templated, even if the template is used only * once. This allows adding e.g. snowed graphics fairly easily. * * Apart from this file, you will also need the following * - Graphics, found in in the gfx folder * - Language files, to be placed in the 'lang' folder. * Currently english.lng is supplied. */ /********************************************** * Header, containing some general stuff: **********************************************/ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the third real grf defined as part of * NML, therefore the last character is set to 2. Successive grfs will * have 3, 4, etc. there, to make sure each example grf has a unique GRFID. */ grfid : "NML\02"; name : string(STR_GRF_NAME); desc : string(STR_GRF_DESCRIPTION); /* This is the first version, start numbering at 1. */ version: 1; min_compatible_version: 1; } /* Check for NuTracks and disable, if we're not active _after_ NuTracks */ if (!grf_order_behind("DJT\01")) { error(FATAL, MUST_LOAD_AFTER, "NuTracks"); } /* Default ground tile template (re-use as needed) */ template ground_tile(x, y) { [x, y, 64, 31, -31, 0] } /********************************************** * Track underlays (tracks + ballast): **********************************************/ /* Underlays (single track bits with ballast)\ * Used for bridge surfaces also, therefore the template is split */ template tmpl_underlay_straight() { ground_tile(75, 0) ground_tile( 0, 0) } template tmpl_underlay_slope() { [ 75, 40, 64,39, -31, -8] [150, 40, 64,23, -31, 0] [225, 40, 64,23, -31, 0] [300, 40, 64,39, -30, -9] } template tmpl_underlay_diagonal() { ground_tile(150, 0) ground_tile(225, 0) ground_tile( 0, 40) ground_tile(300, 0) } template tmpl_underlay_railtypes() { tmpl_underlay_straight() tmpl_underlay_diagonal() tmpl_underlay_slope() /* X-crossing */ ground_tile(0, 120) /* underlay for crossings w/o tracks */ ground_tile( 0, 80) ground_tile(225, 80) ground_tile(150, 80) ground_tile( 75, 80) ground_tile(300, 80) } /* Spriteset containing all underlays */ spriteset(track_underlays, "gfx/rails_overlays.png") { tmpl_underlay_railtypes() } /********************************************** * Track overlays (tracks without ballast): **********************************************/ /* Template for overlays; 2x straight track, 4x diagonal track, 4x slope */ template tmpl_overlay_railtypes() { [ 0,155, 40,21, -19, 5] [ 50,155, 40,21, -19, 5] [100,155, 40, 7, -19, 4] [150,155, 40, 7, -21, 20] [200,155, 12,19, 11, 6] [250,155, 12,19, -21, 6] [ 0,195, 64,39, -33, -8] [ 75,195, 64,23, -31, 0] [150,195, 64,23, -31, 0] [225,195, 64,39, -32, -9] } /* Spriteset for overlays */ spriteset(track_overlays, "gfx/rails_overlays.png") { tmpl_overlay_railtypes() } /********************************************** * Level crossings: **********************************************/ /* Level crossings require differing sprites depending * on the open/closed state and on the driving side */ /* Template for the track overlays (x/y) */ template tmpl_rails_crossing(x,y) { [x, y, 44, 23, -21, 4] [x+50, y, 44, 23, -21, 4] } template tmpl_level_crossing_railtypes_open(y) { tmpl_rails_crossing(5, 5) [ 0, y, 5,12, -3, -8] [ 50, y, 8,21, -5, -14] [100, y, 6,23, -7, -20] [150, y, 5,12, -5, -8] [200, y, 7,21, 3, -15] [250, y, 5,12, -1, -8] [300, y, 5,12, -3, -10] [350, y, 8,22, -3, -19] } template tmpl_level_crossing_railtypes_closed(y) { tmpl_rails_crossing(5, 5) [ 0, y, 5, 12, -3, -8] [ 50, y, 19, 19, -4, -6] [100, y, 23, 17, -24, -9, ANIM] [150, y, 5, 12, -5, -8] [200, y, 25, 14, 3, -9] [250, y, 5, 12, -1, -8, ANIM] [300, y, 5, 12, -3, -10, ANIM] [350, y, 19, 14, -15, -11, ANIM] } template tmpl_level_crossing_railtypes_left_open(y) { tmpl_rails_crossing(5, 5) [ 0, y, 7, 21, 0, -14] [ 50, y, 5, 12, -2, -6] [100, y, 5, 12, -3, -9] [150, y, 7, 21, -7, -15] [200, y, 5, 12, 4, -7] [250, y, 7, 22, 0, -17] [300, y, 6, 21, -2, -19] [350, y, 5, 12, -3, -9] } template tmpl_level_crossing_railtypes_left_closed(y) { tmpl_rails_crossing(5, 5) [ 0, y, 21, 19, -14, -6] [ 50, y, 5, 12, -2, -6] [100, y, 5, 12, -3, -9, ANIM] [150, y, 23, 15, -23, -9] [200, y, 5, 12, 4, -7] [250, y, 23, 17, 0, -7, ANIM] [300, y, 21, 13, -2, -11, ANIM] [350, y, 5, 12, -3, -9, ANIM] } // right hand traffic: spriteset(lc_right_closed, "gfx/lc_right.png") { tmpl_level_crossing_railtypes_closed(100) } spriteset(lc_right_open, "gfx/lc_right.png") { tmpl_level_crossing_railtypes_open(50) } // left hand traffic: spriteset(lc_left_closed, "gfx/lc_left.png") { tmpl_level_crossing_railtypes_left_closed(100) } spriteset(lc_left_open, "gfx/lc_left.png") { tmpl_level_crossing_railtypes_left_open(50) } switch(FEAT_RAILTYPES, SELF, right_level_crossing_state_switch, level_crossing_status) { LEVEL_CROSSING_CLOSED: lc_right_closed; lc_right_open; } switch(FEAT_RAILTYPES, SELF, left_level_crossing_state_switch, level_crossing_status) { LEVEL_CROSSING_CLOSED: lc_left_closed; lc_left_open; } switch(FEAT_RAILTYPES, SELF, level_crossing_switch, traffic_side) { TRAFFIC_SIDE_LEFT: left_level_crossing_state_switch; right_level_crossing_state_switch; } /********************************************** * Tracks in tunnels: **********************************************/ /* Template for tunnel track overlays */ template tmpl_tunnel_tracks() { ground_tile(75, 0) ground_tile( 0, 0) ground_tile(75, 50) ground_tile( 0, 50) } spriteset(tunnel_overlays, "gfx/tunnel_track.png") { tmpl_tunnel_tracks() } /********************************************** * Depots: **********************************************/ /* Template for depot sprites */ template tmpl_depot() { [200, 10, 16, 8, 17, 7] [118, 8, 64, 47, -9, -31] [ 0, 10, 16, 8, -31, 7] [ 37, 8, 64, 47, -53, -31] [ 37, 63, 64, 47, -53, -31] [118, 63, 64, 47, -9, -31] } /* Depots have differing sprites for normal and e-rail */ spriteset(depot_normal_rail, "gfx/depot_normal.png") { tmpl_depot() } spriteset(depot_electric_rail, "gfx/depot_electric.png") { tmpl_depot() } /********************************************** * Bridge surfaces: **********************************************/ /* Bridge surface, uses the same sprites as track underlays, but in a different order */ template tmpl_bridges_underlay() { tmpl_underlay_straight() tmpl_underlay_slope() tmpl_underlay_diagonal() } /* Spriteset for bridge surfaces */ spriteset(bridge_underlay, "gfx/rails_overlays.png") { tmpl_bridges_underlay() } /********************************************** * Fences: **********************************************/ /* Template for fences, parametrized to allow multiple sets of fences (unused) */ template tmpl_fences(y) { [ 0, y, 32,20, -30, -4] [ 48, y, 32,20, 0, -3] [ 96, y, 2,30, 0,-17] [112, y, 64, 5, -30, -4] [192, y, 32,12, -30, -4] [240, y, 32,12, 2, -3] [288, y, 32,28, -31,-12] [350, y, 32,28, 1,-10] } /* Spriteset for (company-coloured) fences */ spriteset(fencesCC, "gfx/fences.png") { tmpl_fences(0) } /********************************************** * GUI sprites: **********************************************/ /* Template for a single icon sprite */ template tmpl_gui_icon(x, y) { [x, y, 20, 20, 0, 0] } /* Template for a single cursor sprite */ template tmpl_gui_cursor(x, y) { [x, y, 32, 32, 0, 0] } /* Template for all the GUI sprites (8 icons + 8 cursors) */ template tmpl_gui() { tmpl_gui_icon( 0, 0) tmpl_gui_icon( 25, 0) tmpl_gui_icon( 50, 0) tmpl_gui_icon( 75, 0) tmpl_gui_icon(100, 0) tmpl_gui_icon(125, 0) tmpl_gui_icon(150, 0) tmpl_gui_icon(175, 0) tmpl_gui_cursor(200, 0) tmpl_gui_cursor(250, 0) tmpl_gui_cursor(300, 0) tmpl_gui_cursor(350, 0) tmpl_gui_cursor(400, 0) tmpl_gui_cursor(450, 0) tmpl_gui_cursor(500, 0) tmpl_gui_cursor(550, 0) } /* Spritesets for the normal and electric GUI */ spriteset(gui_normal, "gfx/gui_rail.png") { tmpl_gui() } spriteset(gui_electric, "gfx/gui_erail.png") { tmpl_gui() } /********************************************** * Railtype definitions: **********************************************/ /* Define the normal rails */ item(FEAT_RAILTYPES, rail) { /* Set only the most essential properties, * Lots of compatible railtypes are defined to allow compatibility with * various other sets out there */ property { label: "RAIL"; // Let this railtype replace the default normal rails compatible_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; powered_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; } /* Associate graphics with this railtype */ graphics { track_overlay: track_overlays; underlay: track_underlays; level_crossings: level_crossing_switch; tunnels: tunnel_overlays; depots: depot_normal_rail; bridge_surfaces: bridge_underlay; fences: fencesCC; gui: gui_normal; /* Caternary is not not implemented here, use the default */ } } /* Define the electric rails */ item(FEAT_RAILTYPES, elrail) { /* Set only the most essential properties, * Lots of compatible railtypes are defined to allow compatibility with * various other sets out there */ property { label: "ELRL"; // Let this railtype replace the default electric rails compatible_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; powered_railtype_list: ["ELRL", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNE", "DBHE"]; } /* Associate graphics with this railtype */ graphics { track_overlay: track_overlays; underlay: track_underlays; level_crossings: level_crossing_switch; tunnels: tunnel_overlays; depots: depot_electric_rail; bridge_surfaces: bridge_underlay; fences: fencesCC; gui: gui_electric; /* Caternary is not not implemented here, use the default */ } } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0454624 nml-0.7.6/examples/railtype/gfx/0000755000175100001660000000000014754345610016161 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/depot_electric.png0000644000175100001660000000647014754345605021667 0ustar00runnerdockerPNG  IHDRf7sRGBPLTE4bn"B RD$(@/}2(t.BOz G*je(Y2DRmNQ3v<^T4fwiQQG,PEAU*/bx ހJjK$؎ t ڹhƿ!oI#B4'BLxAFJ9ȹ)7Y :? ^#$ZXw$櫛*5gvVޕJ3ȍsEI y_|^܀%Q.yn(>vD+3 WZ2a}N1i 1) 5q]V4d%p1!VJn(MQ/>&P|̑d!kB0(ڑ!d02aH,G+^QNn(>.WQ _1Pc6N"2,~ ް$fQR>\7@L N `D;АzU+M vkY$d}߅'a YdFuc{VP)C%L6`>")!O'ҊF Gn*X N;q(3YsDÐCy3 )s] „<8P푋fF9(~Y~y*N!J3L͞ $lI~}Bkǣ% *lDhkԑRkD] u rFYlx?ޮWj OE62?0TyX//6Cvi+?YaV'$#Ds-x[?#(VnpGO ~nQ`}[ȸ@Z;2_&Ai*9v'y֮H1<ߟ&> s'Ќ@G9O Xdu HF+态zqj7Mwљq t0|mc񀁚rrdgEz9?A3i_h-$ $:7~r05k?ùAi TFMHOuMKVC fDȔsRaep^QHiA7:*)45jXo" 9a^TIr -nCE8!f1+?B%Pw/m o@EZN}>P!a}t*E*6d.8I[ITWn7"$uS9PJ'Q7Us *(P!rpI؇.8ZD9i)Pd ,ϢSn}EnzN,< .pc~,>}~ĂX bA, `b6L "ފa311lxK&Moİ⭙VG,&bb(&bb(& C11 XL PL A,&714޻fĠ21 /014D>bobܜatfL KM |F;3 L A&8sc&:!ؐh%BQJŖZ{*1ggM =TT?_kbp;74Yڞ?71 3 w E^kb+s@PoJqn!I'*%A͚NM213<.Wk8-v'ĀpMِ~pZ QCzlb d0j4W71jTH=.1[dJY5L u L KLU/ e]}Q':&#9IA; 9PRQj%CiΆ6zWSs\9uob}1sRF{i6s'?pz&}wwW[Y|!51 =[11M /71C11ĂX bA, ĂX bA, _s .fIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/depot_normal.png0000644000175100001660000000630014754345605021355 0ustar00runnerdockerPNG  IHDRf7sRGBPLTE4(boD&h!.v`l(XEDM3I/ ,D!QFheOuU G**:jQ!=hG ?@;Z_V]#*vʨQ3TQzs ?bAIh,E%K]$unPR8I1N_(NE@PrKQhabģ;2s SS]TL,XO2qV1!Ic%JOb Eg7 %*=$4T1gNߓ2X@y2br $oPV$(1l^ss]Q}]xDao@EDHG#3|G )dEd$8D@4O;1R8}؃\ Q}`(^Ɠ',6ȿfK3uH!hHɕFKi2 XG?d$_PDp䓫 (_}t\EW4}""sP;$o%2ws]1&d9B}^'#{u}2l&@?>Ih*BG$ٲ+EE%rB!9;Af0PrB6x?'[9spwգjT ^1{ɵ<_hjW8<`UkY+ak0X]'򈸦/oܝ@3 ) `>˴ 2f}b٣lY]ZQvxR#/b PB \E"8xqlF]5di=J .HKV AaL<4EDCB 0?Cr Jǣ! 1Ëk2)ΌqeJ="V2\,V[d\hxW8X@*MK*ǥ df~7T1aM7ሑM>-?PE+ ~{`CZȸ5K;2Y/7 EV` XgExp  ; Ƽ25y9NN䏼r|.0TmWBl͓!C5OZ)]}6D 𑡺 rD5b#S'Bcoduy4z$BQ 'Ӷvᛢ]tf|Њ_ݍm]M#XɎ %=_yEtG"~ƈOj$N:,é]CenO!FA */PO*"tW.LҙO 41[P-UOO' 趡"O1ѧdP>WYnAE6U'qovT:X'62q6"sJ>8Pʐ'QEb3ݰO`4ͨȆ: 8h$*Fm@Ňq;^uzJ=U)BԟҘ |Oӟg'U+bE"4"5Pİ*6VDJj)bX qKE naĭ1,XjC-bE 314"jE 10/Y+b+bp7(bгtC|݊L,b0TC|Z1X[Ec_(bXPG ͌"TӘ8_.bxbܚ8WPQ,\b-bO?~#b%@SKag%9rW1ʷtnpĚ05&M-b(Eԩ"ct$/`*sMgͅ"+6bJiNZ";Y0}?/bHjD,?[0}?1D8>jɸ66E g۬̓s+ 8bSNK1dV IdTZY%NYCt[Ŋdk]a|?+b)K/8`g41 >18I7c2W >/Q >p6-^19⧋'Ң$|{ŨY}R?>"V.UĠwR_Ȑf"DmAѴu"Av+bPWE"VĊX+bE"VĊX+bEg˵IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/fences.png0000644000175100001660000000354314754345605020143 0ustar00runnerdockerPNG  IHDR0(LmsRGBgAMA aPLTE4SsNm4),@=4ΙyH@SSs`j9'rhUvbD*#@Y*> &Fs14۶9jй{'v wlLNM&124qΉT5wI!gѼ DhpO-9n 3S!Mch.ݙ@C85 chhgvM4tѡsNm\Q53;&rjY4Egk&tf' E㟕a>לS\SM5șE:,8f'k&ڄj k*i4ScT?lsM43ԲhJO )ҙ]A;h?rSh.HisM?tL-O/D]T˸R6~v4"t!6v4Ω5EBԪTo3  Zxɕ_¯WcĹ@qM6Uͷkn)qG]ʱxh:vaGAGѰxlEohaCԪ@G*l VZi5/귣y,yl;xj:Fh?MW3ܞIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/gui_erail.png0000644000175100001660000000761714754345605020646 0ustar00runnerdockerPNG  IHDRXdmsRGBPLTE4d-=VF"GK\|}z>{)`I=\%vt< ާ6XqUBe(.CW֒aǗRbd,[2RX#Bu0ED=0..g+9)eʏ\'鞂ʸڅq0rǂ6(X)V* cÄ8e|Ů9:9K_yABcVb-xt/g@d$a:97\e&W{; Py^,[2XA!c~AS N äc cA֑Z,/H>n^ "+G ,z~Bv:RX3i&/De&UN*/{#**QcJU; O+L5&A,J'si2`A/_zhr +»v|{a:NCWQS +-7KU zb'w!{#:} ,̕BW#/)y>dF<7 "U9K i/Π;'̺=ƉmhRMnN)څqj'2l ̟,B #!E2tgMɒ՞Z1QI, PykcʲboJKJkžLRZQ]wz/C\])ƣ 'yl6x @%\7'{ar&o\hbͣ,n(kUyI>GQ%z,GX9X,[)u2+.OASe6`/e XUpX/`<6m>^<̠JN&_wwX\Aո<2'A*{XIR|S!r-u4y5LR3zU5ZKmQn}Uh}Y"4tɥ}+_,D,,VFy ) 5V#$WO[@ZNJkVKs`!ʱKO, :LRr9z?AyU R'O>ʴ|$@QvJz+ܵAWOE["GZNJ݅h`m {dTYI%'V2ᦡbmz¨ͣ XJz+WFJA1(|  KjԻ-J IҧOe?J=(KBJKL1<05n.%f S)*s ':)ҋE))ƊyPR|E*kkO ,#9P:*(R)AJ&2!"N*kk7=~4ĊŃFC&&=H)bF_R/)Z"' ?IRB1=()`EhHhǤnCrـU?]H +).ƊED5 UYk)D ?鷐FX I)`-+R|%PIPerXE(aԒSRwBcEzHC=rL=Z=Pat%tv "5Vci_9HUvK\{F`O.,LJSU -J|u~*7*„ [s`j?U)I6lsϘ)ڛLe,6,R|ρ V1_QVe,VI|>q`F%`c,6bc,6&edVaVD;ȏ:Z.oIF~lП9Z;/oݓț)F~K:됝u=Q$ƀuwN*[ãvIoDޜu;c/[:m͓N0#g,+H2@}(}IokDޠu,_>TKn>$&eӋ=j~3'7 =`U,c8a"?__w:M[rƪsy2֩E~hlT+;D'U$&eӋ WhVn]NJiMXE|Z> kAJvGEd7$kuzIe)Dk=򽉯4X//%d֣ 3$fտ@ւe;8gXybga`ZkZ7G T|) s+UjGq`),Qu E1 (6,@~Ue]v_tKN+`Q`=; E|~?$2X 3d]W#~yYBI|e,:R e<1v_,y`1X(2X &Z{_,^W*b;Iy ,6bcc,6bc,66bc,6bcc,6bc,66bc,6< -yIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/gui_rail.png0000644000175100001660000000666314754345605020501 0ustar00runnerdockerPNG  IHDRXdmsRGBPLTE4 Z~C<$[Q x߽#9B.`¥q5tr2=N'1qW*Ԁ\dsV݀^'s`/+F t}wO$r^/8k*>(=Yq JVSP@PkҽVҊ7",䷠ي#"7|66CyJ!Y9VZu8ީɻ3NAXJ} Xj!aOl/=%s"XMrGob4tcuuupj5d+b#O~kp{ +'jB8McIjO:ZC6Bn ѷѳo X>vYq Cl YpxryO޿ťB8Uc!K>j|Av)O)%-Cؽk/á+rG#qXhPsq:uXS*O:+["+Vj0]wXF88`{Wr~ȯðF՟tZA09Q+yҕVnk-Ka_<w PtJ~~rԺmXMּ1ZQm6lZ39C4_sSr֠s}4sB<@sX 1JgP)AZ jʸ%0 W9+tesu `m@ "d阒j0z1|5dEHW2W]Ւ8Ĥ"*kjB=j0yF A|gL#]YVvt2(}1@+ =V#X Wc$_C®4`UHcԶS[WfR ౚ %i=֤Kt]9chK!iگ ÑK9hr®PƨE7`«yFF+u ]Y{`k"Mh#tXj X.#|8ʕ7&2fѓu`~ Ia:?+{l=Vz&H;宪D1fW&=<& Wrkre/#ZHwe6c Zt+`1XyJrkG,< `qp0X &Uߵ;%YL魼ykjdKOH`U݉=`5N88;FV~ 8IRCjo+V՝ۣXM܊^ [(Vŝ[X ̊t \Vmŝ[X `%U|9mۤXͫ=l~KVB`Uމ5`5N:w&KcubU؉eհ;G'%VՑUe'v)V*?LZZ]]VۤXͫ2h)pXw;obe+|h j(d^bWTu[u6z{ZOsV妷+UyE5PPl{}FtfSTu{[bW#6h!X֭L'4׋ zo5`ĩ#>`LX`1X WFFbXFbXo+V}+`iBTWdBece2X WJU`1X)3_,+ӺWFM+`Ъ|e8;O`q0X `qp0X `q0X `q0X `qp0X `q0X `q|?Q4IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/lc_left.png0000644000175100001660000000737214754345605020314 0ustar00runnerdockerPNG  IHDRasRGBPLTE4^c >x. McuQYi~s ŋf~}~כ"o$$E6!+ZqS|+ S|׈4KRqvxi?ju1βFl X__T/UbIḁ_m\o}MR73HANF@oET-o$L8E>dEpxD 2aL9\-Im;$dbuK5d['6%n 0}aݼAT>=+*A+d-6p| 'Y,B"=d[>Vgi*\ (9O?=0ȍ͐eVżCn0 m{K5'8UV:A bu]%U֊Sշn|=F,{@\2G:ˑC\zY%O~oEasQ@2# o|{{Kxj1Hf9,$(3 A΀|x\3: ڿrl :Lϸk: D}@7i~,\{V١if[?,7D7H{8Q r&7aw:1u\Kn݆d8'NOj iݷku 5Ƚ?2鹚ou/SDs"/ oIA/bb7iG }1):OsboD =oee$oؽ콽~"m~gk1/ֺoV#o>jo8}rE nBlCI4]>a'I#NA^siV]_C iffB9=7+Vϻ |vCTجDZ!sH{^Z),#z,zҜÐe4Y+a׏1AJ3 ]wD*jx$ߋ#8o9D*qp}YX!;Dw/J{s_!gBhRI>Q݋Ҟ_/{дD~n»p]!t$]/RM\2K !}TXV. +ǭ!{{^މi xzYs$@yTO$ tJ$?29DU~*)YraBd=/[iYJPU>*diVe` z QV|p4kB҇Oj: A@!B!@!B @ @ X?*gGnlgJ>ߝ>Vn\/*QN]:$I'Vn?*QpөWwKV +zU 9ƀT Y~JNZ*L,RaoScԐe%l>.#?bQ\Oc^]eӷJ8deؙ@3r|BԹ5NIt< l>d%Y$V!9TIc'>+6r) 9l[ N^+$M:Q3Z>QĽ{;9@FUBס`7sG,e #Z\τb v'o=oq 7QMu/uԭj?$w 4 b| oxI ?[K @ @ @!BB  DrI&]҄Gdct<DyAr/ ҞDXE!>xS@ŐUYkp?dyɽ8 Z1* ^@2u>gAd>f+=BxkcٛQ!p݋A2^/A ,E" nw9 ׍E Hn'[yϽ9dH$ $ 8);??z^*88v[QU (0(=žS!QsHB"l#KYTr@: @ @ @!BBlA~$4˄pvKyxdpA" mTp2ī. @Xl_}Xo7 b`~ 87 տ?bY_>כ"g$$E6&+^pH oU|kD rHRpw?Ku:noe􁜫cs+UbaL^ŗ`ۅ`Wdt({,.3nXvbA$ ->\&+X % cjv/(Ab@:8>.6M$4Y#A8M(A^fR>us&с2ob=?wp<GG%j !A$EUHf +sV..*e+{p*@ViιS3պBRi2sȥ:~~V?w9.Uձ jS>*Q<쮾Y~WVm#1 YՑ$r Q!!W o30^2v;۝e G?/A<8f3oA ^|HK6=ϹIm[CNsY1(tϸj5bf׹pST_ (~*vPrr=7vGJo. 3(U47a'1i\ݺM(zq5'?`;I~{N5A2ѾTƺ;=dsw1ɔX?Hk*{`iǾco߬vVF޺_qq{~]vA _n[.&q& +$?ĈO F:'";TxX+1A W ,߇Z'lJX O=(!V,VQYobqe4.lP\+EeҖʎ2|k%>D~T>DCWBUHlB!G@O+`B^pC#O+nK,Ȇv+$cdeBCr )VH$CCJd !}țY:;x~ӯMu1|6H,|q)=(N=v< )sɒUH1`B8Cd!eR\( ORڇ(?0+$_r\ +)vP!K+d݇,H6 /daD2H$\\FN*$삨W,Hq\VG #@- @ @!@!BȰϗ#  Y%}lI?Lڛ\(w8H?ϨVIK'/'`3jo"7+ʭb!fQ~I?L[{?-V1HYVp hVI+߾] dV{k}qכoCՊrXlWz, 7K3ɺҭ֬&يrX쥨MjxtV%[zY3!  _!z*DfK튟r}ONd?rX!Êr8\ߓQ!7R5qP! >|U+$>FTHyFW\[H__FQ.Rynz@4嫊](ר([C1a m&;pܑDӃ\G2/2E|UA!S'Tn w=ɺK_ Ӗ25ɝH9ىQUtÀ]  @ @ @!BB  @h&[O$mۇ9A7 GOANށl!^L/q!hR#M7YD:Q#W '@NE2 x؇ȏJ"GsQ1{8&^>* II<QJC3y8*19y "ZpeLK>H"3$8CdnI9n@yשid9 'AL~`ށ$& -A 0qw Ge@G !Iq\VAT+q W#@- @ @!@!BI_@Ak@setLrT=b$~<8Yt$AC()޾r~nf]/z[QV%IG} d GJxCUR vAnd}-9(: O{OZY=,Ș@m#@CZVzYV!IHl9/ֲjVG.yWuBdD=+hO:L_!a$'%mDnBUE$O+XaŠ}'iܾN.KkF:wbV!Y0=i?~iL*3qsȈn w=X ɝHyDyl@*dad.@!B!B @ @ @!BB  @ @!% @ @ @!B!B @ @ @!BB  @ @!@!B @ @ @!BB  @ @!@!B ?rA-IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/rails_overlays.png0000644000175100001660000001676414754345605021747 0ustar00runnerdockerPNG  IHDR(sRGBPLTE4 +WT X*#JVqTIi,e~~`Azf+8hWXą28I5¯2xXZJk(Ve%gKQ[ V9GQD}5?6wC]_XH\K.?Xd\k~*@V@5`S-.3ӮR= X*IoH3ɁƋ|( 뿃{:VWE> ` aE sg`$U2A?/i_P҅9fFGX 4?ҺZ'?k\yw0w sUy|>x$.Ib*Z;=IVj*떲רP^O0zIwYnBGU$LkVš.Ԃ^d8t NP[[e/(>krQZ풨Md0JB mxeU'q-BO> ,fծwcuGiU˫jTSn`At|֊&;}׹ 횔Ek(}O`*6r/nw]K]* 7Z7x!'jY]#F0Og3+h*˞ViLa+fRB"^H1(Jӻ< bErP"@s4)P Sb~?RY똲YQYgU*sRkf/(9TG"<+QF`D|hc e|KMQ&۹s](K&rhP4fƚ)2ْyVEԴ֮=\kXd'\@b(U)XB~<1YeJ̽v, W1M<ԠcAT LZ`Z#\)ӫ"Ϗ`Xp pXVpXVpyDXVw5Y}`ݤ~R)Y;<ѪN)_= JB=X"âmoQ@8nR#O)Pm{6A ,h#d%cz Û! vU om ZLAZ,oSIz7 K4 FE)#2S`;]kŒ(B|}&WU93^:βh+'9J0MncҒ؅Oj,LT%)(]d8"J@ 49.UӬJUz-JK2ʄ5LC(C 1'7>*ɇ,xQR$G|fSJmgPT6`U-GY#Jk˫wR(hoCC }XnY(KikM0& R՚/T. _k2͂&G A0M-r "R4 }ѢCwmIRLFiMrQ`*Y>K,Y6"e1Ä5LS/@;U5 vPhjiYШ!BSx>c[(*mgBvРQ*+5̺qV0}B#b C`\a)2g26l"Ϫ,۔4'+ݺ+,k0x1j`@R\(TR$cMa4\ `uX?coNe B=]fcty}?)PwEaEG"-:2ԫrgʭ[ J(e C[~5j` a*]Ư ^>-dlKX`0GEQa UaaTF6T7kMÙbA 5yBQ!%_i yȂ]X[ Cb3ߘª 7B1VaYBKR\2ۣ0rh#5Ed8=ns,IHrzfP- ̪YQ\6E>]˧Feè/,7-09 `.N[¢KQ!Or O?7n0B` E6b?Mx9A\Π.T5dS85!XqQHFˈRwQPXd yk+r949V,R.oF 2I sI.h0; `Rpb:SP(V9{It=3re\6* KYz/n nb?(7P7Ň&)BߋxR-:LX|{Xςucak%lfWa c 0Ea`;=lC-3ūEwJ桉m1- iQO#{*!LWXF z ZI'UzC_vUe 2kHRYAΈ¯uQHH@ug2dBp"P~5.ұ(FW)k)؛؃P%Xէa\9ri+= *Q+ @N Y95qu_)5Sqz㤳]W}el&  '<.pr|W]Eㅫ<.IP$ }*"O<.A|W.|}e[Z VY pXVpXVpF ~`Xΰp 78u,@;PX_ZLq9K :kch(g;ªUX&D^es1r;R*2 al)X588aDf+eBaQe2cz]uLOyWA{0,P/ice^ dlQ/#oȾ\CGX4EJO X $ks5maim*zjڭaԺ27O{WX$ K#f`!͵JHLwO}UE P>z+؏1ߵv]kRá#+ AI\ea@Bn>!Ta~њX\Hf2ﴊNc  i*kV܏ K>Wl'DvN zН7E|jch*AT`ݩ訆Q!3h*ѾyR|>A>l QE%,`i `6Xi(ްh _l1Jƶ'gl^}fqŵVJEyV"WY۲RO(?Y|Tu7yUU*ЙFORp[I4hMEY9ʼV0(5ܮ:* ( (X$=g"e#(HGњAL<cU( A,Ŭo򪢎 EbX~?ܵk1Y7s, ZTDw`]ʸ#g3rɳnAO`iXw>kqwgJkn_~Af`M]eX`duXs֭Zg-gkZ'zd>kgyj]rUkjzg*GXv¢m%j NL\BX pX pX pX pX pX pm6_V`XV*Qawq_V+5bQF~!3ݢ D8[,U}U#O' ׷oKfQCA[ҬTvKD@5YiE'kz4! _=ycUz|NkXani'bN\5gpt*6' >a̻’7^嚌!Z7=K, vD 6]i5u@e`1U07H׬($u M֤\,j ++vW0)`S4jhM"jMo # X\kIGfAʹ4?","C*~(0)?gQk&{,&V0xrt laEeΤ6oK5?zy7[ '5]T5\eKE^@9bZthY:&0(0`Ϫd Kf` I]ǃ4՚ a٢E g۞Lᙓ/gX7V{EUG?{nV\;)LoXsZ.g܏ 7K9U `iyG ?X.Q{bF۾8EEeJZ,%^"Fr/!bzXDLa|-RlWssOFѸJȺ+2-,ը5 j3QcWXuTJ+tr>b/XMV,WFjSr3751Ͷ[yXo|: ϯO`݌+ =Xc߱` }*zY)X ܡkEXL5.ٚEiBб y@p]Vwk eKqjo7uUSj֚``|mJ` .U.Z`maqq,!۷Xok sKEUб[G[`mVT+X"eXہUyWvrjkq\:dc~jM*׬FwyW]A6a궪fAZe06V; 3śKG KzkrSX`+ +\ W`+ W`+ W`+ W`+ W`kEPcmIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/railtype/gfx/tunnel_track.png0000644000175100001660000000315014754345605021363 0ustar00runnerdockerPNG  IHDRdS9sRGBPLTE4 Xb. Qa5գAdAXAV Рjju#aƢǨXb%#)b΃a kmLXcX!7Qt.eq Uu8Mn䬘Uq'G$TU[lcT (OI1T``uu%^c-@R,XL4PWt 4B.sMUi)cڪЙ`jb$~#&a#J){2嗑gŐrRوeFf31EU.X 1IK99{#VR*引"J1RX+(`G>h7)F#;)ƣxTlgT_C}ޥ7bѿ^>}t3`,i 2VAXEXXXXXa+cGX- IJn`i :F6Ю}A)Rqܫ 9MwRz\qVᚾSկV{;ێ%2|rN{j^mn{ l#V)&|r52j':n,R+v4<e]bN9?0Ueq_[N9%s澎J(23E~h@SžAXf mU]oUSg4j!jPh?qÛOL֘ċ-ҢK^睛Cl֊LZ.5r /5X/Y v?kŤ,%Ӑl%r[N֐EbKhKSAud#,|AN<QNg^.50O4uj4lk0oċ)6q!~`hm035>([zX)>Vxx8ITD zB2\N483|?% K%P~t(֣OkhjS"Wj/z[k"3>]hAj例T+BlxU$ޕ9RbuGN#҄˟3l%~EeR5%yj\v1lP-r"(á?y(zh4G:?@bfqaJ8Qa#>X9@wOhsj;Kj@gipm_iT퀟v E<\"NiRAuxhD6ޡ""DzՃ߬ dls484z()'{v KO cޮD+T|Nd2 ; hSF('+z_f| ;iPP ja8ά~EH~>-Wq 4+υQ?PN;4/xI0_K$PNE[iI? G)9[[IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/road_vehicle/gfx/flatbed_truck_1_goods.png0000644000175100001660000000402214754345605023710 0ustar00runnerdockerPNG  IHDR^<sRGBPLTE4+} *xi;I3>՟j/DR}78}%ws`oăk݃{ Gx'ӷN}챋S< G=pO@L豌ܧRު/Tx@mɑ. O<%f|/m@*#6 p }s !H҂(x*<G=l?DzǏ;} Kw+as* ,+% 07OSx$$ApқOOfqgosT'yP8BOC[0zC; (Kvz<"".Nշ@e q-0!?QЏP<A齙-?GxE|YkDAq?q]<~-O}d}gn yh'8npXȽʍtҸup+‹\⳪)E":h"9/9Rh ݘ`{9X~U&]Ue$9Ar6/ __|[ &!<?uYx.V}X¦\rp|Àq\Hu>?k$ҡN7m3I @P%>(.^Z#V#d7 /3'c%88R0l.+?]d9]G0 )aZ+nn;H'G1]_ZN6|$m+Ƨ&? = AeNxxՖ`:U`8 |?b!yB)KUV m70IU Ge׫t C%HDF6' Zp&hݎX1~Cɤ8>U H4@?rp8ro)O+)/(aŗ@P ڰ*UcW\v1lP,r"0!?|sPhl @}?Y䀳=U‡5B2Fh?XY@uVKq0n8āsj|דQ-8PV-ZªR[9i|bV_ND zՍ, dSܗÓ9SlFb]<SBĻ) SA|f wq-S~Px$VCTx*<ވmŷuIޗ渙)r2'kB|%T<_9\ Ş 3˨_Q{r( ̖+h¿zsaK`R~<Ml'|wٯ%{(z%"}p-O}?q"{3ο?ݬj #YE0-͙@}kAM tRy5CEenJHM f\X/̧7lkai N4X,d]Ɵ(Bj\5.UtK\NGߗL乺[Ti[ v<)Zp#V_SdՇ>)NzkȽlS2|_` wRI^Л E^5fCecP]B?6x2[ XֆYob}~ɜG)bmuCĪ_V#҈W\ ·UOeA2)Ҁߩ⏼R?1KNx 6 P>y(zh4E:?@l]ma͡Qh Z,:Btfpl mGipQ *_]x=m/)?mApH/BE:% 7Ʒ) *+‰H7Z=@ ;Q@S\" =$uӓ=;x౉woۭ"J 3P|N)zhl432R@Tx*xlozHޗᴙ%?eTO=A& 9Z͎>3_g=9{OŖ3h4+% ~vi]K^&4aeH4õ<5ȸ}'i^~Հ fOIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/road_vehicle/gfx/flatbed_truck_1_wood.png0000644000175100001660000000432514754345605023553 0ustar00runnerdockerPNG  IHDR^<sRGBPLTE4"n-rz( $)S5M'=s.ZU}VeWЪ|6|o'W;?2.˚5vn0SgG(̱<@;>쉷gAw'\])5简er@jl^s .s< qltUkY4qeYRD %CXS|݂r@v4|Js@G.~ʁ^-4.%I|χ=,uYr>ȾbhRnGy&o$a tm^q !JQǴ÷}3> rrgʼA}s $ZkHm1"9*8Z]6hsr;j6n8SIGX)C(qA=>@ŮJG6j%N|' {j[w)TRPי>XS49 / b>!1\cc6e8+~ qp'N9i_ Miw Zprprɗ߮)qLQ+bDA9Z=+. P>r[`[<|㷣V%v,rww.N X\tS%fq}n"9`#h,jG٠  ek΁TH_SE#}q}ŁT ]_9xK:htg_W65]\Ƶq\hv8pXbUA;St-.cj/]L<9|79ug@mp?u=~]5ܣ\.ə6 _pnz@qW "gm>\16B s73AtϰJbj;PYt{Eh5]vϿr@E\BN0ށ̦6%VzۃƷ) *IA?}ku'|+H ]۳?ʁ: ^v%Fp>dm<SBȫr۴oDYVЩNҮ 3ZAǐ|?hw{g2@9:WtowN9Pf|/`|~BO4/Sՠxzt%UgRÙMR(=9fkS|*o UQ=0J;/h=K_aXTN‡yzI8/y7rP}2h&qw*jFu1IENDB`././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0464623 nml-0.7.6/examples/road_vehicle/lang/0000755000175100001660000000000014754345610017111 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/road_vehicle/lang/english.lng0000644000175100001660000000175014754345605021253 0ustar00runnerdocker##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Road Vehicle STR_GRF_DESC :{ORANGE}NML Example NewGRF: Road Vehicle{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}DanMack, Zephyris, {BLACK}coding by {SILVER}Terkhen, planetmaker.{}{BLACK}This NewGRF defines first-generation flatbed truck. STR_ERROR_ENGINE_POOL :enable multiple NewGRF engine sets = on STR_NAME_FLATBED_TRUCK_1 :Flatbed Truck 1 (Normal Road) STR_NAME_FLATBED_TRUCK_2 :Flatbed Truck 2 (Electrified Road) STR_NAME_FLATBED_TRUCK_3 :Flatbed Truck 3 (Yellow Road) STR_NAME_FLATBED_TRUCK_4 :Flatbed Truck 4 (Unknown, fallback to Red then Road) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0464623 nml-0.7.6/examples/roadtype_and_tramtype/0000755000175100001660000000000014754345610020142 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/example_roadtype_and_tramtype.nml0000644000175100001660000002554014754345605026775 0ustar00runnerdocker/* * This file is aimed to provide an example on how to code roadtypes and tramtypes in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. */ /********************************************** * Header, containing some general stuff: **********************************************/ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the fifth real grf defined as part of * NML, therefore the last character is set to 4. Successive grfs will * have 5, 6, etc. there, to make sure each example grf has a unique GRFID. */ grfid : "NML\04"; name : string(STR_GRF_NAME); desc : string(STR_GRF_DESCRIPTION); /* This is the first version, start numbering at 1. */ version: 1; min_compatible_version: 1; } /* Default ground tile template (re-use as needed) */ template ground_tile(x, y) { [x, y, 64, 31, -31, 0] } /********************************************** * Road underlays (tracks + ballast): **********************************************/ /* Template for underlays; 2x straight road, 5x junctions, 4x corners, 4x slope, 4x half-tile road */ /* Used for bridge surfaces also, therefore the template is split */ template tmpl_underlay_straight() { ground_tile( 0, 0) ground_tile(75, 0) } template tmpl_underlay_junctions() { ground_tile(150, 0) ground_tile(225, 0) ground_tile(300, 0) ground_tile(375, 0) ground_tile(450, 0) } template tmpl_underlay_corners() { ground_tile( 0, 40) ground_tile( 75, 40) ground_tile(150, 40) ground_tile(225, 40) } template tmpl_underlay_slope() { [300, 40, 64, 39, -31, -8] [375, 40, 64, 23, -31, 0] [450, 40, 64, 23, -31, 0] [525, 40, 64, 39, -31, -8] } template tmpl_underlay_half_tiles() { ground_tile( 0, 80) ground_tile( 75, 80) ground_tile(150, 80) ground_tile(225, 80) } template tmpl_underlay_roadtypes() { tmpl_underlay_straight() tmpl_underlay_junctions() tmpl_underlay_corners() tmpl_underlay_slope() tmpl_underlay_half_tiles() } /* Spriteset containing all underlays */ spriteset(track_underlays, "gfx/roads_underlay.png") { tmpl_underlay_roadtypes() } /********************************************** * Track overlays (tracks without ballast): **********************************************/ template tmpl_overlay_roadtypes() { [ 0, 0, 64, 31, -31, 0] [75, 0, 64, 31, -31, 0] [150, 0, 64, 31, -31, 0] [225, 0, 64, 31, -31, 0] [300, 0, 64, 31, -31, 0] [375, 0, 64, 31, -31, 0] [450, 0, 64, 31, -31, 0] [ 0, 40, 64, 31, -31, 0] [ 75, 40, 64, 31, -31, 0] [150, 40, 64, 31, -31, 0] [225, 40, 64, 31, -31, 0] [300, 40, 64, 39, -31, -8] [375, 40, 64, 21, -31, 0] [450, 40, 64, 21, -31, 0] [525, 40, 64, 39, -31, -8] [ 0, 80, 64, 31, -31, 0] [ 75, 80, 64, 31, -31, 0] [150, 80, 64, 31, -31, 0] [225, 80, 64, 31, -31, 0] } /* Spriteset for overlays */ spriteset(road_overlays_red, "gfx/roads_red.png") { tmpl_overlay_roadtypes() } /* Spriteset for overlays */ spriteset(road_overlays_blue, "gfx/roads_blue.png") { tmpl_overlay_roadtypes() } /* Spriteset for overlays */ spriteset(road_overlays_yellow, "gfx/roads_yellow.png") { tmpl_overlay_roadtypes() } /* Spriteset for overlays */ spriteset(tram_overlays_green, "gfx/tram_green.png") { tmpl_overlay_roadtypes() } /********************************************** * Depots: **********************************************/ /* Template for depot sprites */ template tmpl_depot() { [200, 10, 16, 8, 17, 7+4] [118, 8, 64, 47, -9+8, -31] [ 0, 10, 16, 8, -31, 7+4] [ 37, 8, 64, 47, -53-8, -31] [ 37, 63, 64, 47, -53-8, -31] [118, 63, 64, 47, -9+8, -31] } /* Depots */ spriteset(depot_normal_road, "gfx/depot_normal.png") { tmpl_depot() } /********************************************** * Bridge surfaces: **********************************************/ /* Bridge surface, uses the same sprites as track underlays, but in a different order */ template tmpl_bridges_underlay() { tmpl_underlay_straight() tmpl_underlay_slope() tmpl_underlay_junctions() } /* Spriteset for bridge surfaces */ spriteset(bridge_underlay, "gfx/roads_red.png") { tmpl_bridges_underlay() } /********************************************** * GUI sprites: **********************************************/ /* Template for a single icon sprite */ template tmpl_gui_icon(x, y) { [x, y, 20, 20, 0, 0] } /* Template for a single cursor sprite */ template tmpl_gui_cursor(x, y) { [x, y, 32, 32, 0, 0] } /* Template for all the GUI sprites (8 icons + 8 cursors) */ template tmpl_gui() { tmpl_gui_icon( 0, 0) tmpl_gui_icon( 25, 0) tmpl_gui_icon( 50, 0) tmpl_gui_icon( 75, 0) tmpl_gui_icon(100, 0) tmpl_gui_icon(125, 0) tmpl_gui_icon(150, 0) tmpl_gui_icon(175, 0) tmpl_gui_cursor(200, 0) tmpl_gui_cursor(250, 0) tmpl_gui_cursor(300, 0) tmpl_gui_cursor(350, 0) tmpl_gui_cursor(400, 0) tmpl_gui_cursor(450, 0) tmpl_gui_cursor(500, 0) tmpl_gui_cursor(550, 0) } /* Spritesets for the normal and electric GUI */ spriteset(gui_normal, "gfx/gui_rail.png") { tmpl_gui() } /********************************************** * Roadstop sprites: **********************************************/ template tmpl_underlay_roadstop() { ground_tile( 0, 120) ground_tile( 75, 120) ground_tile(150, 120) ground_tile(225, 120) } spriteset(roadstop_underlay_red, "gfx/roads_red.png") { tmpl_underlay_roadstop() } spriteset(roadstop_underlay_blue, "gfx/roads_blue.png") { tmpl_underlay_roadstop() } spriteset(roadstop_underlay_yellow, "gfx/roads_yellow.png") { tmpl_underlay_roadstop() } /********************************************** * Direction overlay sprites: **********************************************/ spriteset(st_direction_markings, "gfx/direction_markings.png") { /* Flat ground */ [ 34, 8, 24, 16, -10, -9 ] [ 66, 8, 24, 16, -13, -7 ] [ 98, 8, 24, 16, -12, -8 ] [ 130, 8, 24, 16, -15, -10 ] [ 162, 8, 24, 16, -12, -9 ] [ 194, 8, 24, 16, -11, -8 ] /* Slopes with N corner raised */ [ 34, 40, 24, 16, -13, -10 ] [ 66, 40, 24, 16, -12, -8 ] [ 98, 40, 24, 16, -12, -9 ] [ 130, 40, 24, 16, -11, -8 ] [ 162, 40, 24, 16, -9, -10 ] [ 194, 40, 24, 16, -10, -9 ] /* Slopes with S corner raised */ [ 34, 72, 24, 16, -8, -11 ] [ 66, 72, 24, 16, -11, -5 ] [ 98, 72, 24, 16, -12, -8 ] [ 130, 72, 24, 16, -12, -5 ] [ 162, 72, 24, 16, -14, -10 ] [ 194, 72, 24, 16, -12, -8 ] } /********************************************** * Roadtype definitions: **********************************************/ /* Note that roadtypes only show in OpenTTD if compatible vehicles are available. examples/road_vehicle provides some vehicles for these roadtypes */ item(FEAT_ROADTYPES, red_road, 5) { property { name: string(STR_NAME_RED_ROAD); label: "REDR"; powered_roadtype_list: ["BLUE", "REDR", "ROAD", "ELRD"]; toolbar_caption: string(STR_TOOLBAR_CAPTION_RED_ROAD); menu_text: string(STR_MENU_TEXT_RED_ROAD); build_window_caption: string(STR_BUILD_WINDOW_CAPTION_RED_ROAD); autoreplace_text: string(STR_AUTOREPLACE_TEXT_RED_ROAD); new_engine_text: string(STR_NEW_ENGINE_TEXT_RED_ROAD); roadtype_flags: bitmask(ROADTYPE_FLAG_CATENARY); sort_order: 101; } /* Associate graphics with this roadtype */ graphics { track_overlay: road_overlays_red; underlay: track_underlays; depots: depot_normal_road; bridge_surfaces: bridge_underlay; roadstops: roadstop_underlay_red; // gui: gui_normal; /* Catenary is not not implemented here, use the default */ direction_markings: st_direction_markings; } } item(FEAT_ROADTYPES, blue_road, 6) { property { name: string(STR_NAME_BLUE_ROAD); label: "BLUE"; powered_roadtype_list: ["ROAD"]; toolbar_caption: string(STR_TOOLBAR_CAPTION_BLUE_ROAD); menu_text: string(STR_MENU_TEXT_BLUE_ROAD); build_window_caption: string(STR_BUILD_WINDOW_CAPTION_BLUE_ROAD); autoreplace_text: string(STR_AUTOREPLACE_TEXT_BLUE_ROAD); new_engine_text: string(STR_NEW_ENGINE_TEXT_BLUE_ROAD); roadtype_flags: 0; sort_order: 99; } /* Associate graphics with this roadtype */ graphics { track_overlay: road_overlays_blue; underlay: track_underlays; depots: depot_normal_road; bridge_surfaces: bridge_underlay; roadstops: roadstop_underlay_blue; // gui: gui_normal; /* Catenary is not not implemented here, use the default */ direction_markings: st_direction_markings; } } item(FEAT_ROADTYPES, yellow_road, 7) { property { name: string(STR_NAME_YELLOW_ROAD); label: "2YEL"; powered_roadtype_list: ["ROAD"]; toolbar_caption: string(STR_TOOLBAR_CAPTION_YELLOW_ROAD); menu_text: string(STR_MENU_TEXT_YELLOW_ROAD); build_window_caption: string(STR_BUILD_WINDOW_CAPTION_YELLOW_ROAD); autoreplace_text: string(STR_AUTOREPLACE_TEXT_YELLOW_ROAD); new_engine_text: string(STR_NEW_ENGINE_TEXT_YELLOW_ROAD); roadtype_flags: 0; sort_order: 100; } /* Associate graphics with this roadtype */ graphics { track_overlay: road_overlays_yellow; underlay: track_underlays; depots: depot_normal_road; bridge_surfaces: bridge_underlay; roadstops: roadstop_underlay_yellow; // gui: gui_normal; /* Catenary is not not implemented here, use the default */ direction_markings: st_direction_markings; } } /********************************************** * Tramtype definitions: **********************************************/ item(FEAT_TRAMTYPES, green_tram, 9) { property { name: string(STR_NAME_GREEN_TRAM); label: "GRTR"; powered_tramtype_list: ["TRAM"]; toolbar_caption: string(STR_TOOLBAR_CAPTION_GREEN_TRAM); menu_text: string(STR_MENU_TEXT_GREEN_TRAM); build_window_caption: string(STR_BUILD_WINDOW_CAPTION_GREEN_TRAM); autoreplace_text: string(STR_AUTOREPLACE_TEXT_GREEN_TRAM); new_engine_text: string(STR_NEW_ENGINE_TEXT_GREEN_TRAM); tramtype_flags: 0; } /* Associate graphics with this roadtype */ graphics { track_overlay: tram_overlays_green; underlay: track_underlays; depots: depot_normal_road; bridge_surfaces: bridge_underlay; /* Catenary is not not implemented here, use the default */ } } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0484624 nml-0.7.6/examples/roadtype_and_tramtype/gfx/0000755000175100001660000000000014754345610020726 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/depot_electric.png0000644000175100001660000000647014754345605024434 0ustar00runnerdockerPNG  IHDRf7sRGBPLTE4bn"B RD$(@/}2(t.BOz G*je(Y2DRmNQ3v<^T4fwiQQG,PEAU*/bx ހJjK$؎ t ڹhƿ!oI#B4'BLxAFJ9ȹ)7Y :? ^#$ZXw$櫛*5gvVޕJ3ȍsEI y_|^܀%Q.yn(>vD+3 WZ2a}N1i 1) 5q]V4d%p1!VJn(MQ/>&P|̑d!kB0(ڑ!d02aH,G+^QNn(>.WQ _1Pc6N"2,~ ް$fQR>\7@L N `D;АzU+M vkY$d}߅'a YdFuc{VP)C%L6`>")!O'ҊF Gn*X N;q(3YsDÐCy3 )s] „<8P푋fF9(~Y~y*N!J3L͞ $lI~}Bkǣ% *lDhkԑRkD] u rFYlx?ޮWj OE62?0TyX//6Cvi+?YaV'$#Ds-x[?#(VnpGO ~nQ`}[ȸ@Z;2_&Ai*9v'y֮H1<ߟ&> s'Ќ@G9O Xdu HF+态zqj7Mwљq t0|mc񀁚rrdgEz9?A3i_h-$ $:7~r05k?ùAi TFMHOuMKVC fDȔsRaep^QHiA7:*)45jXo" 9a^TIr -nCE8!f1+?B%Pw/m o@EZN}>P!a}t*E*6d.8I[ITWn7"$uS9PJ'Q7Us *(P!rpI؇.8ZD9i)Pd ,ϢSn}EnzN,< .pc~,>}~ĂX bA, `b6L "ފa311lxK&Moİ⭙VG,&bb(&bb(& C11 XL PL A,&714޻fĠ21 /014D>bobܜatfL KM |F;3 L A&8sc&:!ؐh%BQJŖZ{*1ggM =TT?_kbp;74Yڞ?71 3 w E^kb+s@PoJqn!I'*%A͚NM213<.Wk8-v'ĀpMِ~pZ QCzlb d0j4W71jTH=.1[dJY5L u L KLU/ e]}Q':&#9IA; 9PRQj%CiΆ6zWSs\9uob}1sRF{i6s'?pz&}wwW[Y|!51 =[11M /71C11ĂX bA, ĂX bA, _s .fIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/depot_normal.png0000644000175100001660000000630014754345605024122 0ustar00runnerdockerPNG  IHDRf7sRGBPLTE4(boD&h!.v`l(XEDM3I/ ,D!QFheOuU G**:jQ!=hG ?@;Z_V]#*vʨQ3TQzs ?bAIh,E%K]$unPR8I1N_(NE@PrKQhabģ;2s SS]TL,XO2qV1!Ic%JOb Eg7 %*=$4T1gNߓ2X@y2br $oPV$(1l^ss]Q}]xDao@EDHG#3|G )dEd$8D@4O;1R8}؃\ Q}`(^Ɠ',6ȿfK3uH!hHɕFKi2 XG?d$_PDp䓫 (_}t\EW4}""sP;$o%2ws]1&d9B}^'#{u}2l&@?>Ih*BG$ٲ+EE%rB!9;Af0PrB6x?'[9spwգjT ^1{ɵ<_hjW8<`UkY+ak0X]'򈸦/oܝ@3 ) `>˴ 2f}b٣lY]ZQvxR#/b PB \E"8xqlF]5di=J .HKV AaL<4EDCB 0?Cr Jǣ! 1Ëk2)ΌqeJ="V2\,V[d\hxW8X@*MK*ǥ df~7T1aM7ሑM>-?PE+ ~{`CZȸ5K;2Y/7 EV` XgExp  ; Ƽ25y9NN䏼r|.0TmWBl͓!C5OZ)]}6D 𑡺 rD5b#S'Bcoduy4z$BQ 'Ӷvᛢ]tf|Њ_ݍm]M#XɎ %=_yEtG"~ƈOj$N:,é]CenO!FA */PO*"tW.LҙO 41[P-UOO' 趡"O1ѧdP>WYnAE6U'qovT:X'62q6"sJ>8Pʐ'QEb3ݰO`4ͨȆ: 8h$*Fm@Ňq;^uzJ=U)BԟҘ |Oӟg'U+bE"4"5Pİ*6VDJj)bX qKE naĭ1,XjC-bE 314"jE 10/Y+b+bp7(bгtC|݊L,b0TC|Z1X[Ec_(bXPG ͌"TӘ8_.bxbܚ8WPQ,\b-bO?~#b%@SKag%9rW1ʷtnpĚ05&M-b(Eԩ"ct$/`*sMgͅ"+6bJiNZ";Y0}?/bHjD,?[0}?1D8>jɸ66E g۬̓s+ 8bSNK1dV IdTZY%NYCt[Ŋdk]a|?+b)K/8`g41 >18I7c2W >/Q >p6-^19⧋'Ң$|{ŨY}R?>"V.UĠwR_Ȑf"DmAѴu"Av+bPWE"VĊX+bE"VĊX+bEg˵IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/direction_markings.png0000644000175100001660000001724614754345605025325 0ustar00runnerdockerPNG  IHDR `xzTXtRaw profile type exifxڥ]v$+srsf*{nKϭ+BPy'皞h-񩣎<9׷~}rJsO<}[:yk/#@> ~Ʒ܈q|J~_Ԇ.{_+sd/yJλHI|2Υq:#| ]? _P>|D-G3W_wIяskͬbFgQ_K|q3ŝBk`3XBmɐ \GL9_%kOryȫ\^Fb^גc״#51sfaK?ޞ?}[ "7@/o|s&6I\\ -Ab=O ]3)bj% IQi!.B" &R \ UԳ;5tyݚ5sVJ_?nP9"@ܛ4e198B+?6@ sgzFuwj rQYЋR(tx ,CRP3kCi@53@hVf$ML*)baD>YƜ$Ỿ]gs 8 & ٴ VUs $~όƀ.j8N3pw&0IzŎm⺣){0dȦJA*X-0ͭ} KJx}=%!r xiTXtXML:com.adobe.xmp PLTE 000@@@PPPdddttt42]AkA@fd䅥1s' SX mBт[[Pa =C r3I, ؘ HkK֍<#_}\.V)Yc1`iANN9S9A΃4+;Sdx# 5N39 % ©[DGo̴ [[@g62.H䇼S: }x 0EPPA @ A T# tA{AA]'_$nr - rv7hs>6.HcI)i#-<[䔔. x5%am.mcD}䒂y>u A+,ҧ]E i 4Xr >$v 26 Fo#Dȋ y$( <Ŋ=L}r@ Au;eIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/fences.png0000644000175100001660000000354314754345605022710 0ustar00runnerdockerPNG  IHDR0(LmsRGBgAMA aPLTE4SsNm4),@=4ΙyH@SSs`j9'rhUvbD*#@Y*> &Fs14۶9jй{'v wlLNM&124qΉT5wI!gѼ DhpO-9n 3S!Mch.ݙ@C85 chhgvM4tѡsNm\Q53;&rjY4Egk&tf' E㟕a>לS\SM5șE:,8f'k&ڄj k*i4ScT?lsM43ԲhJO )ҙ]A;h?rSh.HisM?tL-O/D]T˸R6~v4"t!6v4Ω5EBԪTo3  Zxɕ_¯WcĹ@qM6Uͷkn)qG]ʱxh:vaGAGѰxlEohaCԪ@G*l VZi5/귣y,yl;xj:Fh?MW3ܞIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/gui_erail.png0000644000175100001660000000761714754345605023413 0ustar00runnerdockerPNG  IHDRXdmsRGBPLTE4d-=VF"GK\|}z>{)`I=\%vt< ާ6XqUBe(.CW֒aǗRbd,[2RX#Bu0ED=0..g+9)eʏ\'鞂ʸڅq0rǂ6(X)V* cÄ8e|Ů9:9K_yABcVb-xt/g@d$a:97\e&W{; Py^,[2XA!c~AS N äc cA֑Z,/H>n^ "+G ,z~Bv:RX3i&/De&UN*/{#**QcJU; O+L5&A,J'si2`A/_zhr +»v|{a:NCWQS +-7KU zb'w!{#:} ,̕BW#/)y>dF<7 "U9K i/Π;'̺=ƉmhRMnN)څqj'2l ̟,B #!E2tgMɒ՞Z1QI, PykcʲboJKJkžLRZQ]wz/C\])ƣ 'yl6x @%\7'{ar&o\hbͣ,n(kUyI>GQ%z,GX9X,[)u2+.OASe6`/e XUpX/`<6m>^<̠JN&_wwX\Aո<2'A*{XIR|S!r-u4y5LR3zU5ZKmQn}Uh}Y"4tɥ}+_,D,,VFy ) 5V#$WO[@ZNJkVKs`!ʱKO, :LRr9z?AyU R'O>ʴ|$@QvJz+ܵAWOE["GZNJ݅h`m {dTYI%'V2ᦡbmz¨ͣ XJz+WFJA1(|  KjԻ-J IҧOe?J=(KBJKL1<05n.%f S)*s ':)ҋE))ƊyPR|E*kkO ,#9P:*(R)AJ&2!"N*kk7=~4ĊŃFC&&=H)bF_R/)Z"' ?IRB1=()`EhHhǤnCrـU?]H +).ƊED5 UYk)D ?鷐FX I)`-+R|%PIPerXE(aԒSRwBcEzHC=rL=Z=Pat%tv "5Vci_9HUvK\{F`O.,LJSU -J|u~*7*„ [s`j?U)I6lsϘ)ڛLe,6,R|ρ V1_QVe,VI|>q`F%`c,6bc,6&edVaVD;ȏ:Z.oIF~lП9Z;/oݓț)F~K:됝u=Q$ƀuwN*[ãvIoDޜu;c/[:m͓N0#g,+H2@}(}IokDޠu,_>TKn>$&eӋ=j~3'7 =`U,c8a"?__w:M[rƪsy2֩E~hlT+;D'U$&eӋ WhVn]NJiMXE|Z> kAJvGEd7$kuzIe)Dk=򽉯4X//%d֣ 3$fտ@ւe;8gXybga`ZkZ7G T|) s+UjGq`),Qu E1 (6,@~Ue]v_tKN+`Q`=; E|~?$2X 3d]W#~yYBI|e,:R e<1v_,y`1X(2X &Z{_,^W*b;Iy ,6bcc,6bc,66bc,6bcc,6bc,66bc,6< -yIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/gui_rail.png0000644000175100001660000000666314754345605023246 0ustar00runnerdockerPNG  IHDRXdmsRGBPLTE4 Z~C<$[Q x߽#9B.`¥q5tr2=N'1qW*Ԁ\dsV݀^'s`/+F t}wO$r^/8k*>(=Yq JVSP@PkҽVҊ7",䷠ي#"7|66CyJ!Y9VZu8ީɻ3NAXJ} Xj!aOl/=%s"XMrGob4tcuuupj5d+b#O~kp{ +'jB8McIjO:ZC6Bn ѷѳo X>vYq Cl YpxryO޿ťB8Uc!K>j|Av)O)%-Cؽk/á+rG#qXhPsq:uXS*O:+["+Vj0]wXF88`{Wr~ȯðF՟tZA09Q+yҕVnk-Ka_<w PtJ~~rԺmXMּ1ZQm6lZ39C4_sSr֠s}4sB<@sX 1JgP)AZ jʸ%0 W9+tesu `m@ "d阒j0z1|5dEHW2W]Ւ8Ĥ"*kjB=j0yF A|gL#]YVvt2(}1@+ =V#X Wc$_C®4`UHcԶS[WfR ౚ %i=֤Kt]9chK!iگ ÑK9hr®PƨE7`«yFF+u ]Y{`k"Mh#tXj X.#|8ʕ7&2fѓu`~ Ia:?+{l=Vz&H;宪D1fW&=<& Wrkre/#ZHwe6c Zt+`1XyJrkG,< `qp0X &Uߵ;%YL魼ykjdKOH`U݉=`5N88;FV~ 8IRCjo+V՝ۣXM܊^ [(Vŝ[X ̊t \Vmŝ[X `%U|9mۤXͫ=l~KVB`Uމ5`5N:w&KcubU؉eհ;G'%VՑUe'v)V*?LZZ]]VۤXͫ2h)pXw;obe+|h j(d^bWTu[u6z{ZOsV妷+UyE5PPl{}FtfSTu{[bW#6h!X֭L'4׋ zo5`ĩ#>`LX`1X WFFbXFbXo+V}+`iBTWdBece2X WJU`1X)3_,+ӺWFM+`Ъ|e8;O`q0X `qp0X `q0X `q0X `qp0X `q0X `q|?Q4IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/lc_left.png0000644000175100001660000000737214754345605023061 0ustar00runnerdockerPNG  IHDRasRGBPLTE4^c >x. McuQYi~s ŋf~}~כ"o$$E6!+ZqS|+ S|׈4KRqvxi?ju1βFl X__T/UbIḁ_m\o}MR73HANF@oET-o$L8E>dEpxD 2aL9\-Im;$dbuK5d['6%n 0}aݼAT>=+*A+d-6p| 'Y,B"=d[>Vgi*\ (9O?=0ȍ͐eVżCn0 m{K5'8UV:A bu]%U֊Sշn|=F,{@\2G:ˑC\zY%O~oEasQ@2# o|{{Kxj1Hf9,$(3 A΀|x\3: ڿrl :Lϸk: D}@7i~,\{V١if[?,7D7H{8Q r&7aw:1u\Kn݆d8'NOj iݷku 5Ƚ?2鹚ou/SDs"/ oIA/bb7iG }1):OsboD =oee$oؽ콽~"m~gk1/ֺoV#o>jo8}rE nBlCI4]>a'I#NA^siV]_C iffB9=7+Vϻ |vCTجDZ!sH{^Z),#z,zҜÐe4Y+a׏1AJ3 ]wD*jx$ߋ#8o9D*qp}YX!;Dw/J{s_!gBhRI>Q݋Ҟ_/{дD~n»p]!t$]/RM\2K !}TXV. +ǭ!{{^މi xzYs$@yTO$ tJ$?29DU~*)YraBd=/[iYJPU>*diVe` z QV|p4kB҇Oj: A@!B!@!B @ @ X?*gGnlgJ>ߝ>Vn\/*QN]:$I'Vn?*QpөWwKV +zU 9ƀT Y~JNZ*L,RaoScԐe%l>.#?bQ\Oc^]eӷJ8deؙ@3r|BԹ5NIt< l>d%Y$V!9TIc'>+6r) 9l[ N^+$M:Q3Z>QĽ{;9@FUBס`7sG,e #Z\τb v'o=oq 7QMu/uԭj?$w 4 b| oxI ?[K @ @ @!BB  DrI&]҄Gdct<DyAr/ ҞDXE!>xS@ŐUYkp?dyɽ8 Z1* ^@2u>gAd>f+=BxkcٛQ!p݋A2^/A ,E" nw9 ׍E Hn'[yϽ9dH$ $ 8);??z^*88v[QU (0(=žS!QsHB"l#KYTr@: @ @ @!BBlA~$4˄pvKyxdpA" mTp2ī. @Xl_}Xo7 b`~ 87 տ?bY_>כ"g$$E6&+^pH oU|kD rHRpw?Ku:noe􁜫cs+UbaL^ŗ`ۅ`Wdt({,.3nXvbA$ ->\&+X % cjv/(Ab@:8>.6M$4Y#A8M(A^fR>us&с2ob=?wp<GG%j !A$EUHf +sV..*e+{p*@ViιS3պBRi2sȥ:~~V?w9.Uձ jS>*Q<쮾Y~WVm#1 YՑ$r Q!!W o30^2v;۝e G?/A<8f3oA ^|HK6=ϹIm[CNsY1(tϸj5bf׹pST_ (~*vPrr=7vGJo. 3(U47a'1i\ݺM(zq5'?`;I~{N5A2ѾTƺ;=dsw1ɔX?Hk*{`iǾco߬vVF޺_qq{~]vA _n[.&q& +$?ĈO F:'";TxX+1A W ,߇Z'lJX O=(!V,VQYobqe4.lP\+EeҖʎ2|k%>D~T>DCWBUHlB!G@O+`B^pC#O+nK,Ȇv+$cdeBCr )VH$CCJd !}țY:;x~ӯMu1|6H,|q)=(N=v< )sɒUH1`B8Cd!eR\( ORڇ(?0+$_r\ +)vP!K+d݇,H6 /daD2H$\\FN*$삨W,Hq\VG #@- @ @!@!BȰϗ#  Y%}lI?Lڛ\(w8H?ϨVIK'/'`3jo"7+ʭb!fQ~I?L[{?-V1HYVp hVI+߾] dV{k}qכoCՊrXlWz, 7K3ɺҭ֬&يrX쥨MjxtV%[zY3!  _!z*DfK튟r}ONd?rX!Êr8\ߓQ!7R5qP! >|U+$>FTHyFW\[H__FQ.Rynz@4嫊](ר([C1a m&;pܑDӃ\G2/2E|UA!S'Tn w=ɺK_ Ӗ25ɝH9ىQUtÀ]  @ @ @!BB  @h&[O$mۇ9A7 GOANށl!^L/q!hR#M7YD:Q#W '@NE2 x؇ȏJ"GsQ1{8&^>* II<QJC3y8*19y "ZpeLK>H"3$8CdnI9n@yשid9 'AL~`ށ$& -A 0qw Ge@G !Iq\VAT+q W#@- @ @!@!BI_@Ak@setLrT=b$~<8Yt$AC()޾r~nf]/z[QV%IG} d GJxCUR vAnd}-9(: O{OZY=,Ș@m#@CZVzYV!IHl9/ֲjVG.yWuBdD=+hO:L_!a$'%mDnBUE$O+XaŠ}'iܾN.KkF:wbV!Y0=i?~iL*3qsȈn w=X ɝHyDyl@*dad.@!B!B @ @ @!BB  @ @!% @ @ @!B!B @ @ @!BB  @ @!@!B @ @ @!BB  @ @!@!B ?rA-IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/rails_overlays.png0000644000175100001660000001676414754345605024514 0ustar00runnerdockerPNG  IHDR(sRGBPLTE4 +WT X*#JVqTIi,e~~`Azf+8hWXą28I5¯2xXZJk(Ve%gKQ[ V9GQD}5?6wC]_XH\K.?Xd\k~*@V@5`S-.3ӮR= X*IoH3ɁƋ|( 뿃{:VWE> ` aE sg`$U2A?/i_P҅9fFGX 4?ҺZ'?k\yw0w sUy|>x$.Ib*Z;=IVj*떲רP^O0zIwYnBGU$LkVš.Ԃ^d8t NP[[e/(>krQZ풨Md0JB mxeU'q-BO> ,fծwcuGiU˫jTSn`At|֊&;}׹ 횔Ek(}O`*6r/nw]K]* 7Z7x!'jY]#F0Og3+h*˞ViLa+fRB"^H1(Jӻ< bErP"@s4)P Sb~?RY똲YQYgU*sRkf/(9TG"<+QF`D|hc e|KMQ&۹s](K&rhP4fƚ)2ْyVEԴ֮=\kXd'\@b(U)XB~<1YeJ̽v, W1M<ԠcAT LZ`Z#\)ӫ"Ϗ`Xp pXVpXVpyDXVw5Y}`ݤ~R)Y;<ѪN)_= JB=X"âmoQ@8nR#O)Pm{6A ,h#d%cz Û! vU om ZLAZ,oSIz7 K4 FE)#2S`;]kŒ(B|}&WU93^:βh+'9J0MncҒ؅Oj,LT%)(]d8"J@ 49.UӬJUz-JK2ʄ5LC(C 1'7>*ɇ,xQR$G|fSJmgPT6`U-GY#Jk˫wR(hoCC }XnY(KikM0& R՚/T. _k2͂&G A0M-r "R4 }ѢCwmIRLFiMrQ`*Y>K,Y6"e1Ä5LS/@;U5 vPhjiYШ!BSx>c[(*mgBvРQ*+5̺qV0}B#b C`\a)2g26l"Ϫ,۔4'+ݺ+,k0x1j`@R\(TR$cMa4\ `uX?coNe B=]fcty}?)PwEaEG"-:2ԫrgʭ[ J(e C[~5j` a*]Ư ^>-dlKX`0GEQa UaaTF6T7kMÙbA 5yBQ!%_i yȂ]X[ Cb3ߘª 7B1VaYBKR\2ۣ0rh#5Ed8=ns,IHrzfP- ̪YQ\6E>]˧Feè/,7-09 `.N[¢KQ!Or O?7n0B` E6b?Mx9A\Π.T5dS85!XqQHFˈRwQPXd yk+r949V,R.oF 2I sI.h0; `Rpb:SP(V9{It=3re\6* KYz/n nb?(7P7Ň&)BߋxR-:LX|{Xςucak%lfWa c 0Ea`;=lC-3ūEwJ桉m1- iQO#{*!LWXF z ZI'UzC_vUe 2kHRYAΈ¯uQHH@ug2dBp"P~5.ұ(FW)k)؛؃P%Xէa\9ri+= *Q+ @N Y95qu_)5Sqz㤳]W}el&  '<.pr|W]Eㅫ<.IP$ }*"O<.A|W.|}e[Z VY pXVpXVpF ~`Xΰp 78u,@;PX_ZLq9K :kch(g;ªUX&D^es1r;R*2 al)X588aDf+eBaQe2cz]uLOyWA{0,P/ice^ dlQ/#oȾ\CGX4EJO X $ks5maim*zjڭaԺ27O{WX$ K#f`!͵JHLwO}UE P>z+؏1ߵv]kRá#+ AI\ea@Bn>!Ta~њX\Hf2ﴊNc  i*kV܏ K>Wl'DvN zН7E|jch*AT`ݩ訆Q!3h*ѾyR|>A>l QE%,`i `6Xi(ްh _l1Jƶ'gl^}fqŵVJEyV"WY۲RO(?Y|Tu7yUU*ЙFORp[I4hMEY9ʼV0(5ܮ:* ( (X$=g"e#(HGњAL<cU( A,Ŭo򪢎 EbX~?ܵk1Y7s, ZTDw`]ʸ#g3rɳnAO`iXw>kqwgJkn_~Af`M]eX`duXs֭Zg-gkZ'zd>kgyj]rUkjzg*GXv¢m%j NL\BX pX pX pX pX pX pm6_V`XV*Qawq_V+5bQF~!3ݢ D8[,U}U#O' ׷oKfQCA[ҬTvKD@5YiE'kz4! _=ycUz|NkXani'bN\5gpt*6' >a̻’7^嚌!Z7=K, vD 6]i5u@e`1U07H׬($u M֤\,j ++vW0)`S4jhM"jMo # X\kIGfAʹ4?","C*~(0)?gQk&{,&V0xrt laEeΤ6oK5?zy7[ '5]T5\eKE^@9bZthY:&0(0`Ϫd Kf` I]ǃ4՚ a٢E g۞Lᙓ/gX7V{EUG?{nV\;)LoXsZ.g܏ 7K9U `iyG ?X.Q{bF۾8EEeJZ,%^"Fr/!bzXDLa|-RlWssOFѸJȺ+2-,ը5 j3QcWXuTJ+tr>b/XMV,WFjSr3751Ͷ[yXo|: ϯO`݌+ =Xc߱` }*zY)X ܡkEXL5.ٚEiBб y@p]Vwk eKqjo7uUSj֚``|mJ` .U.Z`maqq,!۷Xok sKEUб[G[`mVT+X"eXہUyWvrjkq\:dc~jM*׬FwyW]A6a궪fAZe06V; 3śKG KzkrSX`+ +\ W`+ W`+ W`+ W`+ W`kEPcmIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/roads_blue.png0000644000175100001660000002560614754345605023570 0ustar00runnerdockerPNG  IHDR J 9iCCPPhotoshop ICC profilexڝwTTϽwz0R޻{^Ea`(34!ED"HPĀP$VDT$(1ET,oF֋oZ/K<Qt`)LVF_{ͅ!r_zXp3NY|9,8%K.ϊ,f%f(Aˉ9a >,٩<9SbL!GĈ 3,F0+7T3IlpX"61"H _qW,d ėrIKst.ښAdp&+g]RәY2EE44432PuoJEzg`̉j- -b8o׿M]9La.+-%Mȧg3YះuAxEK i<:ŹPcu*@~(  ]o0 ~y*s7g%9%(3H*@C`-pn VH@ A1 jPA3hA'8΃Kn`Lg` a!2D!H҇ dAP B Byf*z: @]h ~L CUp΅ p%;56< ?" GxG iE>&2 oQEGlQP UFFuzQ7QcYG4G۠t]nB/o'Я1 xb"1I>Lf3bX} *QYvGĩp( &q x)&gsF|7:~@&h!$&B%pH$D.q#xx8F|K!\H$!i.%L";r3EHK-AFCbH$^RSIrdd 3Rx)-))zR#RsiSiT#Wd2Z2n2l2d)EBaQ6S))T UEMSPgeedɆfȞ!4--VJ;N g%K-sɵݖ{'OwO%)P_RRۥEK/+))U<د8䡔TtAiF쨜\|FyZbU)W9.Kw+YUEUOUjꂚZZZCu:C=^\G}VCEO#OE&^WOs^K[+\kV֔vv[]n>z^^u}XROm`m3h01$:fь|:kG23hbabhrT4ߴw3=3Y-s.q_vǂbgբ⃥%߲rJ*֪jAe0JOY6rvvtXLǎl&I']$NϝM.6.\ι"En2nnn[g=,=t٪E2}4\j5loDŽǞ~q=''Z^utv&vvEv >mяN9-{ LOgsΝK?7s>xOL n\x }N}g/]>uɫ,u[dS@u]7ot.<30tKn]p;;SwSyoEVnGyta_oKҖGڛjv 2Pǥ^[JJ|(cFfa{Fm#*U $^N  &+Fi+QnRfr(X{hRbi惢V`W!IxjjUY_{$ޠYEm֍։@UmArx1LUUvzl>Oָ`!SXͷP&yxN㕢^oK[:ǐkP0Z+5f(<+z(yR7 WC:q=vc Ѫ(mƘMٌuk%rUoT\^gܗiP鍬:m8l#)iQ-|%"'h$ceHH FSC`CVyh0Fso୅j])ᰨZbRsw/-F㉃AsH2yB 룵2,&zC$>@ -6IDhEq౷m.scӪ+ '顅ěҵOuqmPF/\-@K,8(dk-҆&zv0~:C[n&>/=v_)mI3DL/yZWڂ}9,F ^:vpw..uP&HRX2rUv?_ z FOEpzD,C٧61l>+8^( &#p!'!NV50&5qU JʹN瑵G%]7]c(UuZS-tFe05-^Xv5硩Ml.;$87;E\2n}!j-.s\T[Zꥡo4w L3Z!h{=}YXr1nK95YpibNg2W|<->d"R8 *ODAۼ3OkeOzr=6ᘢ=ā0J7R.[SqL m:Yt6n] mA!c^*zo\UB4Lg!٤i,$8-CmQCmLwՖ^N-D,>`ˋյ [6 G4}\]^RAh(zl&>ۖT4&k7(i9XрaL*=lYVQAl.\ 5TÇWnKF"n>W-t$êWz.m}w?oWu-϶_WUdjNm<0V;r / /b~z#WmzہUyKoK+"__0u>";Ĥ ykfZ $"UO>HG`]. kXJPW1eݯ>ʷw'bݠ g!Wo0|㣶L=RbG~,vSN~.O[>)=HޓW nCͧoPWP-xK]4gخ_I] qzbRg붘t3ƪ+7 &ݭ5{c>u,K!|QfA=Z6+=w/mtW Im3nPC!FP0o >zE=eL4Uw`ծ䅫j+IY—;'ٌ8{ Է /MQ_  BUD?$AAAA B3ECQK(@m8<7ʷ34_;IF`BߣMI=lj߾R?d K%XTiWUhp ~W?$NI_?$gğ%H[UN4 SВX Ec ANAL/՟v:y9i\ΎJ?H>P6#pؕ l?v\FIR|Kg;e`84;ì1)b.?D},;S)|=Ш#IXpНe4Dݩ_1 '>_|_Kio-rMiocz*!$^^[t|U*FegFZ_ +]UiHՎisKZ$y\ Ne(ƿڔU=$^:PKԒ\UN7rFQdiY\E$DFxkvT|oGrm#-=h? A?NYOQNph%PjdN'LG=tU&2>|w b#riӣgUөYDf֏L]TalzֻNEa7hknu9< n^!Pĵ74t)^g#3Mϋm5G[ }LZ*8^)*؏:ϖgySY1;G.}B Xr3Y(K]URG=Hhɻr.[e.dvxPe1bͣVdebM_إzh߆9>Rw(}ăU:++'6¡c+Xr!Qg I}ܚ4me!2 6?|'>kΣC7:hfa}aRqÕ*m5'aV,աer6CGjk|"n!^q^BuvHk]x̓]bMOҵ]d]fY,>DFv*ɒSMspQsg[')FwoЦ3^1KbxKU4Ð5 jvYeYِio5>[Y_̄A3OF]{lٶ9ϛKXJxu{PT궎d9|V9}kI6Wq={Br{i^z;ڎսXsBf,=PithypttlC6+S[=ؽm)뭉g 2Se$o :ХsQ9n5K bŚzY]iC ?ks]8*yN?$X N$ W6(]T]ofD ZB2kV+Q+NWz)^d32$Z Cbkm9\YzʇUMjviuOs⳪Kj6ܚ&Z)>[#߻oμۺfywYwVl$4}z)}wοYq 'JY-yc2܇MgI.xfQ4e!,'7/F)EO$;k(?rΨuob^ǏxVU|5zY?|^nwS&Ii4y)|ů S>UA __eq|k[!YB+:/\?6.@gP_?R;9s-с -=l| m:  Ȫ"rzC6w7    J_lAA $ww/DTWCvbl ZE3Y/?j8y85Y)ƍle:Ppr HKBO46^]q=.xˑ> b=Emb5긶B@iJem7T-E!Š 3|RG𬧗oyO(nuV:/AA"+m >\7׼9$|"Ej>i{APOAƵ&S>N:Q "R A ~m=]sNӊ[t$%Ж0ÂU!v 9]͓/T΋EQVT+uA{*K~@|Eju ̧hp(OT=O0wt}Nu<[];T=y:R;!k䇴=by(@c[l`rC{X2mE#W *צVxQj4K^Z:d|_u,WaUE65IA:">z?JRcdE|aNR'1g,tЂ eUnE !$i~ {9_:Y$Pj]Ig\KVZF*#\s_iYs8tP"րC=_9&=1'Gɩ Ksku@R{yӞ{@LH'Yr^Q$c kiYrzvWKm>S: yF1fZ'fHһDEIKY-[5Aڽ2/Sd6=< vkQP)lM} 2y)GiuܡiWeܣsy b] ϽȷZv/3+Uf=c?Ic<؀seE؊CWt+32 *X ȶ9R^{,FjۃL>/c[?/D۳}L:j>9׬m;*e£9HVJ)> )ܯ|A<m_Yѵk)zTmu"uAVE6U3iesJ<,OU m5my1UoއytImSwA3,/.c{DX6]a=$ f-|u\+yG_;XXt#k)A&uj:uA{P,k_^$/g4Ŵo1iRc㾕L BUs76PGd{ b_vϢȗn?l_[>^}į5[/r]:uAGgn҇M F BQu&f;!/E|Gt(/SM vfօv.V8ap(  Ax                                       <@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG  31A.ջXIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/roads_red.png0000644000175100001660000002560114754345605023406 0ustar00runnerdockerPNG  IHDR J 9iCCPPhotoshop ICC profilexڝwTTϽwz0R޻{^Ea`(34!ED"HPĀP$VDT$(1ET,oF֋oZ/K<Qt`)LVF_{ͅ!r_zXp3NY|9,8%K.ϊ,f%f(Aˉ9a >,٩<9SbL!GĈ 3,F0+7T3IlpX"61"H _qW,d ėrIKst.ښAdp&+g]RәY2EE44432PuoJEzg`̉j- -b8o׿M]9La.+-%Mȧg3YះuAxEK i<:ŹPcu*@~(  ]o0 ~y*s7g%9%(3H*@C`-pn VH@ A1 jPA3hA'8΃Kn`Lg` a!2D!H҇ dAP B Byf*z: @]h ~L CUp΅ p%;56< ?" GxG iE>&2 oQEGlQP UFFuzQ7QcYG4G۠t]nB/o'Я1 xb"1I>Lf3bX} *QYvGĩp( &q x)&gsF|7:~@&h!$&B%pH$D.q#xx8F|K!\H$!i.%L";r3EHK-AFCbH$^RSIrdd 3Rx)-))zR#RsiSiT#Wd2Z2n2l2d)EBaQ6S))T UEMSPgeedɆfȞ!4--VJ;N g%K-sɵݖ{'OwO%)P_RRۥEK/+))U<د8䡔TtAiF쨜\|FyZbU)W9.Kw+YUEUOUjꂚZZZCu:C=^\G}VCEO#OE&^WOs^K[+\kV֔vv[]n>z^^u}XROm`m3h01$:fь|:kG23hbabhrT4ߴw3=3Y-s.q_vǂbgբ⃥%߲rJ*֪jAe0JOY6rvvtXLǎl&I']$NϝM.6.\ι"En2nnn[g=,=t٪E2}4\j5loDŽǞ~q=''Z^utv&vvEv >mяN9-{ LOgsΝK?7s>xOL n\x }N}g/]>uɫ,u[dS@u]7ot.<30tKn]p;;SwSyoEVnGyta_oKҖGڛjv 2Pǥ^[JJ|(cFfa{Fm#*U $^N  &+Fi+QnRfr(X{hRbi惢V`W!IxjjUY_{$ޠYEm֍։@UmArx1LUUvzl>Oָ`!SXͷP&yxN㕢^oK[:ǐkP0Z+5f(<+z(yR7 WC:q=vc Ѫ(mƘMٌuk%rUoT\^gܗiP鍬:m8l#)iQ-|%"'h$ceHH FSC`CVyh0Fso୅j])ᰨZbRsw/-F㉃AsH2yB 룵2,&zC$>@ -6IDhEq౷m.scӪ+ '顅ěҵOuqmPF/\-@K,8(dk-҆&zv0~:C[n&>/=v_)mI3DL/yZWڂ}9,F ^:vpw..uP&HRX2rUv?_ z FOEpzD,C٧61l>+8^( &#p!'!NV50&5qU JʹN瑵G%]7]c(UuZS-tFe05-^Xv5硩Ml.;$87;E\2n}!j-.s\T[Zꥡo4w L3Z!h{=}YXr1nK95YpibNg2W|<->d"R8 *ODAۼ3OkeOzr=6ᘢ=ā0J7R.[SqL m:Yt6n] mA!c^*zo\UB4Lg!٤i,$8-CmQCmLwՖ^N-D,>`ˋյ [6 G4}\]^RAh(zl&>ۖT4&k7(i9XрaL*=lYVQAl.\ 5TÇWnKF"n>W-t$êWz.m}w?oWu-϶_WUdjNm<0V;r / /b~z#WmzہUyKoK+"__0u>";Ĥ ykfZ $"UO>HG`]. kXJPW1eݯ>ʷw'bݠ g!Wo0|㣶L=RbG~,vSN~.O[>)=HޓW nCͧoPWP-xK]4gخ_I] qzbRg붘t3ƪ+7 &ݭ5{c>u,K!|QfA=Z6+=w/mtW Im3nPC!FP0o >zE=eL4Uw`ծ䅫j+IY—;'ٌ8{ Է /MQ_  BUD?$AAAA B3ECQK(@m8<7ʷ34_;IF`BߣMI=lj߾R?d K%XTiWUhp ~W?$NI_?$gğ%H[UN4 SВX Ec ANAL/՟v:y9i\ΎJ?H>P6#pؕ l?v\FIR|Kg;e`84;ì1)b.?D},;S)|=Ш#IXpНe4Dݩ_1 '>_|_Kio-rMiocz*!$^^[t|U*FegFZ_ +]UiHՎisKZ$y\ Ne(ƿڔU=$^:PKԒ\UN7rFQdiY\E$DFxkvT|oGrm#-=h? A?NYOQNph%PjdN'LG=tU&2>|w b#riӣgUөYDf֏L]TalzֻNEa7hknu9< n^!Pĵ74t)^g#3Mϋm5G[ }LZ*8^)*؏:ϖgySY1;G.}B Xr3Y(K]URG=Hhɻr.[e.dvxPe1bͣVdebM_إzh߆9>Rw(}ăU:++'6¡c+Xr!Qg I}ܚ4me!2 6?|'>kΣC7:hfa}aRqÕ*m5'aV,աer6CGjk|"n!^q^BuvHk]x̓]bMOҵ]d]fY,>DFv*ɒSMspQsg[')FwoЦ3^1KbxKU4Ð5 jvYeYِio5>[Y_̄A3OF]{lٶ9ϛKXJxu{PT궎d9|V9}kI6Wq={Br{i^z;ڎսXsBf,=PithypttlC6+S[=ؽm)뭉g 2Se$o :ХsQ9n5K bŚzY]iC ?ks]8*yN?$X N$ W6(]T]ofD ZB2kV+Q+NWz)^d32$Z Cbkm9\YzʇUMjviuOs⳪Kj6ܚ&Z)>[#߻oμۺfywYwVl$4}z)}wοYq 'JY-yc2܇MgI.xfQ4e!,'7/F)EO$;k(?rΨuob^ǏxVU|5zY?|^nwS&Ii4y)|ů S>UA __eq|k[!YB+:/\?6.@gP_?R;9s-с -=l| m:  Ȫ"rzC6w7    J_lAA $ww/DTWCvbl ZE3Y/?j8y85Y)ƍle:Ppr HKBO46^]q=.xˑ> b=Emb5긶B@iJem7T-E!Š 3|RG𬧗oyO(nuV:/AA"+m >\7׼9$|"Ej>i{APOAƵ&S>N:Q "R A ~m=]sNӊ[t$%Ж0ÂU!v 9]͓/T΋EQVT+uA{*K~@|Eju ̧hp(OT=O0wt}Nu<[];T=y:R;!k䇴=by(@c[l`rC{X2mE#W *צVxQj4K^Z:d|_u,WaUE65IA:">z?JRcdE|aNR'1g,tЂ eUnE !$i~ {9_:Y$Pj]Ig\KVZF*#\s_iYs8tP"րy%?܃XJ.٦=*U{yӞ{@LH'Yr^Q$c kiYrzv*z̦kz\MFA~b{VrV{JsIK3A$d]"͢Jiެ-y r)2k;5(V"bsn|ʿQZmGwhdC|^daXs/򭨖 JY.vOX&dlk 6gY4U89JLz=A VmN˿ OKX.VK4уltZO5kbiζJlhRROC%"{@DWVt-ZU[ݾHAxͬkLZܮ3GSxUCg[~h[^L՛!y!]Rhje] >KQ,8Mmds ¾Y zJ^f/l!l.݈|m~uE{I=NA^$-ڗɋla1Cp[ksL-`toe}:P,z܍&^oݳh-%d0O,؜vG[h%h2EbATE %\'0̯Z dbvRGko Vc2\:iI;%-tP7?uA !Ǜ"InˊσVfll˵}NAY=[!yCQ:CPTݲ

 H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTE4٢Te/BQv@'O[RN/(;YT].}qx,r~`{ӯ]+#TC537xZ롙,rwL`Q"f"Uշ{#}/  b*fj/S>~O2Q5=ɢTLELtPQ91_ 339 7v݇S ,jf|zދRSo]֢&âjLU-kjfnFQ2f+;Tj/*60}*Y؞=^JYVFb{K-Et Jvn\EUTMƠ&BTIڶtDJD̦N M/ 'jX! AMuUWY𺮛]է,^J$VD.iFQ 6UD6\jʾΟ2IiiUe7R;&}7Jڌ QWƷZ}'Ȟج{K6f2B6vTW3R\/_i|(e TY)b"&"VGQWS{E-Ͳ*V,#(VQvۇIgM[sk!]ʐ` Qmb( "cjѸY򳢺\E̬9^yQ댘͵֤Qel ;jUlE"?ZgT$k_&VZxHs-<byF9*RN:#75HM$~yYmF!~2ZĬH2h_o"\,EY4dTĆEj-k;^25k:{iCZ]'龄<Ǩ_>b]׃+3+Y{P4<RD?bk,AQ8lNY)s5KeV%V<i5N=Oc6U듪5tGC7ŃZOdI0Xh3DaY}lf*ZWpG_Ebl2QSij >#&El6+ Tdj0oaԛeb2W1)&1+}j]BO9UyVqn>U㪻[i٬+sDmWݤnX4ײ=zզy:- VdĚi ŽI)h~ n5j.2"WnYVo-WV` lrKd.bU9i}b^/1mE|ZL,`wUo{ಘK),gFŤT)%}1E$W 9Z<*^]Zn֮{E-1ZUGD =]J1O)Wkzɗ:JdtV윽SԔ>OJR$cu}ߍc2]a jIIYHpX^Ea\b{h@l?C EhD|+z[|{7cί6Aѐ܁MpjI%bmiZS.k]?2~A+H m\djB)Ûe-!·%WCO)1\OntxoJz)EcU5KfFGEA֧dzZGlޣJ_V1eir9$H/AIb zZȲE|bt{hl=. װj١?m|51U{W =Ʊ8I` 9,o|clc/HݼOכXO[=)j Ms/bW^=C͇`ZFc6yOQA@rWSr6\KG)b1;ƪ3?(}Un 8娣' -z։͊cϝ|}YJv?wWNI{f<qϾ~ ouLEN~~xQ7koӠo~]?pʍ䅧ZV, r< fWQ#o)J}UMʁpQpVGSԅA    BAXhQP( A HuQ/q=%+ggGٷ/O ϣ$>ǧԇ'EAN孵>>>TyW/HoD /$_#E;ޝMni=d(wwB7$m_!rOJK}x RKv?}ےny̯cz*%$˝v ZklZQâ2U G7Қ>v?NQGkO{-=/h7A2mނyq2lazB*!Ӓltg+Gk9R]d(W-'J{sڼZhE-;vS›E Sa͂4uɳ`ktyXt$S֠뛝ONAbGEo[ɆtS]/"=f>]F!GSxYȭn7xAF^qߺޗut>cFfZNkUR5?\iyᕚj G)ʽy~k)=muuo-\|" =D2׶2R*-oA|I%R*V]?VznBh3ؒ'+;VG*UWiڷ݂l_}UϽ\֧ޢ,.!>슢Y>Ub D"TAqQ"Tًb 9pKܺACŷ%JW]9Ăf~$Ga 6=pD2mEeڞ;3T~t![k"[5tUIQ=^1o 1tHu5ʻmgZ8- ~|-X_D=R뫠Lezj0Ӈ!f5f2]2ͷKdruۍFTwH\w%"%{۪vwx̞spo=Ȕ]̳vZdcxnuOGNZzfAvk==j-f:T>quZrh}f$DcR9W2oՃ43/o:_s 9n5ɩ9H#/\ Hrn>mu20uSwǙܛǺʙ-p%;zu{mLKBBYD>E]"/ŋrfsu;E \e ^ZٲRyTj#;TkiP{xKF6pZU ݭ?Yu.do|ۺz7SۮWUse:sy1ePRzOٮ/Ϗ#'ܞ-8yMכ~[獝3exftI,y,jRns_}l35־D{"yǢΔcF{Mӏ/^/jFuh7i@ZnZUl^\&7rYy֥J1U7{pmd:/߯3Kr^ߏ$7W=Ʒ4;ë/%fs[矧?_uFE4%WM{]bwMd?Njz47r%S~%A߷]@m$$Ml8哛j1n?N'܅$Vf\z$l6Z}m[D-r{@F51?2f?>ڇ8AbS~fŢaߝdAbW ̑m,= l5NQ]RWy۷d,ңC?\ދ&/%7>r\SR,W~ A22"ǭ^"5 h%3.*oVoew@yrٛM2>K4u?M:=SG< %=_ͷdIc\_v I-sq:co'/wrhO:$r 4W۝G{;f?|_T'?x#'뮕P@frZ2ܸ(bO%(̺C WJ㪻0*Y[GsWu$xo?W'P$,q Y}+ < FWuuOm]:@t6\\?ž: OiY1'|4ߙ[ֵ]yPO˭?xq$l:DW\Zwc,٩<9SbL!GĈ 3,F0+7T3IlpX"61"H _qW,d ėrIKst.ښAdp&+g]RәY2EE44432PuoJEzg`̉j- -b8o׿M]9La.+-%Mȧg3YះuAxEK i<:ŹPcu*@~(  ]o0 ~y*s7g%9%(3H*@C`-pn VH@ A1 jPA3hA'8΃Kn`Lg` a!2D!H҇ dAP B Byf*z: @]h ~L CUp΅ p%;56< ?" GxG iE>&2 oQEGlQP UFFuzQ7QcYG4G۠t]nB/o'Я1 xb"1I>Lf3bX} *QYvGĩp( &q x)&gsF|7:~@&h!$&B%pH$D.q#xx8F|K!\H$!i.%L";r3EHK-AFCbH$^RSIrdd 3Rx)-))zR#RsiSiT#Wd2Z2n2l2d)EBaQ6S))T UEMSPgeedɆfȞ!4--VJ;N g%K-sɵݖ{'OwO%)P_RRۥEK/+))U<د8䡔TtAiF쨜\|FyZbU)W9.Kw+YUEUOUjꂚZZZCu:C=^\G}VCEO#OE&^WOs^K[+\kV֔vv[]n>z^^u}XROm`m3h01$:fь|:kG23hbabhrT4ߴw3=3Y-s.q_vǂbgբ⃥%߲rJ*֪jAe0JOY6rvvtXLǎl&I']$NϝM.6.\ι"En2nnn[g=,=t٪E2}4\j5loDŽǞ~q=''Z^utv&vvEv >mяN9-{ LOgsΝK?7s>xOL n\x }N}g/]>uɫ,u[dS@u]7ot.<30tKn]p;;SwSyoEV(H17T1HYۦ"\ݢ FQcooIcn'hʯ 7_V YbHjG(e)Jb{U7oP^nYA I2#\u>R~XJb0tT +QE7=JINQ~-ӕǾ#>2%)εfkMELEKE͉\֍\?أwyxUp&折fu֋tti=Sc:ʕMTrMkZNZ݅?ӪW`g./U-6b;|z ± 痤-7c%HGQzm+Y*OOm\ cU=K.7Rl[:Gt^U)QI!MAhIDZ(mW 6IMd,ʧ,^{%0W,mÓ|RT *$RTmm_x-JIk- UZf[ql/ $_Uڿ*$T]5͆dk EU42EMS+4oU,z=mC*\B-JjU,D\۳K8+ Cǡm'P UQ)";c7e32 I7u|TWb"5̿yӠYuT1pGRO5LiV-|<%"'h$cez R/RE+ؐUVM}ƈ7rM-xk!ZW,C8-֠X*1=`˦|w9$Wg4WXׅZwJKHS! iU%6IDhEaؗm.scӪ) 'a yQwe}7k:pKm/ZOQVe?sGWZXw=)dk҆&z8|:CopTihl _/ʔܤS "&7üu+j{mA6BҜcX/u|zқHN:}(]ѪI<:(-$\wW55|k;ü(KQ>7Xo1(|e9&g !dDW5䤵=q=]o}\c"M[.䪜tY{$ZBo[t܅صl{Wl jNmm3*cP;*.º3?)Mmb^w!ŭ&A0ԹȪ2}!j,&s^T[뚛깡o4O L3Z!h{==}YXr1^gmskZf\u:34=!cPteH☃o>y&^t]qzY.{2۱Y-!&\{+Eaލn.\y6*'B}Jul9 A,y0MۿsUKb0rKg'fg!eAV~E]\? ]. gr!o!b0/V.l$@$T_opԐquAv{,^H]YFCcs6eIEcv4-' =0I{~V-˪bu_|+EB:c]uut3fQlI]0JqU_bAG>z+鲬Oǭ JE몊UVө͔jGMAE/]Omj٨vmUz GQw4p@w?ͬH>&"eXW+6Qɕj}rscjS|~waq+ ߠ}r+7>j4]c,u/x}ُnO v%ig@E-|ǺU}EdU k j`W=vvT >Uq+9x!o}XCLluzXu=4߼&c{LOR_~Xv֍͊˞?o_m}Uoek̽xk(Q>+ 7!En>zQ/kol~§n jWU-+IƗ;'GzNrQpW?'+"E!P #@@@@LQ(P) Av>7oYvf}q$ &=Qw>NJ c+͢O r@co󓰿"H'L!ysOz,'ǿ!y=$~/Ar;䤨ri_t^ǚ(J3o:1UY6vHsyWZz Ei32 ]Iȶcɥ[l ɷD~YqPrf>}գI)M)WirJ?mINi_AsO^JߜCx y!>mI\SGZ1Y^_Gu|U*FegFZ_ ]UiHՎs=x%-n<Gpk`FKr܇UOv_ƿڔU=$:P$dZv\,ën` 띢BҲعfIڍvQQ%˵rS8eA< G9U<{]y;3sVQFD҆[ٳTҤ]HXDf֏_TalzֻNEa7hknu9= RzC١[o*iĽR4;cFfFjRR;AKU{=(*؏:Mg~SY1;G.}B Xr3Y,KMUiRG=HXw\Ce.dH2bĚGimʮUŚj~yI jߺ9?R(}=ăU:++'6¡c/Xr!Qg Imܚ4e!`i:|'9iγCw:hfa}aҒ-tUjN%XTCld\Lϧk|"n!^q^Buv{HkMx̓SSbMx|ok/oק>ݲZijzVک$KN5mEYZϝ*۵?t4 M;f֭b֗ tѡhՐ5 Z ~}זregCƣIal?`\2`& <- luq.KcD+EĻuۓjTwHg7ZOʼ]KޠeO Qݿ.$'\~xVǶ#혮?[݋5'0yfYA5OOYD>sׄKW# M/bجNIlAcM+7v.K%?}_M~ 0%ZFey#KGt.*ݭ&6u)AXS/H5n|ޗ|Vk7][ lV-*yH,a+/Yz]AE9!3cZ7t!イr a{omV ׳Ҷ"vs_ zا Fx|H,,*,È2ud's^;SM,ë:ꜯ_/aӐ߿ݴ{ k&xMnj$+˶wUc]^־> ϥ#y=#y?$7~%yC^h҈w2;ɋͮR%9/|~Aڌ䧋Koeo%WrɗeKPwu䧦<؏cs҂oaŖهÝI+v TjBp[T?,]ǃ/ %t_W7,ۡl^}k[D-A2CW3cNsf颡iE]?=akcQzAh5men{,~?xx6ϥuu=Y' z]KWv!֙EAۊeE 逡 k%d}Ƌ%%La{`ӶS G!'-Lf; # cQ@{b 54aI8Y?Iu=G9 JٜSZˊ6A+jir=>닐##rhNz)5c8eK-oewbXI7oջXf*|ALgFO^~dU-}݈qpi:=:c; ;29t$9$hv =?3$"*z#ҷM*#}ڿm b=Ef~mÍ( $ >LnO3[CDg; ?YO/1 yO(ntV:/A E|cW|)foy#sI&~ 6[vڧ%6&~ 痓6COc!:V+}A=Þ?ɭ x"4Kj2;s 퐮" l`;B. '#5qY|=?Kq.ZQA[;*&!GSsvyҵy(;x:ʽފ*x>e ȺMŎArk/UC[`>G[Eypz's8=9[\: cks#sr9OG~H{#֌׋8NQ {(74̽ǔjH ǂxH7֔NZ(b5K;+P3K2<=hruH|X&2  kt I 1E|}Oj?JRcdE|aHR'1/tޓP$MABIًdm$Pj]I26g\KVZz*#\s_iYsKQ,8d0ҍ}>:ѝ,^C,\]5e RE{`s5: ۽H[Cҽ//3bڇX& *6cyL\[ٞ,aRj=sf//7ݳh-%3O,؜6GвKdł+">.yo#:,J N`w%vZ dbRGq4:ԣ{[ֱM.]$՝:֚͟]tz pCCGXeUgVfq5fZξK 謞Ь@|CQ:CQTݲq uAgYNG_, TF;Yru!98 =  A                                           AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG                                       #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  |޾VIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/tram_green.png0000644000175100001660000002317214754345605023570 0ustar00runnerdockerPNG  IHDR J pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTE4I\ r>]Zkf.z7,BFۛo~{SAh AWs<)72ӿi퍦| nOM~)s[ud3sڔ-3sv727?ӡ:̭u']hI33kbS'Z4;ݷkJ|}U`kʟfoWpD$̿Ie?tIښHlJ ϦtS!5̻:T$77s\R"gnr6HpJyH.}ӓ֏3hJh=D]mC5MD4&).;CnCTW{s%y>QQkqkiSĕ]rI=Fg̷@/މrEQml EC~o{h}kAXΨ>+{OjDW!Ac{U5jvwŘԴbYRi2ɏ_UDvOB}4ٛ%KsrANW;Q*xץ]jduDR##miZ*ģF}U9VkS60?0jMm9n >%H n.˕j{VGb95ȗ#d/jS y/j"J1_OlFSfVP"BE6-Z^QkG֬^Szb5ϲ!hм9} Һ%"O;O*W.xU򫦺\MJ_7τ4Z魢Yek߽5U~j Y_ZqQ,KEڿEf)s-U)˨4bYNYuhCs8:|!Y.65z+yiv-_8_9TKH%=[3wk&DD*s$kc@ʖp\9v/Z)bEu6i}$^~*O [GkGѭ#Y*DD Uғq\l5ˏ]Uc]s.V4?f/Zhl[n{ߔRkR H{]]G^gOs#<ĖY1ϭ-zx%]՘|D(Y*5Ж:rU}Y1e0oa("CS} 8ݎC1PEJ_ڸ=WQh0Sr[σG`LZ\[(T%Behwj>LlϮj0ϡ֧c:gW!J\/±(eYvHp2դ';Sr_dE:)zl(wV` 7eʽjH\$ƈtxe4:dܷEK{e+?ʲ 6s\6r&R<{vmRkD2wX\d}Uh' ;<]YvlcBZ=G¼ظ,noT;'kBs:2:O2güfZ2 f\Jdx쮯OjBːZR[MeW) qһƛ^'b"V0b{9ZȷqcC>v6/4Aِ܉$uzԲAbI'(SW58kl;re_ۮiNJ 'Zʬ_iOo}8BLّ?:}$V]=4oW}W>rLO(nA9Z?o;}*Mf72ye^WО0j!H_\̻~]2L4UwU( %o\ղݦ ё걓l5u?{oi;wjq4M8YAh A Hn    fM!NSoᾩqx(Gw_T߾(v |mߣN|;_*L6MŦlJO'aE'L!ğ-'ǿ!8 4M7il2lגWOkǚ\hq̐!)VYN'whwߊQ&rkdj6%f*Qj[ii2wW)J<@#kbےNV}eaV{T!թ_n/H:/ioKQ:2rŮ!YUͯݽMe5<32Èm %ϯ4PDhur߯7C|$rf6K'7/NmIѵp#8\&- QUվnjS7oŋr\[t_;d+l04XjqTp^_zjT6sz%ʦ#jX-]qs{doNj Mû_v[w$AڸCcW nRjstk31^F2 )Tg\~U67go"rm GHmz1>|LqOcӰg'E>O ?ϫ^m4J]m~LM6U e zt};$|XlVq\N55KTrwg6KCٓ'S~NSm~ZWf=?UA [)-+zt"$;{B~>~ 'W2 YB 3?a>ѿ;A$( ?# zWl~o?AAAAVA@" b@A^KiGބ yȎe<)B839A"kޱ_.z* FdEMɊ  /@I1ZGk~fbNAn^.SZ!ѽ^ͯd)#_,$ÂjZ}^r"t5blӅ͋#9ƙދ􂴆 pbi{+v!};V'ix[ݼXGGaBiL+h',SRY']YO.%)?j67r]Jiaz 5* 7ꋦZ3Hϐ#zW?m 552>GG"Sv H,_Ȇm #N/nfڅ( Ӥ KeɆr߭Hu(,%dy #CZx(5ܳ&Fdits.>Amnvebzv~ qk(>-9Y/[A ҒtqGm>c!>A2T$#cXB!mMF@An7{爭z; =ig4  /gB  >@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@n                                           AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[                                       -@@@@@@@@@@@@@@@@@@@@@o!ETIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/roadtype_and_tramtype/gfx/tunnel_track.png0000644000175100001660000000315014754345605024130 0ustar00runnerdockerPNG  IHDRdS9sRGBPLTE4 Xb. Qa5գAdAXAV Рjju#aƢǨXb%#)b΃a kmLXcX!7Qt.eq Uu8Mn䬘Uq'G$TU[lcT (OI1T``uu%^c-@R,XL4PWt 4B.sMUi)cڪЙ`jb$~#&a#J){2嗑gŐrRوeFf31EU.X 1IK99{#VR*引"J1RX+(`G>h7)F#;)ƣxTlgT_C}ޥ7bѿ^>}t3`,i 2VAXEXXXXXa+cGX- IJn`i :F6Ю}A)Rqܫ 9MwRz\qVᚾSկV{;ێ%2|rN{j^mn{ l#V)&|r52j':n,R+v4<e]bN9?0Ueq_[N9%s澎J$ZvH"\$t# HG@:BzrfBOtHtHtHtHtHDžt\hH.H.H.HAtP HAtP !!!!!q!ҡ ҡ AtP{MɁr%"KɁr.%L(N zMzLzJ,u-zJ:qj=~IJFĐ[mǁrܧQ*b4A! 72(jf"$s(|:oj#91zPВ^Mh)WR,c7j hףf^]R)?>,_ވ)mY.)\͊NswJ6^ j&bjSIey@ 3%童M[7wJ~=۝(-ћ+e֏*Y>^ 544E(ʇbƛeSRgo t, ҝ(B v| ~5RPNʲ|PHJQO*ʊ\?I(X>>mt]I%)f;|QX]v5lC=rg]wNx?lK٨{8QKo;%ch,اtǏA3>?M3(ۍr/e:uܽhlF>{/lFQ+jN!9@9 openttd_version) { error(FATAL, REQUIRES_OPENTTD, string(STR_VERSION_22723)); } cargotable { LVST } spriteset (cow_pen_empty, "cows_cargo.png") { [ 10, 10, 64, 65, -31, -34 ] [ 220, 10, 64, 65, -31, -34 ] } spriteset (cow_pen_half, "cows_cargo.png") { [ 80, 10, 64, 65, -31, -34 ] [ 290, 10, 64, 65, -31, -34 ] } spriteset (cow_pen_full, "cows_cargo.png") { [ 150, 10, 64, 65, -31, -34 ] [ 360, 10, 64, 65, -31, -34 ] } spritelayout cow_pen_X(a) { ground { sprite: 2022 + a; // prevent railtype offset } building { sprite: DEFAULT(0); // first sprite in active spriteset zextent: 36; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } } spritelayout cow_pen_Y(a) { ground { sprite: 2022 + a; // prevent railtype offset } building { sprite: DEFAULT(1); // second sprite in active spriteset zextent: 36; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } } spritegroup cow_pen_1 { little: [cow_pen_empty, cow_pen_half]; lots: cow_pen_full; } spritegroup cow_pen_2 { little: [cow_pen_empty, cow_pen_half, cow_pen_full]; lots: cow_pen_full; } random_switch(FEAT_STATIONS, TILE, random_cow_pen) { 1: cow_pen_1; 1: cow_pen_2; } /* Define the station itself */ item(FEAT_STATIONS, cow_pen) { property { /* The class allows to sort stations into categories. */ class: "NML_"; /* If no other NewGRF provides this class before us, we have to name it */ classname: string(STR_NAME_STATCLASS); /* Name of this particular station */ name: string(STR_NAME_STATION); cargo_threshold: 160; tile_flags: [ bitmask(STAT_TILE_NOWIRE, STAT_TILE_BLOCKED), bitmask(STAT_TILE_NOWIRE, STAT_TILE_BLOCKED) ]; } graphics { sprite_layouts: [cow_pen_X(0), cow_pen_Y(0)]; select_tile_type: 0; purchase: cow_pen_half; LVST: random_cow_pen; cow_pen_empty; } } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0494623 nml-0.7.6/examples/station/lang/0000755000175100001660000000000014754345610016146 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/station/lang/english.lng0000644000175100001660000000111514754345605020303 0ustar00runnerdocker##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Station STR_GRF_DESCRIPTION :{ORANGE}NML Example NewGRF: Station{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Conversion of CHIPS Cow pens. STR_VERSION_22723 :1.2.0 (r22723) STR_NAME_STATCLASS :NML Example STR_NAME_STATION :CHIPS Cow pens ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0494623 nml-0.7.6/examples/train/0000755000175100001660000000000014754345610014661 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/train/cargo_wagons.png0000644000175100001660000004123014754345605020044 0ustar00runnerdockerPNG  IHDR`;; pHYs+;iTXtXML:com.adobe.xmp Adobe Photoshop CC 2017 (Macintosh) 2018-08-02T20:46:45+01:00 2018-12-21T13:03:38Z 2018-12-21T13:03:38Z image/png 2 xmp.iid:9533856e-39e3-4d0d-ba72-c46b231d6e91 xmp.did:67fcb359-3c99-46d5-94b2-9722fd0c8d36 xmp.did:67fcb359-3c99-46d5-94b2-9722fd0c8d36 created xmp.iid:67fcb359-3c99-46d5-94b2-9722fd0c8d36 2018-08-02T20:46:45+01:00 Adobe Photoshop CC 2017 (Macintosh) saved xmp.iid:91e78eba-07b8-4de3-a7d6-089462ed5e41 2018-12-21T12:45:15Z Adobe Photoshop CC 2017 (Macintosh) / saved xmp.iid:9533856e-39e3-4d0d-ba72-c46b231d6e91 2018-12-21T13:03:38Z Adobe Photoshop CC 2017 (Macintosh) / 1 960000/10000 960000/10000 2 65535 224 226  cHRMz%u0`:o_FPLTE4%ÏɧK6Ƙ!.]Ե Ft)p߽~N1F~/$]Tan0c&NNwц9݅~xDcLi«c8z?0th1iYZn-dOcW,2_c bW}M|.Ɗ>4M!lƔRfln/@Ȼ 9`uk )s[.坜Kix]!Ky''{vg :F\Nta~nԖKynם60̭PrΧo?da6f[!Suv0G 2\1UP\dpo8\ "SAmry[!Gf{L 94[ZdpV٥̻_4Nfs;Od6,f_|V! @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @ @ @߻ "IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/train/example_train.nml0000644000175100001660000003412414754345605020231 0ustar00runnerdocker/* * This file is aimed to provide an example on how to code vehicles in NML * In the first case a train is coded, other vehicle types work in a similar fashion. * The second case provides examples for using the sprite stack * https://newgrf-specs.tt-wiki.net/wiki/NML:Vehicles#Composing_vehicles_from_multiple_sprites * * To keep the code readable, not every property or variable is documented in * detail, refer to the vehicle-specific reference in the documentation. * The coded vehicles are quite complex, in order to show the various possibilities. * For a more simple example, refer to the example road vehicle. * * The first vehicle coded here is a Dutch EMU, the ICM 'Koploper' * Graphics are by Purno, the original NFO code is written by DJNekkid * As in real life, you can choose between a 3- and 4-part variant, * to be selected via refitting. This adds some complexity, which * provided the needed excuse to implement a lot of callbacks :) * * The second vehicle coded shows how to use the sprite stack to draw a vehicle * using multiple layers of sprites. * * Apart from this file, you will also need the following * - Graphics, found in icm.png and cargo_wagon.png (in the same folder) * - Language files, to be placed in the 'lang' folder. * Currently english.lng and dutch.lng are supplied. */ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. Additionally, * user-configurable parameters are defined here also. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the first real grf defined as part of * NML, therefore the last character is set to 0. Successive grfs will * have 1, 2, etc. there, to make sure each example grf has a unique GRFID. */ grfid: "NML\00"; /* GRF name and description strings are defined in the lang files */ name: string(STR_GRF_NAME); desc: string(STR_GRF_DESC); /* This is the first version, start numbering at 1. */ version: 1; min_compatible_version: 1; /* Define user-configurable parameters */ param { /* There is one parameter, which can be used to alter the colour scheme */ colour_scheme { type: int; name: string(STR_PARAM_COLOUR_SCHEME_NAME); desc: string(STR_PARAM_COLOUR_SCHEME_DESC); /* There are currently three possible values: * - 1cc * - 2cc (default) * - real-world */ min_value: 0; max_value: 2; def_value: 1; names: { 0: string(STR_PARAM_COLOUR_SCHEME_1CC); 1: string(STR_PARAM_COLOUR_SCHEME_2CC); 2: string(STR_PARAM_COLOUR_SCHEME_REAL); }; } } } /* Define a rail type table, * this allows referring to railtypes * irrespective of the grfs loaded. */ railtypetable { RAIL, ELRL, MONO, MGLV, } /* Next: a series of templates for the graphics * Templates allow you to avoid repetitive coding of sprite offsets, * as long as you consistently use the same alignment * Note that layout in png differs slightly from the orignal graphics in the 2cc set */ /* Basic template for 4 vehicle views */ template tmpl_vehicle_basic(x, y) { // parameters x, y: coordinates of top-left corner of first sprite [x, y, 8, 24, -3, -12] //xpos ypos xsize ysize xrel yrel [x + 9, y, 22, 20, -14, -12] [x + 32, y, 32, 16, -16, -12] [x + 65, y, 22, 20, -6, -12] } /* Template for a vehicle with only 4 views (symmetric) */ template tmpl_vehicle_4_views(num) { // parameter num: Index in the graphics file, assuming vertical ordering of vehicles tmpl_vehicle_basic(1, 1 + 32 * num) } /* Template for a vehicle with 8 views (non-symmetric) */ template tmpl_vehicle_8_views(num, reversed) { // parameter num: Index in the graphics file, assuming vertical ordering of vehicles // parameter reversed: Reverse visible orientation of vehicle, if set to 1 tmpl_vehicle_basic(reversed ? 89 : 1, 1 + 32 * num) tmpl_vehicle_basic(reversed ? 1 : 89, 1 + 32 * num) } /* Template for a single vehicle sprite */ template tmpl_vehicle_single(num, xsize, ysize, xrel, yrel) { [1, 1 + 32 * num, xsize, ysize, xrel, yrel] } /* ------------------ ICM EMU Train Example ------------------ */ /* Define the spritesets, these allow referring to these sprites later on */ spriteset (set_icm_front_lighted, "icm.png") { tmpl_vehicle_8_views(0, 0) } spriteset (set_icm_rear_lighted, "icm.png") { tmpl_vehicle_8_views(1, 1) } spriteset (set_icm_front, "icm.png") { tmpl_vehicle_8_views(2, 0) } spriteset (set_icm_rear, "icm.png") { tmpl_vehicle_8_views(3, 1) } spriteset (set_icm_middle, "icm.png") { tmpl_vehicle_4_views(4) } spriteset (set_icm_purchase, "icm.png") { tmpl_vehicle_single(5, 53, 14, -25, -10) } spriteset (set_icm_invisible, "icm.png") { tmpl_vehicle_single(6, 1, 1, 0, 0) } /* --- Graphics callback --- */ /* Only the frontmost vehicle should have its lights on */ switch(FEAT_TRAINS, SELF, sw_icm_graphics_front, position_in_consist) { 0: set_icm_front_lighted; set_icm_front; } /* Only the rearmost vehicle should have red lights */ switch(FEAT_TRAINS, SELF, sw_icm_graphics_rear, position_in_consist_from_end) { 0: set_icm_rear_lighted; set_icm_rear; } /* In the 3-part version, the 3rd car is invisible */ switch(FEAT_TRAINS, SELF, sw_icm_graphics_middle, ((position_in_consist % 4) == 2) && (cargo_subtype == 0)) { 1: set_icm_invisible; set_icm_middle; } /* Choose between front, middle and back parts */ switch(FEAT_TRAINS, SELF, sw_icm_graphics, position_in_consist % 4) { 0: sw_icm_graphics_front; 1 .. 2: sw_icm_graphics_middle; 3: sw_icm_graphics_rear; CB_FAILED; } /* --- Cargo subtype text --- */ switch(FEAT_TRAINS, SELF, sw_icm_cargo_subtype_text, cargo_subtype) { 0: return string(STR_ICM_SUBTYPE_3_PART); 1: return string(STR_ICM_SUBTYPE_4_PART); return CB_RESULT_NO_TEXT; } /* --- Colour mapping callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_colour_mapping, colour_scheme) { /* Emulate 1cc by making the first colour always yellow, this looks much better (and more realistic) */ 0: return palette_2cc(COLOUR_YELLOW, company_colour1); /* Use the default, i.e. 2 company colours */ 1: return base_sprite_2cc + CB_RESULT_COLOUR_MAPPING_ADD_CC; /* Use realistic colours, i.e. yellow + dark blue */ 2: return palette_2cc(COLOUR_YELLOW, COLOUR_DARK_BLUE); CB_FAILED; // should not be reached } /* --- Start/stop callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_start_stop, num_vehs_in_consist) { /* Vehicles may be coupled to a maximum of 4 units (12-16 cars) */ 1 .. 16: return CB_RESULT_NO_TEXT; return string(STR_ICM_CANNOT_START); } /* --- Articulated part callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_articulated_part, extra_callback_info1) { /* Add three articulated parts, for a total of four */ 1 .. 3: return icm; return CB_RESULT_NO_MORE_ARTICULATED_PARTS; } /* --- Wagon attach callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_can_attach_wagon, vehicle_type_id) { /* SELF refers to the wagon here, check that it's an ICM */ icm: return CB_RESULT_ATTACH_ALLOW; return string(STR_ICM_CANNOT_ATTACH_OTHER); } /* --- Vehicle length callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_length_3_part_vehicle, position_in_consist % 4) { /* In the three part version, shorten the 2nd vehicle to 1/8 and the 3rd to 7/8 * The rear (7/8) part is then made invisisble */ 1: return 1; 2: return 7; return 8; } switch(FEAT_TRAINS, SELF, sw_icm_length, cargo_subtype) { 0: sw_icm_length_3_part_vehicle; return 8; // 4-part vehicle needs no shortening } /* Power, weight and TE are all applied to the front vehicle only */ switch(FEAT_TRAINS, SELF, sw_icm_power, cargo_subtype) { 0: return int(1260 / 0.7457); // kW return int(1890 / 0.7457); // kW } switch(FEAT_TRAINS, SELF, sw_icm_weight, cargo_subtype) { 0: return 48 * 3; // 48 ton per wagon return 48 * 4; } switch(FEAT_TRAINS, SELF, sw_icm_te, cargo_subtype) { /* Base TE coefficient = 0.3 * 3 parts: 1/3 of weight on driving wheels * 4 parts: 3/8 of weight on driving wheels */ 0: return int(0.3 * 255 / 3); return int(0.3 * 255 * 3 / 8); } /* Adjust depot view of trains */ traininfo_y_offset = 2; train_width_32_px = 1; /* Increase base cost to provide a greater range for running costs */ basecost { PR_RUNNING_TRAIN_ELECTRIC: 2; } /* Define the actual train */ item(FEAT_TRAINS, icm) { /* Define properties first, make sure to set all of them */ property { name: string(STR_ICM_NAME); climates_available: bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); // not available in toyland introduction_date: date(1983, 1, 1); model_life: VEHICLE_NEVER_EXPIRES; vehicle_life: 30; reliability_decay: 20; refittable_cargo_classes: bitmask(CC_PASSENGERS); non_refittable_cargo_classes: bitmask(); cargo_allow_refit: []; // refitting is done via cargo classes only, no cargoes need explicit enabling/disabling cargo_disallow_refit: []; // refitting is done via cargo classes only, no cargoes need explicit enabling/disabling loading_speed: 6; // It's an intercity train, loading is relatively slow cost_factor: 45; running_cost_factor: 100; // Changed by callback sprite_id: SPRITE_ID_NEW_TRAIN; speed: 141 km/h; // actually 140, but there are rounding errors misc_flags: bitmask(TRAIN_FLAG_2CC, TRAIN_FLAG_MU); refit_cost: 0; // callback flags are not set manually track_type: ELRL; // from rail type table ai_special_flag: AI_FLAG_PASSENGER; power: 1260 kW; // Changed by CB running_cost_base: RUNNING_COST_ELECTRIC; dual_headed: 0; cargo_capacity: 36; // lower than in real world, for gameplay weight: 144 ton; // Total weight, changed by callback ai_engine_rank: 0; // not intended to be used by the ai engine_class: ENGINE_CLASS_ELECTRIC; extra_power_per_wagon: 0 kW; tractive_effort_coefficient: 0.1; // Changed by callback air_drag_coefficient: 0.06; length: 8; /* Overridden by callback to disable for non-powered wagons */ visual_effect_and_powered: visual_effect_and_powered(VISUAL_EFFECT_ELECTRIC, 2, DISABLE_WAGON_POWER); extra_weight_per_wagon: 0 ton; bitmask_vehicle_info: 0; } /* Define graphics and callbacks * Setting all callbacks is not needed, only define what is used */ graphics { default: sw_icm_graphics; purchase: set_icm_purchase; cargo_subtype_text: sw_icm_cargo_subtype_text; additional_text: return string(STR_ICM_ADDITIONAL_TEXT); colour_mapping: sw_icm_colour_mapping; start_stop: sw_icm_start_stop; articulated_part: sw_icm_articulated_part; can_attach_wagon: sw_icm_can_attach_wagon; running_cost_factor: (cargo_subtype == 1) ? 150 : 100; purchase_running_cost_factor: return 100; /* Capacity is per part */ cargo_capacity: return 36 * (cargo_subtype + 3) / 4; /* In the purchase menu, we want to show the capacity for the three-part version, * i.e. divide the capacity of three cars across four */ purchase_cargo_capacity: return 36 * 3 / 4; /* Only the front vehicle has a visual effect */ visual_effect_and_powered: return (position_in_consist % 4 == 0) ? visual_effect_and_powered(VISUAL_EFFECT_ELECTRIC, 2, DISABLE_WAGON_POWER) : visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER); length: sw_icm_length; /* Only the front vehicle has power */ purchase_power: return int(1260 / 0.7457); // kW power: sw_icm_power; /* Only the front vehicle has weight */ purchase_weight: return 144; //tons weight: sw_icm_weight; /* Only the front vehicle has TE */ purchase_tractive_effort_coefficient: return int(0.3 * 255 / 3); // Only 1/3 of the weight is on the driving weels. tractive_effort_coefficient: sw_icm_te; } } /* Define properties valid only in OpenTTD r22713 or later only for those versions. * Earlier versions will choke on those and otherwise disable the NewGRF. */ if (version_openttd(1,2,0,22713) < openttd_version) { item(FEAT_TRAINS, icm) { property { cargo_age_period: 185; // default value } } } /* ------------------ Cargo Wagons Example ------------------ */ /* This example does not feature complete handling of cargo sprites. There are no loaded/loading states, and there is only one type of cargo used. It's purely to demonstrate how to use the sprite stack */ spriteset (set_cargo_wagon, "cargo_wagons.png") { tmpl_vehicle_4_views(0) } spriteset (set_cargo_wagon_load, "cargo_wagons.png") { tmpl_vehicle_4_views(1) } switch (FEAT_TRAINS, SELF, cargo_wagon_switch_vehicle, STORE_TEMP(CB_FLAG_MORE_SPRITES | PALETTE_USE_DEFAULT, 0x100)) { return set_cargo_wagon; } switch (FEAT_TRAINS, SELF, cargo_wagon_switch_load, STORE_TEMP(PALETTE_USE_DEFAULT, 0x100)) { return set_cargo_wagon_load; } switch (FEAT_TRAINS, SELF, cargo_wagon_switch_graphics, getbits(extra_callback_info1, 8, 8)) { 0: return cargo_wagon_switch_vehicle; return cargo_wagon_switch_load; } item(FEAT_TRAINS, cargo_wagon_1) { property { name: string(STR_CARGO_WAGON_1_NAME); climates_available: ALL_CLIMATES; introduction_date: date(1900, 1, 1); model_life: VEHICLE_NEVER_EXPIRES; refittable_cargo_classes: bitmask(CC_PIECE_GOODS); sprite_id: SPRITE_ID_NEW_TRAIN; misc_flags: bitmask(TRAIN_FLAG_SPRITE_STACK); cargo_capacity: 40; weight: 20 ton; power: 0hp; } graphics { default: cargo_wagon_switch_graphics; purchase: cargo_wagon_switch_graphics; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/examples/train/icm.png0000644000175100001660000000775214754345605016156 0ustar00runnerdockerPNG  IHDR`;;sRGBgAMA aPLTE4I=#0‘W% ˃e%K:un+9UYh `=A)7 9w{z2󺪨  ,b`kDN L8\|],u6ɲD(醀}oqR9d=fJ,USU%K~o#OO%t2X 񲪶~W-x"C>>]]]^@9 QvVo.a WOÖ5T"f. @'1.ʨ4pG=[ɱ{Xӕp '1_/ݖ?oCm"=stכ1/mh]FMch 6su 2|}ٽ; >pmOC;?"vtx_ݚͷټz*Mzg3YMcK?k* @!-<Ϊ.*{nFv <~;j9ohU?s+_>@Z[f#F,p2#co&N\8{g>A|!? ٱ(Vծv6dwcإ=oZju;$ !zT8o&B ? >TOM^??w~PGEwtd7 bB)6qG.~P.:~x^  D :@MF!nw[CS?(~E?5,<da~QA roI^Tt!0DbI?r~P~0I4$Aj(`X+o+2VAZykGP.UanGb= `'KY q!e0e0ܡn?&~N6G;J/>scE煏A s8t[o]?8~p2~~pX?Hގ R 8bA@ |;A$S/Ez ?:A?}`< s᳨A5KVV^d~Z?(LxkZrIe>?g00QWwhS/A̍ n(q'tBfl8CC7 jwEJ%oK)k2L ȕ%K ~Y@.)зKmgeg2C:'YVd}viiwK%.-.6viawK%[@.)гKuwSaA9xZ Ɠq [ /** * Resizeable buffer/array. */ typedef struct { char *buf; ///< Plain data. int used; ///< Number of bytes used. int allocated; ///< Number of bytes allocated. } buffer_t; /** * Append a byte to a buffer. * If the buffer is too small, it is enlarged. * @param data Buffer. * @param b Byte to add. */ static inline void append_byte(buffer_t *data, char b) { if (data->used + 1 > data->allocated) { data->allocated += 0x10000; data->buf = realloc(data->buf, data->allocated); } data->buf[data->used++] = b; } /** * Append a byte array to a buffer. * If the buffer is too small, it is enlarged. * @param data Buffer. * @param b Data to add. * @param l Size of \a b. */ static inline void append_bytes(buffer_t *data, const char *b, int l) { if (data->used + l > data->allocated) { data->allocated = (data->used + l + 0xFFFF) & ~0xFFFF; data->buf = realloc(data->buf, data->allocated); } memcpy(data->buf + data->used, b, l); data->used += l; } /** * Locate a byte sequence in a buffer. * @param pat_data Pattern to look for. * @param pat_size Size of \a pat_data. * @param data Data to search in. * @param data__size Size of \a data. * @return Offset of first occurence, or -1 if no match. */ static inline int find(const char *pat_data, int pat_size, const char *data, int data_size) { int i; for (i = 0; i + pat_size <= data_size; ++i) { /* Apparently this loop is optimised quite well by gcc. * Usage of __builtin_bcmp performed terrible, since I didn't manage to get it inlined. * It it worth noting though, that pat_size is very small (<= 16). */ int j = 0; while (j < pat_size && pat_data[j] == data[i + j]) ++j; if (j == pat_size) return i; } return -1; } /** * GRF compression algorithm. * @param input_data Uncompressed data. * @param input_size Size of \a input_data. * @param output Compressed data. */ static void encode(const char *input_data, int input_size, buffer_t *output) { char literal[0x80]; int literal_size = 0; int position = 0; while (position < input_size) { int start_pos = position - (1 << 11) + 1; if (start_pos < 0) start_pos = 0; /* Loop through the lookahead buffer. */ int max_look = input_size - position + 1; if (max_look > 16) max_look = 16; int overlap_pos = 0; int overlap_len = 0; int i; for (i = 3; i < max_look; ++i) { /* Find the pattern match in the window. */ int result = find(input_data + position, i, input_data + start_pos, position - start_pos); /* If match failed, we've found the longest. */ if (result < 0) break; overlap_pos = position - start_pos - result; overlap_len = i; start_pos += result; } if (overlap_len > 0) { if (literal_size > 0) { append_byte(output, literal_size); append_bytes(output, literal, literal_size); literal_size = 0; } int val = 0x80 | (16 - overlap_len) << 3 | overlap_pos >> 8; append_byte(output, val); append_byte(output, overlap_pos & 0xFF); position += overlap_len; } else { literal[literal_size++] = input_data[position]; if (literal_size == sizeof(literal)) { append_byte(output, 0); append_bytes(output, literal, literal_size); literal_size = 0; } position += 1; } } if (literal_size > 0) { append_byte(output, literal_size); append_bytes(output, literal, literal_size); literal_size = 0; } } /** * Interface method to Python. * * @param self Unused. * @param args Uncompressed data as "str", "bytes", "bytearray", or anything providing the buffer interface. * @return Compressed data as "bytes". */ static PyObject *lz77_encode(PyObject *self, PyObject *args) { PyObject *result = NULL; Py_buffer input; if (PyArg_ParseTuple(args, "s*", &input) && input.buf) { buffer_t output = {0}; Py_BEGIN_ALLOW_THREADS encode((const char*)input.buf, input.len, &output); Py_END_ALLOW_THREADS PyBuffer_Release(&input); result = Py_BuildValue("y#", output.buf, output.used); free(output.buf); } return result; } /** * Function table. */ static PyMethodDef lz77Methods[] = { {"encode", lz77_encode, METH_VARARGS, "GRF compression algorithm"}, {NULL, NULL, 0, NULL} }; /** * Module table. */ static struct PyModuleDef lz77module = { PyModuleDef_HEAD_INIT, "nml_lz77", NULL, -1, lz77Methods }; /** * Module intialisation. * Called by Python upon import. */ PyMODINIT_FUNC PyInit_nml_lz77(void) { return PyModule_Create(&lz77module); } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0594623 nml-0.7.6/nml/actions/0000755000175100001660000000000014754345610014154 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/__init__.py0000644000175100001660000000124214754345605016270 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action0.py0000644000175100001660000012313714754345605016076 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, grfstrings, nmlop from nml.actions import action4, action6, action7, actionD, base_action from nml.actions.action0properties import Action0Property, BaseAction0Property, properties, two_byte_property from nml.ast import general class BlockAllocation: """ Administration of allocated blocks of a size, in a range of addresses. Blocks always start at address C{0}, but the first available freely usable address may be further. The allocation information is kept in L{allocated}. It has the following cases - An address does not exist in the dictionary. The address is free for use. - An address exists, with an integer number as size. At this address a block has been allocated with that size. - An address exists, with value C{None}. The address is part of an allocated block, but not the start. @ivar first: First freely usable address. @type first: C{int} @ivar last: Last freely usable address. @type last: C{int} @ivar name: Name for debug output. @type name: C{str} @ivar dynamic_allocation: True, if ids are allocated. False, if they refer to static entities. @type dynamic_allocation: C{bool} @ivar allocated: Mapping of allocated blocks. @type allocated: C{dict} of C{int} to allocation information. @ivar filled: Mapping of block size to smallest address that may contain free space. Serves as a cache to speed up searches. @type filled: C{dict} of C{int} """ def __init__(self, first, last, name, dynamic_allocation=True): self.first = first self.last = last self.name = name self.dynamic_allocation = dynamic_allocation self.allocated = {} self.filled = {} def get_num_allocated(self): """ Return number of allocated ids. """ if self.dynamic_allocation: return len(self.allocated) else: return 0 def get_max_allocated(self): """ Return maximum number of allocateable ids. """ if self.dynamic_allocation: return self.last - self.first + 1 else: return 0 def in_range(self, addr, length): """ Does a block starting at the provided address with the given length in the available address space? @param addr: First address of the block. @type addr: C{int} @param length: Number of addresses in the block. @type length: C{int} @return: Whether the block fits enitrely in the available address space. @rtype: C{bool} """ return addr >= 0 and addr + length - 1 <= self.last def get_size(self, addr): """ Get the size of the block allocated at the given address. @param addr: Address being queried. @type addr: C{int} @return: Size of the allocated block, if one was allocated here, else C{None}. @rtype: C{int} or C{None} """ return self.allocated.get(addr) def is_address_free(self, addr): """ Is the space at the given address still available? ( cheaper variant of C{self.get_last_used(addr, 1) is None} ) @param addr: Address being queried. @type addr: C{int} @return: Whether the space at the provided address is available. @rtype: C{bool} """ return addr not in self.allocated def get_last_used(self, addr, length): """ Check whether a block of addresses is used. @param addr: First address of the block. @type addr: C{int} @param length: Number of addresses in the block. @type length: C{int} @return: The last used address in the block, or C{None} if all addresses are free. @rtype: C{int} or C{None} @precond: Addresses of the range should be within the available address space. """ for idx in range(addr + length - 1, addr - 1, -1): if idx in self.allocated: return idx return None def mark_used(self, addr, length): """ Mark an area as being allocated. @param addr: First address of the block. @type addr: C{int} @param length: Number of addresses in the block. @type length: C{int} @precond: Addresses of the block should be within the freely available address space. @precond: No address in the block may have been allocated. """ self.allocated[addr] = length for idx in range(addr + 1, addr + length): self.allocated[idx] = None def find_unused(self, length): """ Find an area of unused space. @param length: Number of addresses to find. @type length: C{int} @return: Address in the freely available address space for the new block, or C{None} if no space is available. @rtype: C{int} or C{None} """ idx = self.filled.get(length) if idx is None: # Never searched before with this block size. # Start at the biggest offset previously discovered with a smaller block size. smaller_filleds = [min_f for sz, min_f in self.filled.items() if sz < length] idx = self.first if len(smaller_filleds) == 0 else max(smaller_filleds) last_idx = self.last - length + 1 while idx < last_idx: last_used = self.get_last_used(idx, length) if last_used is None: self.filled[length] = idx + length return idx idx = last_used + 1 return None # Available IDs for each feature. # Maximum allowed id (houses and indtiles in principle allow up to 511, but action3 does not accept extended bytes). used_ids = [ BlockAllocation(116, 0xFFFF, "Train"), BlockAllocation(88, 0xFFFF, "Road Vehicle"), BlockAllocation(11, 0xFFFF, "Ship"), BlockAllocation(41, 0xFFFF, "Aircraft"), BlockAllocation(0, 0xFFFE, "Station"), # UINT16_MAX - 1 BlockAllocation(0, 8, "Canal", False), BlockAllocation(0, 15, "Bridge", False), BlockAllocation(0, 255, "House"), BlockAllocation(0, -1, "Global", False), BlockAllocation(0, 255, "Industry Tile"), BlockAllocation(0, 127, "Industry"), BlockAllocation(0, 63, "Cargo"), BlockAllocation(0, -1, "Sound"), BlockAllocation(0, 127, "Airport"), BlockAllocation(0, -1, "Signal", False), BlockAllocation(0, 64000, "Object"), BlockAllocation(0, 63, "Railtype"), BlockAllocation(0, 255, "Airport Tile"), BlockAllocation(0, 62, "Roadtype"), BlockAllocation(0, 62, "Tramtype"), BlockAllocation(0, 0xFFFE, "RoadStop"), # UINT16_MAX - 1 ] def print_stats(): """ Print statistics about used ids. """ for feature in used_ids: used = feature.get_num_allocated() if used > 0 and feature.dynamic_allocation: generic.print_info("{} items: {}/{}".format(feature.name, used, feature.get_max_allocated())) def mark_id_used(feature, id, num_ids): """ Mark a range of ids as used. @param feature: Feature of the ids. @type feature: C{int} @param id: First id to be marked. @type id: C{int} @param num_ids: Number of ids to mark as used, starting with \a id. @type num_ids: C{int} """ used_ids[feature].mark_used(id, num_ids) def check_id_range(feature, id, num_ids, pos): """ Check that the id is valid, and is either free or points to an already allocated block. @param feature: Feature of the ids. @type feature: C{int} @param id: Base id number. @type id: C{int} @param num_ids: Number of ids to test, starting with \a id. @type num_ids: C{int} @param pos: Source position of the check, for reporting errors. @type pos: L{Position} @return: Whether the block was allocated already. @rtype: C{bool} """ blk_alloc = used_ids[feature] # Check that IDs are valid and in range. if not blk_alloc.in_range(id, num_ids): msg = "Item ID must be in range 0..{:d}, encountered {:d}..{:d}." msg = msg.format(blk_alloc.last, id, id + num_ids - 1) raise generic.ScriptError(msg, pos) # ID already defined, but with the same size: OK if blk_alloc.get_size(id) == num_ids: return True # All IDs free: no problem. if blk_alloc.get_last_used(id, num_ids) is None: return False # No space at the indicated position, report an error. if blk_alloc.get_size(id) is not None: # ID already defined with a different size: error. raise generic.ScriptError( "Item with ID {:d} has already been defined, but with a different size.".format(id), pos ) if blk_alloc.is_address_free(id): # First item id free -> any of the additional tile ids must be blocked. msg = "This multi-tile house requires that item IDs {:d}..{:d} are free, but they are not." msg = msg.format(id, id + num_ids - 1) raise generic.ScriptError(msg, pos) # ID already defined as part of a multi-tile house. raise generic.ScriptError("Item ID {:d} has already used as part of a multi-tile house.".format(id), pos) def get_free_id(feature, num_ids, pos): """ Find an id to allocate a range of \a num_ids ids in a feature. @param feature: Feature of the ids. @type feature: C{int} @param num_ids: Number of ids to allocate. @type num_ids: C{int} @param pos: Position information. @type pos: L{Position} """ blk_alloc = used_ids[feature] addr = blk_alloc.find_unused(num_ids) if addr is None: msg = "Unable to allocate ID for item, no more free IDs available (maximum is {:d})" msg = msg.format(blk_alloc.last) raise generic.ScriptError(msg, pos) blk_alloc.mark_used(addr, num_ids) return addr # Number of tiles for various house sizes house_sizes = { 0: 1, # 1x1 2: 2, # 2x1 3: 2, # 1x2 4: 4, # 2x2 } def adjust_value(value, org_value, unit, ottd_convert_func): """ Make sure that the property value written to the NewGRF will match exactly the value as quoted @param value: The value to check, converted to base units @type value: L{Expression} @param org_value: The original value as written in the input file @type org_value: L{Expression} @param unit: The unit of the org_value @type unit: L{Unit} or C{None} @return: The adjusted value @rtype: L{Expression} """ while ottd_convert_func(value, unit) > org_value.value: value = expression.ConstantNumeric(int(value.value - 1), value.pos) lower_value = value while ottd_convert_func(value, unit) < org_value.value: value = expression.ConstantNumeric(int(value.value + 1), value.pos) higher_value = value lower_value_ottd = abs(ottd_convert_func(lower_value, unit) - org_value.value) higher_value_ottd = abs(ottd_convert_func(higher_value, unit) - org_value.value) if lower_value_ottd < higher_value_ottd: return lower_value return higher_value class Action0(base_action.BaseAction): """ Representation of NFO action 0. Action 0 is used to set properties on all kinds of objects. It can set any number of properties on a list of consecutive IDs. Each property is defined by a unique (per feature) integer. @ivar feature: Feature number to set properties for. @type feature: C{int} @ivar id: First ID to set properties for @type id: C{int} @ivar prop_list: List of all properties that are to be set. @type prop_list: C{list} of L{BaseAction0Property} @ivar num_ids: Number of IDs to set properties for. @type num_ids: C{int} or C{None} """ def __init__(self, feature, id): self.feature = feature self.id = id self.prop_list = [] self.num_ids = None def prepare_output(self, sprite_num): if self.num_ids is None: self.num_ids = 1 def write(self, file): size = 7 for prop in self.prop_list: assert isinstance(prop, BaseAction0Property), type(prop) if isinstance(prop, Action0Property): assert len(prop.values) == self.num_ids size += prop.get_size() file.start_sprite(size) file.print_bytex(0) file.print_bytex(self.feature) file.print_byte(len(self.prop_list)) file.print_bytex(self.num_ids) file.print_bytex(0xFF) file.print_wordx(self.id) file.newline() for prop in self.prop_list: prop.write(file) file.end_sprite() def create_action0(feature, id, act6, action_list): """ Create an action0 with variable id @param feature: Feature of the action0 @type feature: C{int} @param id: ID of the corresponding item @type id: L{Expression} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param action_list: Action list to append any extra actions to @type action_list: C{list} of L{BaseAction} @return: A tuple of (resulting action0, offset to use for action6) @rtype: C{tuple} of (L{Action0}, C{int}) """ id, offset = actionD.write_action_value(id, action_list, act6, 5, 2) action0 = Action0(feature, id.value) return (action0, offset) def get_property_info_list(feature, name): """ Find information on a single property, based on feature and name/number @param feature: Feature of the associated item @type feature: C{int} @param name: Name (or number) of the property @type name: L{Identifier} or L{ConstantNumeric} @return: A list of dictionaries with property information @rtype: C{list} of C{dict} """ # Validate feature assert feature in range(0, len(properties)) # guaranteed by item if properties[feature] is None: raise generic.ScriptError( "Setting properties for feature '{}' is not possible, no properties are defined.".format( general.feature_name(feature) ), name.pos, ) if isinstance(name, expression.Identifier): prop_name = name.value if prop_name not in properties[feature]: raise generic.ScriptError("Unknown property name: " + prop_name, name.pos) prop_info_list = properties[feature][prop_name] if not isinstance(prop_info_list, list): prop_info_list = [prop_info_list] elif isinstance(name, expression.ConstantNumeric): for prop_info_list in properties[feature].values(): if not isinstance(prop_info_list, list): prop_info_list = [prop_info_list] # Only non-compound properties may be set by number if len(prop_info_list) == 1 and "num" in prop_info_list[0] and prop_info_list[0]["num"] == name.value: break else: raise generic.ScriptError("Unknown property number: " + str(name), name.pos) else: raise AssertionError() for prop_info in prop_info_list: if "replaced_by" in prop_info: generic.print_warning( generic.Warning.DEPRECATION, "'{}' is deprecated, consider using '{}' instead".format(prop_name, prop_info["replaced_by"]), name.pos, ) if "warning" in prop_info: generic.print_warning(generic.Warning.GENERIC, prop_info["warning"], name.pos) return prop_info_list def parse_property_value(prop_info, value, unit=None, size_bit=None): """ Parse a single property value / unit To determine the value that is to be used in nfo @param prop_info: A dictionary with property information @type prop_info: C{dict} @param value: Value of the property @type value: L{Expression} @param unit: Unit of the property value (e.g. km/h) @type unit: L{Unit} or C{None} @param size_bit: Bit that indicates the size of a multitile house Set iff the item is a house @type size_bit: C{int} or C{None} @return: List of values to actually use (in nfo) for the property @rtype: L{Expression} """ # Change value to use, except when the 'nfo' unit is used if unit is None or unit.type != "nfo": # Save the original value to test conversion against it org_value = value # Multiply by property-specific conversion factor mul, div = 1, 1 if "unit_conversion" in prop_info: mul = prop_info["unit_conversion"] if isinstance(mul, tuple): mul, div = mul # Divide by conversion factor specified by unit if unit is not None: if "unit_type" not in prop_info or unit.type != prop_info["unit_type"]: raise generic.ScriptError("Invalid unit for property", value.pos) unit_mul, unit_div = unit.convert, 1 if isinstance(unit_mul, tuple): unit_mul, unit_div = unit_mul mul *= unit_div div *= unit_mul # Factor out common factors gcd = generic.greatest_common_divisor(mul, div) mul //= gcd div //= gcd if isinstance(value, (expression.ConstantNumeric, expression.ConstantFloat)): # Even if mul == div == 1, we have to round floats and adjust value value = expression.ConstantNumeric(int(float(value.value) * mul / div + 0.5), value.pos) if unit is not None and "adjust_value" in prop_info: value = adjust_value(value, org_value, unit, prop_info["adjust_value"]) elif mul != div: # Compute (value * mul + div/2) / div value = nmlop.MUL(value, mul) value = nmlop.ADD(value, int(div / 2)) value = nmlop.DIV(value, div) elif isinstance(value, expression.ConstantFloat): # Round floats to ints value = expression.ConstantNumeric(int(value.value + 0.5), value.pos) # Apply value_function if it exists if "value_function" in prop_info: value = prop_info["value_function"](value) # Make multitile houses work if size_bit is not None: num_ids = house_sizes[size_bit] assert "multitile_function" in prop_info ret = prop_info["multitile_function"](value, num_ids, size_bit) assert len(ret) == num_ids return ret else: return [value] def parse_property(prop_info, value_list, feature, id): """ Parse a single property @param prop_info: A dictionary with property information @type prop_info: C{dict} @param value_list: List of values for the property, with unit conversion applied @type value_list: C{list} of L{Expression} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @return: A tuple containing the following: - List of properties to add to the action 0 - List of actions to prepend - List of modifications to apply via action 6 - List of actions to append @rtype: C{tuple} of (C{list} of L{Action0Property}, C{list} of L{BaseAction}, C{list} of 3-C{tuple}, C{list} of L{BaseAction}) """ action_list = [] action_list_append = [] mods = [] if "custom_function" in prop_info: props = prop_info["custom_function"](*value_list) else: def apply_threshold(value): if not isinstance(value, tuple): return value assert len(value) == 3 threshold, below, above = value return below if id.value < threshold else above # First process each element in the value_list final_values = [] for i, value in enumerate(value_list): if "string_literal" in prop_info and ( isinstance(value, expression.StringLiteral) or prop_info["string_literal"] != 4 ): # Parse non-string expressions just like integers. User will have to take care of proper value. # This can be used to set a label (=string of length 4) to the value of a parameter. if not isinstance(value, expression.StringLiteral): raise generic.ScriptError( "Value for property {:d} must be a string literal".format(prop_info["num"]), value.pos ) if grfstrings.get_string_size(value.value, False, True) != prop_info["string_literal"]: raise generic.ScriptError( "Value for property {:d} must be of length {:d}".format( prop_info["num"], prop_info["string_literal"] ), value.pos, ) elif isinstance(value, expression.ConstantNumeric): pass elif isinstance(value, expression.Parameter) and isinstance(value.num, expression.ConstantNumeric): mods.append((value.num.value, prop_info["size"], i * prop_info["size"] + 1)) value = expression.ConstantNumeric(0) elif isinstance(value, expression.String): if "string" not in prop_info: raise generic.ScriptError( "String used as value for non-string property: " + str(prop_info["num"]), value.pos ) string_range = apply_threshold(prop_info["string"]) stringid, string_actions = action4.get_string_action4s(feature, string_range, value, id) value = expression.ConstantNumeric(stringid) action_list_append.extend(string_actions) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(value) mods.append((tmp_param, prop_info["size"], i * prop_info["size"] + 1)) action_list.extend(tmp_param_actions) value = expression.ConstantNumeric(0) final_values.append(value) # Now, write a single Action0 Property with all of these values prop_num = apply_threshold(prop_info["num"]) if prop_num != -1: props = [Action0Property(prop_num, final_values, prop_info["size"])] else: props = [] return (props, action_list, mods, action_list_append) def validate_prop_info_list(prop_info_list, pos_list, feature): """ Perform various checks on a list of properties in a property-block - make sure that properties that should appear first (substitute type) appear first - make sure that required properties are set @param prop_info_list: List of dictionaries with property information @type prop_info_list: C{list} of C{dict} @param pos_list: List containing position information (order matches prop_info_list) @type pos_list: C{list} of L{Position} @param feature: Feature of the associated item @type feature: C{int} """ first_warnings = [(info, pos_list[i]) for i, info in enumerate(prop_info_list) if "first" in info and i != 0] for prop_name, prop_info in properties[feature].items(): if not isinstance(prop_info, list): prop_info = [prop_info] for info, pos in first_warnings: if info in prop_info: generic.print_warning( generic.Warning.GENERIC, "Property '{}' should be set before all other properties and graphics.".format(prop_name), pos, ) break for info in prop_info: if "required" in info and info not in prop_info_list: generic.print_error( "Property '{}' is not set. Item will be invalid.".format(prop_name), pos_list[-1], ) def parse_property_block(prop_list, feature, id, size): """ Parse a property block to an action0 (with possibly various other actions) @param prop_list: List of properties to parse @type prop_list: C{list} of L{Property} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @param size: Size (for houses only) @type size: L{ConstantNumeric} or C{None} @return: List of resulting actions @rtype: C{list} of L{BaseAction} """ action6.free_parameters.save() action_list = [] action_list_append = [] act6 = action6.Action6() action0, offset = create_action0(feature, id, act6, action_list) if feature == 0x07: size_bit = size.value if size is not None else 0 action0.num_ids = house_sizes[size_bit] else: size_bit = None action0.num_ids = 1 prop_info_list = [] value_list_list = [] pos_list = [] for prop in prop_list: new_prop_info_list = get_property_info_list(feature, prop.name) prop_info_list.extend(new_prop_info_list) value_list_list.extend( parse_property_value(prop_info, prop.value, prop.unit, size_bit) for prop_info in new_prop_info_list ) pos_list.extend(prop.name.pos for i in new_prop_info_list) validate_prop_info_list(prop_info_list, pos_list, feature) for prop_info, value_list in zip(prop_info_list, value_list_list): if "test_function" in prop_info and not prop_info["test_function"](*value_list): continue props, extra_actions, mods, extra_append_actions = parse_property(prop_info, value_list, feature, id) action_list.extend(extra_actions) action_list_append.extend(extra_append_actions) for mod in mods: act6.modify_bytes(mod[0], mod[1], mod[2] + offset) for p in props: offset += p.get_size() action0.prop_list.extend(props) if len(act6.modifications) > 0: action_list.append(act6) if len(action0.prop_list) != 0: action_list.append(action0) action_list.extend(action_list_append) action6.free_parameters.restore() return action_list class IDListProp(BaseAction0Property): def __init__(self, prop_num, id_list): self.prop_num = prop_num self.id_list = id_list def write(self, file): file.print_bytex(self.prop_num) for i, id_val in enumerate(self.id_list): if i > 0 and i % 5 == 0: file.newline() file.print_string(id_val.value, False, True) file.newline() def get_size(self): return len(self.id_list) * 4 + 1 def get_cargolist_action(cargo_list): action0 = Action0(0x08, 0) action0.prop_list.append(IDListProp(0x09, cargo_list)) action0.num_ids = len(cargo_list) return [action0] def get_tracktypelist_action(table_prop_id, cond_tracktype_not_defined, tracktype_list): action6.free_parameters.save() act6 = action6.Action6() action_list = [] action0, offset = create_action0(0x08, expression.ConstantNumeric(0), act6, action_list) id_table = [] offset += 1 # Skip property number for tracktype in tracktype_list: if isinstance(tracktype, expression.StringLiteral): id_table.append(tracktype) offset += 4 continue param, extra_actions = actionD.get_tmp_parameter( expression.ConstantNumeric(expression.parse_string_to_dword(tracktype[-1])) ) action_list.extend(extra_actions) for idx in range(len(tracktype) - 2, -1, -1): val = expression.ConstantNumeric(expression.parse_string_to_dword(tracktype[idx])) action_list.append(action7.SkipAction(0x09, 0x00, 4, (cond_tracktype_not_defined, None), val.value, 1)) action_list.append( actionD.ActionD( expression.ConstantNumeric(param), expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0xFF), val, ) ) act6.modify_bytes(param, 4, offset) id_table.append(expression.StringLiteral(r"\00\00\00\00", None)) offset += 4 action0.prop_list.append(IDListProp(table_prop_id, id_table)) action0.num_ids = len(tracktype_list) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(action0) action6.free_parameters.restore() return action_list class ByteListProp(BaseAction0Property): def __init__(self, prop_num, data): self.prop_num = prop_num self.data = data def write(self, file): file.print_bytex(self.prop_num) file.newline() for i, data_val in enumerate(self.data): if i > 0 and i % 8 == 0: file.newline() file.print_bytex(ord(data_val)) file.newline() def get_size(self): return len(self.data) + 1 def get_snowlinetable_action(snowline_table): assert len(snowline_table) == 12 * 32 action6.free_parameters.save() action_list = [] tmp_param_map = {} # Cache for tmp parameters act6 = action6.Action6() act0, offset = create_action0(0x08, expression.ConstantNumeric(0), act6, action_list) act0.num_ids = 1 offset += 1 # Skip property number data_table = [] idx = 0 while idx < len(snowline_table): val = snowline_table[idx] if isinstance(val, expression.ConstantNumeric): data_table.append(val.value) idx += 1 continue if idx + 3 >= len(snowline_table): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(val) tmp_param_map[val] = tmp_param act6.modify_bytes(tmp_param, 1, offset + idx) action_list.extend(tmp_param_actions) data_table.append(0) idx += 1 continue # Merge the next 4 values together in a single parameter. val2 = nmlop.SHIFT_LEFT(snowline_table[idx + 1], 8) val3 = nmlop.SHIFT_LEFT(snowline_table[idx + 2], 16) val4 = nmlop.SHIFT_LEFT(snowline_table[idx + 3], 24) expr = nmlop.OR(val, val2) expr = nmlop.OR(expr, val3) expr = nmlop.OR(expr, val4) expr = expr.reduce() # Cache lookup, saves some ActionDs if expr in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[expr], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) tmp_param_map[expr] = tmp_param act6.modify_bytes(tmp_param, 4, offset + idx) action_list.extend(tmp_param_actions) data_table.extend([0, 0, 0, 0]) idx += 4 act0.prop_list.append(ByteListProp(0x10, "".join([chr(x) for x in data_table]))) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) action6.free_parameters.restore() return action_list def get_basecost_action(basecost): action6.free_parameters.save() action_list = [] tmp_param_map = {} # Cache for tmp parameters # We want to avoid writing lots of action0s if possible i = 0 while i < len(basecost.costs): cost = basecost.costs[i] act6 = action6.Action6() act0, offset = create_action0(0x08, cost.name, act6, action_list) first_id = cost.name.value if isinstance(cost.name, expression.ConstantNumeric) else None num_ids = 1 # Number of values that will be written in one go values = [] # try to capture as much values as possible while True: cost = basecost.costs[i] if isinstance(cost.value, expression.ConstantNumeric): values.append(cost.value) else: # Cache lookup, saves some ActionDs if cost.value in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[cost.value], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(cost.value) tmp_param_map[cost.value] = tmp_param act6.modify_bytes(tmp_param, 1, offset + num_ids) action_list.extend(tmp_param_actions) values.append(expression.ConstantNumeric(0)) # check if we can append the next to this one (it has to be consecutively numbered) if first_id is not None and (i + 1) < len(basecost.costs): nextcost = basecost.costs[i + 1] if isinstance(nextcost.name, expression.ConstantNumeric) and nextcost.name.value == first_id + num_ids: num_ids += 1 i += 1 # Yes We Can, continue the loop to append this value to the list and try further continue # No match, so stop it and write an action0 break act0.prop_list.append(Action0Property(0x08, values, 1)) act0.num_ids = num_ids if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) i += 1 action6.free_parameters.restore() return action_list class LanguageTranslationTable(BaseAction0Property): def __init__(self, num, name_list, extra_names): self.num = num self.mappings = [] for name, idx in name_list.items(): self.mappings.append((idx, name)) if name in extra_names: for extra_name in extra_names[name]: self.mappings.append((idx, extra_name)) # Sort to keep the output deterministic self.mappings.sort() def write(self, file): file.print_bytex(self.num) for mapping in self.mappings: file.print_bytex(mapping[0]) file.print_string(mapping[1]) file.print_bytex(0) file.newline() def get_size(self): size = 2 for mapping in self.mappings: size += 1 + grfstrings.get_string_size(mapping[1]) return size def get_language_translation_tables(lang): action0 = Action0(0x08, lang.langid) if lang.genders is not None: action0.prop_list.append(LanguageTranslationTable(0x13, lang.genders, lang.gender_map)) if lang.cases is not None: action0.prop_list.append(LanguageTranslationTable(0x14, lang.cases, lang.case_map)) if lang.plural is not None: action0.prop_list.append(Action0Property(0x15, expression.ConstantNumeric(lang.plural), 1)) if len(action0.prop_list) > 0: return [action0] return [] disable_info = { # Vehicles: set climates_available to 0 0x00: {"num": 116, "props": [{"num": 0x06, "size": 1, "value": 0}]}, 0x01: {"num": 88, "props": [{"num": 0x06, "size": 1, "value": 0}]}, 0x02: {"num": 11, "props": [{"num": 0x06, "size": 1, "value": 0}]}, 0x03: {"num": 41, "props": [{"num": 0x06, "size": 1, "value": 0}]}, # Houses / industries / airports: Set substitute_type to FF 0x07: {"num": 110, "props": [{"num": 0x08, "size": 1, "value": 0xFF}]}, 0x0A: {"num": 37, "props": [{"num": 0x08, "size": 1, "value": 0xFF}]}, 0x0D: {"num": 10, "props": [{"num": 0x08, "size": 1, "value": 0xFF}]}, # Cargos: Set bitnum to FF and label to 0 0x0B: {"num": 27, "props": [{"num": 0x08, "size": 1, "value": 0xFF}, {"num": 0x17, "size": 4, "value": 0}]}, } def get_disable_actions(disable): """ Get the action list for a disable_item block @param disable: Disable block @type disable: L{DisableItem} @return: A list of resulting actions @rtype: C{list} of L{BaseAction} """ feature = disable.feature.value if feature not in disable_info: raise generic.ScriptError("disable_item() is not available for feature {:d}.".format(feature), disable.pos) if disable.first_id is None: # No ids set -> disable all assert disable.last_id is None first = 0 num = disable_info[feature]["num"] else: first = disable.first_id.value if disable.last_id is None: num = 1 else: num = disable.last_id.value - first + 1 act0 = Action0(feature, first) act0.num_ids = num for prop in disable_info[feature]["props"]: act0.prop_list.append( Action0Property(prop["num"], num * [expression.ConstantNumeric(prop["value"])], prop["size"]) ) return [act0] class EngineOverrideProp(BaseAction0Property): def __init__(self, source, target): self.source = source self.target = target def write(self, file): file.print_bytex(0x11) file.print_dwordx(self.source) file.print_dwordx(self.target) file.newline() def get_size(self): return 9 def get_engine_override_action(override): act0 = Action0(0x08, 0) act0.num_ids = 1 act0.prop_list.append(EngineOverrideProp(override.source_grfid, override.grfid)) return [act0] def parse_sort_block(feature, vehid_list): prop_num = [0x1A, 0x20, 0x1B, 0x1B] action_list = [] if len(vehid_list) >= 2: last = vehid_list[0] idx = len(vehid_list) - 1 while idx >= 0: cur = vehid_list[idx] prop = Action0Property(prop_num[feature], [last], 3) action_list.append(Action0(feature, cur.value)) action_list[-1].prop_list.append(prop) last = cur idx -= 1 return action_list callback_flag_properties = { 0x00: two_byte_property(0x1E, 0x31), 0x01: two_byte_property(0x17, 0x28), 0x02: two_byte_property(0x12, 0x22), 0x03: two_byte_property(0x14, 0x22), 0x04: {"size": 1, "num": 0x0B}, 0x05: {"size": 1, "num": 0x08}, 0x07: two_byte_property(0x14, 0x1D), 0x09: {"size": 1, "num": 0x0E}, 0x0A: two_byte_property(0x21, 0x22), 0x0B: {"size": 1, "num": 0x1A}, 0x0F: {"size": 2, "num": 0x15}, 0x11: {"size": 1, "num": 0x0E}, 0x14: {"size": 1, "num": 0x11}, } def get_callback_flags_actions(feature, id, flags): """ Get a list of actions to set the callback flags of a certain item @param feature: Feature of the item @type feature: C{int} @param id: ID of the item @type id: L{Expression} @param flags: Value of the 'callback_flags' property @type flags: C{int} @return: A list of actions @rtype: C{list} of L{BaseAction} """ assert isinstance(id, expression.ConstantNumeric) act0, offset = create_action0(feature, id, None, None) act0.num_ids = 1 assert feature in callback_flag_properties prop_info_list = callback_flag_properties[feature] if not isinstance(prop_info_list, list): prop_info_list = [prop_info_list] for prop_info in prop_info_list: parsed_value = parse_property_value(prop_info, expression.ConstantNumeric(flags)) if parsed_value[0].value != 0: act0.prop_list.append(Action0Property(prop_info["num"], parsed_value, prop_info["size"])) return [act0] def get_volume_actions(volume_list): """ Get a list of actions to set sound volumes @param volume_list: List of (sound id, volume) tuples, sorted in ascending order @type volume_list: C{list} of (C{int}, C{int})-tuples @return: A list of actions @rtype: C{list} of L{BaseAction} """ action_list = [] first_id = None # First ID in a series of consecutive IDs value_series = [] # Series of values to write in a single action for id, volume in volume_list: if first_id is None: first_id = id continue_series = first_id + len(value_series) == id if continue_series: value_series.append(volume) if not continue_series or id == volume_list[-1][0]: # Write action for this series act0, offset = create_action0(0x0C, expression.ConstantNumeric(first_id), None, None) act0.num_ids = len(value_series) act0.prop_list.append(Action0Property(0x08, [expression.ConstantNumeric(v) for v in value_series], 1)) action_list.append(act0) # start new series, if needed if not continue_series: first_id = id value_series = [volume] return action_list class StationLayoutProp(BaseAction0Property): def __init__(self, layouts): self.layouts = layouts def write(self, file): file.print_bytex(0x1A) file.print_byte(len(self.layouts)) file.newline() for layout in self.layouts: layout.write(file) def get_size(self): size = 2 for layout in self.layouts: size += layout.get_size() return size def get_layout_action0(feature, id, layouts): action_list = [] act0, offset = create_action0(feature, id, None, None) action6.free_parameters.save() act6 = action6.Action6() for layout in layouts: layout.write_action_value(action_list, act6, offset) if len(act6.modifications) > 0: action_list.append(act6) act0.num_ids = 1 act0.prop_list.append(StationLayoutProp(layouts)) action_list.append(act0) action6.free_parameters.restore() return action_list def get_copy_layout_action0(feature, id, source_id): act0, offset = create_action0(feature, id, None, None) act0.num_ids = 1 act0.prop_list.append(Action0Property(0x0A, source_id, 1 if source_id.value < 0xFF else 3)) return [act0] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action0properties.py0000644000175100001660000020163014754345605020206 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import itertools from nml import generic, nmlop, global_constants from nml.expression import ( AcceptCargo, Array, BitMask, ConstantFloat, ConstantNumeric, Identifier, ProduceCargo, StringLiteral, parse_string_to_dword, ) tilelayout_names = {} class BaseAction0Property: """ Base class for Action0 properties. """ def write(self, file): """ Write this property to the output given by file. @param file: The outputfile we have to write to. @type file: L{SpriteOutputBase} """ raise NotImplementedError("write is not implemented in {!r}".format(type(self))) def get_size(self): """ Get the number of bytes that this property will write to the output. @return: The size of this property in bytes. @rtype: C{int} """ raise NotImplementedError("get_size is not implemented in {!r}".format(type(self))) class Action0Property(BaseAction0Property): """ Simple Action 0 property with a fixed size. @ivar num: Number of the property. @type num: C{int} @ivar values: Value of the property for each id. @type values: C{list} of L{ConstantNumeric} @ivar size: Size of the storage, in bytes. @type size: C{int} """ def __init__(self, num, value, size): self.num = num self.values = value if isinstance(value, list) else [value] self.size = size # Make sure the value fits in the size. # Strings have their own check in parse_property for val in self.values: if not isinstance(val, StringLiteral): biggest = 1 << (8 * size) if val.value >= biggest: raise generic.ScriptError("Action 0 property too large", val.pos) elif val.value < 0 and val.value + (biggest // 2) < 0: raise generic.ScriptError("Action 0 property too small", val.pos) def write(self, file): file.print_bytex(self.num) for val in self.values: val.write(file, self.size) file.newline() def get_size(self): return self.size * len(self.values) + 1 # @var properties: A mapping of features to properties. This is a list # with one item per feature. Entries should be a dictionary of properties, # or C{None} if no properties are defined for that feature. # # Each property is a mapping of property name to its characteristics. # These characteristics are either a dictionary or a list of # dictionaries, the latter can be used to set multiple properties. # First a short summary is given, then the recognized characteristics # are outlined below in more detail. # # Summary: If 'string' or 'string_literal' is set, the value should be a # string or literal string, else the value is a number. 'unit_type' and # 'unit_conversion' are used to convert user-entered values to nfo values. # If some more arithmetic is needed to convert the entered value into an nfo # value, 'value_function' can be used. For even more complicated things, # 'custom_function' can be used to create a special mapping of the value to nfo # properties, else 'num' and 'size' are used to provide a 'normal' action0. # # 'string', if set, means that the value of the property should be a string. # The value of characteristic indicates the string range to use (usually 0xD0 or 0xDC) # If set to None, the string will use the ID of the item (used for vehicle names) # It can also be a (threshold, below, above) tuple to select range depending on ID # # 'string_literal', if set, indicates that the value of the property should # be a literal (quoted) string. The value of the characteristic is equal to # the required length (usually 4) of said literal string. # # 'unit_type' means that units of the given type (power, speed) can be applied # to this property. A list of units can be found in ../unit.py. The value is then # converted to a certain reference unit (for example m/s for speed) # Leaving this unset means that no units (except 'nfo' which is an identity mapping) # can be applied to this property. # # 'unit_conversion' defines a conversion factor between the value entered by # the user and the resulting value in nfo. This is either an integer or a # rational number entered as a 2-tuple (numerator, denominator). # The entered value (possibly converted # to the appropriate reference unit, # see 'unit_type' above) is multiplied by this factor and then rounded to an # integer to provide the final value. # This parameter is not required and defaults to 1. # # 'value_function' can be used to alter the mapping of nml values to nfo values # it takes one argument (the value) and returns the new value to use # Both the parameter and the return value are expressions that do not have to be # constants # # 'custom_function' can be used to bypass the normal way of converting the # name / value to an Action0Property. This function is normally called with one # argument, which is the value of the property. For houses, there may be multiple # values, passed as a vararg-list. It should return a list of Action0Property. # To pass extra parameters to the function, a dose of lambda calculus can be used. # Consult the code for examples. # # 'test_function' can be used to determine if the property should be set in the # first place. It normally takes one argument (the value) and should return True # if the property is to be set, False if it is to be ignored. Default is True # For houses, there may be multiple values, passed as a vararg-list. # # 'num' is the Action0 property number of the action 0 property, as given by the # nfo specs. If set to -1, no action0 property will be generated. If # 'custom_function' is set, this value is not needed and can be left out. # It can also be a (threshold, below, above) tuple to set 'num' depending on ID. # # 'size' is the size (in bytes) of the resulting action 0 property. Valid # values are 1 (byte), 2 (word) or 4 (dword). For other (or variable) sizes, # 'custom_function' is needed. If 'custom_function' is set or 'num' is equal # to -1, this parameter is not needed and can be left out. # # 'warning' is a string (optional) containing a warning message that will be # shown if a property is used. Use for deprecating properties. # # 'first' (value doesn't matter) if the property should be set first (generally a substitute type). # # 'required' (value doesn't matter) if the property is required for the item to be valid. properties = 0x15 * [None] # # Some helper functions that are used for multiple features # def two_byte_property(low_prop, high_prop, low_prop_info=None, high_prop_info=None): """ Decode a two byte value into two action 0 properties. @param low_prop: Property number for the low 8 bits of the value. @type low_prop: C{int} @param high_prop: Property number for the high 8 bits of the value. @type high_prop: C{int} @param low_prop_info: Dictionary with additional property information for the low byte. @type low_prop_info: C{dict} @param high_prop_info: Dictionary with additional property information for the low byte. @type high_prop_info: C{dict} @return: Sequence of two dictionaries with property information (low part, high part). @rtype: C{list} of C{dict} """ low_byte_info = { **(low_prop_info or {}), "num": low_prop, "size": 1, "value_function": lambda value: nmlop.AND(value, 0xFF).reduce(), } high_byte_info = { **(high_prop_info or {}), "num": high_prop, "size": 1, "value_function": lambda value: nmlop.SHIFT_RIGHT(value, 8).reduce(), } return [low_byte_info, high_byte_info] def animation_info(value, loop_bit=8, max_frame=253): """ Convert animation info array of two elements to an animation info property. The first is 0/1, and defines whether or not the animation loops. The second is the number of frames, at most 253 frames. @param value: Array of animation info. @type value: C{Array} @param loop_bit: Bit the loop information is stored. @type loop_bit: C{int} @param max_frame: Max frames possible. @type max_frame: C{int} @return: Value to use for animation property. @rtype: L{Expression} """ if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("animation_info must be an array with exactly 2 constant values", value.pos) looping = value.values[0].reduce_constant().value frames = value.values[1].reduce_constant().value if looping not in (0, 1): raise generic.ScriptError("First field of the animation_info array must be either 0 or 1", value.values[0].pos) if frames < 1 or frames > max_frame: raise generic.ScriptError( "Second field of the animation_info array must be between 1 and " + str(max_frame), value.values[1].pos ) return ConstantNumeric((looping << loop_bit) + frames - 1) def cargo_list(value, max_num_cargos): """ Encode an array of cargo types in a single property value. If less than the maximum number of cargos are given the rest is filled up with 0xFF (=invalid cargo). @param value: Array of cargo types. @type value: C{Array} @param max_num_cargos: The maximum number of cargos in the array. @type max_num_cargos: C{int} @param prop_num: Property number. @type prop_num: C{int} @param prop_size: Property size in bytes. @type prop_size: C{int} """ if not isinstance(value, Array) or len(value.values) > max_num_cargos: raise generic.ScriptError( "Cargo list must be an array with no more than {:d} values".format(max_num_cargos), value.pos ) cargoes = value.values + [ConstantNumeric(0xFF, value.pos) for _ in range(max_num_cargos - len(value.values))] ret = None for i, cargo in enumerate(cargoes): byte = nmlop.AND(cargo, 0xFF) if i == 0: ret = byte else: byte = nmlop.SHIFT_LEFT(byte, i * 8) ret = nmlop.OR(ret, byte) return ret.reduce() # # General vehicle properties that apply to feature 0x00 .. 0x03 # # fmt: off general_veh_props = { "reliability_decay": {"size": 1, "num": 0x02}, "vehicle_life": {"size": 1, "num": 0x03}, "model_life": {"size": 1, "num": 0x04}, "climates_available": {"size": 1, "num": 0x06}, "loading_speed": {"size": 1, "num": 0x07}, "name": {"num": -1, "string": None}, } # fmt: on def ottd_display_speed(value, mul, div, unit): # Convert value to km/h-ish. kmh_ish = value.value * mul // div # Duplicate OpenTTD's `ConvertKmhishSpeedToDisplaySpeed()` return ((10 * kmh_ish * unit.ottd_mul) >> unit.ottd_shift) // 16 class VariableListProp(BaseAction0Property): """ Property value that is a variable-length list of variable sized values, the list length is written before the data. """ def __init__(self, prop_num, data, size, extended): # data is a list, each element belongs to an item ID # Each element in the list is a list of cargo types self.prop_num = prop_num self.data = data self.size = size self.extended = extended def write(self, file): file.print_bytex(self.prop_num) for elem in self.data: if self.extended: file.print_bytex(0xFF) file.print_word(len(elem)) else: file.print_byte(len(elem)) for i, val in enumerate(elem): if i % 8 == 0: file.newline() file.print_varx(val, self.size) file.newline() def get_size(self): total_len = 1 # Prop number for elem in self.data: # For each item ID to set, make space for all values + 3 or 1 for the length total_len += len(elem) * self.size + (3 if self.extended else 1) return total_len def VariableByteListProp(prop_num, data, extended=False): return VariableListProp(prop_num, data, 1, extended) def ctt_list(prop_num, *values): # values may have multiple entries, if more than one item ID is set (e.g. multitile houses) # Each value is an expression.Array of cargo types for value in values: if not isinstance(value, Array): raise generic.ScriptError("Value of cargolist property must be an array", value.pos) return [ VariableByteListProp( prop_num, [[ctype.reduce_constant().value for ctype in single_item_array.values] for single_item_array in values], ) ] def VariableWordListProp(num_prop, data, extended=False): return VariableListProp(num_prop, data, 2, extended) def accepted_cargos(prop_num, *values): # values may have multiple entries, if more than one item ID is set (e.g. multitile houses) # Each value is an expression.Array of cargo types and amount arrays cargos = [] for value in values: if not isinstance(value, Array) or len(value.values) > 16: raise generic.ScriptError("accepted_cargos must be an array with no more than 16 values", value.pos) tile_cargos = [] for cargo_amount_pair in value.values: if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError( "Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos, ) cargo_id = cargo_amount_pair.values[0].reduce_constant().value cargo_amount = cargo_amount_pair.values[1].reduce_constant().value tile_cargos.append((cargo_amount << 8) | cargo_id) cargos.append(tile_cargos) return [VariableWordListProp(prop_num, cargos)] def vehicle_length(value): if isinstance(value, ConstantNumeric): generic.check_range(value.value, 1, 8, "vehicle length", value.pos) return nmlop.SUB(8, value).reduce() def zero_refit_mask(prop_num): # Zero the refit mask, in addition to setting some other refit property return {"size": 4, "num": prop_num, "value_function": lambda value: ConstantNumeric(0)} def refittable_cargo_classes(prop_or_num, prop_and_num, refit_mask_num): def prop_value(value, index): if isinstance(value, ConstantNumeric): return value if index == 0 else None if isinstance(value, Array) and len(value.values) == 2 and isinstance(value.values[index], ConstantNumeric): return value.values[index] raise generic.ScriptError("refittable_cargo_classes must be a constant or an array of 2 constants") def prop_test(value): return value is not None return [ {"size": 2, "num": prop_or_num, "value_function": lambda value: prop_value(value, 0)}, { "size": 2, "num": prop_and_num, "value_function": lambda value: prop_value(value, 1), "test_function": prop_test, }, zero_refit_mask(refit_mask_num), ] # # Feature 0x00 (Trains) # # fmt: off properties[0x00] = { **general_veh_props, "track_type": {"size": 1, "num": 0x05}, "ai_special_flag": {"size": 1, "num": 0x08}, "speed": { "size": 2, "num": 0x09, "unit_type": "speed", "unit_conversion": (5000, 1397), "adjust_value": lambda val, unit: ottd_display_speed(val, 1, 1, unit) }, # 09 doesn"t exist "power": {"size": 2, "num": 0x0B, "unit_type": "power"}, # 0A doesn"t exist "running_cost_factor": {"size": 1, "num": 0x0D}, "running_cost_base": {"size": 4, "num": 0x0E}, # 0F -11 don"t exist "sprite_id": {"size": 1, "num": 0x12}, "dual_headed": {"size": 1, "num": 0x13}, "cargo_capacity": {"size": 1, "num": 0x14}, "default_cargo_type": {"size": 1, "num": 0x15}, "weight": two_byte_property( 0x16, 0x24, {"unit_type": "weight"}, {"unit_type": "weight"} ), "cost_factor": {"size": 1, "num": 0x17}, "ai_engine_rank": {"size": 1, "num": 0x18}, "engine_class": {"size": 1, "num": 0x19}, # 1A (sort purchase list) is implemented elsewhere "extra_power_per_wagon": {"size": 2, "num": 0x1B, "unit_type": "power"}, "refit_cost": {"size": 1, "num": 0x1C}, # 1D (refittable cargo types) is removed, it is zeroed when setting a different refit property # 1E (callback flags) is not set by user "tractive_effort_coefficient": {"size": 1, "num": 0x1F, "unit_conversion": 255}, "air_drag_coefficient": {"size": 1, "num": 0x20, "unit_conversion": 255}, "length": {"size": 1, "num": 0x21, "value_function": vehicle_length}, # 22 has two names, to simplify docs "visual_effect_and_powered": {"size": 1, "num": 0x22}, "effect_spawn_model_and_powered": {"size": 1, "num": 0x22}, "extra_weight_per_wagon": {"size": 1, "num": 0x23, "unit_type": "weight"}, # 24 is high byte of 16 (weight) "bitmask_vehicle_info": {"size": 1, "num": 0x25}, "retire_early": {"size": 1, "num": 0x26}, "misc_flags": {"size": 1, "num": 0x27}, "refittable_cargo_classes": refittable_cargo_classes(0x28, 0x32, 0x1D), "non_refittable_cargo_classes": [{"size": 2, "num": 0x29}, zero_refit_mask(0x1D)], "introduction_date": {"size": 4, "num": 0x2A}, "cargo_age_period": {"size": 2, "num": 0x2B}, "cargo_allow_refit": [ {"custom_function": lambda value: ctt_list(0x2C, value)}, zero_refit_mask(0x1D) ], "cargo_disallow_refit": [ {"custom_function": lambda value: ctt_list(0x2D, value)} , zero_refit_mask(0x1D) ], "curve_speed_mod": {"size": 2, "num": 0x2E, "unit_conversion": 256}, "variant_group": {"size": 2, "num": 0x2F}, "extra_flags": {"size": 4, "num": 0x30}, } # fmt: on # # Feature 0x01 (Road Vehicles) # def roadveh_speed_prop(prop_info): # prop 08 value is min(value, 255) def prop08_value(value): return nmlop.MIN(value, 0xFF).reduce() # prop 15 value is (value + 3) / 4 def prop15_value(value): return nmlop.DIV(nmlop.ADD(value, 3), 4).reduce() # prop 15 should not be set if value(prop08_value) <= 255. # But as we test prop15 and prop15 = 0.25/prop08, test for 64: def prop15_test(value): return isinstance(value, ConstantNumeric) and value.value >= 0x40 prop08 = {"size": 1, "num": 0x08, "value_function": prop08_value} prop15 = {"size": 1, "num": 0x15, "value_function": prop15_value, "test_function": prop15_test} for key in prop_info: prop08[key] = prop15[key] = prop_info[key] return [prop08, prop15] # fmt: off properties[0x01] = { **general_veh_props, "road_type": {"size": 1, "num": 0x05}, "tram_type": {"size": 1, "num": 0x05}, "speed": roadveh_speed_prop( { "unit_type": "speed", "unit_conversion": (10000, 1397), "adjust_value": lambda val, unit: ottd_display_speed(val, 1, 2, unit), } ), "running_cost_factor": {"size": 1, "num": 0x09}, "running_cost_base": {"size": 4, "num": 0x0A}, # 0B -0D don"t exist "sprite_id": {"size": 1, "num": 0x0E}, "cargo_capacity": {"size": 1, "num": 0x0F}, "default_cargo_type": {"size": 1, "num": 0x10}, "cost_factor": {"size": 1, "num": 0x11}, "sound_effect": {"size": 1, "num": 0x12}, "power": {"size": 1, "num": 0x13, "unit_type": "power", "unit_conversion": (1, 10)}, "weight": {"size": 1, "num": 0x14, "unit_type": "weight", "unit_conversion": 4}, # 15 is set together with 08 (see above) # 16 (refittable cargo types) is removed, it is zeroed when setting a different refit property # 17 (callback flags) is not set by user "tractive_effort_coefficient": {"size": 1, "num": 0x18, "unit_conversion": 255}, "air_drag_coefficient": {"size": 1, "num": 0x19, "unit_conversion": 255}, "refit_cost": {"size": 1, "num": 0x1A}, "retire_early": {"size": 1, "num": 0x1B}, "misc_flags": {"size": 1, "num": 0x1C}, "refittable_cargo_classes": refittable_cargo_classes(0x1D, 0x29, 0x16), "non_refittable_cargo_classes": [{"size": 2, "num": 0x1E}, zero_refit_mask(0x16)], "introduction_date": {"size": 4, "num": 0x1F}, # 20 (sort purchase list) is implemented elsewhere # 21 has two names, to simplify docs "visual_effect": {"size": 1, "num": 0x21}, "effect_spawn_model": {"size": 1, "num": 0x21}, "cargo_age_period": {"size": 2, "num": 0x22}, "length": {"size": 1, "num": 0x23, "value_function": vehicle_length}, "cargo_allow_refit": [ {"custom_function": lambda value: ctt_list(0x24, value)}, zero_refit_mask(0x16), ], "cargo_disallow_refit": [ {"custom_function": lambda value: ctt_list(0x25, value)}, zero_refit_mask(0x16), ], "variant_group": {"size": 2, "num": 0x26}, "extra_flags": {"size": 4, "num": 0x27}, } # fmt: on # # Feature 0x02 (Ships) # def speed_fraction(value): # Unit is already converted to 0 .. 255 range when we get here if isinstance(value, ConstantNumeric) and not (0 <= value.value <= 255): # Do not use check_range to provide better error message raise generic.ScriptError("speed fraction must be in range 0 .. 1", value.pos) return nmlop.SUB(255, value).reduce() def ship_speed_prop(prop_info): # prop 0B value is min(value, 255) def prop0B_value(value): return nmlop.MIN(value, 0xFF).reduce() # prop 23 value is min(value, 65535) def prop23_value(value): return nmlop.MIN(value, 0xFFFF).reduce() # prop 23 should not be set if value(prop0B_value) <= 255. def prop23_test(value): return isinstance(value, ConstantNumeric) and value.value >= 0xFF prop0B = {"size": 1, "num": 0x0B, "value_function": prop0B_value} prop23 = {"size": 2, "num": 0x23, "value_function": prop23_value, "test_function": prop23_test} for key in prop_info: prop0B[key] = prop23[key] = prop_info[key] return [prop0B, prop23] # fmt: off properties[0x02] = { **general_veh_props, "sprite_id": {"size": 1, "num": 0x08}, "is_refittable": {"size": 1, "num": 0x09}, "cost_factor": {"size": 1, "num": 0x0A}, "speed": ship_speed_prop( { "unit_type": "speed", "unit_conversion": (10000, 1397), "adjust_value": lambda val, unit: ottd_display_speed(val, 1, 2, unit), } ), "default_cargo_type": {"size": 1, "num": 0x0C}, "cargo_capacity": {"size": 2, "num": 0x0D}, # 0E does not exist "running_cost_factor": {"size": 1, "num": 0x0F}, "sound_effect": {"size": 1, "num": 0x10}, # 11 (refittable cargo types) is removed, it is zeroed when setting a different refit property # 12 (callback flags) is not set by user "refit_cost": {"size": 1, "num": 0x13}, "ocean_speed_fraction": { "size": 1, "num": 0x14, "unit_conversion": 255, "value_function": speed_fraction, }, "canal_speed_fraction": { "size": 1, "num": 0x15, "unit_conversion": 255, "value_function": speed_fraction, }, "retire_early": {"size": 1, "num": 0x16}, "misc_flags": {"size": 1, "num": 0x17}, "refittable_cargo_classes": refittable_cargo_classes(0x18, 0x25, 0x11), "non_refittable_cargo_classes": [{"size": 2, "num": 0x19}, zero_refit_mask(0x11)], "introduction_date": {"size": 4, "num": 0x1A}, # 1B (sort purchase list) is implemented elsewhere # 1C has two names, to simplify docs "visual_effect": {"size": 1, "num": 0x1C}, "effect_spawn_model": {"size": 1, "num": 0x1C}, "cargo_age_period": {"size": 2, "num": 0x1D}, "cargo_allow_refit": [ {"custom_function": lambda value: ctt_list(0x1E, value)}, zero_refit_mask(0x11), ], "cargo_disallow_refit": [ {"custom_function": lambda value: ctt_list(0x1F, value)}, zero_refit_mask(0x11), ], "variant_group": {"size": 2, "num": 0x20}, "extra_flags": {"size": 4, "num": 0x21}, "acceleration": {"size": 1, "num": 0x24}, } # fmt: on # # Feature 0x03 (Aircraft) # def aircraft_is_heli(value): if isinstance(value, ConstantNumeric) and value.value not in (0, 2, 3): raise generic.ScriptError("Invalid value for aircraft_type", value.pos) return nmlop.AND(value, 2).reduce() def aircraft_is_large(value): return nmlop.AND(value, 1).reduce() # fmt: off properties[0x03] = { **general_veh_props, "sprite_id": {"size": 1, "num": 0x08}, "aircraft_type": [ {"size": 1, "num": 0x09, "value_function": aircraft_is_heli}, {"size": 1, "num": 0x0A, "value_function": aircraft_is_large} ], "cost_factor": {"size": 1, "num": 0x0B}, "speed": { "size": 1, "num": 0x0C, "unit_type": "speed", "unit_conversion": (701, 2507), "adjust_value": lambda val, unit: ottd_display_speed(val, 128, 10, unit) }, "acceleration": {"size": 1, "num": 0x0D}, "running_cost_factor": {"size": 1, "num": 0x0E}, "passenger_capacity": {"size": 2, "num": 0x0F}, # 10 does not exist "mail_capacity": {"size": 1, "num": 0x11}, "sound_effect": {"size": 1, "num": 0x12}, # 13 (refittable cargo types) is removed, it is zeroed when setting a different refit property # 14 (callback flags) is not set by user "refit_cost": {"size": 1, "num": 0x15}, "retire_early": {"size": 1, "num": 0x16}, "misc_flags": {"size": 1, "num": 0x17}, "refittable_cargo_classes": refittable_cargo_classes(0x18, 0x23, 0x13), "non_refittable_cargo_classes": [{"size": 2, "num": 0x19}, zero_refit_mask(0x13)], "introduction_date": {"size": 4, "num": 0x1A}, # 1B (sort purchase list) is implemented elsewhere "cargo_age_period": {"size": 2, "num": 0x1C}, "cargo_allow_refit": [ {"custom_function": lambda value: ctt_list(0x1D, value)}, zero_refit_mask(0x13), ], "cargo_disallow_refit": [ {"custom_function": lambda value: ctt_list(0x1E, value)}, zero_refit_mask(0x13), ], "range": {"size": 2, "num": 0x1F}, "variant_group": {"size": 2, "num": 0x20}, "extra_flags": {"size": 4, "num": 0x21}, } # fmt: on # # Feature 0x04 (Stations) # def station_platforms_length(value): # Writing bitmask(2) to disable platform/length 3 is not very intuitive. # Instead we expect the user will write bitmask(3) and we shift the result. return nmlop.SHIFT_RIGHT(value, 1, value.pos).reduce() def station_flags(value): # bit 4 (extended foundations) can't be set without bit 3 (custom foundations) cust_found = nmlop.SHIFT_RIGHT(value, 4, value.pos) cust_found = nmlop.AND(cust_found, 1) cust_found = nmlop.MUL(cust_found, 0x08) return nmlop.OR(value, cust_found).reduce() def cargo_bitmask(value): if not isinstance(value, Array): raise generic.ScriptError("Cargo list must be an array", value.pos) return BitMask(value.values, value.pos).reduce() class StationLayoutProp(BaseAction0Property): def __init__(self, prop_num, data): self.prop_num = prop_num self.data = data def write(self, file): file.print_bytex(self.prop_num) for (length, number), layout in self.data.items(): file.print_byte(length) file.print_byte(number) file.newline() for platform in layout: for type in platform: file.print_bytex(type) file.newline() file.print_byte(0) file.print_byte(0) file.newline() def get_size(self): total_len = 3 # Prop number + ending for length, number in self.data: total_len += length * number + 2 return total_len def station_layouts(value): if isinstance(value, ConstantNumeric): return [Action0Property(0x0F, value, 1 if value.value < 0xFF else 3)] if not isinstance(value, Array) or len(value.values) == 0: raise generic.ScriptError("station_layouts must be an array of layouts, or the ID of a station", value.pos) layouts = {} for layout in value.values: if not isinstance(layout, Array) or len(layout.values) == 0: raise generic.ScriptError("A station layout must be an array of platforms", layout.pos) length = len(layout.values[0].values) number = len(layout.values) if (length, number) in layouts: generic.print_warning( generic.Warning.GENERIC, "Redefinition of layout {}x{}".format(length, number), layout.pos ) layouts[(length, number)] = [] for platform in layout.values: if not isinstance(platform, Array) or len(platform.values) == 0: raise generic.ScriptError("A platform must be an array of tile types") if len(platform.values) != length: raise generic.ScriptError("All platforms in a station layout must have the same length", platform.pos) for type in platform.values: if type.reduce_constant().value % 2 != 0: generic.print_warning( generic.Warning.GENERIC, "Invalid tile {} in layout {}x{}".format(type, length, number), type.pos, ) layouts[(length, number)].append( [nmlop.AND(type, 0xFE).reduce_constant().value for type in platform.values] ) return [StationLayoutProp(0x0E, layouts)] def station_tile_flags(value): if not isinstance(value, Array) or len(value.values) % 2 != 0: raise generic.ScriptError("Flag list must be an array of even length", value.pos) if len(value.values) > 8: return [VariableByteListProp(0x1E, [[flags.reduce_constant().value for flags in value.values]], True)] pylons = 0 wires = 0 blocked = 0 for i, val in enumerate(value.values): flag = val.value if flag & 1 << global_constants.constant_numbers["STAT_TILE_PYLON"]: pylons = pylons | 1 << i if flag & 1 << global_constants.constant_numbers["STAT_TILE_NOWIRE"]: wires = wires | 1 << i if flag & 1 << global_constants.constant_numbers["STAT_TILE_BLOCKED"]: blocked = blocked | 1 << i return [ Action0Property(0x11, ConstantNumeric(pylons), 1), Action0Property(0x14, ConstantNumeric(wires), 1), Action0Property(0x15, ConstantNumeric(blocked), 1), ] # fmt: off properties[0x04] = { "class": {"size": 4, "num": 0x08, "first": None, "string_literal": 4}, # 09 (sprite layout) is implemented elsewhere # 0A (copy sprite layout) is implemented elsewhere # 0B (callback flags) is not set by user "disabled_platforms": {"size": 1, "num": 0x0C, "value_function": station_platforms_length}, "disabled_length": {"size": 1, "num": 0x0D, "value_function": station_platforms_length}, "station_layouts": {"custom_function": station_layouts}, # = prop 0E # 0F (copy station layout) is handled by station_layouts "cargo_threshold": {"size": 2, "num": 0x10}, "draw_pylon_tiles": {"size": 1, "num": 0x11, "replaced_by": "tile_flags"}, "cargo_random_triggers": {"size": 4, "num": 0x12, "value_function": cargo_bitmask}, "general_flags": {"size": 1, "num": 0x13, "value_function": station_flags}, "hide_wire_tiles": {"size": 1, "num": 0x14, "replaced_by": "tile_flags"}, "non_traversable_tiles": {"size": 1, "num": 0x15, "replaced_by": "tile_flags"}, "animation_info": {"size": 2, "num": 0x16, "value_function": animation_info}, "animation_speed": {"size": 1, "num": 0x17}, "animation_triggers": {"size": 2, "num": 0x18}, # 19 (road routing) reserved for future use # 1A (advanced sprite layout) is implemented elsewhere # 1B (minimum bridge height) JGR only "name": {"size": 2, "num": (256, -1, 0x1C), "string": (256, 0xC5, 0xDC), "required": True}, "classname": {"size": 2, "num": (256, -1, 0x1D), "string": (256, 0xC4, 0xDC)}, "tile_flags": {"custom_function": station_tile_flags}, # = prop 1E } # fmt: on # # Feature 0x05 (Canals) # properties[0x05] = { # 08 (callback flags) not set by user "graphic_flags": {"size": 1, "num": 0x09}, } # TODO: Feature 0x06 # # Feature 0x07 (Houses) # def house_prop_0A(value): # User sets an array [min_year, max_year] as property value # House property 0A is set to ((max_year - 1920) << 8) | (min_year - 1920) # With both bytes clamped to the 0 .. 255 range if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = nmlop.SUB(value.values[0], 1920, value.pos) min_year = nmlop.MAX(min_year, 0) min_year = nmlop.MIN(min_year, 255) max_year = nmlop.SUB(value.values[1], 1920, value.pos) max_year = nmlop.MAX(max_year, 0) max_year = nmlop.MIN(max_year, 255) max_year = nmlop.SHIFT_LEFT(max_year, 8) return nmlop.OR(max_year, min_year).reduce() def house_prop_21_22(value, index): # Take one of the values from the years_available array if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) return value.values[index] def house_random_colours(value): # User sets array with 4 values (range 0..15) # Output is a dword, each byte being a value from the array if not isinstance(value, Array) or len(value.values) != 4: raise generic.ScriptError("Random colours must be an array with exactly four values", value.pos) ret = None for i, colour in enumerate(value.values): if isinstance(colour, ConstantNumeric): generic.check_range(colour.value, 0, 15, "Random house colours", colour.pos) byte = nmlop.AND(colour, 0xFF) if i == 0: ret = byte else: byte = nmlop.SHIFT_LEFT(byte, i * 8) ret = nmlop.OR(ret, byte) return ret.reduce() def house_available_mask(value): # User sets [town_zones, climates] array # Which is mapped to (town_zones | (climates & 0x800) | ((climates & 0xF) << 12)) if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("availability_mask must be an array with exactly 2 values", value.pos) climates = nmlop.AND(value.values[1], 0xF, value.pos) climates = nmlop.SHIFT_LEFT(climates, 12) above_snow = nmlop.AND(value.values[1], 0x800, value.pos) ret = nmlop.OR(climates, value.values[0]) ret = nmlop.OR(ret, above_snow) return ret.reduce() # List of valid IDs of old house types old_houses = { 0: set(), # 1x1, see below 2: {74, 76, 87}, # 2x1 3: {7, 66, 68, 99}, # 1x2 4: {20, 32, 40}, # 2x2 } # All houses not part of a multitile-house, are 1x1 houses old_houses[0] = set(range(110)).difference( house + i for house in (itertools.chain(*list(old_houses.values()))) for i in range(4 if house in old_houses[4] else 2) ) def mt_house_old_id(value, num_ids, size_bit): # For substitute / override properties # Set value for tile i (0 .. 3) to (value + i) # Also validate that the size of the old house matches if isinstance(value, ConstantNumeric) and value.value not in old_houses[size_bit]: raise generic.ScriptError( "Substitute / override house type must have the same size as the newly defined house.", value.pos ) ret = [value] for i in range(1, num_ids): ret.append(nmlop.ADD(value, i).reduce()) return ret def mt_house_prop09(value, num_ids, size_bit): # Only bit 5 should be set for additional tiles # Additionally, correctly set the size bit (0, 2, 3 or 4) for the first tile if isinstance(value, ConstantNumeric) and (value.value & 0x1D) != 0: raise generic.ScriptError("Invalid bits set in house property 'building_flags'.", value.pos) ret = [nmlop.OR(value, 1 << size_bit).reduce()] for _i in range(1, num_ids): ret.append(nmlop.AND(value, 1 << 5).reduce()) return ret def mt_house_mask(mask, value, num_ids, size_bit): # Mask out the bits not present in the 'mask' parameter for additional tiles ret = [value] for _i in range(1, num_ids): ret.append(nmlop.AND(value, mask).reduce()) return ret def mt_house_zero(value, num_ids, size_bit): return [value] + (num_ids - 1) * [ConstantNumeric(0, value.pos)] def mt_house_same(value, num_ids, size_bit): # Set to the same value for all tiles return num_ids * [value] def mt_house_class(value, num_ids, size_bit): # Set class to 0xFF for additional tiles return [value] + (num_ids - 1) * [ConstantNumeric(0xFF, value.pos)] # fmt: off properties[0x07] = { "substitute": {"size": 1, "num": 0x08, "multitile_function": mt_house_old_id, "first": None}, "building_flags": two_byte_property( 0x09, 0x19, {"multitile_function": mt_house_prop09}, {"multitile_function": lambda *args: mt_house_mask(0xFE, *args)} ), "years_available": [ {"size": 2, "num": 0x0A, "multitile_function": mt_house_zero, "value_function": house_prop_0A}, { "size": 2, "num": 0x21, "multitile_function": mt_house_zero, "value_function": lambda value: house_prop_21_22(value, 0), }, { "size": 2, "num": 0x22, "multitile_function": mt_house_zero, "value_function": lambda value: house_prop_21_22(value, 1), }, ], "population": {"size": 1, "num": 0x0B, "multitile_function": mt_house_zero}, "mail_multiplier": {"size": 1, "num": 0x0C, "multitile_function": mt_house_zero}, # prop 0D - 0F are replaced by prop 23 "local_authority_impact": {"size": 2, "num": 0x10, "multitile_function": mt_house_same}, "removal_cost_multiplier": {"size": 1, "num": 0x11, "multitile_function": mt_house_same}, "name": {"size": 2, "num": 0x12, "string": 0xDC, "multitile_function": mt_house_same}, "availability_mask": { "size": 2, "num": 0x13, "multitile_function": mt_house_zero, "value_function": house_available_mask, }, # prop 14 (callback flags 1) is not set by user "override": {"size": 1, "num": 0x15, "multitile_function": mt_house_old_id}, "refresh_multiplier": {"size": 1, "num": 0x16, "multitile_function": mt_house_same}, "random_colours": { "size": 4, "num": 0x17, "multitile_function": mt_house_same, "value_function": house_random_colours, }, "probability": {"size": 1, "num": 0x18, "multitile_function": mt_house_zero, "unit_conversion": 16}, # prop 19 is the high byte of prop 09 "animation_info": { "size": 1, "num": 0x1A, "multitile_function": mt_house_same, "value_function": lambda value: animation_info(value, 7, 128), }, "animation_speed": {"size": 1, "num": 0x1B, "multitile_function": mt_house_same}, "building_class": {"size": 1, "num": 0x1C, "multitile_function": mt_house_class}, # prop 1D (callback flags 2) is not set by user # prop 1E is replaced by prop 23 "minimum_lifetime": {"size": 1, "num": 0x1F, "multitile_function": mt_house_zero}, "watched_cargo_types": { "multitile_function": mt_house_same, "custom_function": lambda *values: ctt_list(0x20, *values), }, # prop 21 -22 see above (years_available, prop 0A) "accepted_cargos": { "multitile_function": mt_house_same, "custom_function": lambda *values: accepted_cargos(0x23, *values), }, } # fmt: on # Feature 0x08 (General Vars) is implemented elsewhere (e.g. basecost, snowline) # # Feature 0x09 (Industry Tiles) # # fmt: off properties[0x09] = { "substitute": {"size": 1, "num": 0x08, "first": None}, "override": {"size": 1, "num": 0x09}, # prop 0A - 0C are replaced by prop 13 "land_shape_flags": {"size": 1, "num": 0x0D}, # prop 0E (callback flags) is not set by user "animation_info": {"size": 2, "num": 0x0F, "value_function": animation_info}, "animation_speed": {"size": 1, "num": 0x10}, "animation_triggers": {"size": 1, "num": 0x11}, "special_flags": {"size": 1, "num": 0x12}, "accepted_cargos": {"custom_function": lambda value: accepted_cargos(0x13, value)}, } # fmt: on # # Feature 0x0A (Industries) # class IndustryLayoutProp(BaseAction0Property): def __init__(self, layout_list): self.layout_list = layout_list def write(self, file): file.print_bytex(0x0A) file.print_byte(len(self.layout_list)) # -6 because prop_num, num_layouts and size should not be included file.print_dword(self.get_size() - 6) file.newline() for layout in self.layout_list: layout.write(file) file.newline() def get_size(self): size = 6 for layout in self.layout_list: size += layout.get_size() return size def industry_layouts(value): if not isinstance(value, Array) or not all(isinstance(x, Identifier) for x in value.values): raise generic.ScriptError("layouts must be an array of layout names", value.pos) layouts = [] for name in value.values: if name.value not in tilelayout_names: raise generic.ScriptError("Unknown layout name '{}'".format(name.value), name.pos) layouts.append(tilelayout_names[name.value]) return [IndustryLayoutProp(layouts)] def industry_prod_multiplier(value): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Prod multiplier must be an array of up to two values", value.pos) props = [] for i in range(0, 2): val = value.values[i].reduce_constant() if i < len(value.values) else ConstantNumeric(0) props.append(Action0Property(0x12 + i, val, 1)) return props class RandomSoundsProp(BaseAction0Property): def __init__(self, sound_list): self.sound_list = sound_list def write(self, file): file.print_bytex(0x15) file.print_byte(len(self.sound_list)) for sound in self.sound_list: sound.write(file, 1) file.newline() def get_size(self): return len(self.sound_list) + 2 def random_sounds(value): if not isinstance(value, Array) or not all(isinstance(x, ConstantNumeric) for x in value.values): raise generic.ScriptError("random_sound_effects must be an array with sounds effects", value.pos) return [RandomSoundsProp(value.values)] class ConflictingTypesProp(BaseAction0Property): def __init__(self, types_list): self.types_list = types_list assert len(self.types_list) == 3 def write(self, file): file.print_bytex(0x16) for type in self.types_list: type.write(file, 1) file.newline() def get_size(self): return len(self.types_list) + 1 def industry_conflicting_types(value): if not isinstance(value, Array): raise generic.ScriptError("conflicting_ind_types must be an array of industry types", value.pos) if len(value.values) > 3: raise generic.ScriptError("conflicting_ind_types may have at most three entries", value.pos) types_list = [] for val in value.values: types_list.append(val.reduce_constant()) while len(types_list) < 3: types_list.append(ConstantNumeric(0xFF)) return [ConflictingTypesProp(types_list)] def industry_input_multiplier(value, prop_num): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Input multiplier must be an array of up to two values", value.pos) val1 = value.values[0].reduce() if len(value.values) > 0 else ConstantNumeric(0) val2 = value.values[1].reduce() if len(value.values) > 1 else ConstantNumeric(0) if not isinstance(val1, (ConstantNumeric, ConstantFloat)) or not isinstance(val2, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Expected a compile-time constant", value.pos) generic.check_range(val1.value, 0, 256, "input_multiplier", val1.pos) generic.check_range(val2.value, 0, 256, "input_multiplier", val2.pos) mul1 = int(val1.value * 256) mul2 = int(val2.value * 256) return [Action0Property(prop_num, ConstantNumeric(mul1 | (mul2 << 16)), 4)] class IndustryInputMultiplierProp(BaseAction0Property): def __init__(self, prop_num, data): self.prop_num = prop_num self.data = data def write(self, file): file.print_bytex(self.prop_num) if len(self.data) == 0: file.print_byte(0) file.print_byte(0) else: file.print_byte(len(self.data)) file.print_byte(len(self.data[0])) # assume all sub-arrays are equal length file.newline() for out_muls in self.data: for mul in out_muls: file.print_wordx(mul) file.newline() def get_size(self): if len(self.data) == 0: return 3 else: return 3 + len(self.data) * len(self.data[0]) * 2 def industry_cargo_types(value): if isinstance(value, Array): cargo_types = value.values else: cargo_types = [value] if not all(isinstance(item, (ProduceCargo, AcceptCargo)) for item in value.values): raise generic.ScriptError( "Cargo types definition must be an array produce_cargo() and accept_cargo() expressions", value.pos ) # collect all the cargo types involved input_cargos = [] output_cargos = [] def check_produce(prd): if len(prd.value) != 1: raise generic.ScriptError("Cargo types produce_cargo() expressions require 2 arguments", prd.pos) if not isinstance(prd.value[0], (ConstantNumeric, ConstantFloat)): raise generic.ScriptError( "Cargo types produce_cargo() expressions must have numeric constant values", prd.pos ) if prd.cargotype not in output_cargos: output_cargos.append(prd.cargotype) def check_accept(acp): if item.cargotype not in input_cargos: input_cargos.append(item.cargotype) for outitem in item.value: if isinstance(outitem, ProduceCargo): check_produce(outitem) else: raise generic.ScriptError( "Cargo types accept_cargo() expressions must only contain produce_cargo() expressions", outitem.pos ) for item in cargo_types: # use "if not in: append" idiom rather than sets to preserve ordering of cargotypes between NML and NFO if isinstance(item, ProduceCargo): check_produce(item) elif isinstance(item, AcceptCargo): check_accept(item) else: raise AssertionError() if len(input_cargos) > 16: raise generic.ScriptError( "Cargo types definition contains more than 16 different accept_cargo() cargotypes", value.pos ) if len(output_cargos) > 16: raise generic.ScriptError( "Cargo types definition contains more than 16 different produce_cargo() cargotypes", value.pos ) # prepare lists for the remaining output properties prod_multipliers = [0] * len(output_cargos) input_multipliers = [[0] * len(output_cargos) for _ in input_cargos] has_inpmult = False # populate prod_multipliers and input_multipliers for item in cargo_types: if isinstance(item, ProduceCargo): prod_multipliers[output_cargos.index(item.cargotype)] = int(item.value[0].value) elif isinstance(item, AcceptCargo): row = input_multipliers[input_cargos.index(item.cargotype)] for outitem in item.value: row[output_cargos.index(outitem.cargotype)] = int(outitem.value[0].value * 256) if outitem.value[0].value > 0: has_inpmult = True return [ VariableByteListProp(0x25, [output_cargos]), VariableByteListProp(0x26, [input_cargos]), VariableByteListProp(0x27, [prod_multipliers]), IndustryInputMultiplierProp(0x28, input_multipliers if has_inpmult else []), ] # fmt: off properties[0x0A] = { "substitute": {"size": 1, "num": 0x08, "first": None}, "override": {"size": 1, "num": 0x09}, "layouts": {"custom_function": industry_layouts}, # = prop 0A "life_type": {"size": 1, "num": 0x0B}, "closure_msg": {"size": 2, "num": 0x0C, "string": 0xDC}, "prod_increase_msg": {"size": 2, "num": 0x0D, "string": 0xDC}, "prod_decrease_msg": {"size": 2, "num": 0x0E, "string": 0xDC}, "fund_cost_multiplier": {"size": 1, "num": 0x0F}, "prod_cargo_types": { "size": 2, "num": 0x10, "value_function": lambda value: cargo_list(value, 2), "replaced_by": "cargo_types", }, "accept_cargo_types": { "size": 4, "num": 0x11, "value_function": lambda value: cargo_list(value, 3), "replaced_by": "cargo_types", }, # prop 12,13 "prod_multiplier": { "custom_function": industry_prod_multiplier, "replaced_by": "cargo_types", }, "min_cargo_distr": {"size": 1, "num": 0x14}, "random_sound_effects": {"custom_function": random_sounds}, # = prop 15 "conflicting_ind_types": {"custom_function": industry_conflicting_types}, # = prop 16 "prob_random": {"size": 1, "num": 0x17}, # Obsolete, ambiguous name, use "prob_map_gen" instead "prob_map_gen": {"size": 1, "num": 0x17}, "prob_in_game": {"size": 1, "num": 0x18}, "map_colour": {"size": 1, "num": 0x19}, "spec_flags": {"size": 4, "num": 0x1A}, "new_ind_msg": {"size": 2, "num": 0x1B, "string": 0xDC}, "input_multiplier_1": { "custom_function": lambda value: industry_input_multiplier(value, 0x1C), "replaced_by": "cargo_types", }, "input_multiplier_2": { "custom_function": lambda value: industry_input_multiplier(value, 0x1D), "replaced_by": "cargo_types", }, "input_multiplier_3": { "custom_function": lambda value: industry_input_multiplier(value, 0x1E), "replaced_by": "cargo_types", }, "name": {"size": 2, "num": 0x1F, "string": 0xDC}, "prospect_chance": {"size": 4, "num": 0x20, "unit_conversion": 0xFFFFFFFF}, # prop 21, 22 (callback flags) are not set by user "remove_cost_multiplier": {"size": 4, "num": 0x23}, "nearby_station_name": {"size": 2, "num": 0x24, "string": 0xDC}, # prop 25+26+27+28 combined in one structure "cargo_types": {"custom_function": industry_cargo_types}, } # fmt: on # # Feature 0x0B (Cargos) # # fmt: off properties[0x0B] = { "number": {"num": 0x08, "size": 1}, "type_name": {"num": 0x09, "size": 2, "string": 0xDC}, "unit_name": {"num": 0x0A, "size": 2, "string": 0xDC}, # Properties 0B, 0C are not used by OpenTTD "type_abbreviation": {"num": 0x0D, "size": 2, "string": 0xDC}, "sprite": {"num": 0x0E, "size": 2}, "weight": {"num": 0x0F, "size": 1, "unit_type": "weight", "unit_conversion": 16}, "penalty_lowerbound": {"num": 0x10, "size": 1}, "single_penalty_length": {"num": 0x11, "size": 1}, # 10 units of cargo across 20 tiles, with time factor = 255 "price_factor": {"num": 0x12, "size": 4, "unit_conversion": (1 << 21, 10 * 20 * 255)}, "station_list_colour": {"num": 0x13, "size": 1}, "cargo_payment_list_colour": {"num": 0x14, "size": 1}, "is_freight": {"num": 0x15, "size": 1}, "cargo_classes": {"num": 0x16, "size": 2}, "cargo_label": {"num": 0x17, "size": 4, "string_literal": 4}, "town_growth_effect": {"num": 0x18, "size": 1}, "town_growth_multiplier": {"num": 0x19, "size": 2, "unit_conversion": 0x100}, # 1A (callback flags) is not set by user "units_of_cargo": {"num": 0x1B, "size": 2, "string": 0xDC}, "items_of_cargo": {"num": 0x1C, "size": 2, "string": 0xDC}, "capacity_multiplier": {"num": 0x1D, "size": 2, "unit_conversion": 0x100}, "town_production_effect": {"num": 0x1E, "size": 1}, "town_production_multiplier": {"num": 0x1F, "size": 2, "unit_conversion": 0x100}, } # fmt: on # Feature 0x0C (Sound Effects) is implemented differently # # Feature 0x0D (Airports) # def airport_years(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = value.values[0].reduce_constant() max_year = value.values[1].reduce_constant() return [Action0Property(0x0C, ConstantNumeric(max_year.value << 16 | min_year.value), 4)] class AirportLayoutProp(BaseAction0Property): def __init__(self, layout_list): self.layout_list = layout_list def write(self, file): file.print_bytex(0x0A) file.print_byte(len(self.layout_list)) # -6 because prop_num, num_layouts and size should not be included file.print_dword(self.get_size() - 6) file.newline() for layout in self.layout_list: file.print_bytex(layout.properties["rotation"].value) layout.write(file) file.newline() def get_size(self): size = 6 for layout in self.layout_list: size += layout.get_size() + 1 return size def airport_layouts(value): if not isinstance(value, Array) or not all(isinstance(x, Identifier) for x in value.values): raise generic.ScriptError("layouts must be an array of layout names", value.pos) layouts = [] for name in value.values: if name.value not in tilelayout_names: raise generic.ScriptError("Unknown layout name '{}'".format(name.value), name.pos) layout = tilelayout_names[name.value] if "rotation" not in layout.properties: raise generic.ScriptError("Airport layouts must have the 'rotation' property", layout.pos) if layout.properties["rotation"].value not in (0, 2, 4, 6): raise generic.ScriptError( "Airport layout rotation is not a valid direction.", layout.properties["rotation"].pos ) layouts.append(layout) return [AirportLayoutProp(layouts)] # fmt: off properties[0x0D] = { "override": {"size": 1, "num": 0x08, "first": None}, # 09 does not exist "layouts": {"custom_function": airport_layouts}, # = prop 0A # 0B does not exist "years_available": {"custom_function": airport_years}, # = prop 0C "ttd_airport_type": {"size": 1, "num": 0x0D}, "catchment_area": {"size": 1, "num": 0x0E}, "noise_level": {"size": 1, "num": 0x0F}, "name": {"size": 2, "num": 0x10, "string": 0xDC}, "maintenance_cost": {"size": 2, "num": 0x11}, } # fmt: on # Feature 0x0E (Signals) doesn't currently have any action0 # # Feature 0x0F (Objects) # def object_size(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Object size must be an array with exactly two values", value.pos) sizex = value.values[0].reduce_constant() sizey = value.values[1].reduce_constant() if sizex.value < 1 or sizex.value > 15 or sizey.value < 1 or sizey.value > 15: raise generic.ScriptError("The size of an object must be at least 1x1 and at most 15x15 tiles", value.pos) return [Action0Property(0x0C, ConstantNumeric(sizey.value << 4 | sizex.value), 1)] # fmt: off properties[0x0F] = { "class": {"size": 4, "num": 0x08, "first": None, "string_literal": 4}, # strings might be according to specs be either 0xD0 or 0xD4 "classname": {"size": 2, "num": 0x09, "string": 0xD0}, "name": {"size": 2, "num": 0x0A, "string": 0xD0, "required": True}, "climates_available": {"size": 1, "num": 0x0B, "required": True}, "size": {"custom_function": object_size, "required": True}, # = prop 0C "build_cost_multiplier": {"size": 1, "num": 0x0D}, "introduction_date": {"size": 4, "num": 0x0E}, "end_of_life_date": {"size": 4, "num": 0x0F}, "object_flags": {"size": 2, "num": 0x10}, "animation_info": {"size": 2, "num": 0x11, "value_function": animation_info}, "animation_speed": {"size": 1, "num": 0x12}, "animation_triggers": {"size": 2, "num": 0x13}, "remove_cost_multiplier": {"size": 1, "num": 0x14}, # 15 (callback flags) is not set by user "height": {"size": 1, "num": 0x16}, "num_views": {"size": 1, "num": 0x17}, "count_per_map256": {"size": 1, "num": 0x18}, } # fmt: on # # General tracktype properties that apply to features 0x10 & 0x12/13 (rail/road/tramtypes) # class LabelListProp(BaseAction0Property): def __init__(self, prop_num, labels): self.prop_num = prop_num self.labels = labels def write(self, file): file.print_bytex(self.prop_num) file.print_byte(len(self.labels)) for label in self.labels: parse_string_to_dword(label) # Error if the wrong length or not ASCII label.write(file, 4) file.newline() def get_size(self): return len(self.labels) * 4 + 2 def label_list(value, prop_num, description): if not isinstance(value, Array): raise generic.ScriptError(description + " list must be an array of literal strings", value.pos) return [LabelListProp(prop_num, value.values)] # fmt: off common_tracktype_props = { # label allocated during reservation stage, so doesn't need to be set first. "label": {"size": 4, "num": 0x08, "string_literal": 4}, "toolbar_caption": {"size": 2, "num": 0x09, "string": 0xDC}, "menu_text": {"size": 2, "num": 0x0A, "string": 0xDC}, "build_window_caption": {"size": 2, "num": 0x0B, "string": 0xDC}, "autoreplace_text": {"size": 2, "num": 0x0C, "string": 0xDC}, "new_engine_text": {"size": 2, "num": 0x0D, "string": 0xDC}, "construction_cost": {"size": 2, "num": 0x13}, "map_colour": {"size": 1, "num": 0x16}, "introduction_date": {"size": 4, "num": 0x17}, "sort_order": {"size": 1, "num": 0x1A}, "name": {"size": 2, "num": 0x1B, "string": 0xDC}, "maintenance_cost": {"size": 2, "num": 0x1C}, } # # Feature 0x10 (Rail Types) # properties[0x10] = { **common_tracktype_props, "compatible_railtype_list": {"custom_function": lambda x: label_list(x, 0x0E, "Railtype")}, "powered_railtype_list": {"custom_function": lambda x: label_list(x, 0x0F, "Railtype")}, "railtype_flags": {"size": 1, "num": 0x10}, "curve_speed_multiplier": {"size": 1, "num": 0x11}, "station_graphics": {"size": 1, "num": 0x12}, "speed_limit": { "size": 2, "num": 0x14, "unit_type": "speed", "unit_conversion": (5000, 1397), "adjust_value": lambda val, unit: ottd_display_speed(val, 1, 1, unit), }, "acceleration_model": {"size": 1, "num": 0x15}, "requires_railtype_list": {"custom_function": lambda x: label_list(x, 0x18, "Railtype")}, "introduces_railtype_list": {"custom_function": lambda x: label_list(x, 0x19, "Railtype")}, "alternative_railtype_list": {"custom_function": lambda x: label_list(x, 0x1D, "Railtype")}, } # # Feature 0x11 (Airport Tiles) # properties[0x11] = { "substitute": {"size": 1, "num": 0x08, "first": None}, "override": {"size": 1, "num": 0x09}, # 0A - 0D don"t exist (yet?) # 0E (callback flags) is not set by user "animation_info": {"size": 2, "num": 0x0F, "value_function": animation_info}, "animation_speed": {"size": 1, "num": 0x10}, "animation_triggers": {"size": 1, "num": 0x11}, } # # Feature 0x12 (Road Types) # properties[0x12] = { **common_tracktype_props, "powered_roadtype_list": {"custom_function": lambda x: label_list(x, 0x0F, "Roadtype")}, "roadtype_flags": {"size": 1, "num": 0x10}, "speed_limit": { "size": 2, "num": 0x14, "unit_type": "speed", "unit_conversion": (10000, 1397), "adjust_value": lambda val, unit: ottd_display_speed(val, 1, 2, unit) }, "requires_roadtype_list": {"custom_function": lambda x: label_list(x, 0x18, "Roadtype")}, "introduces_roadtype_list": {"custom_function": lambda x: label_list(x, 0x19, "Roadtype")}, "alternative_roadtype_list": {"custom_function": lambda x: label_list(x, 0x1D, "Roadtype")}, } # # Feature 0x13 (Tram Types) # properties[0x13] = { **common_tracktype_props, "powered_tramtype_list": {"custom_function": lambda x: label_list(x, 0x0F, "Tramtype")}, "tramtype_flags": {"size": 1, "num": 0x10}, "speed_limit": { "size": 2, "num": 0x14, "unit_type": "speed", "unit_conversion": (10000, 1397), "adjust_value": lambda val, unit: ottd_display_speed(val, 1, 2, unit), }, "requires_tramtype_list": {"custom_function": lambda x: label_list(x, 0x18, "Tramtype")}, "introduces_tramtype_list": {"custom_function": lambda x: label_list(x, 0x19, "Tramtype")}, "alternative_tramtype_list": {"custom_function": lambda x: label_list(x, 0x1D, "Tramtype")}, } # # Feature 0x14 (Road stops) # class ByteSequenceProp(BaseAction0Property): def __init__(self, prop_num, items, description, pos): self.prop_num = prop_num self.items = items self.description = description self.pos = pos def write(self, file): file.print_bytex(self.prop_num) for item in self.items: val = item.reduce_constant().value if val > 0xFF or val < 0: raise generic.ScriptError(self.description + " items must be bytes", self.pos) file.print_byte(val) file.newline() def get_size(self): return 1 + len(self.items) def byte_sequence_list(value, prop_num, description, expected_count): if not isinstance(value, Array) or len(value.values) != expected_count: raise generic.ScriptError(description + " must be an array of " + str(expected_count) + " bytes", value.pos) return [ByteSequenceProp(prop_num, value.values, description, value.pos)] properties[0x14] = { 'class': {'size': 4, 'num': 0x08, "first": None, "string_literal": 4}, 'availability_type': {'size': 1, 'num': 0x09}, 'name': {'size': 2, 'num': 0x0A, "string": 0xDC, "required": True}, 'classname': {'size': 2, 'num': 0x0B, "string": 0xDC}, 'draw_mode': {'size': 1, 'num': 0x0C}, "cargo_random_triggers": {"size": 4, "num": 0x0D}, "animation_info": {"size": 2, "num": 0x0E, "value_function": animation_info}, "animation_speed": {"size": 1, "num": 0x0F}, "animation_triggers": {"size": 2, "num": 0x10}, # 11 (callback flags) is not set by user "general_flags": {"size": 4, "num": 0x12}, "cost_multipliers": {"custom_function": lambda x: byte_sequence_list(x, 0x15, "Cost multipliers", 2)}, } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action1.py0000644000175100001660000002214114754345605016070 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import base_action, real_sprite, action2 class Action1(base_action.BaseAction): """ Class representing an Action1 @ivar feature: Feature of this action1 @type feature: C{int} @ivar first_set: Number of the first sprite set in this action 1. @type first_set: C{int} @ivar num_sets: Number of (sprite) sets that follow this action 1. @type num_sets: C{int} @ivar num_ent: Number of sprites per set (e.g. (usually) 8 for vehicles) @type num_ent: C{int} """ def __init__(self, feature, first_set, num_sets, num_ent): self.feature = feature self.first_set = first_set self.num_sets = num_sets self.num_ent = num_ent def write(self, file): if self.first_set == 0 and self.num_sets < 256: # * 01 file.start_sprite(6) file.print_bytex(1) file.print_bytex(self.feature) file.print_byte(self.num_sets) file.print_varx(self.num_ent, 3) file.newline() file.end_sprite() else: # * 01 00 file.start_sprite(12) file.print_bytex(1) file.print_bytex(self.feature) file.print_bytex(0) file.print_varx(self.first_set, 3) file.print_varx(self.num_sets, 3) file.print_varx(self.num_ent, 3) file.newline() file.end_sprite() class SpritesetCollection(base_action.BaseAction): """ A collection that contains multiple spritesets. All spritesets will be written to the same Action1, so they need to have the same number of sprites. @ivar feature: The feature number the action1 will get. @type feature: C{int} @ivar first_set: Number of the first sprite set in this action 1. @type first_set: C{int} @ivar num_sprites_per_spriteset: The number of sprites in each spriteset. @type num_sprites_per_spriteset: C{int} @ivar spritesets: A mapping from spritesets to indices. This allows for quick lookup of whether a spriteset is already in this collection. The indices are unique integers in the range 0 .. len(spritesets) - 1. @type spritesets: C{dict} mapping L{SpriteSet} to C{int}. """ def __init__(self, feature, first_set, num_sprites_per_spriteset): self.feature = feature self.first_set = first_set self.num_sprites_per_spriteset = num_sprites_per_spriteset self.spritesets = {} self.max_id = 0x3FFF if feature in action2.features_sprite_layout else 0xFFFF def skip_action7(self): return False def skip_action9(self): return False def skip_needed(self): return False def can_add(self, spriteset): """ Test whether the given list of spritesets can be added to this collection. @param spriteset: The spriteset to test for addition. @type spriteset: L{SpriteSet} @return: True iff the given spriteset can be added to this collection. @rtype: C{bool} """ assert self.first_set + 1 <= self.max_id if len(real_sprite.parse_sprite_data(spriteset)) != self.num_sprites_per_spriteset: return False return self.first_set + len(self.spritesets) + (1 if spriteset not in self.spritesets else 0) <= self.max_id def add(self, spriteset): """ Add a spriteset to this collection. @param spriteset: The spriteset to add. @type spriteset: L{SpriteSet} @pre: can_add(spriteset). """ assert self.can_add(spriteset) if spriteset not in self.spritesets: self.spritesets[spriteset] = len(self.spritesets) def get_index(self, spriteset): """ Get the index of the given spriteset in the final action1. @param spriteset: The spriteset to get the index of. @type spriteset: L{SpriteSet}. @pre: The spriteset must have been previously added to this collection via #add. """ assert spriteset in self.spritesets return self.first_set + self.spritesets[spriteset] def get_action_list(self): """ Create a list of actions needed to write this collection to the output. This will generate a single Action1 and as many realsprites as needed. @return: A list of actions needed to represet this collection in a GRF. @rtype: C{list} of L{BaseAction} """ actions = [Action1(self.feature, self.first_set, len(self.spritesets), self.num_sprites_per_spriteset)] for idx in range(len(self.spritesets)): for spriteset, spriteset_offset in self.spritesets.items(): if idx == spriteset_offset: actions.extend(real_sprite.parse_sprite_data(spriteset)) break return actions """ The collection which was previoulsy used. add_to_action1 will try to reuse this collection as long as possible to reduce the duplication of sprites. As soon as a spriteset with a different feature or amount of sprites is added a new collection will be created. """ spriteset_collections = {} def add_to_action1(spritesets, feature, pos): """ Add a list of spritesets to a spriteset collection. This will try to reuse one collection as long as possible and create a new one when needed. @param spritesets: List of spritesets that will be used by the next action2. @type spritesets: C{list} of L{SpriteSet} @param feature: Feature of the spritesets. @type feature: C{int} @param pos: Position reference to source. @type pos: L{Position} @return: List of collections that needs to be added to the global action list. @rtype: C{list} of L{SpritesetCollection}. """ if not spritesets: return [] actions = [] global spriteset_collections if feature not in spriteset_collections: spriteset_collections[feature] = [ SpritesetCollection(feature, 0, len(real_sprite.parse_sprite_data(spritesets[0]))) ] actions.append(spriteset_collections[feature][-1]) current_collection = spriteset_collections[feature][-1] for spriteset in spritesets: for spriteset_collection in spriteset_collections[feature]: if spriteset in spriteset_collection.spritesets: continue if not current_collection.can_add(spriteset): spriteset_collections[feature].append( SpritesetCollection( feature, current_collection.first_set + len(current_collection.spritesets), len(real_sprite.parse_sprite_data(spriteset)), ) ) current_collection = spriteset_collections[feature][-1] actions.append(current_collection) current_collection.add(spriteset) return actions def get_action1_index(spriteset, feature): """ Get the index of a spriteset in the action1. The given spriteset must have been added in the last call to #add_to_action1. Any new calls to #add_to_action1 may or may not allocate a new spriteset collection and as such make previous spritesets inaccessible. @param spriteset: The spriteset to get the index of. @type spriteset: L{SpriteSet}. @param feature: Feature of the spriteset. @type feature: C{int} @return: The index in the action1 of the given spriteset. @rtype: C{int} """ assert feature in spriteset_collections for spriteset_collection in spriteset_collections[feature]: if spriteset in spriteset_collection.spritesets: return spriteset_collection.get_index(spriteset) assert False def make_cb_failure_action1(feature): """ Create an action1 that may be used for a callback failure If the last action1 is of the correct feature, no new action1 is needed Else, add a new action1 with 1 spriteset containing 0 sprites @param feature: Feature of the requested action 1 @type feature: C{int} @return: List of actions to append (if any) and action1 index to use @rtype: C{tuple} of (C{list} of L{BaseAction}, C{int}) """ if feature in spriteset_collections: actions = [] else: actions = [Action1(feature, 0, 1, 0)] return (actions, 0) # Index is currently always 0, but will change with ext. A1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action10.py0000644000175100001660000000207414754345605016153 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import base_action class Action10(base_action.BaseAction): def __init__(self, label): self.label = label def write(self, file): file.start_sprite(2) file.print_bytex(0x10) file.print_bytex(self.label) file.newline() file.end_sprite() def skip_action9(self): return False def skip_needed(self): return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action11.py0000644000175100001660000001007014754345605016147 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Action 11 support classes (sounds). """ import os from nml import generic from nml.actions import action0, base_action class Action11(base_action.BaseAction): def __init__(self, num_sounds): self.num_sounds = num_sounds def write(self, file): file.start_sprite(3) file.print_bytex(0x11) file.print_word(self.num_sounds) file.end_sprite() class LoadBinaryFile(base_action.BaseAction): """ * FF 00 """ def __init__(self, fname, pos): self.fname = fname self.pos = pos self.last = False def prepare_output(self, sprite_num): if not os.access(self.fname, os.R_OK): raise generic.ScriptError("Sound file '{}' does not exist.".format(self.fname), self.pos) size = os.path.getsize(self.fname) if size == 0: raise generic.ScriptError( "Expected sound file '{}' to have a non-zero length.".format(self.fname), self.pos ) def write(self, file): file.print_named_filedata(self.fname) if self.last: file.newline() class ImportSound(base_action.BaseAction): """ Import a sound from another grf:: * FE 00 @ivar grfid: ID of the other grf. @type grfid: C{int} @ivar number: Sound number to load. @type number: C{int} @ivar pos: Position information @type pos: L{Position} """ def __init__(self, grfid, number, pos): self.grfid = grfid self.number = number self.pos = pos self.last = False def write(self, file): file.start_sprite(8) file.print_bytex(0xFE) file.print_bytex(0) file.print_dwordx(self.grfid) file.print_wordx(self.number) file.end_sprite() if self.last: file.newline() registered_sounds = {} SOUND_OFFSET = 73 # No of original sounds NUM_ANIMATION_SOUNDS = 0x80 - SOUND_OFFSET # Number of custom sound ids, which can be returned by animation callbacks. def print_stats(): """ Print statistics about used ids. """ if len(registered_sounds) > 0: # Currently NML does not optimise the order of sound effects. So we assume NUM_ANIMATION_SOUNDS as the maximum. generic.print_info("Sound effects: {}/{}".format(len(registered_sounds), NUM_ANIMATION_SOUNDS)) def add_sound(args, pos): if args not in registered_sounds: registered_sounds[args] = (len(registered_sounds), pos) return registered_sounds[args][0] + SOUND_OFFSET def get_sound_actions(): """ Get a list of actions that actually includes all sounds in the output file. """ if not registered_sounds: return [] action_list = [] action_list.append(Action11(len(registered_sounds))) volume_list = [] sound_data = [(sound_id, args, pos) for args, (sound_id, pos) in registered_sounds.items()] # Sort on first item, i.e. sound ID for i, args, pos in sorted(sound_data): if len(args) == 3: action_list.append(ImportSound(args[0], args[1], pos)) else: action_list.append(LoadBinaryFile(args[0], pos)) if args[-1] != 100: volume_list.append((i + SOUND_OFFSET, int(args[-1] * 128 / 100))) if volume_list: action_list.extend(action0.get_volume_actions(volume_list)) return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action12.py0000644000175100001660000000614014754345605016153 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import base_action, real_sprite class Action12(base_action.BaseAction): # sets: list of (font_size, num_char, base_char) tuples def __init__(self, sets): self.sets = sets def write(self, file): # * 12 ( ){n} size = 2 + 4 * len(self.sets) file.start_sprite(size) file.print_bytex(0x12) file.print_byte(len(self.sets)) file.newline() for font_size, num_char, base_char in self.sets: font_size.write(file, 1) file.print_byte(num_char) file.print_word(base_char) file.newline() file.end_sprite() font_sizes = { "NORMAL": 0, "SMALL": 1, "LARGE": 2, "MONO": 3, } def parse_action12(font_glyphs): try: font_size = font_glyphs.font_size.reduce_constant([font_sizes]) if isinstance(font_glyphs.base_char, expression.StringLiteral) and len(font_glyphs.base_char.value) == 1: base_char = ord(font_glyphs.base_char.value) else: base_char = font_glyphs.base_char.reduce_constant() except generic.ConstError: raise generic.ScriptError("Parameters of font_glyph have to be compile-time constants", font_glyphs.pos) if font_size.value not in font_sizes.values(): raise generic.ScriptError( "Invalid value for parameter 'font_size' in font_glyph, valid values are 0, 1, 2", font_size.pos ) if not (0 <= base_char.value <= 0xFFFF): raise generic.ScriptError( "Invalid value for parameter 'base_char' in font_glyph, valid values are 0-0xFFFF", base_char.pos ) real_sprite_list = real_sprite.parse_sprite_data(font_glyphs) char = base_char.value last_char = char + len(real_sprite_list) if last_char > 0xFFFF: raise generic.ScriptError( "Character numbers in font_glyph block exceed the allowed range (0-0xFFFF)", font_glyphs.pos ) sets = [] while char < last_char: # each set of characters must fit in a single 128-char block, according to specs / TTDP if (char // 128) * 128 != (last_char // 128) * 128: num_in_set = (char // 128 + 1) * 128 - char else: num_in_set = last_char - char sets.append((font_size, num_in_set, char)) char += num_in_set return [Action12(sets)] + real_sprite_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action14.py0000644000175100001660000002364114754345605016162 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, grfstrings from nml.actions import base_action class Action14(base_action.BaseAction): def __init__(self, nodes): self.nodes = nodes def skip_action7(self): return False def write(self, file): size = 2 # final 0-byte for node in self.nodes: size += node.get_size() file.start_sprite(size) file.print_bytex(0x14) for node in self.nodes: node.write(file) file.print_bytex(0) file.end_sprite() def split_action14(node, max_size): if node.get_size() <= max_size: return [node, None] if not isinstance(node, BranchNode): return [None, node] new_node = BranchNode(node.id) rest = BranchNode(node.id) copy_to_rest = False for subnode in node.subnodes: if copy_to_rest: rest.subnodes.append(subnode) continue new_subnode, subnode_rest = split_action14(subnode, max_size - new_node.get_size()) if new_subnode is not None: new_node.subnodes.append(new_subnode) if subnode_rest is not None: rest.subnodes.append(subnode_rest) copy_to_rest = True assert len(rest.subnodes) > 0 if len(new_node.subnodes) == 0: return [None, rest] return [new_node, rest] def get_actions(root): action_list = [] while True: node, root = split_action14(root, 65535) assert node is not None action_list.append(Action14([node])) if root is None: break return action_list class Action14Node: def __init__(self, type_string, id): self.type_string = type_string self.id = id def get_size(self): """ How many bytes will be written to the output file by L{write}? @return: The size (in bytes) of this node. """ raise NotImplementedError("get_size must be implemented in Action14Node-subclass {!r}".format(type(self))) def write(self, file): """ Write this node to the output file. @param file: The file to write the output to. """ raise NotImplementedError("write must be implemented in Action14Node-subclass {!r}".format(type(self))) def write_type_id(self, file): file.print_string(self.type_string, False, True) if isinstance(self.id, str): file.print_string(self.id, False, True) else: file.print_dword(self.id) class TextNode(Action14Node): def __init__(self, id, string, skip_default_langid=False): Action14Node.__init__(self, "T", id) self.string = string grfstrings.validate_string(self.string) self.skip_default_langid = skip_default_langid def get_size(self): if self.skip_default_langid: size = 0 else: size = 6 + grfstrings.get_string_size(grfstrings.get_translation(self.string)) for lang_id in grfstrings.get_translations(self.string): # 6 is for "T" (1), id (4), langid (1) size += 6 + grfstrings.get_string_size(grfstrings.get_translation(self.string, lang_id)) return size def write(self, file): if not self.skip_default_langid: self.write_type_id(file) file.print_bytex(0x7F) file.print_string(grfstrings.get_translation(self.string)) file.newline() for lang_id in grfstrings.get_translations(self.string): self.write_type_id(file) file.print_bytex(lang_id) file.print_string(grfstrings.get_translation(self.string, lang_id)) file.newline() class BranchNode(Action14Node): def __init__(self, id): Action14Node.__init__(self, "C", id) self.subnodes = [] def get_size(self): size = 6 # "C", id, final 0-byte for node in self.subnodes: size += node.get_size() return size def write(self, file): self.write_type_id(file) file.newline() for node in self.subnodes: node.write(file) file.print_bytex(0) file.newline() class BinaryNode(Action14Node): def __init__(self, id, size, val=None): Action14Node.__init__(self, "B", id) self.size = size self.val = val def get_size(self): return 7 + self.size # "B" (1), id (4), size (2), data (self.size) def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_varx(self.val, self.size) file.newline() class UsedPaletteNode(BinaryNode): def __init__(self, pal): BinaryNode.__init__(self, "PALS", 1) self.pal = pal def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_string(self.pal, False, True) file.newline() class BlitterNode(BinaryNode): def __init__(self, blitter): BinaryNode.__init__(self, "BLTR", 1) self.blitter = blitter def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_string(self.blitter, False, True) file.newline() class SettingMaskNode(BinaryNode): def __init__(self, param_num, first_bit, num_bits): BinaryNode.__init__(self, "MASK", 3) self.param_num = param_num self.first_bit = first_bit self.num_bits = num_bits def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_byte(self.param_num) file.print_byte(self.first_bit) file.print_byte(self.num_bits) file.newline() class LimitNode(BinaryNode): def __init__(self, min_val, max_val): BinaryNode.__init__(self, "LIMI", 8) self.min_val = min_val self.max_val = max_val def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_dword(self.min_val) file.print_dword(self.max_val) file.newline() def grf_name_desc_actions(root, name, desc, url, version, min_compatible_version): if len(grfstrings.get_translations(name)) > 0: name_node = TextNode("NAME", name, True) root.subnodes.append(name_node) if len(grfstrings.get_translations(desc)) > 0: desc_node = TextNode("DESC", desc, True) root.subnodes.append(desc_node) if url is not None: desc_node = TextNode("URL_", url) root.subnodes.append(desc_node) version_node = BinaryNode("VRSN", 4, version.value) root.subnodes.append(version_node) min_compatible_version_node = BinaryNode("MINV", 4, min_compatible_version.value) root.subnodes.append(min_compatible_version_node) def param_desc_actions(root, params): num_params = 0 for param_desc in params: num_params += len(param_desc.setting_list) root.subnodes.append(BinaryNode("NPAR", 1, num_params)) param_root = BranchNode("PARA") param_num = 0 setting_num = 0 for param_desc in params: if param_desc.num is not None: param_num = param_desc.num.value for setting in param_desc.setting_list: setting_node = BranchNode(setting_num) if setting.name_string is not None: setting_node.subnodes.append(TextNode("NAME", setting.name_string)) if setting.desc_string is not None: setting_node.subnodes.append(TextNode("DESC", setting.desc_string)) if setting.type == "int": setting_node.subnodes.append(BinaryNode("MASK", 1, param_num)) min_val = setting.min_val.uvalue if setting.min_val is not None else 0 max_val = setting.max_val.uvalue if setting.max_val is not None else 0xFFFFFFFF def_val = setting.def_val.uvalue if setting.def_val is not None else 0 if min_val > max_val or def_val < min_val or def_val > max_val: generic.print_warning( generic.Warning.GENERIC, "Limits for GRF parameter {} are incoherent, ignoring.".format(param_num), ) min_val = 0 max_val = 0xFFFFFFFF setting_node.subnodes.append(LimitNode(min_val, max_val)) if len(setting.val_names) > 0: value_names_node = BranchNode("VALU") for set_val_pair in setting.val_names: value_names_node.subnodes.append(TextNode(set_val_pair[0], set_val_pair[1])) setting_node.subnodes.append(value_names_node) else: assert setting.type == "bool" setting_node.subnodes.append(BinaryNode("TYPE", 1, 1)) bit = setting.bit_num.value if setting.bit_num is not None else 0 setting_node.subnodes.append(SettingMaskNode(param_num, bit, 1)) if setting.def_val is not None: setting_node.subnodes.append(BinaryNode("DFLT", 4, setting.def_val.value)) param_root.subnodes.append(setting_node) setting_num += 1 param_num += 1 if len(param_root.subnodes) > 0: root.subnodes.append(param_root) def PaletteAction(pal): root = BranchNode("INFO") pal_node = UsedPaletteNode(pal) root.subnodes.append(pal_node) return [Action14([root])] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action2.py0000644000175100001660000005131614754345605016077 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants from nml.actions import base_action from nml.ast import base_statement, general total_action2_ids = 0x100 free_action2_ids = list(range(0, total_action2_ids)) """ Statistics about spritegroups. The 1st field of type C{int} contains the largest number of concurrently active spritegroup ids. The 2nd field of type L{Position} contains a positional reference to the last spritegroup of the concurrently active ones. """ spritegroup_stats = (0, None) total_tmp_locations = 0x7F """ Statistics about temporary Action2 registers. The 1st field of type C{int} contains the largest number of concurrently active register ids. The 2nd field of type L{Position} contains a positional reference to the spritegroup. """ a2register_stats = (0, None) def print_stats(): """ Print statistics about used ids. """ if spritegroup_stats[0] > 0: generic.print_info( "Concurrent spritegroups: {}/{} ({})".format( spritegroup_stats[0], total_action2_ids, str(spritegroup_stats[1]) ) ) if a2register_stats[0] > 0: generic.print_info( "Concurrent Action2 registers: {}/{} ({})".format( a2register_stats[0], total_tmp_locations, str(a2register_stats[1]) ) ) class Action2(base_action.BaseAction): """ Abstract Action2 base class. @ivar name: Name of the action2. @type name: C{str} @ivar feature: Action2 feature byte. @type feature: C{int} @ivar pos: Position reference to source. @type pos: L{Position} @ivar num_refs: Number of references to this action2. @type num_refs: C{int} @ivar id: Number of this action2. @type id: C{int}, or C{None} if no number is allocated yet. @ivar references: All Action2s that are referenced by this Action2. @type references: C{list} of L{Action2Reference} @ivar tmp_locations: List of address in the temporary storage that are free to be used in this varaction2. @type tmp_locations: C{list} of C{int} """ def __init__(self, feature, name, pos): self.feature = feature self.name = name self.pos = pos self.num_refs = 0 self.id = None self.references = [] # 0x00 - 0x7F: available to user # 0x80 - 0xFE: used by NML # 0xFF: Used for some house variables # 0x100 - 0x10F: Special meaning (used for some CB results) self.tmp_locations = list(range(0x80, 0x80 + total_tmp_locations)) def prepare_output(self, sprite_num): free_references(self) global spritegroup_stats try: if self.num_refs == 0: self.id = free_action2_ids[0] else: self.id = free_action2_ids.pop() num_used = total_action2_ids - len(free_action2_ids) if num_used > spritegroup_stats[0]: spritegroup_stats = (num_used, self.pos) except IndexError: raise generic.ScriptError( "Unable to allocate ID for [random]switch, sprite set/layout/group or produce-block." " Try reducing the number of such blocks.", self.pos, ) def write_sprite_start(self, file, size, extra_comment=None): assert self.num_refs == 0, "Action2 reference counting has {:d} dangling references.".format(self.num_refs) file.comment("Name: " + self.name) if extra_comment: for c in extra_comment: file.comment(c) file.start_sprite(size + 3) file.print_bytex(2) file.print_bytex(self.feature) file.print_bytex(self.id) def skip_action7(self): return False def skip_action9(self): return False def skip_needed(self): return False def remove_tmp_location(self, location, force_recursive): """ Recursively remove a location from the list of available temporary storage locations. It is not only removed from the the list of the current Action2Var but also from all Action2Var it calls. If an Action2Var is referenced as a procedure call, the location is always removed recursively, otherwise only if force_recursive is True. @param location: Number of the storage register to remove. @type location: C{int} @param force_recursive: Force removing this location recursively, also for 'chained' action2s. @type force_recursive: C{bool} """ global a2register_stats if location in self.tmp_locations: self.tmp_locations.remove(location) num_used = total_tmp_locations - len(self.tmp_locations) if num_used > a2register_stats[0]: a2register_stats = (num_used, self.pos) for act2_ref in self.references: if force_recursive or act2_ref.is_proc: act2_ref.action2.remove_tmp_location(location, True) class Action2Reference: """ Container class to store information about an action2 reference @ivar action2: The target action2 @type action2: L{Action2} @ivar is_proc: Whether this reference is made because of a procedure call @type is_proc: C{bool} """ def __init__(self, action2, is_proc): self.action2 = action2 self.is_proc = is_proc def add_ref(ref, source_action, reference_as_proc=False): """ Add a reference to a certain action2. This is needed so we can correctly reserve / free action2 IDs later on. To be called when creating the actions from the AST. @param ref: Reference to the sprite group that corresponds to the action2. @type ref: L{SpriteGroupRef} @param source_action: Source action (act2 or act3) that contains the reference. @type source_action: L{Action2} or L{Action3} @param reference_as_proc: True iff the reference source is a procedure call, which needs special precautions for temp registers. @type reference_as_proc: C{bool} """ # Add reference to list of references of the source action act2 = ref.act2 if ref.act2 is not None else resolve_spritegroup(ref.name).get_action2(source_action.feature) source_action.references.append(Action2Reference(act2, reference_as_proc)) act2.num_refs += 1 def free_references(source_action): """ Free all references to other action2s from a certain action 2/3 @param source_action: Action that contains the reference @type source_action: L{Action2} or L{Action3} """ for act2_ref in source_action.references: act2 = act2_ref.action2 act2.num_refs -= 1 if act2.num_refs == 0: free_action2_ids.append(act2.id) # Features using sprite groups directly: vehicles, stations, canals, cargos, railtypes, airports, roadtypes, tramtypes features_sprite_group = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x0B, 0x0D, 0x10, 0x12, 0x13] # Features using sprite layouts: houses, industry tiles, objects, airport tiles, and road stops features_sprite_layout = [0x07, 0x09, 0x0F, 0x11, 0x14] # All features that need sprite sets features_sprite_set = features_sprite_group + features_sprite_layout def make_sprite_group_class(cls_is_spriteset, cls_is_referenced, cls_has_explicit_feature, cls_is_relocatable=False): """ Metaclass factory which makes base classes for all nodes 'Action 2 graph' This graph is made up of all blocks that are eventually compiled to Action2, which use the same name space. Spritesets do inherit from this class to make referencing them possible, but they are not part of the refernce graph that is built. @param cls_is_spriteset: Whether this class represents a spriteset @type cls_is_spriteset: C{bool} @param cls_is_referenced: True iff this node can be referenced by other nodes @type cls_is_referenced: C{bool} @param cls_has_explicit_feature: Whether the feature of an instance is explicitly set, or derived from nodes that link to it. @type cls_has_explicit_feature: C{bool} @param cls_is_relocatable: Whether instances of this class can be freely moved around or whether they need to to be converted to nfo code at the same location as they are in the nml code. @type cls_is_relocatable: C{bool} @return: The constructed class @rtype: C{type} """ # without either references or an explicit feature, we have nothing to base our feature on assert cls_is_referenced or cls_has_explicit_feature class ASTSpriteGroup(base_statement.BaseStatement): """ Abstract base class for all AST nodes that represent a sprite group This handles all the relations between the various nodes Child classes should do the following: - Implement their own __init__ method - Call BaseStatement.__init__ - Call initialize, pre_process and perpare_output (in that order) - Implement collect_references - Call set_action2 after generating the corresponding action2 (if applicable) @ivar _referencing_nodes: Set of nodes that refer to this node @type _referencing_nodes: C{set} @ivar _referenced_nodes: Set of nodes that this node refers to @type _referenced_nodes: C{set} @ivar _prepared: True iff prepare_output has already been executed @type _prepared: C{bool} @ivar _action2: Mapping of features to action2s @type _action2: C{dict} that maps C{int} to L{Action2} @ivar feature_set: Set of features that use this node @type feature_set: C{set} of C{int} @ivar name: Name of this node, as declared by the user @type name: L{Identifier} @ivar num_params: Number of parameters that can be (and have to be) passed @type num_params: C{int} @ivar used_sprite_sets: List of sprite sets used by this node @type used_sprite_sets: C{list} of L{SpriteSet} """ def __init__(self): """ Subclasses should implement their own __init__ method. This method should not be called, because calling a method on a meta class can be troublesome. Instead, call initialize(..). """ raise NotImplementedError( ( "__init__ must be implemented in ASTSpriteGroup-subclass {!r}," " initialize(..) should be called instead" ).format(type(self)) ) def initialize(self, name=None, feature=None, num_params=0): """ Initialize this instance. This function is generally, but not necessarily, called from the child class' constructor. Calling it later (during pre-processing) is also possible, as long as it's called before any other actions are done. @param name: Name of this node, as set by the user (if applicable) Should be be set (not None) iff cls_is_referenced is True @type name: L{Identifier} or C{None} if N/A @param feature: Feature of this node, if set by the user. Should be set (not None) iff cls_has_explicit_feature is True @type feature: C{int} or C{None} """ assert not (self._has_explicit_feature() and feature is None) assert not (cls_is_referenced and name is None) self._referencing_nodes = set() self._referenced_nodes = set() self._prepared = False self._action2 = {} self.feature_set = {feature} if feature is not None else set() self.name = name self.num_params = num_params self.used_sprite_sets = [] self.optimised = None def register_names(self): if cls_is_relocatable and cls_is_referenced: register_spritegroup(self) def pre_process(self): """ Pre-process this node. During this stage, the reference graph is built. """ refs = self.collect_references() for ref in refs: self._add_reference(ref) if (not cls_is_relocatable) and cls_is_referenced: register_spritegroup(self) def prepare_act2_output(self): """ Prepare this node for outputting. This sets the feature and makes sure it is correct. @return: True iff parsing of this node is needed @rtype: C{bool} """ if not cls_is_referenced: return True if not self._prepared: self._prepared = True # copy, since we're going to modify ref_nodes = self._referencing_nodes.copy() for node in ref_nodes: used = node.prepare_act2_output() if not used: node._remove_reference(self) # now determine the feature if self._has_explicit_feature(): # by this time, feature should be set assert len(self.feature_set) == 1 for n in self._referencing_nodes: if n.feature_set != self.feature_set: msg = "Cannot refer to block '{}' with feature '{}', expected feature is '{}'" msg = msg.format( self.name.value, general.feature_name(next(iter(self.feature_set))), general.feature_name(n.feature_set.difference(self.feature_set).pop()), ) raise generic.ScriptError(msg, n.pos) elif len(self._referencing_nodes) != 0: for n in self._referencing_nodes: # Add the features from all calling blocks to the set self.feature_set.update(n.feature_set) if len(self._referencing_nodes) == 0 and (not self.optimised or self.optimised is self): # if we can be 'not used', there ought to be a way to refer to this block assert self.name is not None generic.print_warning( generic.Warning.OPTIMISATION, "Block '{}' is not referenced, ignoring.".format(self.name.value), self.pos, ) return len(self._referencing_nodes) != 0 def referenced_nodes(self): """ Get the nodes that this node refers to. @note: Make sure to sort this in a deterministic way when the order of items affects the output. @return: A set of nodes @rtype: C{set} of L{ASTSpriteGroup} """ return self._referenced_nodes def referencing_nodes(self): """ Get the nodes that refer to this node. @note: Make sure to sort this in a deterministic way when the order of items affects the output. @return: A set of nodes @rtype: C{set} of L{ASTSpriteGroup} """ return self._referencing_nodes def optimise(self): """ Optimise this sprite group. @return: True iff this sprite group has been optimised @rtype: C{bool} """ return False def collect_references(self): """ This function should collect all references to other nodes from this instance. @return: A collection containing all links to other nodes. @rtype: C{iterable} of L{SpriteGroupRef} """ raise NotImplementedError( "collect_references must be implemented in ASTSpriteGroup-subclass {!r}".format(type(self)) ) def set_action2(self, action2, feature): """ Set this node's resulting action2 @param feature: Feature of the Action2 @type feature: C{int} @param action2: Action2 to set @type action2: L{Action2} """ assert feature not in self._action2 self._action2[feature] = action2 def get_action2(self, feature): """ Get this node's resulting action2 @param feature: Feature of the Action2 @type feature: C{int} @return: Action2 to get @rtype: L{Action2} """ assert feature in self._action2 return self._action2[feature] def has_action2(self, feature): """ Check, if this node already has an action2 for a given feature @param feature: Feature to check @type feature: C{int} @return: True iff there is an action2 for this feature @rtype: C{bool} """ return feature in self._action2 def _add_reference(self, target_ref): """ Add a reference from C{self} to a target with a given name. @param target_ref: Name of the reference target @type target_ref: L{SpriteGroupRef} """ if target_ref.name.value == "CB_FAILED": return target = resolve_spritegroup(target_ref.name) if target.is_spriteset(): assert target.num_params == 0 # Referencing a spriteset directly from graphics/[random]switch # Passing parameters is not possible here if len(target_ref.param_list) != 0: raise generic.ScriptError( "Passing parameters to '{}' is only possible from a spritelayout.".format( target_ref.name.value ), target_ref.pos, ) self.used_sprite_sets.append(target) else: if len(target_ref.param_list) != target.num_params: msg = "'{}' expects {:d} parameters, encountered {:d}." msg = msg.format(target_ref.name.value, target.num_params, len(target_ref.param_list)) raise generic.ScriptError(msg, target_ref.pos) self._referenced_nodes.add(target) target._referencing_nodes.add(self) def _remove_reference(self, target): """ Add a reference from C{self} to a target @param target: Existing reference target to be removed @type target: L{ASTSpriteGroup} """ assert target in self._referenced_nodes assert self in target._referencing_nodes self._referenced_nodes.remove(target) target._referencing_nodes.remove(self) # Make metaclass arguments available outside of the class def is_spriteset(self): return cls_is_spriteset def _has_explicit_feature(self): return cls_has_explicit_feature return ASTSpriteGroup # list of all registered sprite sets and sprite groups spritegroup_list = {} def register_spritegroup(spritegroup): """ Register a sprite group, so it can be resolved by name later @param spritegroup: Sprite group to register @type spritegroup: L{ASTSpriteGroup} """ name = spritegroup.name.value if name in spritegroup_list: raise generic.ScriptError("Block with name '{}' has already been defined".format(name), spritegroup.pos) spritegroup_list[name] = spritegroup global_constants.spritegroups[name] = name def resolve_spritegroup(name): """ Resolve a sprite group with a given name @param name: Name of the sprite group. @type name: L{Identifier} @return: The sprite group that the name refers to. @rtype: L{ASTSpriteGroup} """ if name.value not in spritegroup_list: raise generic.ScriptError("Unknown identifier encountered: '{}'".format(name.value), name.pos) return spritegroup_list[name.value] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action2layout.py0000644000175100001660000010051514754345605017331 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, nmlop from nml.actions import action0, action1, action2, action2real, action2var, action6, actionD, real_sprite from nml.ast import general, spriteblock class Action2Layout(action2.Action2): def __init__(self, feature, name, pos, layout, param_registers): action2.Action2.__init__(self, feature, name, pos) self.layout = layout self.param_registers = param_registers def resolve_tmp_storage(self): for reg in self.param_registers: if not self.tmp_locations: raise generic.ScriptError( "There are not enough registers available " + "to perform all required computations in switch blocks. " + "Please reduce the complexity of your code.", self.pos, ) location = self.tmp_locations[0] self.remove_tmp_location(location, False) reg.set_register(location) def write(self, file): size = self.layout.get_size() regs = ["{} : register {:X}".format(reg.name, reg.register) for reg in self.param_registers] action2.Action2.write_sprite_start(self, file, size, regs) self.layout.write(file) file.end_sprite() class Action2LayoutSpriteType: GROUND = 0 BUILDING = 1 CHILD = 2 # these keywords are used to identify a ground/building/childsprite layout_sprite_types = { "ground": Action2LayoutSpriteType.GROUND, "building": Action2LayoutSpriteType.BUILDING, "childsprite": Action2LayoutSpriteType.CHILD, } class Action2LayoutSprite: def __init__(self, feature, type, layout_registers=None, pos=None, extra_dicts=None): self.feature = feature self.type = type self.layout_registers = layout_registers self.pos = pos self.extra_dicts = extra_dicts or [] self.params = { "sprite": {"value": None, "validator": self._validate_sprite}, "recolour_mode": {"value": 0, "validator": self._validate_recolour_mode}, "palette": {"value": expression.ConstantNumeric(0), "validator": self._validate_palette}, "always_draw": {"value": 0, "validator": self._validate_always_draw}, "xoffset": {"value": expression.ConstantNumeric(0), "validator": self._validate_bounding_box}, "yoffset": {"value": expression.ConstantNumeric(0), "validator": self._validate_bounding_box}, "zoffset": {"value": expression.ConstantNumeric(0), "validator": self._validate_bounding_box}, "xextent": {"value": expression.ConstantNumeric(16), "validator": self._validate_bounding_box}, "yextent": {"value": expression.ConstantNumeric(16), "validator": self._validate_bounding_box}, "zextent": {"value": expression.ConstantNumeric(16), "validator": self._validate_bounding_box}, "hide_sprite": {"value": None, "validator": self._validate_hide_sprite}, # Value not used } for i in self.params: self.params[i]["is_set"] = False self.params[i]["register"] = None self.sprite_from_action1 = False self.palette_from_action1 = False self.var10_for_sprite = None self.var10_for_palette = None def is_advanced_sprite(self): if self.feature == 0x04: return True if self.palette_from_action1: return True return len(self.get_all_registers()) != 0 def get_registers_size(self): # Number of registers to write size = len(self.get_all_registers()) # Add station specific registers if self.feature == 0x04: if self.var10_for_sprite: size += 1 if self.var10_for_palette: size += 1 # Add 2 for the flags size += 2 return size def write_flags(self, file): flags = 0 if self.get_register("hide_sprite") is not None: flags |= 1 << 0 if self.get_register("sprite") is not None: flags |= 1 << 1 if self.get_register("palette") is not None: flags |= 1 << 2 if self.palette_from_action1: flags |= 1 << 3 # for building sprites: bit 4 => xoffset+yoffset, bit 5 => zoffset (x and y always set totgether) # for child sprites: bit 4 => xoffset, bit 5 => yoffset if self.type == Action2LayoutSpriteType.BUILDING: assert (self.get_register("xoffset") is not None) == (self.get_register("yoffset") is not None) if self.get_register("xoffset") is not None: flags |= 1 << 4 nextreg = "zoffset" if self.type == Action2LayoutSpriteType.BUILDING else "yoffset" if self.get_register(nextreg) is not None: flags |= 1 << 5 if self.feature == 0x04: if self.var10_for_sprite is not None: flags |= 1 << 6 if self.var10_for_palette is not None: flags |= 1 << 7 file.print_wordx(flags) def write_register(self, file, name): register = self.get_register(name)[0] file.print_bytex(register.parameter) def write_registers(self, file): if self.is_set("hide_sprite"): self.write_register(file, "hide_sprite") if self.get_register("sprite") is not None: self.write_register(file, "sprite") if self.get_register("palette") is not None: self.write_register(file, "palette") if self.get_register("xoffset") is not None: self.write_register(file, "xoffset") if self.get_register("yoffset") is not None: self.write_register(file, "yoffset") if self.get_register("zoffset") is not None: self.write_register(file, "zoffset") if self.feature == 0x04: if self.var10_for_sprite is not None: file.print_bytex(self.var10_for_sprite) if self.var10_for_palette is not None: file.print_bytex(self.var10_for_palette) def write_sprite_number(self, file): num = self.get_sprite_number() if isinstance(num, expression.ConstantNumeric): num.write(file, 4) else: file.print_dwordx(0) def get_sprite_number(self): # Layout of sprite number # bit 0 - 13: Sprite number # bit 14 - 15: Recolour mode (normal/transparent/remap) # bit 16 - 29: Palette sprite number # bit 30: Always draw sprite, even in transparent mode # bit 31: This is a custom sprite (from action1), not a TTD sprite if not self.is_set("sprite"): raise generic.ScriptError("'sprite' must be set for this layout sprite", self.pos) # Make sure that recolouring is set correctly if self.get_param("recolour_mode") == 0 and self.is_set("palette"): raise generic.ScriptError("'palette' may not be set when 'recolour_mode' is RECOLOUR_NONE.") elif self.get_param("recolour_mode") != 0 and not self.is_set("palette"): raise generic.ScriptError("'palette' must be set when 'recolour_mode' is not set to RECOLOUR_NONE.") # Add the constant terms first sprite_num = self.get_param("recolour_mode") << 14 if self.get_param("always_draw"): sprite_num |= 1 << 30 if self.sprite_from_action1: sprite_num |= 1 << 31 # Add the sprite expr = nmlop.ADD(self.get_param("sprite"), sprite_num, self.pos) # Add the palette expr = nmlop.ADD(nmlop.SHIFT_LEFT(self.get_param("palette"), 16, self.pos), expr) return expr.reduce() def get_param(self, name): assert name in self.params return self.params[name]["value"] def is_set(self, name): assert name in self.params return self.params[name]["is_set"] def get_register(self, name): assert name in self.params return self.params[name]["register"] def get_all_registers(self): return [self.get_register(name) for name in sorted(self.params) if self.get_register(name) is not None] def create_register(self, name, value): if ( # Always copy values from "prepare_layout" into new registers, to prevent "default" from modifying them. self.feature != 0x04 and isinstance(value, expression.StorageOp) and value.name == "LOAD_TEMP" and isinstance(value.register, expression.ConstantNumeric) ): store_tmp = None load_tmp = action2var.VarAction2Var(0x7D, 0, 0xFFFFFFFF, value.register.value) else: if self.layout_registers is None: store_tmp = action2var.VarAction2StoreTempVar() else: store_tmp = self.layout_registers.add(value) load_tmp = action2var.VarAction2LoadTempVar(store_tmp) self.params[name]["register"] = (load_tmp, store_tmp, value) def set_param(self, name, value): assert isinstance(name, expression.Identifier) assert isinstance(value, expression.Expression) name = name.value if name not in self.params: raise generic.ScriptError("Unknown sprite parameter '{}'".format(name), value.pos) if self.is_set(name): raise generic.ScriptError("Sprite parameter '{}' can be set only once per sprite.".format(name), value.pos) self.params[name]["value"] = self.params[name]["validator"](name, value) self.params[name]["is_set"] = True def _validate_offset(self, offset, spriteset, pos): if len(offset) == 0: offset = None elif len(offset) == 1: id_dicts = [ (spriteset.labels if spriteset else {}, lambda name, val, pos: expression.ConstantNumeric(val, pos)) ] offset = action2var.reduce_varaction2_expr( offset[0], action2var.get_scope(self.feature), self.extra_dicts + id_dicts ) if spriteset and isinstance(offset, expression.ConstantNumeric): generic.check_range( offset.value, 0, len(real_sprite.parse_sprite_data(spriteset)) - 1, "offset within spriteset", pos, ) else: raise generic.ScriptError("Expected 0 or 1 parameter, got " + str(len(offset)), pos) return offset def resolve_spritegroup_ref(self, sg_ref): """ Resolve a reference to a (sprite/palette) sprite group @param sg_ref: Reference to a sprite group @type sg_ref: L{SpriteGroupRef} @return: Sprite number (index of action1 set) to use @rtype: L{Expression} """ spriteset = action2.resolve_spritegroup(sg_ref.name) offset = self._validate_offset(sg_ref.param_list, spriteset, sg_ref.pos) num = action1.get_action1_index(spriteset, self.feature) generic.check_range(num, 0, (1 << 14) - 1, "sprite", sg_ref.pos) return expression.ConstantNumeric(num), offset def _validate_sprite(self, name, value): if isinstance(value, expression.SpriteGroupRef): assert self.feature != 0x04 self.sprite_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val elif isinstance(value, StationSpriteset): assert self.feature == 0x04 self.sprite_from_action1 = True if value.offset is not None: offset = self._validate_offset(value.offset, value.spriteset, value.pos) if offset is not None: self.create_register(name, offset) self.var10_for_sprite = value.var10 return expression.ConstantNumeric(0x42D) else: self.sprite_from_action1 = False if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "sprite", value.pos) return value if value.supported_by_actionD(raise_error=False): return value self.create_register(name, value) return expression.ConstantNumeric(0) def _validate_recolour_mode(self, name, value): if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Expected a compile-time constant.", value.pos) if value.value not in (0, 1, 2): raise generic.ScriptError( "Value of 'recolour_mode' must be RECOLOUR_NONE, RECOLOUR_TRANSPARENT or RECOLOUR_REMAP." ) return value.value def _validate_palette(self, name, value): if isinstance(value, expression.SpriteGroupRef): assert self.feature != 0x04 self.palette_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val elif isinstance(value, StationSpriteset): assert self.feature == 0x04 self.palette_from_action1 = True if value.offset is not None: offset = self._validate_offset(value.offset, value.spriteset, value.pos) if offset is not None: self.create_register(name, offset) self.var10_for_palette = value.var10 return expression.ConstantNumeric(0x42D) else: self.palette_from_action1 = False if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "palette", value.pos) return value if value.supported_by_actionD(raise_error=False): return value self.create_register(name, value) return expression.ConstantNumeric(0) def _validate_always_draw(self, name, value): if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Expected a compile-time constant number.", value.pos) # Not valid for ground sprites, raise error if self.type == Action2LayoutSpriteType.GROUND: raise generic.ScriptError( "'always_draw' may not be set for groundsprites, these are always drawn anyways.", value.pos ) if value.value not in (0, 1): raise generic.ScriptError("Value of 'always_draw' should be 0 or 1", value.pos) return value.value def _validate_bounding_box(self, name, value): if self.type == Action2LayoutSpriteType.GROUND: raise generic.ScriptError(name + " can not be set for ground sprites", value.pos) elif self.type == Action2LayoutSpriteType.CHILD: if name not in ("xoffset", "yoffset"): raise generic.ScriptError(name + " can not be set for child sprites", value.pos) if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, 255, name, value.pos) return value else: assert self.type == Action2LayoutSpriteType.BUILDING if name in ("xoffset", "yoffset", "zoffset"): if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, -128, 127, name, value.pos) return value else: assert name in ("xextent", "yextent", "zextent") if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError( "Value of '{}' must be a compile-time constant number.".format(name), value.pos ) generic.check_range(value.value, 0, 255, name, value.pos) return value # Value must be written to a register self.create_register(name, value) if self.type == Action2LayoutSpriteType.BUILDING: # For building sprites, x and y registers are always written together if name == "xoffset" and self.get_register("yoffset") is None: self.create_register("yoffset", expression.ConstantNumeric(0)) if name == "yoffset" and self.get_register("xoffset") is None: self.create_register("xoffset", expression.ConstantNumeric(0)) return expression.ConstantNumeric(0) def _validate_hide_sprite(self, name, value): self.create_register(name, expression.Not(value).reduce()) class ParsedSpriteLayout: def __init__(self): self.ground_sprite = None self.building_sprites = [] self.advanced = False def get_size(self): size = 5 if self.advanced: size += self.ground_sprite.get_registers_size() for sprite in self.building_sprites: if sprite.type == Action2LayoutSpriteType.CHILD: size += 7 else: size += 10 if self.advanced: size += sprite.get_registers_size() if len(self.building_sprites) == 0 and not self.advanced: size += 9 return size def write(self, file): if self.advanced: file.print_byte(0x40 | len(self.building_sprites)) else: file.print_byte(len(self.building_sprites)) self.ground_sprite.write_sprite_number(file) if self.advanced: self.ground_sprite.write_flags(file) self.ground_sprite.write_registers(file) file.newline() if len(self.building_sprites) == 0 and not self.advanced: file.print_dwordx(0) # sprite number 0 == no sprite for _ in range(0, 5): file.print_byte(0) # empty bounding box. Note that number of zeros is 5, not 6 else: for sprite in self.building_sprites: sprite.write_sprite_number(file) if self.advanced: sprite.write_flags(file) file.print_byte(sprite.get_param("xoffset").value) file.print_byte(sprite.get_param("yoffset").value) if sprite.type == Action2LayoutSpriteType.CHILD: file.print_bytex(0x80) else: # normal building sprite file.print_byte(sprite.get_param("zoffset").value) file.print_byte(sprite.get_param("xextent").value) file.print_byte(sprite.get_param("yextent").value) file.print_byte(sprite.get_param("zextent").value) if self.advanced: sprite.write_registers(file) file.newline() def process(self, spritelayout, feature, param_map, actions, layout_registers, var10map=None): if not isinstance(param_map, list): param_map = [param_map] # Reduce all expressions, can't do that earlier as feature is not known all_sprite_sets = [] layout_sprite_list = [] # Create a new structure for layout_sprite in spritelayout.layout_sprite_list: param_list = [] layout_sprite_list.append((layout_sprite.type, layout_sprite.pos, param_list)) for param in layout_sprite.param_list: param_val = action2var.reduce_varaction2_expr(param.value, action2var.get_scope(feature), param_map) if isinstance(param_val, expression.SpriteGroupRef): spriteset = action2.resolve_spritegroup(param_val.name) if not spriteset.is_spriteset(): raise generic.ScriptError("Expected a reference to a spriteset.", param_val.pos) if feature == 0x04: assert var10map is not None param_val = var10map.translate(spriteset, param_val.param_list, param_val.pos) else: all_sprite_sets.append(spriteset) param_list.append((param.name, param_val)) actions.extend(action1.add_to_action1(all_sprite_sets, feature, spritelayout.pos)) for type, pos, param_list in layout_sprite_list: if type.value not in layout_sprite_types: raise generic.ScriptError( "Invalid sprite type '{}' encountered. Expected 'ground', 'building', or 'childsprite'.".format( type.value ), type.pos, ) sprite = Action2LayoutSprite(feature, layout_sprite_types[type.value], layout_registers, pos, param_map) for name, value in param_list: sprite.set_param(name, value) if sprite.type == Action2LayoutSpriteType.GROUND: if self.ground_sprite is not None: raise generic.ScriptError("Sprite layout can have no more than one ground sprite", spritelayout.pos) self.ground_sprite = sprite else: self.building_sprites.append(sprite) if self.ground_sprite is None: if len(self.building_sprites) == 0: # no sprites defined at all, that's not very much. raise generic.ScriptError("Sprite layout requires at least one sprite", spritelayout.pos) # set to 0 for no ground sprite self.ground_sprite = Action2LayoutSprite(feature, Action2LayoutSpriteType.GROUND, layout_registers) self.ground_sprite.set_param(expression.Identifier("sprite"), expression.ConstantNumeric(0)) self.advanced = any(x.is_advanced_sprite() for x in self.building_sprites + [self.ground_sprite]) def write_action_value(self, actions, act6, offset): sprite_num = self.ground_sprite.get_sprite_number() sprite_num, offset = actionD.write_action_value(sprite_num, actions, act6, offset, 4) if self.advanced: offset += self.ground_sprite.get_registers_size() for sprite in self.building_sprites: sprite_num = sprite.get_sprite_number() sprite_num, offset = actionD.write_action_value(sprite_num, actions, act6, offset, 4) if self.advanced: offset += sprite.get_registers_size() offset += 3 if sprite.type == Action2LayoutSpriteType.CHILD else 6 return offset class LayoutRegisters: def __init__(self): self.registers = {} def add(self, value): if value not in self.registers: self.registers[value] = action2var.VarAction2StoreTempVar() return self.registers[value] def parse(self, varact2parser): for expr, reg in self.registers.items(): varact2parser.parse_expr(expr) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(reg) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += reg.get_size() + 2 def get_layout_action2s(spritelayout, feature): actions = [] if feature not in action2.features_sprite_layout: raise generic.ScriptError( "Sprite layouts are not supported for feature '{}'.".format(general.feature_name(feature)) ) # Allocate registers param_map = {} param_registers = [] for param in spritelayout.param_list: reg = action2var.VarAction2CallParam(param.value) param_registers.append(reg) param_map[param.value] = reg param_map = (param_map, lambda name, value, pos: action2var.VarAction2LoadCallParam(value, name)) spritelayout.register_map[feature] = param_registers layout_registers = LayoutRegisters() layout = ParsedSpriteLayout() layout.process(spritelayout, feature, param_map, actions, layout_registers) action6.free_parameters.save() act6 = action6.Action6() layout.write_action_value(actions, act6, 4) if len(act6.modifications) > 0: actions.append(act6) layout_action = Action2Layout( feature, spritelayout.name.value + " - feature {:02X}".format(feature), spritelayout.pos, layout, param_registers, ) actions.append(layout_action) varact2parser = action2var.Varaction2Parser(feature) layout_registers.parse(varact2parser) # Only continue if we actually needed any new registers if varact2parser.var_list: # Remove the last VAL2 operator varact2parser.var_list.pop() varact2parser.var_list_size -= 1 actions.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: actions.append(extra_act6) varaction2 = action2var.Action2Var( feature, "{}@registers - feature {:02X}".format(spritelayout.name.value, feature), spritelayout.pos, 0x89 ) varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(spritelayout.name, [], None, layout_action) varaction2.ranges.append( action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, "") ) varaction2.default_result = ref varaction2.default_comment = "" # Add two references (default + range) # Make sure that registers allocated here are not used by the spritelayout action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) spritelayout.set_action2(varaction2, feature) actions.append(varaction2) else: spritelayout.set_action2(layout_action, feature) action6.free_parameters.restore() return actions def make_empty_layout_action2(feature, pos): """ Make an empty layout action2 For use with failed callbacks @param feature: Feature of the sprite layout to create @type feature: C{int} @param pos: Positional context. @type pos: L{Position} @return: The created sprite layout action2 @rtype: L{Action2Layout} """ layout = ParsedSpriteLayout() layout.ground_sprite = Action2LayoutSprite(feature, Action2LayoutSpriteType.GROUND) layout.ground_sprite.set_param(expression.Identifier("sprite"), expression.ConstantNumeric(0)) return Action2Layout(feature, "@CB_FAILED_LAYOUT{:02X}".format(feature), pos, layout, []) class StationSpriteset(expression.Expression): def __init__(self, spriteset, args, var10, pos=None): expression.Expression.__init__(self, pos) self.spriteset = spriteset self.offset = args self.var10 = var10 class StationSpritesetVar10Map: def __init__(self): self.spritesets = {} self.var10 = 1 # Reserving 0 for SPRITESET() (basic action2) def translate(self, spriteset, args, pos): if spriteset not in self.spritesets: if self.var10 == 8: raise generic.ScriptError("A station can't use more than 6 different sprite sets", pos) self.spritesets[spriteset] = self.var10 self.var10 += 1 if self.var10 != 1 else 2 # Reserving 2 for custom foundations return StationSpriteset( None if isinstance(spriteset, int) else spriteset, args, self.spritesets[spriteset], pos ) def append_mapping(self, mapping, feature, actions, default, custom_spritesets): for spriteset, var10 in self.spritesets.items(): if not isinstance(spriteset, int): if not spriteset.has_action2(feature): actions.extend(action1.add_to_action1([spriteset], feature, None)) real_action2 = action2real.make_simple_real_action2( feature, spriteset.name.value + " - feature {:02X}".format(feature), None, action1.get_action1_index(spriteset, feature), ) actions.append(real_action2) spriteset.set_action2(real_action2, feature) ref = expression.SpriteGroupRef(spriteset.name, [], None, spriteset.get_action2(feature)) else: if spriteset > len(custom_spritesets): raise generic.ScriptError("Index out of range") ref = custom_spritesets[spriteset] # Skip default result if ref == default: continue mapping[var10] = (ref, None) return mapping def parse_station_layouts(feature, id, layouts): var10map = StationSpritesetVar10Map() # Add DEFAULT([offset]) to reference active spriteset, selected by basic action2 # and CUSTOM(index, [offset]) to reference a spriteset from the custom list def parse_spriteset(name, args, pos, info): if info: if len(args) < 1: raise generic.ScriptError("'{}' expects 1 or 2 parameters".format(name), pos) if not isinstance(args[0], expression.ConstantNumeric): raise generic.ScriptError("First parameter for '{}' must be a constant".format(name), pos) return var10map.translate(args[0].value, args[1:], pos) return StationSpriteset(None, args, info, pos) default_param = [ ( {"DEFAULT": None, "CUSTOM": True}, lambda name, value, pos: expression.FunctionPtr(expression.Identifier(name, pos), parse_spriteset, value), ) ] actions = [] param_registers = [] parsed_layouts = [] varact2parser = action2var.Varaction2Parser(feature) layout_registers = LayoutRegisters() for layout in layouts: spritelayout = ( action2.resolve_spritegroup(layout.name) if isinstance(layout, expression.SpriteGroupRef) else None ) if not spritelayout or not isinstance(spritelayout, spriteblock.SpriteLayout): raise generic.ScriptError("Expected a SpriteLayout", layout.pos) # Allocate registers param_map = {} for i, param in enumerate(spritelayout.param_list): reg = action2var.VarAction2CallParam(param.value) param_registers.append(reg) param_map[param.value] = reg store_tmp = action2var.VarAction2StoreCallParam(reg) varact2parser.parse_expr(layout.param_list[i]) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(store_tmp) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += store_tmp.get_size() + 2 param_map = [(param_map, lambda name, value, pos: action2var.VarAction2LoadCallParam(value, name))] param_map.extend(default_param) layout = ParsedSpriteLayout() layout.process(spritelayout, feature, param_map, actions, layout_registers, var10map) parsed_layouts.append(layout) layout_registers.parse(varact2parser) actions.extend(action0.get_layout_action0(feature, id, parsed_layouts)) registers_ref = None # Create a procedure only if needed if varact2parser.var_list: # Remove the last VAL2 operator varact2parser.var_list.pop() varact2parser.var_list_size -= 1 actions.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: actions.append(extra_act6) varaction2 = action2var.Action2Var( feature, "Station Layout@registers - Id {:02X}".format(id.value), None, 0x89, param_registers ) varaction2.var_list = varact2parser.var_list varaction2.default_result = expression.ConstantNumeric(0) varaction2.default_comment = "Return computed value" actions.append(varaction2) registers_ref = expression.SpriteGroupRef(expression.Identifier(varaction2.name), [], None, varaction2) return (actions, var10map, registers_ref) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action2production.py0000644000175100001660000002001014754345605020171 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.actions import action2, action2var, action6, actionD class Action2Production(action2.Action2): """ Class corresponding to Action2Industries (=production CB) @ivar version: Production CB version. Version 0 uses constants, version 1 uses registers. @type version: C{int} @ivar sub_in: Amounts (v0) or registers (v1) to subtract from incoming cargos. @type sub_in: C{list} of (C{int} or L{VarAction2Var}) @ivar add_out: Amounts (v0) or registers (v1) to add to the output cargos. @type add_out: C{list} of (C{int} or L{VarAction2Var}) @ivar again: Number (v0) or register (v1), production CB will be run again if nonzero. @type again C{int} or L{VarAction2Var} """ def __init__(self, name, pos, version, sub_in, add_out, again): action2.Action2.__init__(self, 0x0A, name, pos) self.version = version if version in (0, 1): self.sub_in = sub_in assert len(self.sub_in) == 3 self.add_out = add_out assert len(self.add_out) == 2 elif version == 2: self.sub_in = sub_in self.add_out = add_out else: raise AssertionError() self.again = again def prepare_output(self, sprite_num): action2.Action2.prepare_output(self, sprite_num) def write(self, file): if self.version in (0, 1): cargo_size = 2 if self.version == 0 else 1 size = 2 + 5 * cargo_size action2.Action2.write_sprite_start(self, file, size) file.print_bytex(self.version) values = self.sub_in + self.add_out + [self.again] # Read register numbers if needed if self.version == 1: values = [val.parameter for val in values] for val in values[:-1]: file.print_varx(val, cargo_size) file.print_bytex(values[-1]) elif self.version == 2: size = 4 + 2 * (len(self.sub_in) + len(self.add_out)) action2.Action2.write_sprite_start(self, file, size) file.print_bytex(self.version) file.print_byte(len(self.sub_in)) for cargoindex, value in self.sub_in: file.print_bytex(cargoindex) file.print_bytex(value.parameter) file.print_byte(len(self.add_out)) for cargoindex, value in self.add_out: file.print_bytex(cargoindex) file.print_bytex(value.parameter) file.print_bytex(self.again.parameter) file.newline() file.end_sprite() def resolve_prodcb_register(param, varact2parser): if ( isinstance(param, expression.StorageOp) and param.name == "LOAD_TEMP" and isinstance(param.register, expression.ConstantNumeric) ): # We can load a register directly res = action2var.VarAction2Var(0x7D, 0, 0xFFFFFFFF, param.register.value) else: if len(varact2parser.var_list) != 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr(action2var.reduce_varaction2_expr(param, action2var.get_scope(0x0A))) store_tmp = action2var.VarAction2StoreTempVar() res = action2var.VarAction2LoadTempVar(store_tmp) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size() + 1 # Add 1 for operator return res def finish_production_actions(produce, prod_action, action_list, varact2parser): action_list.append(prod_action) if len(varact2parser.var_list) == 0: produce.set_action2(prod_action, 0x0A) else: # Create intermediate varaction2 varaction2 = action2var.Action2Var(0x0A, "{}@registers".format(produce.name.value), produce.pos, 0x89) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(produce.name, [], None, prod_action) varaction2.ranges.append( action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, "") ) varaction2.default_result = ref varaction2.default_comment = "" # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) produce.set_action2(varaction2, 0x0A) action_list.append(varaction2) action6.free_parameters.restore() return action_list def get_production_actions(produce): """ Get the action list that implements the given produce-block in nfo. @param produce: Produce-block to parse. @type produce: L{Produce} """ action_list = [] act6 = action6.Action6() action6.free_parameters.save() result_list = [] varact2parser = action2var.Varaction2Parser(0x0A) if all(x.supported_by_actionD(False) for x in produce.param_list): version = 0 offset = 4 for i, param in enumerate(produce.param_list): result, offset = actionD.write_action_value(param, action_list, act6, offset, 2 if i < 5 else 1) result_list.append(result.value) else: version = 1 for param in produce.param_list: result_list.append(resolve_prodcb_register(param, varact2parser)) if len(act6.modifications) > 0: action_list.append(act6) prod_action = Action2Production( produce.name.value, produce.pos, version, result_list[0:3], result_list[3:5], result_list[5] ) return finish_production_actions(produce, prod_action, action_list, varact2parser) def get_production_v2_actions(produce): """ Get the action list that implements the given produce-block in nfo. @param produce: Produce-block to parse. @type produce: L{Produce2} """ action_list = [] action6.free_parameters.save() varact2parser = action2var.Varaction2Parser(0x0A) def resolve_cargoitem(item): cargolabel = item.name.value if cargolabel not in global_constants.cargo_numbers: raise generic.ScriptError("Cargo label {0} not found in your cargo table".format(cargolabel), produce.pos) cargoindex = global_constants.cargo_numbers[cargolabel] valueregister = resolve_prodcb_register(item.value, varact2parser) return (cargoindex, valueregister) sub_in = [resolve_cargoitem(item) for item in produce.subtract_in] add_out = [resolve_cargoitem(item) for item in produce.add_out] again = resolve_prodcb_register(produce.again, varact2parser) prod_action = Action2Production(produce.name.value, produce.pos, 2, sub_in, add_out, again) return finish_production_actions(produce, prod_action, action_list, varact2parser) def make_empty_production_action2(pos): """ Make an empty production action2 For use with failed callbacks @param pos: Positional context. @type pos: L{Position} @return: The created production action2 @rtype: L{Action2Production} """ return Action2Production("@CB_FAILED_PROD", pos, 0, [0, 0, 0], [0, 0], 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action2random.py0000644000175100001660000003630214754345605017276 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, nmlop from nml.actions import action2, action2real, action2var, action6 class Action2Random(action2.Action2): def __init__(self, feature, name, pos, type_byte, count, triggers, randbit, nrand): action2.Action2.__init__(self, feature, name, pos) self.type_byte = type_byte self.count = count self.triggers = triggers self.randbit = randbit self.nrand = nrand self.choices = [] def prepare_output(self, sprite_num): action2.Action2.prepare_output(self, sprite_num) for choice in self.choices: if isinstance(choice.result, expression.SpriteGroupRef): choice.result = choice.result.get_action2_id(self.feature) else: choice.result = choice.result.value | 0x8000 def write(self, file): # [] size = 4 + 2 * self.nrand + (self.count is not None) action2.Action2.write_sprite_start(self, file, size) file.print_bytex(self.type_byte) if self.count is not None: file.print_bytex(self.count) file.print_bytex(self.triggers) file.print_byte(self.randbit) file.print_bytex(self.nrand) file.newline() for choice in self.choices: for _ in range(0, choice.prob): file.print_wordx(choice.result) file.comment(choice.comment) file.end_sprite() class RandomAction2Choice: def __init__(self, result, prob, comment): self.result = result self.prob = prob self.comment = comment vehicle_random_types = { "SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 16, "triggers": True}, "PARENT": {"type": 0x83, "param": 0, "first_bit": 0, "num_bits": 16, "triggers": False}, "TILE": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 16, "triggers": False}, "BACKWARD_SELF": {"type": 0x84, "param": 1, "first_bit": 0, "num_bits": 16, "triggers": False, "value": 0x00}, "FORWARD_SELF": {"type": 0x84, "param": 1, "first_bit": 0, "num_bits": 16, "triggers": False, "value": 0x40}, "BACKWARD_ENGINE": {"type": 0x84, "param": 1, "first_bit": 0, "num_bits": 16, "triggers": False, "value": 0x80}, "BACKWARD_SAMEID": {"type": 0x84, "param": 1, "first_bit": 0, "num_bits": 16, "triggers": False, "value": 0xC0}, } random_types = { 0x00: vehicle_random_types, 0x01: vehicle_random_types, 0x02: vehicle_random_types, 0x03: vehicle_random_types, 0x04: { "SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 16, "triggers": True}, "TILE": {"type": 0x80, "param": 0, "first_bit": 16, "num_bits": 4, "triggers": True}, }, 0x05: {"SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 8, "triggers": False}}, 0x06: {}, 0x07: {"SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 8, "triggers": True}}, 0x08: {}, 0x09: { "SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 8, "triggers": True}, "PARENT": {"type": 0x83, "param": 0, "first_bit": 0, "num_bits": 16, "triggers": True}, }, 0x0A: {"SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 16, "triggers": False}}, 0x0B: {}, 0x0C: {}, 0x0D: {}, 0x0E: {}, 0x0F: {"SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 8, "triggers": False}}, 0x10: {"SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 2, "triggers": False}}, 0x11: { "SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 16, "triggers": False}, "TILE": {"type": 0x80, "param": 0, "first_bit": 16, "num_bits": 4, "triggers": False}, }, 0x12: {"SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 2, "triggers": False}}, 0x13: {"SELF": {"type": 0x80, "param": 0, "first_bit": 0, "num_bits": 2, "triggers": False}}, } def parse_randomswitch_type(random_switch): """ Parse the type of a random switch to determine the type and random bits to use. @param random_switch: Random switch to parse the type of @type random_switch: L{RandomSwitch} @return: A tuple containing the following: - The type byte of the resulting random action2. - The value to use as , None if N/A. - Expression to parse in a preceding switch-block, None if N/A. - The first random bit that should be used (often 0) - The number of random bits available @rtype: C{tuple} of (C{int}, C{int} or C{None}, L{Expression} or C{None}, C{int}, C{int}) """ # Extract some stuff we'll often need type_str = random_switch.type.value type_pos = random_switch.type.pos feature_val = next(iter(random_switch.feature_set)) # Validate type name / param combination if type_str not in random_types[feature_val]: raise generic.ScriptError( "Invalid combination for random_switch feature {:d} and type '{}'. ".format(feature_val, type_str), type_pos ) type_info = random_types[feature_val][type_str] count_expr = None if random_switch.type_count is None: # No param given if type_info["param"] == 1: raise generic.ScriptError( "Value '{}' for random_switch parameter 2 'type' requires a parameter.".format(type_str), type_pos ) count = None else: # Param given if type_info["param"] == 0: raise generic.ScriptError( "Value '{}' for random_switch parameter 2 'type' should not have a parameter.".format(type_str), type_pos, ) if ( isinstance(random_switch.type_count, expression.ConstantNumeric) and 1 <= random_switch.type_count.value <= 15 ): count = random_switch.type_count.value else: count = 0 count_expr = nmlop.STO_TMP(random_switch.type_count, 0x100, type_pos) count = type_info["value"] | count if random_switch.triggers.value != 0 and not type_info["triggers"]: raise generic.ScriptError( "Triggers may not be set for random_switch feature {:d} and type '{}'. ".format(feature_val, type_str), type_pos, ) # Determine type byte and random bits type_byte = type_info["type"] start_bit = type_info["first_bit"] bits_available = type_info["num_bits"] return type_byte, count, count_expr, start_bit, bits_available def lookup_random_action2(sg_ref): """ Lookup a sprite group reference to find the corresponding random action2 @param sg_ref: Reference to random action2 @type sg_ref: L{SpriteGroupRef} @return: Random action2 corresponding to this sprite group, or None if N/A @rtype: L{Action2Random} or C{None} """ spritegroup = action2.resolve_spritegroup(sg_ref.name) act2 = spritegroup.random_act2 assert act2 is None or isinstance(act2, Action2Random) return act2 def parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand): """ Handle the dependencies between random chains to determine the random bits to use @param random_switch: Random switch to parse @type random_switch: L{RandomSwitch} @param start_bit: First available random bit @type start_bit: C{int} @param bits_available: Number of random bits available @type bits_available: C{int} @param nrand: Number of random choices to use @type nrand: C{int} @return: A tuple of two values: - The first random bit to use - The number of random choices to use. This may be higher the the original amount passed as paramter @rtype: C{tuple} of (C{int}, C{int}) """ # Dependent random chains act2_to_copy = None for dep in random_switch.dependent: act2 = lookup_random_action2(dep) if act2 is None: continue # May happen if said random switch is not used and therefore not parsed if act2_to_copy is not None: if act2_to_copy.randbit != act2.randbit: msg = ( "random_switch '{}' cannot be dependent on both '{}' and '{}'" " as these are independent of each other." ).format(random_switch.name.value, act2_to_copy.name, act2.name) raise generic.ScriptError(msg, random_switch.pos) if act2_to_copy.nrand != act2.nrand: msg = ( "random_switch '{}' cannot be dependent on both '{}' and '{}'" " as they don't use the same amount of random data." ).format(random_switch.name.value, act2_to_copy.name, act2.name) raise generic.ScriptError(msg, random_switch.pos) else: act2_to_copy = act2 if act2_to_copy is not None: randbit = act2_to_copy.randbit if nrand > act2_to_copy.nrand: msg = "random_switch '{}' cannot be dependent on '{}' as it requires more random data." msg = msg.format(random_switch.name.value, act2_to_copy.name) raise generic.ScriptError(msg, random_switch.pos) nrand = act2_to_copy.nrand else: randbit = -1 # INdependent random chains possible_mask = ((1 << bits_available) - 1) << start_bit for indep in random_switch.independent: act2 = lookup_random_action2(indep) if act2 is None: continue # May happen if said random switch is not used and therefore not parsed possible_mask &= ~((act2.nrand - 1) << act2.randbit) required_mask = nrand - 1 if randbit != -1: # randbit has already been determined. Check that it is suitable if possible_mask & (required_mask << randbit) != (required_mask << randbit): msg = ( "Combination of dependence on and independence from" " random_switches is not possible for random_switch '{}'." ).format(random_switch.name.value) raise generic.ScriptError(msg, random_switch.pos) else: # find a suitable randbit for i in range(start_bit, bits_available + start_bit): if possible_mask & (required_mask << i) == (required_mask << i): randbit = i break else: msg = "Independence of all given random_switches is not possible for random_switch '{}'." msg = msg.format(random_switch.name.value) raise generic.ScriptError(msg, random_switch.pos) return randbit, nrand def parse_randomswitch(random_switch): """ Parse a randomswitch block into actions @param random_switch: RandomSwitch block to parse @type random_switch: L{RandomSwitch} @return: List of actions @rtype: C{list} of L{BaseAction} """ action_list = action2real.create_spriteset_actions(random_switch) feature = next(iter(random_switch.feature_set)) type_byte, count, count_expr, start_bit, bits_available = parse_randomswitch_type(random_switch) total_prob = sum(choice.probability.value for choice in random_switch.choices) assert total_prob > 0 nrand = 1 while nrand < total_prob: nrand <<= 1 # Verify that enough random data is available if min(1 << bits_available, 0x80) < nrand: msg = "The maximum sum of all random_switch probabilities is {:d}, encountered {:d}." msg = msg.format(min(1 << bits_available, 0x80), total_prob) raise generic.ScriptError(msg, random_switch.pos) randbit, nrand = parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand) random_action2 = Action2Random( feature, random_switch.name.value, random_switch.pos, type_byte, count, random_switch.triggers.value, randbit, nrand, ) random_switch.random_act2 = random_action2 action6.free_parameters.save() act6 = action6.Action6() offset = 8 if count is not None else 7 # divide the 'extra' probabilities in an even manner i = 0 resulting_prob = {c: c.probability.value for c in random_switch.choices} while i < (nrand - total_prob): best_choice = None best_ratio = 0 for choice in random_switch.choices: # float division, so 9 / 10 = 0.9 ratio = choice.probability.value / float(resulting_prob[choice] + 1) if ratio > best_ratio: best_ratio = ratio best_choice = choice assert best_choice is not None resulting_prob[best_choice] += 1 i += 1 for choice in random_switch.choices: res_prob = resulting_prob[choice] result, comment = action2var.parse_result( choice.result.value, action_list, act6, offset, random_action2, None, 0x8A if type_byte == 0x83 else 0x89, res_prob, ) offset += res_prob * 2 comment = "({:d}/{:d}) -> ({:d}/{:d}): ".format(choice.probability.value, total_prob, res_prob, nrand) + comment random_action2.choices.append(RandomAction2Choice(result, res_prob, comment)) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(random_action2) if count_expr is None: random_switch.set_action2(random_action2, feature) else: # Create intermediate varaction2 to compute parameter for type 0x84 varaction2 = action2var.Action2Var( feature, "{}@registers".format(random_switch.name.value), random_switch.pos, 0x89 ) varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(count_expr) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(random_switch.name, [], None, random_action2) varaction2.ranges.append( action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, "") ) varaction2.default_result = ref varaction2.default_comment = "" # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) random_switch.set_action2(varaction2, feature) action_list.append(varaction2) action6.free_parameters.restore() return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action2real.py0000644000175100001660000001423214754345605016737 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import action1, action2 from nml.ast import general class Action2Real(action2.Action2): def __init__(self, feature, name, pos, loaded_list, loading_list): action2.Action2.__init__(self, feature, name, pos) self.loaded_list = loaded_list self.loading_list = loading_list def write(self, file): size = 2 + 2 * len(self.loaded_list) + 2 * len(self.loading_list) action2.Action2.write_sprite_start(self, file, size) file.print_byte(len(self.loaded_list)) file.print_byte(len(self.loading_list)) file.newline() for i in self.loaded_list: file.print_word(i) file.newline() for i in self.loading_list: file.print_word(i) file.newline() file.end_sprite() def get_real_action2s(spritegroup, feature): loaded_list = [] loading_list = [] actions = [] if feature not in action2.features_sprite_group: raise generic.ScriptError( "Sprite groups that combine sprite sets are not supported for feature '{}'.".format( general.feature_name(feature) ), spritegroup.pos, ) # First make sure that all referenced real sprites are put in a single action1 spriteset_list = [] for view in spritegroup.spriteview_list: spriteset_list.extend([action2.resolve_spritegroup(sg_ref.name) for sg_ref in view.spriteset_list]) if feature == 0x04: if view.name.value not in ["little", "lots"]: raise generic.ScriptError("Unexpected '{}' (list of) sprite set(s).".format(view.name), view.pos) view.name.value = "loading" if view.name.value == "lots" else "loaded" actions.extend(action1.add_to_action1(spriteset_list, feature, spritegroup.pos)) view_names = sorted(view.name.value for view in spritegroup.spriteview_list) if feature in (0x00, 0x01, 0x02, 0x03): if view_names != sorted(["loading", "loaded"]): raise generic.ScriptError("Expected a 'loading' and a 'loaded' (list of) sprite set(s).", spritegroup.pos) elif feature == 0x04: if "loading" not in view_names: raise generic.ScriptError("Expected at least a 'lots' (list of) sprite set(s).", spritegroup.pos) elif feature in (0x05, 0x0B, 0x0D, 0x10): msg = ( "Sprite groups for feature {:02X} will not be supported in the future, as they are no longer needed." " Directly refer to sprite sets instead." ).format(feature) generic.print_warning(generic.Warning.GENERIC, msg, spritegroup.pos) if view_names != ["default"]: raise generic.ScriptError("Expected only a 'default' (list of) sprite set(s).", spritegroup.pos) for view in spritegroup.spriteview_list: if len(view.spriteset_list) == 0: raise generic.ScriptError("Expected at least one sprite set, encountered 0.", view.pos) for set_ref in view.spriteset_list: spriteset = action2.resolve_spritegroup(set_ref.name) action1_index = action1.get_action1_index(spriteset, feature) if view.name.value == "loading": loading_list.append(action1_index) else: loaded_list.append(action1_index) actions.append( Action2Real( feature, spritegroup.name.value + " - feature {:02X}".format(feature), spritegroup.pos, loaded_list, loading_list, ) ) spritegroup.set_action2(actions[-1], feature) return actions def make_simple_real_action2(feature, name, pos, action1_index): """ Make a simple real action2, referring to only 1 action1 sprite set @param feature: Feature of the needed action2 @type feature: C{int} @param name: Name of the action2 @type name: C{str} @param pos: Positional context. @type pos: L{Position} @param action1_index: Index of the action1 sprite set to use @type action1_index: C{int} @return: The created real action2 @rtype: L{Action2Real} """ return Action2Real( feature, name, pos, [action1_index] if feature != 0x04 else [], [action1_index] if feature <= 0x04 else [] ) def create_spriteset_actions(spritegroup): """ Create action2s for directly-referenced sprite sets @param spritegroup: Spritegroup to create the sprite sets for @type spritegroup: L{ASTSpriteGroup} @return: Resulting list of actions @rtype: C{list} of L{BaseAction} """ action_list = [] # Iterate over features first for more efficient action1s for feature in spritegroup.feature_set: if len(spritegroup.used_sprite_sets) != 0 and feature not in action2.features_sprite_group: raise generic.ScriptError( "Directly referring to sprite sets is not possible for feature {:02X}".format(feature), spritegroup.pos ) for spriteset in spritegroup.used_sprite_sets: if spriteset.has_action2(feature): continue action_list.extend(action1.add_to_action1([spriteset], feature, spritegroup.pos)) real_action2 = make_simple_real_action2( feature, spriteset.name.value + " - feature {:02X}".format(feature), spritegroup.pos, action1.get_action1_index(spriteset, feature), ) action_list.append(real_action2) spriteset.set_action2(real_action2, feature) return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action2var.py0000644000175100001660000014302414754345605016606 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.actions import action2, action2real, action2var_variables, action4, action6, actionD from nml.ast import switch class Action2Var(action2.Action2): """ Variational Action2. This is the NFO equivalent of a switch-block in NML. It computes a single integer from one or more variables and picks it's return value based on the result of the computation. The return value can be either a 15bit integer or a reference to another action2. @ivar type_byte: The size (byte, word, double word) and access type (own object or related object). 0x89 (own object, double word) and 0x8A (related object, double word) and the only supported values. @type type_byte: C{int} @ivar ranges: List of return value ranges. Each range contains a minimum and a maximum value and a return value. The list is checked in order, if the result of the computation is between the minimum and maximum (inclusive) of one range the result of that range is returned. The result can be either an integer of another action2. @ivar ranges: C{list} of L{VarAction2Range} """ def __init__(self, feature, name, pos, type_byte, param_registers=None): action2.Action2.__init__(self, feature, name, pos) self.type_byte = type_byte self.ranges = [] self.param_registers = param_registers or [] def resolve_tmp_storage(self): # A return action may use the parameters of its parent # Make sure param registers are not reused for var in self.var_list: if isinstance(var, VarAction2LoadCallParam): self.remove_tmp_location(var.parameter, False) for var in self.param_registers + self.var_list: # Allocate param registers first if isinstance(var, (VarAction2StoreTempVar, VarAction2CallParam)): if isinstance(var, VarAction2CallParam) and var.register: continue if not self.tmp_locations: raise generic.ScriptError( "There are not enough registers available " + "to perform all required computations in switch blocks. " + "Please reduce the complexity of your code.", self.pos, ) location = self.tmp_locations[0] self.remove_tmp_location(location, False) var.set_register(location) def prepare_output(self, sprite_num): action2.Action2.prepare_output(self, sprite_num) for i in range(0, len(self.var_list) - 1, 2): self.var_list[i].shift |= 0x20 for i in range(0, len(self.var_list), 2): if isinstance(self.var_list[i], VarAction2ProcCallVar): self.var_list[i].resolve_parameter(self.feature) for r in self.ranges: if isinstance(r.result, expression.SpriteGroupRef): r.result = r.result.get_action2_id(self.feature) else: r.result = r.result.value | 0x8000 if isinstance(self.default_result, expression.SpriteGroupRef): self.default_result = self.default_result.get_action2_id(self.feature) else: self.default_result = self.default_result.value | 0x8000 def write(self, file): # type_byte, num_ranges, default_result = 4 # 2 bytes for the result, 8 bytes for the min/max range. size = 4 + (2 + 8) * len(self.ranges) for var in self.var_list: if isinstance(var, nmlop.Operator): size += 1 else: size += var.get_size() regs = ["{} : register {:X}".format(reg.name, reg.register) for reg in self.param_registers] self.write_sprite_start(file, size, regs) file.print_bytex(self.type_byte) file.newline() for var in self.var_list: if isinstance(var, nmlop.Operator): file.print_bytex(var.act2_num, var.act2_str) else: var.write(file, 4) file.newline(var.comment) file.print_byte(len(self.ranges)) file.newline() for r in self.ranges: file.print_wordx(r.result) file.print_varx(r.min.value, 4) file.print_varx(r.max.value, 4) file.newline(r.comment) file.print_wordx(self.default_result) file.comment(self.default_comment) file.end_sprite() class VarAction2Var: """ Represents a variable for use in a (advanced) variational action2. @ivar var_num: Number of the variable to use. @type var_num: C{int} @ivar shift: The number of bits to shift the value of the given variable to the right. @type shift: C{int} @ivar mask: Bitmask to use on the value after shifting it. @type mask: C{int} @ivar parameter: Parameter to be used as argument for the variable. @type parameter: C{int} or C{None} @precondition: (0x60 <= var_num <= 0x7F) == (parameter is not None) @ivar add: If not C{None}, add this value to the result. @type add: C{int} or C{None} @ivar div: If not C{None}, divide the result by this. @type add: C{int} or C{None} @ivar mod: If not C{None}, compute (result module mod). @type add: C{int} or C{None} @ivar comment: Textual description of this variable. @type comment: C{basestr} """ def __init__(self, var_num, shift, mask, parameter=None, comment=""): self.var_num = var_num self.shift = shift self.mask = mask self.parameter = parameter self.add = None self.div = None self.mod = None self.comment = comment def write(self, file, size): file.print_bytex(self.var_num) if self.parameter is not None: file.print_bytex(self.parameter) if self.mod is not None: self.shift |= 0x80 elif self.add is not None or self.div is not None: self.shift |= 0x40 file.print_bytex(self.shift) file.print_varx(self.mask, size) if self.add is not None: file.print_varx(self.add, size) if self.div is not None: file.print_varx(self.div, size) elif self.mod is not None: file.print_varx(self.mod, size) else: # no div or add, just divide by 1 file.print_varx(1, size) def get_size(self): # var number (1) [+ parameter (1)] + shift num (1) + and mask (4) [+ add (4) + div/mod (4)] size = 6 if self.parameter is not None: size += 1 if self.add is not None or self.div is not None or self.mod is not None: size += 8 return size def supported_by_actionD(self, raise_error): assert not raise_error return False # Class for var 7E procedure calls class VarAction2ProcCallVar(VarAction2Var): def __init__(self, sg_ref): if not sg_ref.act2: if not isinstance(action2.resolve_spritegroup(sg_ref.name), (switch.Switch, switch.RandomSwitch)): raise generic.ScriptError( "Block with name '{}' is not a valid procedure".format(sg_ref.name), sg_ref.pos ) if not sg_ref.is_procedure: raise generic.ScriptError("Unexpected identifier encountered: '{}'".format(sg_ref.name), sg_ref.pos) VarAction2Var.__init__(self, 0x7E, 0, 0, comment=str(sg_ref)) # Reference to the called action2 self.sg_ref = sg_ref def resolve_parameter(self, feature): self.parameter = self.sg_ref.get_action2_id(feature) def get_size(self): return 7 def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) # General load and store class for temp parameters # Register is allocated at the store operation class VarAction2StoreTempVar(VarAction2Var): def __init__(self): VarAction2Var.__init__(self, 0x1A, 0, 0) # mask holds the number, it's resolved in Action2Var.resolve_tmp_storage self.load_vars = [] def set_register(self, register): self.mask = register for load_var in self.load_vars: load_var.parameter = register def get_size(self): return 6 def get_mask(size): if size == 1: return 0xFF elif size == 2: return 0xFFFF return 0xFFFFFFFF class VarAction2LoadTempVar(VarAction2Var, expression.Expression): def __init__(self, tmp_var): VarAction2Var.__init__(self, 0x7D, 0, 0) expression.Expression.__init__(self, None) assert isinstance(tmp_var, VarAction2StoreTempVar) tmp_var.load_vars.append(self) def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) def get_size(self): return 7 def reduce(self, id_dicts=None, unknown_id_fatal=True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): assert not raise_error return False # Temporary load and store classes used for spritelayout parameters # Register is allocated in a separate entity class VarAction2CallParam: def __init__(self, name): self.register = None self.store_vars = [] self.load_vars = [] self.name = name def set_register(self, register): self.register = register for store_var in self.store_vars: store_var.mask = register for load_var in self.load_vars: load_var.parameter = register class VarAction2LoadCallParam(VarAction2Var, expression.Expression): def __init__(self, param, name): assert isinstance(param, VarAction2CallParam) VarAction2Var.__init__(self, 0x7D, 0, 0, comment=param.name) expression.Expression.__init__(self, None) assert isinstance(param, VarAction2CallParam) param.load_vars.append(self) self.name = name # Register is stored in parameter def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) def get_size(self): return 7 def reduce(self, id_dicts=None, unknown_id_fatal=True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): assert not raise_error return False def __str__(self): return self.name class VarAction2StoreCallParam(VarAction2Var): def __init__(self, param): VarAction2Var.__init__(self, 0x1A, 0, 0) assert isinstance(param, VarAction2CallParam) param.store_vars.append(self) # Register is stored in mask def get_size(self): return 6 class VarAction2Range: def __init__(self, min, max, result, comment): self.min = min self.max = max self.result = result self.comment = comment class Modification: def __init__(self, param, size, offset): self.param = param self.size = size self.offset = offset class Varaction2Parser: def __init__(self, action_feature, var_range=0x89): self.action_feature = action_feature self.var_range = var_range self.var_scope = get_scope(action_feature, var_range) # Depends on action_feature and var_range self.extra_actions = [] self.mods = [] self.var_list = [] self.var_list_size = 0 self.proc_call_list = [] self.in_store_op = False def preprocess_binop(self, expr): """ Several nml operators are not directly support by nfo so we have to work around that by implementing those operators in terms of others. @return: A pre-processed version of the expression. @rtype: L{Expression} """ assert isinstance(expr, expression.BinOp) if expr.op == nmlop.CMP_LT: # return value is 0, 1 or 2, we want to map 0 to 1 and the others to 0 expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2) # reduce the problem to 0/1 expr = nmlop.MIN(expr, 1) # and invert the result expr = nmlop.XOR(expr, 1) elif expr.op == nmlop.CMP_GT: # return value is 0, 1 or 2, we want to map 2 to 1 and the others to 0 expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2) # subtract one expr = nmlop.SUB(expr, 1) # map -1 and 0 to 0 expr = nmlop.MAX(expr, 0) elif expr.op == nmlop.CMP_LE: # return value is 0, 1 or 2, we want to map 2 to 0 and the others to 1 expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2) # swap 0 and 2 expr = nmlop.XOR(expr, 2) # map 1/2 to 1 expr = nmlop.MIN(expr, 1) elif expr.op == nmlop.CMP_GE: # return value is 0, 1 or 2, we want to map 1/2 to 1 expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2) expr = nmlop.MIN(expr, 1) elif expr.op == nmlop.CMP_EQ: # return value is 0, 1 or 2, we want to map 1 to 1, other to 0 expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2) expr = nmlop.AND(expr, 1) elif expr.op == nmlop.CMP_NEQ: # same as CMP_EQ but invert the result expr = nmlop.VACT2_CMP(expr.expr1, expr.expr2) expr = nmlop.AND(expr, 1) expr = nmlop.XOR(expr, 1) elif expr.op == nmlop.HASBIT: # hasbit(x, n) ==> (x >> n) & 1 expr = nmlop.SHIFTU_RIGHT(expr.expr1, expr.expr2) expr = nmlop.AND(expr, 1) elif expr.op == nmlop.NOTHASBIT: # !hasbit(x, n) ==> ((x >> n) & 1) ^ 1 expr = nmlop.SHIFTU_RIGHT(expr.expr1, expr.expr2) expr = nmlop.AND(expr, 1) expr = nmlop.XOR(expr, 1) return expr.reduce() def preprocess_ternaryop(self, expr): assert isinstance(expr, expression.TernaryOp) if not expr.expr1.is_read_only() or not expr.expr2.is_read_only(): return create_ternary_action( expr.guard, expr.expr1, expr.expr2, self.extra_actions, self.action_feature, self.var_scope, self.var_range, ) guard = expression.Boolean(expr.guard).reduce() self.parse(guard) if isinstance(expr.expr1, expression.ConstantNumeric) and isinstance(expr.expr2, expression.ConstantNumeric): # This can be done more efficiently as (guard)*(expr1-expr2) + expr2 self.var_list.append(nmlop.MUL) diff_var = VarAction2Var(0x1A, 0, expr.expr1.value - expr.expr2.value) diff_var.comment = "expr1 - expr2" self.var_list.append(diff_var) self.var_list.append(nmlop.ADD) # Add var sizes, +2 for the operators self.var_list_size += 2 + diff_var.get_size() return expr.expr2 else: guard_var = VarAction2StoreTempVar() guard_var.comment = "guard" inverted_guard_var = VarAction2StoreTempVar() inverted_guard_var.comment = "!guard" self.var_list.append(nmlop.STO_TMP) self.var_list.append(guard_var) self.var_list.append(nmlop.XOR) var = VarAction2Var(0x1A, 0, 1) self.var_list.append(var) self.var_list.append(nmlop.STO_TMP) self.var_list.append(inverted_guard_var) self.var_list.append(nmlop.VAL2) # the +4 is for the 4 operators added above (STO_TMP, XOR, STO_TMP, VAL2) self.var_list_size += 4 + guard_var.get_size() + inverted_guard_var.get_size() + var.get_size() expr1 = nmlop.MUL(expr.expr1, VarAction2LoadTempVar(guard_var)) expr2 = nmlop.MUL(expr.expr2, VarAction2LoadTempVar(inverted_guard_var)) return nmlop.ADD(expr1, expr2) def preprocess_absop(self, expr): assert isinstance(expr, expression.AbsOp) self.parse(expr.expr.reduce()) input_var = VarAction2StoreTempVar() input_var.comment = "input" self.var_list.append(nmlop.STO_TMP) self.var_list.append(input_var) self.var_list.append(nmlop.VACT2_CMP) var = VarAction2Var(0x1A, 0, 0) self.var_list.append(var) self.var_list.append(nmlop.SUB) var2 = VarAction2Var(0x1A, 0, 1) self.var_list.append(var2) self.var_list.append(nmlop.MUL) # the +4 is for the 4 operators added above (STO_TMP, VACT2_CMP, SUB, MUL) self.var_list_size += 4 + input_var.get_size() + var.get_size() + var2.get_size() return VarAction2LoadTempVar(input_var) def preprocess_storageop(self, expr): assert isinstance(expr, expression.StorageOp) if expr.info["perm"] and not self.var_scope.has_persistent_storage: raise generic.ScriptError( "Persistent storage is not supported for feature '{}'".format(self.var_scope.name), expr.pos, ) if expr.info["store"]: op = nmlop.STO_PERM if expr.info["perm"] else nmlop.STO_TMP ret = expression.BinOp(op, expr.value, expr.register, expr.pos) else: var_num = 0x7C if expr.info["perm"] else 0x7D ret = expression.Variable(expression.ConstantNumeric(var_num), param=expr.register, pos=expr.pos) if expr.info["perm"] and self.var_scope is action2var_variables.scope_towns: # store grfid in register 0x100 for town persistent storage grfid = expression.ConstantNumeric( 0xFFFFFFFF if expr.grfid is None else expression.parse_string_to_dword(expr.grfid) ) store_op = nmlop.STO_TMP(grfid, 0x100, expr.pos) ret = nmlop.VAL2(store_op, ret) elif expr.grfid is not None: raise generic.ScriptError("Specifying a grfid is only possible for town persistent storage.", expr.pos) return ret def parse_expr_to_constant(self, expr, offset): if isinstance(expr, expression.ConstantNumeric): return expr.value tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) self.mods.append(Modification(tmp_param, 4, self.var_list_size + offset)) return 0 def parse_variable(self, expr): """ Parse a variable in an expression. @param expr: @type expr: L{expression.Variable} """ if not isinstance(expr.num, expression.ConstantNumeric): raise generic.ScriptError("Variable number must be a constant number", expr.pos) if not (expr.param is None or isinstance(expr.param, expression.ConstantNumeric)): raise generic.ScriptError("Variable parameter must be a constant number", expr.pos) if len(expr.extra_params) > 0: first_var = len(self.var_list) == 0 backup_op = None value_backup = None if not first_var: backup_op = self.var_list.pop() value_backup = VarAction2StoreTempVar() self.var_list.append(nmlop.STO_TMP) self.var_list.append(value_backup) self.var_list.append(nmlop.VAL2) self.var_list_size += value_backup.get_size() + 1 # Last value == 0, and this is right before we're going to use # the extra parameters. Set them to their correct value here. for extra_param in expr.extra_params: self.parse(extra_param[1]) self.var_list.append(nmlop.STO_TMP) var = VarAction2Var(0x1A, 0, extra_param[0]) self.var_list.append(var) self.var_list.append(nmlop.VAL2) self.var_list_size += var.get_size() + 2 if not first_var: value_loadback = VarAction2LoadTempVar(value_backup) self.var_list.append(value_loadback) self.var_list.append(backup_op) self.var_list_size += value_loadback.get_size() + 1 if expr.param is None: offset = 2 param = None else: offset = 3 param = expr.param.value mask = self.parse_expr_to_constant(expr.mask, offset) var = VarAction2Var(expr.num.value, expr.shift.value, mask, param) if expr.add is not None: var.add = self.parse_expr_to_constant(expr.add, offset + 4) if expr.div is not None: var.div = self.parse_expr_to_constant(expr.div, offset + 8) if expr.mod is not None: var.mod = self.parse_expr_to_constant(expr.mod, offset + 8) self.var_list.append(var) self.var_list_size += var.get_size() def parse_not(self, expr): self.parse_binop(nmlop.XOR(expr.expr, 1)) def parse_binop(self, expr): if expr.op.act2_num is None: expr.supported_by_action2(True) if expr.op == nmlop.STO_TMP: self.in_store_op = True if ( isinstance(expr.expr2, (expression.ConstantNumeric, expression.Variable)) or isinstance(expr.expr2, (VarAction2LoadTempVar, VarAction2LoadCallParam)) or (isinstance(expr.expr2, expression.Parameter) and isinstance(expr.expr2.num, expression.ConstantNumeric)) or (isinstance(expr.expr2, expression.StorageOp) and expr.expr2.name == "LOAD_TEMP") or expr.op == nmlop.VAL2 ): expr2 = expr.expr2 elif expr.expr2.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr.expr2) self.extra_actions.extend(tmp_param_actions) expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param)) else: # The expression is so complex we need to compute it first, store the # result and load it back later. self.parse(expr.expr2) tmp_var = VarAction2StoreTempVar() self.var_list.append(nmlop.STO_TMP) self.var_list.append(tmp_var) self.var_list.append(nmlop.VAL2) # the +2 is for both operators self.var_list_size += tmp_var.get_size() + 2 expr2 = VarAction2LoadTempVar(tmp_var) # parse expr1 self.parse(expr.expr1) self.var_list.append(expr.op) self.var_list_size += 1 self.parse(expr2) if expr.op == nmlop.STO_TMP: self.in_store_op = False def parse_constant(self, expr): var = VarAction2Var(0x1A, 0, expr.value) self.var_list.append(var) self.var_list_size += var.get_size() def parse_param(self, expr): self.mods.append(Modification(expr.num.value, 4, self.var_list_size + 2)) var = VarAction2Var(0x1A, 0, 0) var.comment = str(expr) self.var_list.append(var) self.var_list_size += var.get_size() def parse_string(self, expr): str_id, actions = action4.get_string_action4s(0, 0xDC if self.in_store_op else 0xD0, expr) self.extra_actions.extend(actions) self.parse_constant(expression.ConstantNumeric(str_id)) def parse_via_actionD(self, expr): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) num = expression.ConstantNumeric(tmp_param) self.parse(expression.Parameter(num)) def parse_proc_call(self, expr): assert isinstance(expr, expression.SpriteGroupRef) assert not expr.param_list or not expr.act2 var_access = VarAction2ProcCallVar(expr) target = action2.resolve_spritegroup(expr.name) if not expr.act2 else None refs = expr.collect_references() # Fill param registers for the call tmp_vars = [] for i, param in enumerate(expr.param_list): if i > 0: # No operator before first param as per advanced VarAct2 syntax self.var_list.append(nmlop.VAL2) self.var_list_size += 1 if refs != [expr]: # For f(x, g(y)), x can be overwritten by y if f and g share the same param registers # Use temporary variables as an intermediate step store_tmp = VarAction2StoreTempVar() tmp_vars.append( ( VarAction2LoadTempVar(store_tmp), VarAction2StoreCallParam(target.register_map[self.action_feature][i]), ) ) else: store_tmp = VarAction2StoreCallParam(target.register_map[self.action_feature][i]) self.parse_expr(reduce_varaction2_expr(param, self.var_scope)) self.var_list.append(nmlop.STO_TMP) self.var_list.append(store_tmp) self.var_list_size += store_tmp.get_size() + 1 # Add 1 for operator # Fill param registers with temporary variables if needed for src, dest in tmp_vars: self.var_list.append(nmlop.VAL2) self.var_list.append(src) self.var_list.append(nmlop.STO_TMP) self.var_list.append(dest) self.var_list_size += src.get_size() + dest.get_size() + 2 # Add 2 for operators if expr.param_list: self.var_list.append(nmlop.VAL2) self.var_list_size += 1 self.var_list.append(var_access) self.var_list_size += var_access.get_size() self.proc_call_list.append(expr) def parse_expr(self, expr): if isinstance(expr, expression.Array): if len(expr.values) == 0: raise generic.ScriptError("An array of expressions cannot be empty", expr.pos) for expr2 in expr.values: self.parse(expr2) self.var_list.append(nmlop.VAL2) self.var_list_size += 1 # Drop the trailing VAL2 again self.var_list.pop() self.var_list_size -= 1 else: self.parse(expr) def parse(self, expr): # Preprocess the expression if isinstance(expr, expression.SpecialParameter): # do this first, since it may evaluate to a BinOp expr = expr.to_reading() if isinstance(expr, expression.BinOp): expr = self.preprocess_binop(expr) elif isinstance(expr, expression.Boolean): expr = nmlop.MINU(expr.expr, 1) elif isinstance(expr, expression.BinNot): expr = nmlop.XOR(expr.expr, 0xFFFFFFFF) elif isinstance(expr, expression.TernaryOp) and not expr.supported_by_actionD(False): expr = self.preprocess_ternaryop(expr) elif isinstance(expr, expression.AbsOp) and not expr.supported_by_actionD(False): expr = self.preprocess_absop(expr) elif isinstance(expr, expression.StorageOp): expr = self.preprocess_storageop(expr) # Try to parse the expression to a list of variables+operators if isinstance(expr, expression.ConstantNumeric): self.parse_constant(expr) elif isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): self.parse_param(expr) elif isinstance(expr, expression.Variable): self.parse_variable(expr) elif expr.supported_by_actionD(False): self.parse_via_actionD(expr) elif isinstance(expr, expression.BinOp): self.parse_binop(expr) elif isinstance(expr, expression.Not): self.parse_not(expr) elif isinstance(expr, expression.String): self.parse_string(expr) elif isinstance(expr, (VarAction2LoadTempVar, VarAction2LoadCallParam)): self.var_list.append(expr) self.var_list_size += expr.get_size() elif isinstance(expr, expression.SpriteGroupRef): self.parse_proc_call(expr) else: expr.supported_by_action2(True) raise AssertionError("supported_by_action2 should have raised the correct error already") def parse_var(name, info, pos): if "replaced_by" in info: generic.print_warning( generic.Warning.DEPRECATION, "'{}' is deprecated, consider using '{}' instead".format(name, info["replaced_by"]), pos, ) param = expression.ConstantNumeric(info["param"]) if "param" in info else None res = expression.Variable( expression.ConstantNumeric(info["var"]), expression.ConstantNumeric(info["start"]), expression.ConstantNumeric((1 << info["size"]) - 1), param, pos, ) if "value_function" in info: return info["value_function"](res, info) return res def parse_60x_var(name, args, pos, info): if "param_function" in info: # Special function to extract parameters if there is more than one param, extra_params = info["param_function"](name, args, pos, info) else: # Default function to extract parameters param, extra_params = action2var_variables.default_60xvar(name, args, pos, info) if isinstance(param, expression.ConstantNumeric) and (0 <= param.value <= 255): res = expression.Variable( expression.ConstantNumeric(info["var"]), expression.ConstantNumeric(info["start"]), expression.ConstantNumeric((1 << info["size"]) - 1), param, pos, ) res.extra_params.extend(extra_params) else: # Make use of var 7B to pass non-constant parameters var = expression.Variable( expression.ConstantNumeric(0x7B), expression.ConstantNumeric(info["start"]), expression.ConstantNumeric((1 << info["size"]) - 1), expression.ConstantNumeric(info["var"]), pos, ) var.extra_params.extend(extra_params) # Set the param in the accumulator beforehand res = nmlop.VAL2(param, var, pos) if "value_function" in info: res = info["value_function"](res, info) return res def parse_minmax(value, unit_str, action_list, act6, offset): """ Parse a min or max value in a switch block. @param value: Value to parse @type value: L{Expression} @param unit_str: Unit to use @type unit_str: C{str} or C{None} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param offset: Current offset to use for action6 @type offset: C{int} @return: A tuple of two values: - The value to use as min/max - Whether the resulting range may need a sanity check @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{bool} """ if unit_str is not None: raise generic.ScriptError("Using a unit is in switch-ranges is not (temporarily) not supported", value.pos) result, offset = actionD.write_action_value(value, action_list, act6, offset, 4) check_range = isinstance(value, expression.ConstantNumeric) return (result, offset, check_range) return_action_id = 0 def create_ternary_action(guard, expr_true, expr_false, action_list, feature, var_scope, var_range): act6 = action6.Action6() global return_action_id name = "@ternary_action_{:d}".format(return_action_id) varaction2 = Action2Var(feature, name, guard.pos, var_range) return_action_id += 1 expr = reduce_varaction2_expr(guard, var_scope) offset = 4 # first var parser = Varaction2Parser(feature, var_range) parser.parse_expr(expr) action_list.extend(parser.extra_actions) for mod in parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + offset) varaction2.var_list = parser.var_list offset += parser.var_list_size + 1 # +1 for the byte num-ranges for proc in parser.proc_call_list: action2.add_ref(proc, varaction2, True) result, result_comment = parse_result(expr_false, action_list, act6, offset, varaction2, None, var_range) offset += 2 # size of result varaction2.ranges.append( VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), result, result_comment) ) default, default_comment = parse_result(expr_true, action_list, act6, offset, varaction2, None, var_range) varaction2.default_result = default varaction2.default_comment = default_comment if len(act6.modifications) > 0: action_list.append(act6) action_list.append(varaction2) return expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) def create_return_action(expr, feature, name, var_range): """ Create a varaction2 to return the computed value @param expr: Expression to return @type expr: L{Expression} @param feature: Feature of the switch-block @type feature: C{int} @param name: Name of the new varaction2 @type name: C{str} @return: A tuple of two values: - Action list to prepend - Reference to the created varaction2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ varact2parser = Varaction2Parser(feature, var_range) varact2parser.parse_expr(expr) action_list = varact2parser.extra_actions extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) varaction2 = Action2Var(feature, name, expr.pos, var_range) varaction2.var_list = varact2parser.var_list varaction2.default_result = expression.ConstantNumeric(0) # Bogus result, it's the nvar == 0 that matters varaction2.default_comment = "Return computed value" for proc in varact2parser.proc_call_list: action2.add_ref(proc, varaction2, True) ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) return (action_list, ref) failed_cb_results = {} def get_failed_cb_result(feature, action_list, parent_action, pos): """ Get a sprite group reference to use for a failed callback The actions needed are created on first use, then cached in L{failed_cb_results} @param feature: Feature to use @type feature: C{int} @param action_list: Action list to append any extra actions to @type action_list: C{list} of L{BaseAction} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param pos: Positional context. @type pos: L{Position} @return: Sprite group reference to use @rtype: L{SpriteGroupRef} """ if feature in failed_cb_results: varaction2 = failed_cb_results[feature] else: # Create action2 (+ action1, if needed) # Import here to avoid circular imports from nml.actions import action1, action2layout, action2production, action2real if feature == 0x0A: # Industries -> production action2 act2 = action2production.make_empty_production_action2(pos) elif feature in (0x07, 0x09, 0x0F, 0x11, 0x14): # Tile layout action2 act2 = action2layout.make_empty_layout_action2(feature, pos) else: # Normal action2 act1_actions, act1_index = action1.make_cb_failure_action1(feature) action_list.extend(act1_actions) act2 = action2real.make_simple_real_action2( feature, "@CB_FAILED_REAL{:02X}".format(feature), pos, act1_index ) action_list.append(act2) # Create varaction2, to choose between returning graphics and 0, depending on CB varact2parser = Varaction2Parser(feature) varact2parser.parse_expr( expression.Variable(expression.ConstantNumeric(0x0C), mask=expression.ConstantNumeric(0xFFFF)) ) varaction2 = Action2Var(feature, "@CB_FAILED{:02X}".format(feature), pos, 0x89) varaction2.var_list = varact2parser.var_list varaction2.ranges.append( VarAction2Range( expression.ConstantNumeric(0), expression.ConstantNumeric(0), expression.ConstantNumeric(0), "graphics callback -> return 0", ) ) varaction2.default_result = expression.SpriteGroupRef(expression.Identifier(act2.name), [], None, act2) varaction2.default_comment = "Non-graphics callback, return graphics result" action2.add_ref(varaction2.default_result, varaction2) action_list.append(varaction2) failed_cb_results[feature] = varaction2 ref = expression.SpriteGroupRef(expression.Identifier(varaction2.name), [], None, varaction2) action2.add_ref(ref, parent_action) return ref def parse_sg_ref_result(result, action_list, parent_action, var_range): """ Parse a result that is a sprite group reference. @param result: Result to parse @type result: L{SpriteGroupRef} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @return: Result to use in the calling varaction2 @rtype: L{SpriteGroupRef} """ if result.name.value == "CB_FAILED": return get_failed_cb_result(parent_action.feature, action_list, parent_action, result.pos) if len(result.param_list) == 0: action2.add_ref(result, parent_action) return result # Result is parametrized # Insert an intermediate varaction2 to store expressions in registers var_scope = get_scope(parent_action.feature, var_range) varact2parser = Varaction2Parser(parent_action.feature, var_range) layout = action2.resolve_spritegroup(result.name) for i, param in enumerate(result.param_list): if i > 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr(reduce_varaction2_expr(param, var_scope)) varact2parser.var_list.append(nmlop.STO_TMP) store_tmp = VarAction2StoreCallParam(layout.register_map[parent_action.feature][i]) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size() + 1 # Add 1 for operator action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) global return_action_id name = "@return_action_{:d}".format(return_action_id) varaction2 = Action2Var(parent_action.feature, name, result.pos, var_range) return_action_id += 1 varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(result.name, [], result.pos) varaction2.ranges.append( VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, result.name.value) ) varaction2.default_result = ref varaction2.default_comment = result.name.value # Add the references as procs, to make sure, that any intermediate registers # are freed at the spritelayout and thus not selected to pass parameters # Reference is used twice (range + default) so call add_ref twice action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) action2.add_ref(ref, parent_action) return ref def parse_result(value, action_list, act6, offset, parent_action, none_result, var_range, repeat_result=1): """ Parse a result (another switch or CB result) in a switch block. @param value: Value to parse @type value: L{Expression} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param offset: Current offset to use for action6 @type offset: C{int} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param none_result: Result to use to return the computed value @type none_result: L{Expression} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @param repeat_result: Repeat any action6 modifying of the next sprite this many times. @type repeat_result: C{int} @return: A tuple of two values: - The value to use as return value - Comment to add to this value @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{str} """ if value is None: comment = "return;" assert none_result is not None if isinstance(none_result, expression.SpriteGroupRef): result = parse_sg_ref_result(none_result, action_list, parent_action, var_range) else: result = none_result elif isinstance(value, expression.SpriteGroupRef): result = parse_sg_ref_result(value, action_list, parent_action, var_range) comment = result.name.value + ";" elif isinstance(value, expression.ConstantNumeric): comment = "return {:d};".format(value.value) result = value if not (-16384 <= value.value <= 32767): msg = ( "Callback results are limited to -16384..16383 (when the result is a signed number)" " or 0..32767 (unsigned), encountered {:d}." ).format(value.value) raise generic.ScriptError(msg, value.pos) elif isinstance(value, expression.String): comment = "return {};".format(str(value)) str_id, actions = action4.get_string_action4s(0, 0xD0, value) action_list.extend(actions) result = expression.ConstantNumeric(str_id - 0xD000 + 0x8000) elif value.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(nmlop.OR(value, 0x8000).reduce()) comment = "return param[{:d}];".format(tmp_param) action_list.extend(tmp_param_actions) for i in range(repeat_result): act6.modify_bytes(tmp_param, 2, offset + 2 * i) result = expression.ConstantNumeric(0) else: global return_action_id extra_actions, result = create_return_action( value, parent_action.feature, "@return_action_{:d}".format(return_action_id), var_range ) return_action_id += 1 action2.add_ref(result, parent_action) action_list.extend(extra_actions) comment = "return {}".format(value) return (result, comment) def get_scope(action_feature, var_range=0x89): return action2var_variables.varact2features[action_feature].get_scope(var_range) def reduce_varaction2_expr(expr, var_scope, extra_dicts=None): # 'normal' and 60+x variables to use vars_normal = var_scope.vars_normal vars_60x = var_scope.vars_60x def func60x(name, value, pos): return expression.FunctionPtr(expression.Identifier(name, pos), parse_60x_var, value) id_dicts = ( (extra_dicts or []) + [(action2var_variables.varact2_globalvars, parse_var), (vars_normal, parse_var), (vars_60x, func60x)] + global_constants.const_list ) return expr.reduce(id_dicts) def parse_varaction2(switch_block): global return_action_id return_action_id = 0 action6.free_parameters.save() act6 = action6.Action6() action_list = action2real.create_spriteset_actions(switch_block) feature = next(iter(switch_block.feature_set)) var_scope = get_scope(feature, switch_block.var_range) varaction2 = Action2Var( feature, switch_block.name.value, switch_block.pos, switch_block.var_range, switch_block.register_map[feature], ) expr = reduce_varaction2_expr(switch_block.expr, var_scope) offset = 4 # first var parser = Varaction2Parser(feature, switch_block.var_range) parser.parse_expr(expr) action_list.extend(parser.extra_actions) for mod in parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + offset) varaction2.var_list = parser.var_list offset += parser.var_list_size + 1 # +1 for the byte num-ranges for proc in parser.proc_call_list: action2.add_ref(proc, varaction2, True) none_result = None if any( x is not None and x.value is None for x in [r.result for r in switch_block.body.ranges] + [switch_block.body.default] ): # Computed result is returned in at least one result if len(switch_block.body.ranges) == 0: # There is only a default, which is 'return computed result', so we're fine none_result = expression.ConstantNumeric(0) # Return value does not matter else: # Add an extra action to return the computed value extra_actions, none_result = create_return_action( expression.Variable(expression.ConstantNumeric(0x1C)), feature, switch_block.name.value + "@return", 0x89, ) action_list.extend(extra_actions) used_ranges = [] for r in switch_block.body.ranges: comment = str(r.min) + " .. " + str(r.max) + ": " range_result, range_comment = parse_result( r.result.value, action_list, act6, offset, varaction2, none_result, switch_block.var_range ) comment += range_comment offset += 2 # size of result range_min, offset, check_min = parse_minmax(r.min, r.unit, action_list, act6, offset) range_max, offset, check_max = parse_minmax(r.max, r.unit, action_list, act6, offset) range_overlap = False if check_min and check_max: for existing_range in used_ranges: if existing_range[0] <= range_min.value and range_max.value <= existing_range[1]: generic.print_warning( generic.Warning.GENERIC, "Range overlaps with existing ranges so it'll never be reached", r.min.pos, ) range_overlap = True break if not range_overlap: used_ranges.append([range_min.value, range_max.value]) used_ranges.sort() i = 0 while i + 1 < len(used_ranges): if used_ranges[i + 1][0] <= used_ranges[i][1] + 1: used_ranges[i][1] = max(used_ranges[i][1], used_ranges[i + 1][1]) used_ranges.pop(i + 1) else: i += 1 if not range_overlap: varaction2.ranges.append(VarAction2Range(range_min, range_max, range_result, comment)) if len(switch_block.body.ranges) == 0 and ( switch_block.body.default is None or switch_block.body.default.value is not None ): # Computed result is not returned, but there are no ranges # Add one range, to avoid the nvar == 0 bear trap offset += 10 range_result, range_comment = parse_result( expression.SpriteGroupRef(expression.Identifier("CB_FAILED", None), [], None), action_list, act6, offset, varaction2, none_result, switch_block.var_range, ) varaction2.ranges.append( VarAction2Range( expression.ConstantNumeric(1), expression.ConstantNumeric(0), range_result, "Bogus range to avoid nvar == 0", ) ) if len(varaction2.ranges) > 255: raise generic.ScriptError("Too many ranges.", varaction2.pos) # Handle default result if switch_block.body.default is not None: # there is a default value default_result = switch_block.body.default.value else: # Default to CB_FAILED default_result = expression.SpriteGroupRef(expression.Identifier("CB_FAILED", None), [], None) default, default_comment = parse_result( default_result, action_list, act6, offset, varaction2, none_result, switch_block.var_range ) varaction2.default_result = default if switch_block.body.default is None: varaction2.default_comment = "No default specified -> fail callback" elif switch_block.body.default.value is None: varaction2.default_comment = "Return computed value" else: varaction2.default_comment = "default: " + default_comment if len(act6.modifications) > 0: action_list.append(act6) action_list.append(varaction2) switch_block.set_action2(varaction2, feature) action6.free_parameters.restore() return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action2var_variables.py0000644000175100001660000015316114754345605020641 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, nmlop def default_60xvar(name, args, pos, info): """ Function to convert arguments into a variable parameter. This function handles the default case of one argument. @param name: Name of the variable @type name: C{str} @param args: List of passed arguments @type args: C{list} of L{Expression} @param pos: Position information @type pos: L{Position} @param info: Information of the variable, as found in the dictionary @type info: C{dict} @return: A tuple of two values: - Parameter to use for the 60+x variable - List of possible extra parameters that need to be passed via registers @rtype: C{tuple} of (L{Expression}, C{list} C{tuple} of (C{int}, L{Expression})) """ if len(args) != 1: raise generic.ScriptError("'{}'() requires one argument, encountered {:d}".format(name, len(args)), pos) return (args[0], []) # Some commonly used functions that apply some modification to the raw variable value # To pass extra parameters, lambda calculus may be used def value_sign_extend(var, info): #r = (x ^ m) - m; with m being (1 << (num_bits -1)) m = expression.ConstantNumeric(1 << (info['size'] - 1)) return nmlop.SUB(nmlop.XOR(var, m), m) def value_mul_div(mul, div): return lambda var, info: nmlop.DIV(nmlop.MUL(var, mul), div) def value_add_constant(const): return lambda var, info: nmlop.ADD(var, const) def value_equals(const): return lambda var, info: nmlop.CMP_EQ(var, const) # Commonly used functions to let a variable accept an (x, y)-offset as parameters def tile_offset(name, args, pos, info, min, max): if len(args) != 2: raise generic.ScriptError("'{}'() requires 2 arguments, encountered {:d}".format(name, len(args)), pos) for arg in args: if isinstance(arg, expression.ConstantNumeric): generic.check_range(arg.value, min, max, "Argument of '{}'".format(name), arg.pos) x = nmlop.AND(args[0], 0xF) y = nmlop.AND(args[1], 0xF) # Shift y left by four y = nmlop.SHIFT_LEFT(y, 4) param = nmlop.ADD(x, y) #Make sure to reduce the result return ( param.reduce(), [] ) def signed_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, -8, 7) def unsigned_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, 0, 15) # # Global variables, usable for all features # varact2_globalvars = { 'current_month' : {'var': 0x02, 'start': 0, 'size': 8}, 'current_day_of_month' : {'var': 0x02, 'start': 8, 'size': 5}, 'is_leapyear' : {'var': 0x02, 'start': 15, 'size': 1}, 'current_day_of_year' : {'var': 0x02, 'start': 16, 'size': 9}, 'traffic_side' : {'var': 0x06, 'start': 4, 'size': 1}, 'animation_counter' : {'var': 0x0A, 'start': 0, 'size': 16}, 'current_callback' : {'var': 0x0C, 'start': 0, 'size': 16}, 'extra_callback_info1' : {'var': 0x10, 'start': 0, 'size': 32}, 'game_mode' : {'var': 0x12, 'start': 0, 'size': 8}, 'extra_callback_info2' : {'var': 0x18, 'start': 0, 'size': 32}, 'display_options' : {'var': 0x1B, 'start': 0, 'size': 6}, 'last_computed_result' : {'var': 0x1C, 'start': 0, 'size': 32}, 'snowline_height' : {'var': 0x20, 'start': 0, 'size': 8}, 'difficulty_level' : {'var': 0x22, 'start': 0, 'size': 8}, 'current_date' : {'var': 0x23, 'start': 0, 'size': 32}, 'current_year' : {'var': 0x24, 'start': 0, 'size': 32}, } # # Vehicles (features 0x00 - 0x03) # A few variables have an implementation that differs per vehicle type # These are to be found below # varact2vars_vehicles = { 'grfid' : {'var': 0x25, 'start': 0, 'size': 32}, 'position_in_consist' : {'var': 0x40, 'start': 0, 'size': 8}, 'position_in_consist_from_end' : {'var': 0x40, 'start': 8, 'size': 8}, 'num_vehs_in_consist' : {'var': 0x40, 'start': 16, 'size': 8, 'value_function': value_add_constant(1)}, # Zero-based, add 1 to make sane 'position_in_vehid_chain' : {'var': 0x41, 'start': 0, 'size': 8}, 'position_in_vehid_chain_from_end' : {'var': 0x41, 'start': 8, 'size': 8}, 'num_vehs_in_vehid_chain' : {'var': 0x41, 'start': 16, 'size': 8}, # One-based, already sane 'cargo_classes_in_consist' : {'var': 0x42, 'start': 0, 'size': 8}, 'most_common_cargo_type' : {'var': 0x42, 'start': 8, 'size': 8}, 'most_common_cargo_subtype' : {'var': 0x42, 'start': 16, 'size': 8}, 'bitmask_consist_info' : {'var': 0x42, 'start': 24, 'size': 8}, 'company_num' : {'var': 0x43, 'start': 0, 'size': 8}, 'company_type' : {'var': 0x43, 'start': 16, 'size': 2}, 'company_colour1' : {'var': 0x43, 'start': 24, 'size': 4}, 'company_colour2' : {'var': 0x43, 'start': 28, 'size': 4}, 'aircraft_height' : {'var': 0x44, 'start': 8, 'size': 8}, 'airport_type' : {'var': 0x44, 'start': 0, 'size': 8}, 'curv_info_prev_cur' : {'var': 0x45, 'start': 0, 'size': 4, 'value_function': value_sign_extend}, 'curv_info_cur_next' : {'var': 0x45, 'start': 8, 'size': 4, 'value_function': value_sign_extend}, 'curv_info_prev_next' : {'var': 0x45, 'start': 16, 'size': 4, 'value_function': value_sign_extend}, 'curv_info' : {'var': 0x45, 'start': 0, 'size': 12, 'value_function': lambda var, info: nmlop.AND(var, 0x0F0F).reduce()}, 'motion_counter' : {'var': 0x46, 'start': 8, 'size': 24}, 'cargo_type_in_veh' : {'var': 0x47, 'start': 0, 'size': 8}, 'cargo_unit_weight' : {'var': 0x47, 'start': 8, 'size': 8}, 'cargo_classes' : {'var': 0x47, 'start': 16, 'size': 16}, 'vehicle_is_available' : {'var': 0x48, 'start': 0, 'size': 1}, 'vehicle_is_testing' : {'var': 0x48, 'start': 1, 'size': 1}, 'vehicle_is_offered' : {'var': 0x48, 'start': 2, 'size': 1}, 'build_year' : {'var': 0x49, 'start': 0, 'size': 32}, 'vehicle_is_potentially_powered' : {'var': 0x4A, 'start': 8, 'size': 1}, 'tile_has_catenary' : {'var': 0x4A, 'start': 9, 'size': 1}, 'date_of_last_service' : {'var': 0x4B, 'start': 0, 'size': 32}, 'position_in_articulated_veh' : {'var': 0x4D, 'start': 0, 'size': 8}, 'position_in_articulated_veh_from_end' : {'var': 0x4D, 'start': 8, 'size': 8}, 'waiting_triggers' : {'var': 0x5F, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 16}, 'direction' : {'var': 0x9F, 'start': 0, 'size': 8}, 'vehicle_is_hidden' : {'var': 0xB2, 'start': 0, 'size': 1}, 'vehicle_is_stopped' : {'var': 0xB2, 'start': 1, 'size': 1}, 'vehicle_is_crashed' : {'var': 0xB2, 'start': 7, 'size': 1}, 'cargo_capacity' : {'var': 0xBA, 'start': 0, 'size': 16}, 'cargo_count' : {'var': 0xBC, 'start': 0, 'size': 16}, 'age_in_days' : {'var': 0xC0, 'start': 0, 'size': 16}, 'max_age_in_days' : {'var': 0xC2, 'start': 0, 'size': 16}, 'vehicle_type_id' : {'var': 0xC6, 'start': 0, 'size': 16}, 'vehicle_is_flipped' : {'var': 0xC8, 'start': 1, 'size': 1}, 'breakdowns_since_last_service' : {'var': 0xCA, 'start': 0, 'size': 8}, 'vehicle_is_broken' : {'var': 0xCB, 'start': 0, 'size': 8, 'value_function': value_equals(1)}, 'reliability' : {'var': 0xCE, 'start': 0, 'size': 16, 'value_function': value_mul_div(101, 0x10000)}, 'cargo_subtype' : {'var': 0xF2, 'start': 0, 'size': 8}, 'vehicle_is_unloading' : {'var': 0xFE, 'start': 1, 'size': 1}, 'vehicle_is_powered' : {'var': 0xFE, 'start': 5, 'size': 1}, 'vehicle_is_not_powered' : {'var': 0xFE, 'start': 6, 'size': 1}, 'vehicle_is_reversed' : {'var': 0xFE, 'start': 8, 'size': 1}, 'built_during_preview' : {'var': 0xFE, 'start': 10, 'size': 1}, } # Vehicle-type-specific variables varact2vars_trains = { **varact2vars_vehicles, #0x4786 / 0x10000 is an approximation of 3.5790976, the conversion factor #for train speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'current_railtype' : {'var': 0x4A, 'start': 0, 'size': 8}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 7, 'size': 1}, } varact2vars_roadvehs = { **varact2vars_vehicles, #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor #for road vehicle speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_roadtype' : {'var': 0x4A, 'start': 0, 'size': 8}, 'current_tramtype' : {'var': 0x4A, 'start': 0, 'size': 8}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 0, 'size': 8, 'value_function': value_equals(0xFE)}, } varact2vars_ships = { **varact2vars_vehicles, #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor #for ship speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 7, 'size': 1}, } varact2vars_aircraft = { **varact2vars_vehicles, #0x3939 / 0x1000 is an approximation of 0.279617, the conversion factor #Note that the denominator has one less zero here! #for aircraft speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'vehicle_is_in_depot' : {'var': 0xE6, 'start': 0, 'size': 8, 'value_function': value_equals(0)}, } def signed_byte_parameter(name, args, pos, info): # Convert to a signed byte by AND-ing with 0xFF if len(args) != 1: raise generic.ScriptError("{}() requires one argument, encountered {:d}".format(name, len(args)), pos) if isinstance(args[0], expression.ConstantNumeric): generic.check_range(args[0].value, -128, 127, "parameter of {}()".format(name), pos) ret = nmlop.AND(args[0], 0xFF, pos).reduce() return (ret, []) def vehicle_railtype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="railtype"), []) def vehicle_roadtype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="roadtype"), []) def vehicle_tramtype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="tramtype"), []) varact2vars60x_vehicles = { 'count_veh_id' : {'var': 0x60, 'start': 0, 'size': 8}, 'other_veh_curv_info' : {'var': 0x62, 'start': 0, 'size': 4, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_is_hidden' : {'var': 0x62, 'start': 7, 'size': 1, 'param_function':signed_byte_parameter}, 'other_veh_x_offset' : {'var': 0x62, 'start': 8, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_y_offset' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_z_offset' : {'var': 0x62, 'start': 24, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, } varact2vars60x_trains = { **varact2vars60x_vehicles, # Var 0x63 bit 0 is only useful when testing multiple bits at once. On its own it is already covered by railtype_available(). 'tile_supports_railtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function':vehicle_railtype}, 'tile_powers_railtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_railtype}, 'tile_is_railtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_railtype}, } varact2vars60x_roadvehs = { **varact2vars60x_vehicles, # Var 0x63 bit 0 is only useful when testing multiple bits at once. On its own it is already covered by road/tramtype_available(). 'tile_supports_roadtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function':vehicle_roadtype}, 'tile_supports_tramtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function':vehicle_tramtype}, 'tile_powers_roadtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_roadtype}, 'tile_powers_tramtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_tramtype}, 'tile_is_roadtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_roadtype}, 'tile_is_tramtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_tramtype}, } # # Stations (feature 0x04) # # 'Base station' variables are shared between stations and airports varact2vars_base_stations = { 'random_bits_station' : {'var': 0x5F, 'start': 8, 'size': 16}, # Var 48 doesn't work with newcargos, do not use 'had_vehicle_of_type' : {'var': 0x8A, 'start': 1, 'size': 5}, # Only read bits 1-5 'is_waypoint' : {'var': 0x8A, 'start': 6, 'size': 1}, 'facilities' : {'var': 0xF0, 'start': 0, 'size': 8}, 'airport_type' : {'var': 0xF1, 'start': 0, 'size': 8}, # Variables F2, F3, F6 (roadstop, airport flags) are next to useless # Also, their values are not the same as in TTDP / spec # Therefore, these are not implemented 'build_date' : {'var': 0xFA, 'start': 0, 'size': 16, 'value_function': value_add_constant(701265)} } varact2vars60x_base_stations = { 'cargo_amount_waiting' : {'var': 0x60, 'start': 0, 'size': 32}, 'cargo_time_since_pickup' : {'var': 0x61, 'start': 0, 'size': 32}, 'cargo_rating' : {'var': 0x62, 'start': 0, 'size': 32, 'value_function': value_mul_div(101, 256)}, 'cargo_time_en_route' : {'var': 0x63, 'start': 0, 'size': 32}, 'cargo_last_vehicle_speed' : {'var': 0x64, 'start': 0, 'size': 8}, 'cargo_last_vehicle_age' : {'var': 0x64, 'start': 8, 'size': 8}, 'cargo_accepted' : {'var': 0x65, 'start': 3, 'size': 1}, 'cargo_accepted_ever' : {'var': 0x69, 'start': 0, 'size': 1}, 'cargo_accepted_last_month' : {'var': 0x69, 'start': 1, 'size': 1}, 'cargo_accepted_this_month' : {'var': 0x69, 'start': 2, 'size': 1}, 'cargo_accepted_bigtick' : {'var': 0x69, 'start': 3, 'size': 1}, } varact2vars_stations = { **varact2vars_base_stations, # Vars 40, 41, 46, 47, 49 are implemented as 60+x vars, # except for the 'tile type' part which is always the same anyways 'tile_type' : {'var': 0x40, 'start': 24, 'size': 8}, 'terrain_type' : {'var': 0x42, 'start': 0, 'size': 8}, 'track_type' : {'var': 0x42, 'start': 8, 'size': 8}, 'company_num' : {'var': 0x43, 'start': 0, 'size': 8}, 'company_type' : {'var': 0x43, 'start': 16, 'size': 2}, 'company_colour1' : {'var': 0x43, 'start': 24, 'size': 4}, 'company_colour2' : {'var': 0x43, 'start': 28, 'size': 4}, 'pbs_reserved' : {'var': 0x44, 'start': 0, 'size': 1}, 'pbs_reserved_or_disabled' : {'var': 0x44, 'start': 1, 'size': 1}, 'pbs_enabled' : {'var': 0x44, 'start': 2, 'size': 1}, 'rail_continuation' : {'var': 0x45, 'start': 0, 'size': 8}, 'rail_present' : {'var': 0x45, 'start': 8, 'size': 8}, 'animation_frame' : {'var': 0x4A, 'start': 0, 'size': 8}, 'random_bits_tile' : {'var': 0x5F, 'start': 24, 'size': 4}, } # Mapping of param values for platform_xx vars to variable numbers mapping_platform_param = { (0, False) : 0x40, (1, False) : 0x41, (0, True) : 0x46, (1, True) : 0x47, (2, False) : 0x49, } def platform_info_param(name, args, pos, info): if len(args) != 1: raise generic.ScriptError("'{}'() requires one argument, encountered {:d}".format(name, len(args)), pos) if (not isinstance(args[0], expression.ConstantNumeric)) or args[0].value not in (0, 1, 2): raise generic.ScriptError("Invalid argument for '{}'(), must be one of PLATFORM_SAME_XXX.".format(name), pos) is_middle = 'middle' in info and info['middle'] if is_middle and args[0].value == 2: raise generic.ScriptError("Invalid argument for '{}'(), PLATFORM_SAME_DIRECTION is not supported here.".format(name), pos) # Temporarily store variable number in the param, this will be fixed in the value_function return (expression.ConstantNumeric(mapping_platform_param[(args[0].value, is_middle)]), []) def platform_info_fix_var(var, info): # Variable to use was temporarily stored in the param # Fix this now var.num = var.param var.param = None return var varact2vars60x_stations = { **varact2vars60x_base_stations, 'nearby_tile_animation_frame' : {'var': 0x66, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'nearby_tile_slope' : {'var': 0x67, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x67, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x67, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x67, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x67, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x67, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_is_station' : {'var': 0x68, 'start': 0, 'size': 32, 'param_function': signed_tile_offset, 'value_function': lambda var, info: nmlop.CMP_NEQ(var, 0xFFFFFFFF)}, 'nearby_tile_original_gfx' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(0)}, 'nearby_tile_same_grf' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(1)}, 'nearby_tile_other_grf' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(2)}, 'nearby_tile_same_station' : {'var': 0x68, 'start': 10, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_perpendicular' : {'var': 0x68, 'start': 11, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_tile_type' : {'var': 0x68, 'start': 11, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_grfid' : {'var': 0x6A, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'nearby_tile_station_id' : {'var': 0x6B, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, # 'var' will be set in the value_function, depending on parameter 'platform_length' : {'var': 0x00, 'start': 16, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_count' : {'var': 0x00, 'start': 20, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_position_from_start' : {'var': 0x00, 'start': 0, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_position_from_end' : {'var': 0x00, 'start': 4, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_number_from_start' : {'var': 0x00, 'start': 8, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_number_from_end' : {'var': 0x00, 'start': 12, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_position_from_middle' : {'var': 0x00, 'start': 0, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param 'value_function': lambda var, info: value_sign_extend(platform_info_fix_var(var, info), info)}, 'platform_number_from_middle' : {'var': 0x00, 'start': 4, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param 'value_function': lambda var, info: value_sign_extend(platform_info_fix_var(var, info), info)}, } # # Canals (feature 0x05) # varact2vars_canals = { 'tile_height' : {'var': 0x80, 'start': 0, 'size': 8}, 'terrain_type' : {'var': 0x81, 'start': 0, 'size': 8}, 'dike_map' : {'var': 0x82, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x83, 'start': 0, 'size': 8}, } # Canals have no 60+X variables # # Bridges (feature 0x06) have no variational action2 # # # Houses (feature 0x07) # def house_same_class(var, info): # Just using var 44 fails for non-north house tiles, as these have no class # Therefore work around it using var 61 # Load ID of the north tile from register FF bits 24..31, and use that as param for var 61 north_tile = expression.Variable(expression.ConstantNumeric(0x7D), expression.ConstantNumeric(24), expression.ConstantNumeric(0xFF), expression.ConstantNumeric(0xFF), var.pos) var61 = expression.Variable(expression.ConstantNumeric(0x7B), expression.ConstantNumeric(info['start']), expression.ConstantNumeric((1 << info['size']) - 1), expression.ConstantNumeric(0x61), var.pos) return nmlop.VAL2(north_tile, var61, var.pos) varact2vars_houses = { 'construction_state' : {'var': 0x40, 'start': 0, 'size': 2}, 'pseudo_random_bits' : {'var': 0x40, 'start': 2, 'size': 2}, 'age' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_zone' : {'var': 0x42, 'start': 0, 'size': 8}, 'terrain_type' : {'var': 0x43, 'start': 0, 'size': 8}, 'same_house_count_town' : {'var': 0x44, 'start': 0, 'size': 8}, 'same_house_count_map' : {'var': 0x44, 'start': 8, 'size': 8}, 'same_class_count_town' : {'var': 0xFF, 'start': 16, 'size': 8, 'value_function': house_same_class}, # 'var' is unused 'same_class_count_map' : {'var': 0xFF, 'start': 24, 'size': 8, 'value_function': house_same_class}, # 'var' is unused 'generating_town' : {'var': 0x45, 'start': 0, 'size': 1}, 'animation_frame' : {'var': 0x46, 'start': 0, 'size': 8}, 'x_coordinate' : {'var': 0x47, 'start': 0, 'size': 16}, 'y_coordinate' : {'var': 0x47, 'start': 16, 'size': 16}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, 'relative_x' : {'var': 0x7D, 'start': 0, 'size': 8, 'param': 0xFF}, 'relative_y' : {'var': 0x7D, 'start': 8, 'size': 8, 'param': 0xFF}, 'relative_pos' : {'var': 0x7D, 'start': 0, 'size': 16, 'param': 0xFF}, 'house_tile' : {'var': 0x7D, 'start': 16, 'size': 8, 'param': 0xFF}, 'house_type_id' : {'var': 0x7D, 'start': 24, 'size': 8, 'param': 0xFF}, } def cargo_accepted_nearby(name, args, pos, info): # cargo_accepted_nearby(cargo[, xoffset, yoffset]) if len(args) not in (1, 3): raise generic.ScriptError("{}() requires 1 or 3 arguments, encountered {:d}".format(name, len(args)), pos) if len(args) > 1: offsets = args[1:3] for i, offs in enumerate(offsets[:]): if isinstance(offs, expression.ConstantNumeric): generic.check_range(offs.value, -128, 127, "{}-parameter {:d} '{}offset'".format(name, i + 1, "x" if i == 0 else "y"), pos) offsets[i] = nmlop.AND(offs, 0xFF, pos).reduce() # Register 0x100 should be set to xoffset | (yoffset << 8) reg100 = nmlop.OR(nmlop.MUL(offsets[1], 256, pos), offsets[0]).reduce() else: reg100 = expression.ConstantNumeric(0, pos) return (args[0], [(0x100, reg100)]) def nearest_house_matching_criterion(name, args, pos, info): # nearest_house_matching_criterion(radius, criterion) # parameter is radius | (criterion << 6) if len(args) != 2: raise generic.ScriptError("{}() requires 2 arguments, encountered {:d}".format(name, len(args)), pos) if isinstance(args[0], expression.ConstantNumeric): generic.check_range(args[0].value, 1, 63, "{}()-parameter 1 'radius'".format(name), pos) if isinstance(args[1], expression.ConstantNumeric) and args[1].value not in (0, 1, 2): raise generic.ScriptError("Invalid value for {}()-parameter 2 'criterion'".format(name), pos) radius = nmlop.AND(args[0], 0x3F, pos) criterion = nmlop.AND(args[1], 0x03, pos) criterion = nmlop.MUL(criterion, 0x40) retval = nmlop.OR(criterion, radius).reduce() return (retval, []) varact2vars60x_houses = { 'old_house_count_town' : {'var': 0x60, 'start': 0, 'size': 8}, 'old_house_count_map' : {'var': 0x60, 'start': 8, 'size': 8}, 'other_house_count_town' : {'var': 0x61, 'start': 0, 'size': 8}, 'other_house_count_map' : {'var': 0x61, 'start': 8, 'size': 8}, 'other_class_count_town' : {'var': 0x61, 'start': 16, 'size': 8}, 'other_class_count_map' : {'var': 0x61, 'start': 24, 'size': 8}, 'nearby_tile_slope' : {'var': 0x62, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x62, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x62, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x62, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x62, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x63, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'cargo_accepted_nearby_ever' : {'var': 0x64, 'start': 0, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_last_month' : {'var': 0x64, 'start': 1, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_this_month' : {'var': 0x64, 'start': 2, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_last_bigtick' : {'var': 0x64, 'start': 3, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_watched' : {'var': 0x64, 'start': 4, 'size': 1, 'param_function': cargo_accepted_nearby}, 'nearest_house_matching_criterion' : {'var': 0x65, 'start': 0, 'size': 8, 'param_function': nearest_house_matching_criterion}, 'nearby_tile_house_id' : {'var': 0x66, 'start': 0, 'size': 16, 'param_function': signed_tile_offset, 'value_function': value_sign_extend}, 'nearby_tile_house_class' : {'var': 0x66, 'start': 16, 'size': 16, 'param_function': signed_tile_offset, 'value_function': value_sign_extend}, 'nearby_tile_house_grfid' : {'var': 0x67, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, } # # Global variables (feature 0x08) have no variational action2 # # # Industry tiles (feature 0x09) # varact2vars_industrytiles = { 'construction_state' : {'var': 0x40, 'start': 0, 'size': 2}, 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_zone' : {'var': 0x42, 'start': 0, 'size': 3}, 'relative_x' : {'var': 0x43, 'start': 0, 'size': 8}, 'relative_y' : {'var': 0x43, 'start': 8, 'size': 8}, 'relative_pos' : {'var': 0x43, 'start': 0, 'size': 16}, 'animation_frame' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, } varact2vars60x_industrytiles = { 'nearby_tile_slope' : {'var': 0x60, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_same_industry' : {'var': 0x60, 'start': 8, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x60, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x60, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x60, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x60, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_industrytile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, } # # Industries (feature 0x0A) # varact2vars_industries = { 'waiting_cargo_1' : {'var': 0x40, 'start': 0, 'size': 16, 'replaced_by': 'incoming_cargo_waiting'}, 'waiting_cargo_2' : {'var': 0x41, 'start': 0, 'size': 16, 'replaced_by': 'incoming_cargo_waiting'}, 'waiting_cargo_3' : {'var': 0x42, 'start': 0, 'size': 16, 'replaced_by': 'incoming_cargo_waiting'}, 'water_distance' : {'var': 0x43, 'start': 0, 'size': 32}, 'layout_num' : {'var': 0x44, 'start': 0, 'size': 8}, # bits 0 .. 16 are either useless or already covered by var A7 'founder_type' : {'var': 0x45, 'start': 16, 'size': 2}, 'founder_colour1' : {'var': 0x45, 'start': 24, 'size': 4}, 'founder_colour2' : {'var': 0x45, 'start': 28, 'size': 4}, 'build_date' : {'var': 0x46, 'start': 0, 'size': 32}, 'gs_disallows_prod_decrease' : {'var': 0x47, 'start': 0, 'size': 1}, 'gs_disallows_prod_increase' : {'var': 0x47, 'start': 1, 'size': 1}, 'gs_disallows_closure' : {'var': 0x47, 'start': 2, 'size': 1}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 16}, 'produced_cargo_waiting_1' : {'var': 0x8A, 'start': 0, 'size': 16, 'replaced_by': 'produced_cargo_waiting'}, 'produced_cargo_waiting_2' : {'var': 0x8C, 'start': 0, 'size': 16, 'replaced_by': 'produced_cargo_waiting'}, 'production_rate_1' : {'var': 0x8E, 'start': 0, 'size': 8, 'replaced_by': 'production_rate'}, 'production_rate_2' : {'var': 0x8F, 'start': 0, 'size': 8, 'replaced_by': 'production_rate'}, 'production_level' : {'var': 0x93, 'start': 0, 'size': 8}, 'produced_this_month_1' : {'var': 0x94, 'start': 0, 'size': 16, 'replaced_by': 'this_month_production'}, 'produced_this_month_2' : {'var': 0x96, 'start': 0, 'size': 16, 'replaced_by': 'this_month_production'}, 'transported_this_month_1' : {'var': 0x98, 'start': 0, 'size': 16, 'replaced_by': 'this_month_transported'}, 'transported_this_month_2' : {'var': 0x9A, 'start': 0, 'size': 16, 'replaced_by': 'this_month_transported'}, 'transported_last_month_pct_1' : {'var': 0x9C, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256), 'replaced_by': 'transported_last_month_pct'}, 'transported_last_month_pct_2' : {'var': 0x9D, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256), 'replaced_by': 'transported_last_month_pct'}, 'produced_last_month_1' : {'var': 0x9E, 'start': 0, 'size': 16, 'replaced_by': 'last_month_production'}, 'produced_last_month_2' : {'var': 0xA0, 'start': 0, 'size': 16, 'replaced_by': 'last_month_production'}, 'transported_last_month_1' : {'var': 0xA2, 'start': 0, 'size': 16, 'replaced_by': 'last_month_transported'}, 'transported_last_month_2' : {'var': 0xA4, 'start': 0, 'size': 16, 'replaced_by': 'last_month_transported'}, 'founder' : {'var': 0xA7, 'start': 0, 'size': 8}, 'colour' : {'var': 0xA8, 'start': 0, 'size': 8}, 'counter' : {'var': 0xAA, 'start': 0, 'size': 16}, 'build_type' : {'var': 0xB3, 'start': 0, 'size': 2}, 'last_accept_date' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_add_constant(701265)}, } def industry_count(name, args, pos, info): if len(args) < 1 or len(args) > 2: raise generic.ScriptError("'{}'() requires between 1 and 2 argument(s), encountered {:d}".format(name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 1 else args[1] extra_params = [(0x100, grfid)] return (args[0], extra_params) def industry_layout_count(name, args, pos, info): if len(args) < 2 or len(args) > 3: raise generic.ScriptError("'{}'() requires between 2 and 3 argument(s), encountered {:d}".format(name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 2 else args[2] extra_params = [] extra_params.append( (0x100, grfid) ) extra_params.append( (0x101, nmlop.AND(args[1], 0xFF).reduce()) ) return (args[0], extra_params) def industry_town_count(name, args, pos, info): if len(args) < 1 or len(args) > 2: raise generic.ScriptError("'{}'() requires between 1 and 2 argument(s), encountered {:d}".format(name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 1 else args[1] extra_params = [] extra_params.append( (0x100, grfid) ) extra_params.append( (0x101, expression.ConstantNumeric(0x0100)) ) return (args[0], extra_params) def industry_cargotype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="cargotype"), []) varact2vars60x_industries = { 'nearby_tile_industry_tile_id' : {'var': 0x60, 'start': 0, 'size': 16, 'param_function': unsigned_tile_offset}, 'nearby_tile_random_bits' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': unsigned_tile_offset}, 'nearby_tile_slope' : {'var': 0x62, 'start': 0, 'size': 5, 'param_function': unsigned_tile_offset}, 'nearby_tile_is_water' : {'var': 0x62, 'start': 9, 'size': 1, 'param_function': unsigned_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x62, 'start': 10, 'size': 3, 'param_function': unsigned_tile_offset}, 'nearby_tile_water_class' : {'var': 0x62, 'start': 13, 'size': 2, 'param_function': unsigned_tile_offset}, 'nearby_tile_height' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function': unsigned_tile_offset}, 'nearby_tile_class' : {'var': 0x62, 'start': 24, 'size': 4, 'param_function': unsigned_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x63, 'start': 0, 'size': 8, 'param_function': unsigned_tile_offset}, 'town_manhattan_dist' : {'var': 0x65, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, 'town_zone' : {'var': 0x65, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'town_euclidean_dist' : {'var': 0x66, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, 'industry_count' : {'var': 0x67, 'start': 16, 'size': 8, 'param_function': industry_count}, 'industry_distance' : {'var': 0x67, 'start': 0, 'size': 16, 'param_function': industry_count}, 'industry_layout_count' : {'var': 0x68, 'start': 16, 'size': 8, 'param_function': industry_layout_count}, 'industry_layout_distance' : {'var': 0x68, 'start': 0, 'size': 16, 'param_function': industry_layout_count}, 'industry_town_count' : {'var': 0x68, 'start': 16, 'size': 8, 'param_function': industry_town_count}, 'produced_cargo_waiting' : {'var': 0x69, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'this_month_production' : {'var': 0x6A, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'this_month_transported' : {'var': 0x6B, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'last_month_production' : {'var': 0x6C, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'last_month_transported' : {'var': 0x6D, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'last_cargo_accepted_at' : {'var': 0x6E, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'incoming_cargo_waiting' : {'var': 0x6F, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'production_rate' : {'var': 0x70, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'transported_last_month_pct' : {'var': 0x71, 'start': 0, 'size': 32, 'param_function': industry_cargotype, 'value_function': value_mul_div(101, 256)}, } # # Cargos (feature 0x0B) have no own varaction2 variables # Sounds (feature 0x0C) have no variational action2 # # # Airports (feature 0x0D) # varact2vars_airports = { **varact2vars_base_stations, 'layout' : {'var': 0x40, 'start': 0, 'size': 32}, } varact2vars60x_airports = { **varact2vars60x_base_stations, } # # New Signals (feature 0x0E) are not implemented in OpenTTD # # # Objects (feature 0x0F) # varact2vars_objects = { 'relative_x' : {'var': 0x40, 'start': 0, 'size': 8}, 'relative_y' : {'var': 0x40, 'start': 8, 'size': 8}, 'relative_pos' : {'var': 0x40, 'start': 0, 'size': 16}, 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 3}, 'tile_slope' : {'var': 0x41, 'start': 8, 'size': 5}, 'build_date' : {'var': 0x42, 'start': 0, 'size': 32}, 'animation_frame' : {'var': 0x43, 'start': 0, 'size': 8}, 'owner' : {'var': 0x44, 'start': 0, 'size': 8}, 'town_manhattan_dist' : {'var': 0x45, 'start': 0, 'size': 16}, 'town_zone' : {'var': 0x45, 'start': 16, 'size': 8}, 'town_euclidean_dist' : {'var': 0x46, 'start': 0, 'size': 16}, 'colour' : {'var': 0x47, 'start': 0, 'size': 8}, 'view' : {'var': 0x48, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, } varact2vars60x_objects = { 'nearby_tile_object_type' : {'var': 0x60, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, 'nearby_tile_object_view' : {'var': 0x60, 'start': 16, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_random_bits' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_slope' : {'var': 0x62, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_same_object' : {'var': 0x62, 'start': 8, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x62, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x62, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x62, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x62, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x63, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'object_count' : {'var': 0x64, 'start': 16, 'size': 8, 'param_function': industry_count}, 'object_distance' : {'var': 0x64, 'start': 0, 'size': 16, 'param_function': industry_count}, } # # Railtypes (feature 0x10) # varact2vars_railtype = { 'terrain_type' : {'var': 0x40, 'start': 0, 'size': 8}, 'enhanced_tunnels' : {'var': 0x41, 'start': 0, 'size': 8}, 'level_crossing_status' : {'var': 0x42, 'start': 0, 'size': 8}, 'build_date' : {'var': 0x43, 'start': 0, 'size': 32}, 'town_zone' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } # Railtypes have no 60+x variables # # Airport tiles (feature 0x11) # varact2vars_airporttiles = { 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_radius_group' : {'var': 0x42, 'start': 0, 'size': 3}, 'relative_x' : {'var': 0x43, 'start': 0, 'size': 8}, 'relative_y' : {'var': 0x43, 'start': 8, 'size': 8}, 'relative_pos' : {'var': 0x43, 'start': 0, 'size': 16}, 'animation_frame' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits_station' : {'var': 0x5F, 'start': 8, 'size': 16}, 'random_bits_tile' : {'var': 0x5F, 'start': 24, 'size': 4}, } varact2vars60x_airporttiles = { 'nearby_tile_slope' : {'var': 0x60, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_same_airport' : {'var': 0x60, 'start': 8, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x60, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x60, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x60, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x60, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_airporttile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, } # # Roadtypes (feature 0x12) # varact2vars_roadtype = { 'terrain_type' : {'var': 0x40, 'start': 0, 'size': 8}, 'enhanced_tunnels' : {'var': 0x41, 'start': 0, 'size': 8}, 'level_crossing_status' : {'var': 0x42, 'start': 0, 'size': 8}, 'build_date' : {'var': 0x43, 'start': 0, 'size': 32}, 'town_zone' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } # Roadtypes have no 60+x variables # # Tramtypes (feature 0x13) # varact2vars_tramtype = { 'terrain_type' : {'var': 0x40, 'start': 0, 'size': 8}, 'enhanced_tunnels' : {'var': 0x41, 'start': 0, 'size': 8}, 'level_crossing_status' : {'var': 0x42, 'start': 0, 'size': 8}, 'build_date' : {'var': 0x43, 'start': 0, 'size': 32}, 'town_zone' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } # Tramtypes have no 60+x variables # # Towns are not a true feature, but accessible via the parent scope of e.g. industries, stations # varact2vars_towns = { 'is_city' : {'var': 0x40, 'start': 0, 'size': 1}, 'cities_enabled' : {'var': 0x40, 'start': 1, 'size': 1, 'value_function': lambda var, info: expression.Not(var, var.pos)}, 'town_index' : {'var': 0x41, 'start': 0, 'size': 16}, 'population' : {'var': 0x82, 'start': 0, 'size': 16}, 'has_church' : {'var': 0x92, 'start': 1, 'size': 1}, 'has_stadium' : {'var': 0x92, 'start': 2, 'size': 1}, 'town_zone_0_radius_square' : {'var': 0x94, 'start': 0, 'size': 16}, 'town_zone_1_radius_square' : {'var': 0x96, 'start': 0, 'size': 16}, 'town_zone_2_radius_square' : {'var': 0x98, 'start': 0, 'size': 16}, 'town_zone_3_radius_square' : {'var': 0x9A, 'start': 0, 'size': 16}, 'town_zone_4_radius_square' : {'var': 0x9C, 'start': 0, 'size': 16}, 'num_houses' : {'var': 0xB6, 'start': 0, 'size': 16}, 'percent_transported_passengers' : {'var': 0xCA, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256)}, 'percent_transported_mail' : {'var': 0xCB, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256)}, } # # Roadstops (feature road_stops) # varact2vars_roadstop = { **varact2vars_base_stations, 'view' : {'var': 0x40, 'start': 0, 'size': 8}, 'stop_type' : {'var': 0x41, 'start': 0, 'size': 8}, 'terrain_type' : {'var': 0x42, 'start': 0, 'size': 8}, 'tile_slope' : {'var': 0x42, 'start': 8, 'size': 5}, 'has_road' : {'var': 0x43, 'start': 0, 'size': 32, 'value_function': lambda var, info: nmlop.CMP_NEQ(var, 0xFFFFFFFF)}, 'has_tram' : {'var': 0x44, 'start': 0, 'size': 32, 'value_function': lambda var, info: nmlop.CMP_NEQ(var, 0xFFFFFFFF)}, 'road_type' : {'var': 0x43, 'start': 0, 'size': 8}, # The roadtype of this tile 'tram_type' : {'var': 0x44, 'start': 0, 'size': 8}, # The tramtype of this tile 'town_manhattan_dist' : {'var': 0x45, 'start': 0, 'size': 16}, 'town_zone' : {'var': 0x45, 'start': 16, 'size': 8}, 'town_euclidean_dist' : {'var': 0x46, 'start': 0, 'size': 32}, 'company_num' : {'var': 0x47, 'start': 0, 'size': 8}, # 0..14 company number 'company_type' : {'var': 0x47, 'start': 16, 'size': 2}, # PLAYERTYPE_HUMAN, PLAYERTYPE_AI etc. 'company_colour1' : {'var': 0x47, 'start': 24, 'size': 4}, # COLOUR_XXX. See https://newgrf-specs.tt-wiki.net/wiki/NML:List_of_default_colour_translation_palettes#Company_colour_helper_functions 'company_colour2' : {'var': 0x47, 'start': 28, 'size': 4}, # Same as above 'animation_frame' : {'var': 0x49, 'start': 0, 'size': 8}, 'drawn_in_gui' : {'var': 0x50, 'start': 4, 'size': 1}, 'waiting_triggers' : {'var': 0x5F, 'start': 0, 'size': 8}, 'random_bits_tile' : {'var': 0x5F, 'start': 24, 'size': 8}, } varact2vars60x_roadstop = { **varact2vars60x_base_stations, 'nearby_tile_animation_frame' : {'var': 0x66, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'nearby_tile_info' : {'var': 0x67, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'nearby_tile_slope' : {'var': 0x67, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x67, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x67, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x67, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x67, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x67, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_road_stop_info' : {'var': 0x68, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'nearby_tile_is_road_stop' : {'var': 0x68, 'start': 0, 'size': 32, 'param_function': signed_tile_offset, 'value_function': lambda var, info: nmlop.CMP_NEQ(var, 0xFFFFFFFF)}, 'nearby_tile_same_grf' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(0)}, 'nearby_tile_other_grf' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(1)}, 'nearby_tile_original_gfx' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(2)}, 'nearby_tile_same_station' : {'var': 0x68, 'start': 10, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_different_view' : {'var': 0x68, 'start': 11, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_view' : {'var': 0x68, 'start': 12, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_is_drive_through' : {'var': 0x68, 'start': 12, 'size': 4, 'param_function': signed_tile_offset, 'value_function': lambda var, info: nmlop.CMP_GE(var, 4)}, 'nearby_tile_stop_type' : {'var': 0x68, 'start': 16, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_same_stop_type' : {'var': 0x68, 'start': 20, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_grfid' : {'var': 0x6A, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'nearby_tile_road_stop_id' : {'var': 0x6B, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, } class VarAct2Scope: def __init__(self, name, vars_normal, vars_60x, has_persistent_storage=False): self.name = name self.vars_normal = vars_normal self.vars_60x = vars_60x self.has_persistent_storage = has_persistent_storage class VarAct2Feature: def __init__(self, self_scope, parent_scope): self.self_scope = self_scope self.parent_scope = parent_scope def get_scope(self, var_range): assert var_range in (0x89, 0x8A) return self.self_scope if var_range == 0x89 else self.parent_scope scope_towns = VarAct2Scope("Towns", varact2vars_towns, {}, has_persistent_storage=True) scope_trains = VarAct2Scope("Trains", varact2vars_trains, varact2vars60x_trains) scope_roadvehs = VarAct2Scope("RoadVehs", varact2vars_roadvehs, varact2vars60x_roadvehs) scope_ships = VarAct2Scope("Ships", varact2vars_ships, varact2vars60x_vehicles) scope_aircraft = VarAct2Scope("Aircraft", varact2vars_aircraft, varact2vars60x_vehicles) scope_stations = VarAct2Scope("Stations", varact2vars_stations, varact2vars60x_stations) scope_canals = VarAct2Scope("Canals", varact2vars_canals, {}) scope_houses = VarAct2Scope("Houses", varact2vars_houses, varact2vars60x_houses) scope_industrytiles = VarAct2Scope("IndustryTiles", varact2vars_industrytiles, varact2vars60x_industrytiles) scope_industries = VarAct2Scope( "Industries", varact2vars_industries, varact2vars60x_industries, has_persistent_storage=True ) scope_cargos = VarAct2Scope("Cargos", {}, {}) scope_soundeffects = VarAct2Scope("SoundEffects", {}, {}) scope_airports = VarAct2Scope("Airports", varact2vars_airports, varact2vars60x_airports, has_persistent_storage=True) scope_objects = VarAct2Scope("Objects", varact2vars_objects, varact2vars60x_objects) scope_railtypes = VarAct2Scope("RailTypes", varact2vars_railtype, {}) scope_airporttiles = VarAct2Scope("AirportTiles", varact2vars_airporttiles, varact2vars60x_airporttiles) scope_roadtypes = VarAct2Scope("RoadTypes", varact2vars_roadtype, {}) scope_tramtypes = VarAct2Scope("TramTypes", varact2vars_tramtype, {}) scope_roadstops = VarAct2Scope("RoadStops", varact2vars_roadstop, varact2vars60x_roadstop) varact2features = [ VarAct2Feature(scope_trains, scope_trains), VarAct2Feature(scope_roadvehs, scope_roadvehs), VarAct2Feature(scope_ships, scope_ships), VarAct2Feature(scope_aircraft, scope_aircraft), VarAct2Feature(scope_stations, scope_towns), VarAct2Feature(scope_canals, None), None, # bridges VarAct2Feature(scope_houses, scope_towns), None, # globalvars VarAct2Feature(scope_industrytiles, scope_industries), VarAct2Feature(scope_industries, scope_towns), VarAct2Feature(scope_cargos, None), VarAct2Feature(scope_soundeffects, None), VarAct2Feature(scope_airports, None), None, # signals VarAct2Feature(scope_objects, scope_towns), VarAct2Feature(scope_railtypes, None), VarAct2Feature(scope_airporttiles, None), VarAct2Feature(scope_roadtypes, None), VarAct2Feature(scope_tramtypes, None), VarAct2Feature(scope_roadstops, scope_towns), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action3.py0000644000175100001660000006366414754345605016111 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.actions import ( action0, action2, action2layout, action2real, action2var, action3_callbacks, action6, actionD, base_action, ) class Action3(base_action.BaseAction): """ Class representing a single Action3. @ivar feature: Action3 feature byte. @type feature: C{int} @ivar id: Item ID of the item that this action3 represents. @type id: C{int} @ivar is_livery_override: Whether this action 3 is a livery override @type is_livery_override: C{bool} @ivar cid_mappings: List of mappings that map cargo IDs to Action2s. @type cid_mappings: C{list} of C{tuple}: (C{int}, L{Expression} before prepare_output, C{int} afterwards, C{str}) @ivar def_cid: Default Action2 to use if no cargo ID matches. @type def_cid: C{None} or L{SpriteGroupRef} before prepare_output, C{int} afterwards @ivar references: All Action2s that are referenced by this Action3. @type references: C{list} of L{Action2Reference} """ def __init__(self, feature, id, is_livery_override): self.feature = feature self.id = id self.is_livery_override = is_livery_override self.cid_mappings = [] self.references = [] def prepare_output(self, sprite_num): action2.free_references(self) def map_cid(cid): if isinstance(cid, expression.SpriteGroupRef): return cid.get_action2_id(self.feature) else: return cid.value | 0x8000 self.cid_mappings = [(cargo, map_cid(cid), comment) for cargo, cid, comment in self.cid_mappings] if self.def_cid is None: self.def_cid = 0 else: self.def_cid = map_cid(self.def_cid) def write(self, file): size = 7 + 3 * len(self.cid_mappings) if self.feature <= 3 or self.id >= 0xFF: size += 2 # Vehicles or IDs >= 255 use extended byte file.start_sprite(size) file.print_bytex(3) file.print_bytex(self.feature) file.print_bytex(1 if not self.is_livery_override else 0x81) # a single id file.print_varx(self.id, 3 if self.feature <= 3 or self.id >= 0xFF else 1) file.print_byte(len(self.cid_mappings)) file.newline() for cargo, cid, comment in self.cid_mappings: file.print_bytex(cargo) file.print_wordx(cid) file.newline(comment) file.print_wordx(self.def_cid) file.newline(self.default_comment) file.end_sprite() def skip_action9(self): return False # Make sure all action2s created here get a unique name action2_id = 0 def create_intermediate_varaction2(feature, varact2parser, mapping, default, pos): """ Create a varaction2 based on a parsed expression and a value mapping @param feature: Feature of the varaction2 @type feature: C{int} @param varact2parser: Parser containing a parsed expression @type varact2parser: L{Varaction2Parser} @param mapping: Mapping of various values to sprite groups / return values, with a possible extra function to apply to the return value @type mapping: C{dict} that maps C{int} to C{tuple} of (L{SpriteGroupRef}, C{function}, or C{None}) @param default: Default sprite group if no value matches @type default: L{SpriteGroupRef} @param pos: Positional context. @type pos: L{Position} @return: A tuple containing the action list and a reference to the created action2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ global action2_id action_list = varact2parser.extra_actions act6 = action6.Action6() for mod in varact2parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + 4) name = expression.Identifier("@action3_{:d}".format(action2_id)) action2_id += 1 varaction2 = action2var.Action2Var(feature, name.value, pos, 0x89) varaction2.var_list = varact2parser.var_list offset = 5 + varact2parser.var_list_size for proc in varact2parser.proc_call_list: action2.add_ref(proc, varaction2, True) for switch_value in sorted(mapping): return_value, ret_value_function = mapping[switch_value] if ret_value_function is None: result, comment = action2var.parse_result(return_value, action_list, act6, offset, varaction2, None, 0x89) else: if isinstance(return_value, expression.SpriteGroupRef): # We need to execute the callback via a procedure call # then return CB_FAILED if the CB failed, # or the CB result (with ret_value_function applied) if successful if return_value.name.value == "CB_FAILED": result, comment = action2var.parse_result( return_value, action_list, act6, offset, varaction2, None, 0x89 ) else: extra_actions, result, comment = create_proc_call_varaction2( feature, return_value, ret_value_function, varaction2, pos ) action_list.extend(extra_actions) else: return_value = ret_value_function(return_value).reduce() result, comment = action2var.parse_result( return_value, action_list, act6, offset, varaction2, None, 0x89 ) varaction2.ranges.append( action2var.VarAction2Range( expression.ConstantNumeric(switch_value), expression.ConstantNumeric(switch_value), result, comment ) ) offset += 10 result, comment = action2var.parse_result(default, action_list, act6, offset, varaction2, None, 0x89) varaction2.default_result = result varaction2.default_comment = comment return_ref = expression.SpriteGroupRef(name, [], None, varaction2) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(varaction2) return (action_list, return_ref) def create_proc_call_varaction2(feature, proc, ret_value_function, parent_action, pos): """ Create a varaction2 that executes a procedure call and applies a function on the result @param feature: Feature of the varaction2 @type feature: C{int} @param proc: Procedure to execute @type proc: L{SpriteGroupRef} @param ret_value_function: Function to apply on the result (L{Expression} -> L{Expression}) @type ret_value_function: C{function} @param pos: Positional context. @type pos: L{Position} @return: A list of extra actions, reference to the created action2 and a comment to add @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}, C{str}) """ proc.is_procedure = True varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_proc_call(proc) mapping = {0xFFFF: (expression.SpriteGroupRef(expression.Identifier("CB_FAILED"), [], None), None)} default = ret_value_function(expression.Variable(expression.ConstantNumeric(0x1C))) action_list, result = create_intermediate_varaction2(feature, varact2parser, mapping, default, pos) action2.add_ref(result, parent_action) comment = result.name.value + ";" return (action_list, result, comment) def create_cb_choice_varaction2(feature, expr, mapping, default, pos): """ Create a varaction2 that maps callback numbers to various sprite groups @param feature: Feature of the varaction2 @type feature: C{int} @param expr: Expression to evaluate @type expr: L{Expression} @param mapping: Mapping of various values to sprite groups, with a possible extra function to apply to the return value @type mapping: C{dict} that maps C{int} to C{tuple} of (L{SpriteGroupRef}, C{function}, or C{None}) @param default: Default sprite group if no value matches @type default: L{SpriteGroupRef} @param pos: Positional context. @type pos: L{Position} @return: A tuple containing the action list and a reference to the created action2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(expr) return create_intermediate_varaction2(feature, varact2parser, mapping, default, pos) def create_action3(feature, id, action_list, act6, is_livery_override): # Vehicles use an extended byte size = 2 if feature <= 3 else 1 offset = 4 if feature <= 3 else 3 id, offset = actionD.write_action_value(id, action_list, act6, offset, size) return Action3(feature, id.value, is_livery_override) house_tiles = { 0: "n", # 1x1 2: "nw", # 2x1 3: "ne", # 1x2 4: "news", # 2x2 } def parse_graphics_block(graphics_block, feature, id, size, is_livery_override=False): """ Parse a graphics block (or livery override) into a list of actions, mainly action3 @param graphics_block: Graphics-block to parse @type graphics_block: L{GraphicsBlock} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @param size: Size of the associated item (relevant for houses only) @type size: L{ConstantNumeric} or C{None} @param is_livery_override: Whether this is a livery override instead of a normal graphics block @type is_livery_override: C{bool} @return: The resulting list of actions @rtype: L{BaseAction} """ action_list = action2real.create_spriteset_actions(graphics_block) if feature == 0x07: # Multi-tile houses need more work size_bit = size.value if size is not None else 0 for i, tile in enumerate(house_tiles[size_bit]): tile_id = id if i == 0 else nmlop.ADD(id, i).reduce() action_list.extend( parse_graphics_block_single_id(graphics_block, feature, tile_id, is_livery_override, tile, id) ) else: action_list.extend(parse_graphics_block_single_id(graphics_block, feature, id, is_livery_override)) return action_list station_sprite_layouts = {} def parse_graphics_block_single_id( graphics_block, feature, id, is_livery_override, house_tile=None, house_north_tile_id=None ): action6.free_parameters.save() prepend_action_list = [] action_list = [] act6 = action6.Action6() act3 = create_action3(feature, id, action_list, act6, is_livery_override) cargo_gfx = {} seen_callbacks = set() callbacks = [] livery_override = None # Used for rotor graphics layouts = [] custom_spritesets = [] prepare_layout = None purchase_prepare_layout = None foundations = None for graphics in graphics_block.graphics_list: cargo_id = graphics.cargo_id if isinstance(cargo_id, expression.Identifier): cb_name = cargo_id.value cb_table = action3_callbacks.callbacks[feature] if cb_name in cb_table: if cb_name in seen_callbacks: raise generic.ScriptError("Callback '{}' is defined multiple times.".format(cb_name), cargo_id.pos) seen_callbacks.add(cb_name) info_list = cb_table[cb_name] if not isinstance(info_list, list): info_list = [info_list] for info in info_list: if "deprecate_message" in info: generic.print_warning(generic.Warning.DEPRECATION, info["deprecate_message"], cargo_id.pos) if house_tile is not None and "tiles" in info and house_tile not in info["tiles"]: continue if info["type"] == "cargo": # Not a callback, but an alias for a certain cargo type if info["num"] in cargo_gfx: raise generic.ScriptError( "Graphics for '{}' are defined multiple times.".format(cb_name), cargo_id.pos ) cargo_gfx[info["num"]] = graphics.result.value elif info["type"] == "cb": callbacks.append((info, graphics.result.value)) elif info["type"] == "override": assert livery_override is None livery_override = graphics.result.value elif info["type"] == "layout": layouts = graphics.result.value if isinstance(layouts, expression.ConstantNumeric): if layouts.value not in station_sprite_layouts: raise generic.ScriptError("Unknown station", cargo_id.pos) actions = action0.get_copy_layout_action0(feature, id, layouts) layouts = station_sprite_layouts[layouts.value] elif isinstance(layouts, expression.Array) and len(layouts.values) % 2 == 0: actions, var10map, registers_ref = action2layout.parse_station_layouts( feature, id, layouts.values ) layouts = (var10map, registers_ref) else: raise generic.ScriptError( "'{}' must be an array of even length, or the ID of a station".format(cb_name), cargo_id.pos, ) prepend_action_list.extend(actions) station_sprite_layouts[id.value] = layouts elif info["type"] == "custom_spritesets": if ( not isinstance(graphics.result.value, expression.Array) or len(graphics.result.value.values) > 6 ): raise generic.ScriptError( "'{}' must be an array of at most 6 elements".format(cb_name), cargo_id.pos ) custom_spritesets = graphics.result.value.values elif info["type"] == "prepare_layout": if "purchase" in info: assert purchase_prepare_layout is None purchase_prepare_layout = graphics.result.value else: assert prepare_layout is None prepare_layout = graphics.result.value elif info["type"] == "foundations": assert foundations is None foundations = graphics.result.value else: raise AssertionError() continue # Not a callback, so it must be a 'normal' cargo (vehicles/stations only) cargo_id = cargo_id.reduce_constant(global_constants.const_list) # Raise the error only now, to let the 'unknown identifier' take precedence if feature >= 5: raise generic.ScriptError( "Associating graphics with a specific cargo is possible only for vehicles and stations.", cargo_id.pos ) if cargo_id.value in cargo_gfx: raise generic.ScriptError( "Graphics for cargo {:d} are defined multiple times.".format(cargo_id.value), cargo_id.pos ) cargo_gfx[cargo_id.value] = graphics.result.value if graphics_block.default_graphics is not None: if "default" not in action3_callbacks.callbacks[feature]: raise generic.ScriptError( "Default graphics may not be defined for this feature (0x{:02X}).".format(feature), graphics_block.default_graphics.pos, ) if None in cargo_gfx: raise generic.ScriptError("Default graphics are defined twice.", graphics_block.default_graphics.pos) cargo_gfx[None] = graphics_block.default_graphics.value if layouts or foundations: if None not in cargo_gfx: cargo_gfx[None] = expression.SpriteGroupRef(expression.Identifier("CB_FAILED", None), [], None) if purchase_prepare_layout and 0xFF not in cargo_gfx: cargo_gfx[0xFF] = cargo_gfx[None] var10map, registers_ref = layouts or (None, None) for cargo in sorted(cargo_gfx, key=lambda x: -1 if x is None else x): default = cargo_gfx[cargo] varact2parser = action2var.Varaction2Parser(feature) # Prepare registers for sprite layout if cargo == 0xFF and purchase_prepare_layout: varact2parser.parse_expr(purchase_prepare_layout) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 elif prepare_layout: if not isinstance(prepare_layout, expression.SpriteGroupRef): actions, prepare_layout = action2var.create_return_action( prepare_layout, feature, "Station Layout@prepare - Id {:02X}".format(id.value), 0x89 ) prepend_action_list.extend(actions) varact2parser.parse(prepare_layout) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 # Call the registers procedure if registers_ref: var_access = action2var.VarAction2ProcCallVar(registers_ref) varact2parser.var_list.append(var_access) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += var_access.get_size() + 1 varact2parser.proc_call_list.append(registers_ref) # Fill var10 dependant choices mapping = {0x02: (foundations, None)} if foundations else {} if var10map: var10map.append_mapping(mapping, feature, prepend_action_list, default, custom_spritesets) # No need for in-between varaction2 if there are no registers to set and no var10 dependant choices if len(mapping) == 0: if not varact2parser.var_list: continue # mapping must not be empty mapping[0x00] = (default, None) expr = expression.Variable(expression.ConstantNumeric(0x10), mask=expression.ConstantNumeric(0xFF)) varact2parser.parse(expr) actions, cb_ref = create_intermediate_varaction2( feature, varact2parser, mapping, default, graphics_block.pos ) prepend_action_list.extend(actions) cargo_gfx[cargo] = cb_ref # An in-between varaction2 is always needed for houses if len(callbacks) != 0 or feature == 0x07: cb_flags = 0 # Determine the default value if None not in cargo_gfx: cargo_gfx[None] = expression.SpriteGroupRef(expression.Identifier("CB_FAILED", None), [], None) default_val = cargo_gfx[None] cb_mapping = {} cb_buy_mapping = {} # Special case for vehicle cb 36, maps var10 values to spritegroups cb36_mapping = {} cb36_buy_mapping = {} # Sspecial case for industry production CB, maps var18 values to spritegroups prod_cb_mapping = {} for cb_info, gfx in callbacks: if "flag_bit" in cb_info: # Set a bit in the CB flags property cb_flags |= 1 << cb_info["flag_bit"] value_function = cb_info.get("value_function", None) mapping_val = (gfx, value_function) # See action3_callbacks for info on possible values purchase = cb_info.get("purchase", 0) if isinstance(purchase, str): # Not in purchase list, if separate purchase CB is set purchase = 0 if purchase in seen_callbacks else 1 # Explicit purchase CBs will need a purchase cargo, even if not needed for graphics if purchase == 2 and 0xFF not in cargo_gfx: cargo_gfx[0xFF] = default_val num = cb_info["num"] if num == 0x36: if purchase != 2: cb36_mapping[cb_info["var10"]] = mapping_val if purchase != 0: cb36_buy_mapping[cb_info["var10"]] = mapping_val elif feature == 0x0A and num == 0x00: # Industry production CB assert purchase == 0 prod_cb_mapping[cb_info["var18"]] = mapping_val else: if purchase != 2: cb_mapping[num] = mapping_val if purchase != 0: cb_buy_mapping[num] = mapping_val if cb_flags != 0: prepend_action_list.extend(action0.get_callback_flags_actions(feature, id, cb_flags)) # Handle CB 36 if len(cb36_mapping) != 0: expr = expression.Variable(expression.ConstantNumeric(0x10), mask=expression.ConstantNumeric(0xFF)) actions, cb36_ref = create_cb_choice_varaction2( feature, expr, cb36_mapping, default_val, graphics_block.pos ) prepend_action_list.extend(actions) cb_mapping[0x36] = (cb36_ref, None) if len(cb36_buy_mapping) != 0: expr = expression.Variable(expression.ConstantNumeric(0x10), mask=expression.ConstantNumeric(0xFF)) actions, cb36_ref = create_cb_choice_varaction2( feature, expr, cb36_buy_mapping, default_val, graphics_block.pos ) prepend_action_list.extend(actions) cb_buy_mapping[0x36] = (cb36_ref, None) if len(prod_cb_mapping) != 0: expr = expression.Variable(expression.ConstantNumeric(0x18), mask=expression.ConstantNumeric(0xFF)) actions, cb_ref = create_cb_choice_varaction2( feature, expr, prod_cb_mapping, default_val, graphics_block.pos ) prepend_action_list.extend(actions) cb_mapping[0x00] = (cb_ref, None) for cargo in sorted(cargo_gfx, key=lambda x: -1 if x is None else x): mapping = cb_buy_mapping if cargo == 0xFF else cb_mapping if len(mapping) == 0 and feature != 0x07: # No callbacks here, so move along # Except for houses, where we need to store some stuff in a register continue if cargo_gfx[cargo] != default_val: # There are cargo-specific graphics, be sure to handle those # Unhandled callbacks should chain to the default, though mapping = mapping.copy() mapping[0x00] = (cargo_gfx[cargo], None) expr = expression.Variable(expression.ConstantNumeric(0x0C), mask=expression.ConstantNumeric(0xFFFF)) if feature == 0x07: # Store relative x/y, item id (of the north tile) and house tile (HOUSE_TILE_XX constant) in register FF # Format: 0xIIHHYYXX: II: item ID, HH: house tile, YY: relative y, XX: relative x lowbytes_dict = { "n": 0x000000, "e": 0x010100, "w": 0x020001, "s": 0x030101, } lowbytes = expression.ConstantNumeric(lowbytes_dict[house_tile]) highbyte = nmlop.SHIFT_LEFT(house_north_tile_id, 24) register_FF = nmlop.OR(lowbytes, highbyte).reduce() register_FF = nmlop.STO_TMP(register_FF, 0xFF) expr = nmlop.VAL2(register_FF, expr) if len(mapping) == 0: # mapping must not be empty mapping[0x00] = (default_val, None) actions, cb_ref = create_cb_choice_varaction2(feature, expr, mapping, default_val, graphics_block.pos) prepend_action_list.extend(actions) cargo_gfx[cargo] = cb_ref # Make sure to sort to make the order well-defined offset = 7 if feature <= 3 else 5 for cargo_id in sorted(cg for cg in cargo_gfx if cg is not None): result, comment = action2var.parse_result(cargo_gfx[cargo_id], action_list, act6, offset + 1, act3, None, 0x89) act3.cid_mappings.append((cargo_id, result, comment)) offset += 3 if None in cargo_gfx: result, comment = action2var.parse_result(cargo_gfx[None], action_list, act6, offset, act3, None, 0x89) act3.def_cid = result act3.default_comment = comment else: act3.def_cid = None act3.default_comment = "" if livery_override is not None: act6livery = action6.Action6() # Add any extra actions before the main action3 (TTDP requirement) act3livery = create_action3(feature, id, action_list, act6livery, True) offset = 7 if feature <= 3 else 5 result, comment = action2var.parse_result( livery_override, action_list, act6livery, offset, act3livery, None, 0x89 ) act3livery.def_cid = result act3livery.default_comment = comment if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act3) if livery_override is not None: if len(act6livery.modifications) > 0: action_list.append(act6livery) action_list.append(act3livery) # lgtm[py/uninitialized-local-variable] action6.free_parameters.restore() return prepend_action_list + action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action3_callbacks.py0000644000175100001660000004527314754345605020104 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import nmlop callbacks = 0x15 * [{}] # Possible values for 'purchase': # 0 (or not set): not called from purchase list # 1: called normally and from purchase list # 2: only called from purchase list # 'cbname': as 1) but if 'cbname' is set also, then 'cbname' overrides this # in the purchase list. 'cbname' should have a value of 2 for 'purchase' # Callbacks common to all vehicle types general_vehicle_cbs = { 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, 'random_trigger' : {'type': 'cb', 'num': 0x01}, # Almost undocumented, but really neccesary! 'loading_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x07}, 'cargo_subtype_text' : {'type': 'cb', 'num': 0x19, 'flag_bit': 5}, 'additional_text' : {'type': 'cb', 'num': 0x23, 'purchase': 2}, 'colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 'purchase_colour_mapping'}, 'purchase_colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 2}, 'start_stop' : {'type': 'cb', 'num': 0x31}, 'every_32_days' : {'type': 'cb', 'num': 0x32}, 'sound_effect' : {'type': 'cb', 'num': 0x33, 'flag_bit': 7}, 'refit_cost' : {'type': 'cb', 'num': 0x15E, 'purchase': 1}, 'name' : {'type': 'cb', 'num': 0x161, 'flag_bit': 8, 'purchase': 2}, 'refit' : {'type': 'cb', 'num': 0x163, "purchase": 2}, } # Function to convert vehicle length to the actual property value, which is (8 - length) def vehicle_length(value): return nmlop.SUB(8, value) # Trains callbacks[0x00] = { 'visual_effect_and_powered' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model_and_powered' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'length' : {'type': 'cb', 'num': 0x36, 'var10': 0x21, 'value_function': vehicle_length}, 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 2}, 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'can_attach_wagon' : {'type': 'cb', 'num': 0x1D}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, 'power' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 'purchase_power'}, 'purchase_power' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 2}, 'weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x16, 'purchase': 'purchase_weight'}, 'purchase_weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x16, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x17, 'purchase': 2}, 'tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 'purchase_tractive_effort_coefficient'}, 'purchase_tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 2}, 'bitmask_vehicle_info' : {'type': 'cb', 'num': 0x36, 'var10': 0x25}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x2B}, 'curve_speed_mod' : {'type': 'cb', 'num': 0x36, 'var10': 0x2E}, 'create_effect' : {'type': 'cb', 'num': 0x160}, 'reverse_build_probability' : {'type': 'cb', 'num': 0x162, 'var10': 0x00}, } callbacks[0x00].update(general_vehicle_cbs) # Road vehicles callbacks[0x01] = { 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'length' : {'type': 'cb', 'num': 0x36, 'var10': 0x23, 'value_function': vehicle_length}, 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 2}, 'power' : {'type': 'cb', 'num': 0x36, 'var10': 0x13, 'purchase': 'purchase_power'}, 'purchase_power' : {'type': 'cb', 'num': 0x36, 'var10': 0x13, 'purchase': 2}, 'weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 'purchase_weight'}, 'purchase_weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x15, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x15, 'purchase': 2}, 'tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x18, 'purchase': 'purchase_tractive_effort_coefficient'}, 'purchase_tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x18, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x22}, 'create_effect' : {'type': 'cb', 'num': 0x160}, } callbacks[0x01].update(general_vehicle_cbs) # Ships callbacks[0x02] = { 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0A, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x1D}, 'create_effect' : {'type': 'cb', 'num': 0x160}, } callbacks[0x02].update(general_vehicle_cbs) # Aircraft callbacks[0x03] = { 'passenger_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_passenger_capacity'}], 'purchase_passenger_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0C, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0C, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0E, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0E, 'purchase': 2}, 'mail_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 'purchase_mail_capacity'}, 'purchase_mail_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x1C}, 'range' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 'purchase_range'}, 'purchase_range' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 2}, 'rotor' : {'type': 'override'}, } callbacks[0x03].update(general_vehicle_cbs) # Stations callbacks[0x04] = { 'availability' : {'type': 'cb', 'num': 0x13, 'flag_bit': 0, 'purchase': 2}, 'select_sprite_layout' : {'type': 'cb', 'num': 0x14, 'flag_bit': 1, 'purchase': 'purchase_select_sprite_layout'}, 'purchase_select_sprite_layout' : {'type': 'cb', 'num': 0x14, 'flag_bit': 1, 'purchase': 2}, 'select_tile_type' : {'type': 'cb', 'num': 0x24, 'purchase': 2}, 'anim_control' : {'type': 'cb', 'num': 0x140}, 'anim_next_frame' : {'type': 'cb', 'num': 0x141, 'flag_bit': 2}, 'anim_speed' : {'type': 'cb', 'num': 0x142, 'flag_bit': 3}, 'tile_check' : {'type': 'cb', 'num': 0x149, 'flag_bit': 4, 'purchase': 2}, 'sprite_layouts' : {'type': 'layout'}, 'prepare_layout' : {'type': 'prepare_layout'}, 'purchase_prepare_layout' : {'type': 'prepare_layout', 'purchase': 2}, 'foundations' : {'type': 'foundations'}, 'custom_spritesets' : {'type': 'custom_spritesets'}, 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, } # Canals callbacks[0x05] = { 'sprite_offset' : {'type': 'cb', 'num': 0x147, 'flag_bit': 0}, 'default' : {'type': 'cargo', 'num': None}, } # Bridges (0x06) have no action3 # Houses callbacks[0x07] = { 'random_trigger' : {'type': 'cb', 'num': 0x01}, 'construction_check' : {'type': 'cb', 'num': 0x17, 'flag_bit': 0, 'tiles': 'n'}, 'anim_next_frame' : {'type': 'cb', 'num': 0x1A, 'flag_bit': 1}, 'anim_control' : {'type': 'cb', 'num': 0x1B, 'flag_bit': 2}, 'construction_anim' : {'type': 'cb', 'num': 0x1C, 'flag_bit': 3}, 'colour' : {'type': 'cb', 'num': 0x1E, 'flag_bit': 4}, 'cargo_amount_accept' : {'type': 'cb', 'num': 0x1F, 'flag_bit': 5}, 'anim_speed' : {'type': 'cb', 'num': 0x20, 'flag_bit': 6}, 'destruction' : {'type': 'cb', 'num': 0x21, 'flag_bit': 7, 'tiles': 'n'}, 'cargo_type_accept' : {'type': 'cb', 'num': 0x2A, 'flag_bit': 8}, 'cargo_production' : {'type': 'cb', 'num': 0x2E, 'flag_bit': 9}, 'protection' : {'type': 'cb', 'num': 0x143, 'flag_bit': 10}, 'watched_cargo_accepted' : {'type': 'cb', 'num': 0x148}, 'name' : {'type': 'cb', 'num': 0x14D}, 'foundations' : {'type': 'cb', 'num': 0x14E, 'flag_bit': 11}, 'autoslope' : {'type': 'cb', 'num': 0x14F, 'flag_bit': 12}, 'graphics_north' : {'type': 'cb', 'num': 0x00, 'tiles': 'n'}, 'graphics_east' : {'type': 'cb', 'num': 0x00, 'tiles': 'e'}, 'graphics_south' : {'type': 'cb', 'num': 0x00, 'tiles': 's'}, 'graphics_west' : {'type': 'cb', 'num': 0x00, 'tiles': 'w'}, 'default' : {'type': 'cargo', 'num': None}, } # General variables (0x08) have no action3 # Industry tiles callbacks[0x09] = { 'random_trigger' : {'type': 'cb', 'num': 0x01}, 'anim_control' : {'type': 'cb', 'num': 0x25}, 'anim_next_frame' : {'type': 'cb', 'num': 0x26, 'flag_bit': 0}, 'anim_speed' : {'type': 'cb', 'num': 0x27, 'flag_bit': 1}, 'cargo_amount_accept' : {'type': 'cb', 'num': 0x2B, 'flag_bit': 2}, # Should work like the industry CB, i.e. call multiple times 'cargo_type_accept' : {'type': 'cb', 'num': 0x2C, 'flag_bit': 3}, # Should work like the industry CB, i.e. call multiple times 'tile_check' : {'type': 'cb', 'num': 0x2F, 'flag_bit': 4}, 'foundations' : {'type': 'cb', 'num': 0x30, 'flag_bit': 5}, 'autoslope' : {'type': 'cb', 'num': 0x3C, 'flag_bit': 6}, 'default' : {'type': 'cargo', 'num': None}, } # Industries callbacks[0x0A] = { 'construction_probability' : {'type': 'cb', 'num': 0x22, 'flag_bit': 0}, 'produce_cargo_arrival' : {'type': 'cb', 'num': 0x00, 'flag_bit': 1, 'var18': 0}, 'produce_256_ticks' : {'type': 'cb', 'num': 0x00, 'flag_bit': 2, 'var18': 1}, 'location_check' : {'type': 'cb', 'num': 0x28, 'flag_bit': 3}, # We need a way to access all those special variables 'random_prod_change' : {'type': 'cb', 'num': 0x29, 'flag_bit': 4}, 'monthly_prod_change' : {'type': 'cb', 'num': 0x35, 'flag_bit': 5}, 'cargo_subtype_display' : {'type': 'cb', 'num': 0x37, 'flag_bit': 6}, 'extra_text_fund' : {'type': 'cb', 'num': 0x38, 'flag_bit': 7}, 'extra_text_industry' : {'type': 'cb', 'num': 0x3A, 'flag_bit': 8}, 'control_special' : {'type': 'cb', 'num': 0x3B, 'flag_bit': 9}, 'stop_accept_cargo' : {'type': 'cb', 'num': 0x3D, 'flag_bit': 10}, 'colour' : {'type': 'cb', 'num': 0x14A, 'flag_bit': 11}, 'cargo_input' : {'type': 'cb', 'num': 0x14B, 'flag_bit': 12}, 'cargo_output' : {'type': 'cb', 'num': 0x14C, 'flag_bit': 13}, 'build_prod_change' : {'type': 'cb', 'num': 0x15F, 'flag_bit': 14}, 'default' : {'type': 'cargo', 'num': None}, } # Cargos callbacks[0x0B] = { 'profit' : {'type': 'cb', 'num': 0x39, 'flag_bit': 0}, 'station_rating' : {'type': 'cb', 'num': 0x145, 'flag_bit': 1}, 'default' : {'type': 'cargo', 'num': None}, } # Sound effects (0x0C) have no item-specific action3 # Airports callbacks[0x0D] = { 'additional_text' : {'type': 'cb', 'num': 0x155}, 'layout_name' : {'type': 'cb', 'num': 0x156}, 'default' : {'type': 'cargo', 'num': None}, } # New signals (0x0E) have no item-specific action3 # Objects callbacks[0x0F] = { 'tile_check' : {'type': 'cb', 'num': 0x157, 'flag_bit': 0, 'purchase': 2}, 'anim_next_frame' : {'type': 'cb', 'num': 0x158, 'flag_bit': 1}, 'anim_control' : {'type': 'cb', 'num': 0x159}, 'anim_speed' : {'type': 'cb', 'num': 0x15A, 'flag_bit': 2}, 'colour' : {'type': 'cb', 'num': 0x15B, 'flag_bit': 3}, 'additional_text' : {'type': 'cb', 'num': 0x15C, 'flag_bit': 4, 'purchase': 2}, 'autoslope' : {'type': 'cb', 'num': 0x15D, 'flag_bit': 5}, 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, } # Railtypes callbacks[0x10] = { # No default here, it makes no sense 'gui' : {'type': 'cargo', 'num': 0x00}, 'track_overlay' : {'type': 'cargo', 'num': 0x01}, 'underlay' : {'type': 'cargo', 'num': 0x02}, 'tunnels' : {'type': 'cargo', 'num': 0x03}, 'catenary_wire' : {'type': 'cargo', 'num': 0x04}, 'catenary_pylons' : {'type': 'cargo', 'num': 0x05}, 'bridge_surfaces' : {'type': 'cargo', 'num': 0x06}, 'level_crossings' : {'type': 'cargo', 'num': 0x07}, 'depots' : {'type': 'cargo', 'num': 0x08}, 'fences' : {'type': 'cargo', 'num': 0x09}, 'tunnel_overlay' : {'type': 'cargo', 'num': 0x0A}, 'signals' : {'type': 'cargo', 'num': 0x0B}, 'precombined' : {'type': 'cargo', 'num': 0x0C}, } # Airport tiles callbacks[0x11] = { 'foundations' : {'type': 'cb', 'num': 0x150, 'flag_bit': 5}, 'anim_control' : {'type': 'cb', 'num': 0x152}, 'anim_next_frame' : {'type': 'cb', 'num': 0x153, 'flag_bit': 0}, 'anim_speed' : {'type': 'cb', 'num': 0x154, 'flag_bit': 1}, 'default' : {'type': 'cargo', 'num': None}, } # Roadtypes callbacks[0x12] = { # No default here, it makes no sense 'gui' : {'type': 'cargo', 'num': 0x00}, 'track_overlay' : {'type': 'cargo', 'num': 0x01}, 'underlay' : {'type': 'cargo', 'num': 0x02}, 'tunnels' : {'type': 'cargo', 'num': 0x03}, 'catenary_front' : {'type': 'cargo', 'num': 0x04}, 'catenary_back' : {'type': 'cargo', 'num': 0x05}, 'bridge_surfaces' : {'type': 'cargo', 'num': 0x06}, 'depots' : {'type': 'cargo', 'num': 0x08}, 'roadstops' : {'type': 'cargo', 'num': 0x0A}, 'direction_markings' : {'type': 'cargo', 'num': 0x0B}, } # Tramtypes callbacks[0x13] = { # No default here, it makes no sense 'gui' : {'type': 'cargo', 'num': 0x00}, 'track_overlay' : {'type': 'cargo', 'num': 0x01}, 'underlay' : {'type': 'cargo', 'num': 0x02}, 'tunnels' : {'type': 'cargo', 'num': 0x03}, 'catenary_front' : {'type': 'cargo', 'num': 0x04}, 'catenary_back' : {'type': 'cargo', 'num': 0x05}, 'bridge_surfaces' : {'type': 'cargo', 'num': 0x06}, 'depots' : {'type': 'cargo', 'num': 0x08}, } # Roadstops callbacks[0x14] = { 'availability' : {'type': 'cb', 'num': 0x13, 'flag_bit': 0}, 'anim_control' : {'type': 'cb', 'num': 0x140}, 'anim_next_frame' : {'type': 'cb', 'num': 0x141, 'flag_bit': 1}, 'anim_speed' : {'type': 'cb', 'num': 0x142, 'flag_bit': 2}, 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action4.py0000644000175100001660000002055114754345605016076 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, grfstrings from nml.actions import action6, actionD, base_action class Action4(base_action.BaseAction): """ Class representing a single action 4. Format: 04 @ivar feature: Feature of this action 4 @type feature: C{int} @ivar lang: Language ID of the text (set as ##grflangid in the .lng file, 0x7F for default) @type lang: C{int} @ivar size: Size of the id, may be 1 (byte), 2 (word) or 3 (ext. byte) @type size: C{int} @ivar id: ID of the first string to write @type id: C{int} @ivar texts: List of strings to write @type texts: C{list} of C{str} """ def __init__(self, feature, lang, size, id, texts): self.feature = feature self.lang = lang self.size = size self.id = id self.texts = texts def prepare_output(self, sprite_num): # To indicate a word value, bit 7 of the lang ID must be set if self.size == 2: self.lang = self.lang | 0x80 def write(self, file): size = 4 + self.size for text in self.texts: size += grfstrings.get_string_size(text) file.start_sprite(size) file.print_bytex(4) file.print_bytex(self.feature) file.print_bytex(self.lang) file.print_bytex(len(self.texts)) file.print_varx(self.id, self.size) for text in self.texts: file.print_string(text) file.newline() file.end_sprite() def skip_action9(self): return False # List of various string ranges that may be used # Attributes: # - random_id: If true, string IDs may be allocated randomly, else the ID has a special meaning and must be assigned # (e.g. for vehicles, string ID = vehicle ID) # - ids: List of free IDs, only needed if random_id is true. Whenever an ID is used, it's removed from the list string_ranges = { 0xC4: {"random_id": False}, # Station class names 0xC5: {"random_id": False}, # Station names 0xC9: {"random_id": False}, # House name # Misc. text ids, used for callbacks and such 0xD0: {"random_id": True, "total": 0x400, "ids": list(range(0xD3FF, 0xCFFF, -1))}, # Misc. persistent text ids, used to set properties. # Use Ids DC00..DCFF first to keep compatibility with older versions of OTTD. 0xDC: { "random_id": True, "total": 0x2800, "ids": list(range(0xFFFF, 0xDFFF, -1)) + list(range(0xDBFF, 0xD7FF, -1)) + list(range(0xDFFF, 0xDBFF, -1)), }, } # Mapping of string identifiers to D0xx/DCxx text ids # This allows outputting strings only once, instead of everywhere they are used used_strings = { 0xD0: {}, 0xDC: {}, } def print_stats(): """ Print statistics about used ids. """ for t, l in string_ranges.items(): if l["random_id"]: num_used = l["total"] - len(l["ids"]) if num_used > 0: generic.print_info("{:02X}xx strings: {}/{}".format(t, num_used, l["total"])) def get_global_string_actions(): """ Get a list of global string actions i.e. print all D0xx / DCxx texts at once @return: A list of all D0xx / DCxx action4s @rtype: C{list} of L{BaseAction} """ texts = [] actions = [] for strings in used_strings.values(): for feature_name, id in strings.items(): feature, string_name = feature_name texts.append((0x7F, id, grfstrings.get_translation(string_name), feature)) for lang_id in grfstrings.get_translations(string_name): texts.append((lang_id, id, grfstrings.get_translation(string_name, lang_id), feature)) last_lang = -1 last_id = -1 last_feature = -1 # Sort to have a deterministic ordering and to have as much consecutive IDs as possible texts.sort(key=lambda text: (-1 if text[0] == 0x7F else text[0], text[1])) for text in texts: str_lang, str_id, str_text, feature = text # If possible, append strings to the last action 4 instead of creating a new one if str_lang != last_lang or str_id - 1 != last_id or feature != last_feature or len(actions[-1].texts) == 0xFF: actions.append(Action4(feature, str_lang, 2, str_id, [str_text])) else: actions[-1].texts.append(str_text) last_lang = str_lang last_id = str_id last_feature = feature return actions def get_string_action4s(feature, string_range, string, id=None): """ Let a string from the lang files be used in the rest of NML. This may involve adding actions directly, but otherwise an ID is allocated and the string will be written later @param feature: Feature that uses the string @type feature: C{int} @param string_range: String range to use, either a value from L{string_ranges} or C{None} if N/A (item names) @type string_range: C{int} or C{None} @param string: String to parse @type string: L{expression.String} @param id: ID to use for this string, or C{None} if it will be allocated dynamically (random_id is true for the string range) @type id: L{Expression} or C{None} @return: A tuple of two values: - ID of the string (useful if allocated dynamically) - Resulting action list to be appended @rtype: C{tuple} of (C{int}, C{list} of L{BaseAction}) """ grfstrings.validate_string(string) write_action4s = True action6.free_parameters.save() actions = [] mod = None if string_range is not None: size = 2 if string_ranges[string_range]["random_id"]: # ID is allocated randomly, we will output the actions later write_action4s = False if (feature, string) in used_strings[string_range]: id_val = used_strings[string_range][(feature, string)] else: try: id_val = string_ranges[string_range]["ids"].pop() used_strings[string_range][(feature, string)] = id_val except IndexError: raise generic.ScriptError( "Unable to allocate ID for string, no more free IDs available (maximum is {:d})".format( string_ranges[string_range]["total"] ), string.pos, ) else: # ID must be supplied assert id is not None assert isinstance(id, expression.ConstantNumeric) id_val = id.value | (string_range << 8) else: # Not a string range, so we must have an id assert id is not None size = 3 if feature <= 3 else 1 if isinstance(id, expression.ConstantNumeric): id_val = id.value else: id_val = 0 tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) actions.extend(tmp_param_actions) # Apply ID via action4 later mod = (tmp_param, 2 if feature <= 3 else 1, 5 if feature <= 3 else 4) if write_action4s: strings = [ (lang_id, grfstrings.get_translation(string, lang_id)) for lang_id in grfstrings.get_translations(string) ] # Sort the strings for deterministic ordering and prepend the default language strings = [(0x7F, grfstrings.get_translation(string))] + sorted(strings, key=lambda lang_text: lang_text[0]) for lang_id, text in strings: if mod is not None: act6 = action6.Action6() act6.modify_bytes(*mod) actions.append(act6) actions.append(Action4(feature, lang_id, size, id_val, [text])) action6.free_parameters.restore() return (id_val, actions) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action5.py0000644000175100001660000001344514754345605016103 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import base_action, real_sprite class Action5(base_action.BaseAction): def __init__(self, type, num_sprites, offset): self.type = type self.num_sprites = num_sprites self.offset = offset def prepare_output(self, sprite_num): if self.offset is not None: self.type |= 0x80 def write(self, file): # * 05 [] size = 5 if self.offset is None else 8 file.start_sprite(size) file.print_bytex(0x05) file.print_bytex(self.type) file.print_bytex(0xFF) file.print_word(self.num_sprites) if self.offset is not None: file.print_bytex(0xFF) file.print_word(self.offset) file.newline() file.end_sprite() def skip_action7(self): # skipping with Action7 should work, according to the Action7/9 specs # However, skipping invalid (OpenTTD-only) Action5s in TTDP can only be done using Action9, else an error occurs # To be on the safe side, don't allow skipping with Action7 at all return False class Action5BlockType: FIXED = (0,) # fixed number of sprites ANY = (1,) # any number of sprites OFFSET = (2,) # flexible number of sprites, offset may be set action5_table = { "PRE_SIGNAL": (0x04, 48, Action5BlockType.OFFSET), # deprecated, use "SIGNALS" in all cases "PRE_SIGNAL_SEMAPHORE": (0x04, 112, Action5BlockType.OFFSET), # deprecated, use "SIGNALS" in all cases "PRE_SIGNAL_SEMAPHORE_PBS": (0x04, 240, Action5BlockType.OFFSET), # deprecated, use "SIGNALS" in all cases "SIGNALS": (0x04, 240, Action5BlockType.OFFSET), "CATENARY": (0x05, 48, Action5BlockType.OFFSET), "FOUNDATIONS_SLOPES": (0x06, 74, Action5BlockType.FIXED), "FOUNDATIONS_SLOPES_HALFTILES": (0x06, 90, Action5BlockType.OFFSET), "TTDP_GUI_25": (0x07, 73, Action5BlockType.FIXED), "TTDP_GUI": (0x07, 93, Action5BlockType.FIXED), "CANALS": (0x08, 65, Action5BlockType.OFFSET), "ONE_WAY_ROAD": (0x09, 18, Action5BlockType.OFFSET), "COLOURMAP_2CC": (0x0A, 256, Action5BlockType.OFFSET), "TRAMWAY": (0x0B, 119, Action5BlockType.OFFSET), "SNOWY_TEMPERATE_TREES": (0x0C, 133, Action5BlockType.FIXED), "COAST_TILES": (0x0D, 16, Action5BlockType.FIXED), "COAST_TILES_BASEGFX": (0x0D, 10, Action5BlockType.FIXED), "COAST_TILES_DIAGONAL": (0x0D, 18, Action5BlockType.FIXED), "NEW_SIGNALS": (0x0E, 0, Action5BlockType.ANY), "SLOPED_RAILS": (0x0F, 12, Action5BlockType.OFFSET), "AIRPORTS": (0x10, 15, Action5BlockType.OFFSET), "ROAD_STOPS": (0x11, 8, Action5BlockType.OFFSET), "AQUEDUCTS": (0x12, 8, Action5BlockType.OFFSET), "AUTORAIL": (0x13, 55, Action5BlockType.OFFSET), "FLAGS": (0x14, 36, Action5BlockType.OFFSET), "OTTD_GUI": (0x15, 191, Action5BlockType.OFFSET), "AIRPORT_PREVIEW": (0x16, 9, Action5BlockType.OFFSET), "RAILTYPE_TUNNELS": (0x17, 16, Action5BlockType.OFFSET), "OTTD_RECOLOUR": (0x18, 1, Action5BlockType.OFFSET), "ROAD_WAYPOINTS": (0x19, 4, Action5BlockType.OFFSET), } def parse_action5(replaces): real_sprite_list = real_sprite.parse_sprite_data(replaces) num_sprites = len(real_sprite_list) if replaces.type.value not in action5_table: raise generic.ScriptError(replaces.type.value + " is not a valid sprite replacement type", replaces.type.pos) type_id, num_required, block_type = action5_table[replaces.type.value] offset = None if block_type == Action5BlockType.FIXED: if num_sprites < num_required: msg = "Invalid sprite count for sprite replacement type '{}', expected {:d}, got {:d}" msg = msg.format(replaces.type, num_required, num_sprites) raise generic.ScriptError(msg, replaces.pos) elif num_sprites > num_required: msg = ( "Too many sprites specified for sprite replacement type '{}'," " expected {:d}, got {:d}, extra sprites may be ignored" ).format(replaces.type, num_required, num_sprites) generic.print_warning(generic.Warning.GENERIC, msg, replaces.pos) if replaces.offset != 0: msg = "replacenew parameter 'offset' must be zero for sprite replacement type '{}'".format(replaces.type) raise generic.ScriptError(msg, replaces.pos) elif block_type == Action5BlockType.ANY: if replaces.offset != 0: msg = "replacenew parameter 'offset' must be zero for sprite replacement type '{}'".format(replaces.type) raise generic.ScriptError(msg, replaces.pos) elif block_type == Action5BlockType.OFFSET: if num_sprites + replaces.offset > num_required: msg = "Exceeding the limit of {:d} sprites for sprite replacement type '{}', extra sprites may be ignored" msg = msg.format(num_required, replaces.type) generic.print_warning(generic.Warning.GENERIC, msg, replaces.pos) if replaces.offset != 0 or num_sprites != num_required: offset = replaces.offset else: assert 0 return [Action5(type_id, num_sprites, offset)] + real_sprite_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action6.py0000644000175100001660000000374214754345605016103 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import free_number_list, generic from nml.actions import base_action free_parameters = free_number_list.FreeNumberList( list(range(0x40, 0x80)), "No free parameters available to use for internal computations.", "No unique free parameters available for internal computations.", ) def print_stats(): """ Print statistics about used ids. """ if free_parameters.stats[0] > 0: generic.print_info( "Concurrent ActionD registers: {}/{} ({})".format( free_parameters.stats[0], free_parameters.total_amount, str(free_parameters.stats[1]) ) ) class Action6(base_action.BaseAction): def __init__(self): self.modifications = [] def modify_bytes(self, param, num_bytes, offset): self.modifications.append((param, num_bytes, offset)) def write(self, file): size = 2 + 5 * len(self.modifications) file.start_sprite(size) file.print_bytex(6) file.newline() for mod in self.modifications: file.print_bytex(mod[0]) file.print_bytex(mod[1]) file.print_bytex(0xFF) file.print_wordx(mod[2]) file.newline() file.print_bytex(0xFF) file.newline() file.end_sprite() def skip_action7(self): return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action7.py0000644000175100001660000003151114754345605016077 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, free_number_list, generic, nmlop from nml.actions import action6, action10, actionD, base_action free_labels = free_number_list.FreeNumberList( list(range(0xFF, 0x0F, -1)), "No label available to use for large if-blocks and loops.", "No unique label available to use for large if-blocks and loops.", ) def print_stats(): """ Print statistics about used ids. """ if free_labels.stats[0] > 0: generic.print_info( "Concurrent Action10 labels: {}/{} ({})".format( free_labels.stats[0], free_labels.total_amount, str(free_labels.stats[1]) ) ) class SkipAction(base_action.BaseAction): def __init__(self, action_type, var, varsize, condtype, value, label): self.action_type = action_type self.label = label self.var = var self.varsize = varsize self.condtype = condtype self.value = value self.label = label if self.condtype[0] == 0 or self.condtype[0] == 1: assert self.varsize == 1 def write(self, file): size = 5 + self.varsize file.start_sprite(size) file.print_bytex(self.action_type) file.print_bytex(self.var) file.print_bytex(self.varsize) file.print_bytex(self.condtype[0], self.condtype[1]) if self.varsize == 8: # grfid + mask file.print_dwordx(self.value & 0xFFFFFFFF) file.print_dwordx(self.value >> 32) else: file.print_varx(self.value, self.varsize) file.print_bytex(self.label) file.newline() file.end_sprite() def skip_action7(self): return self.action_type == 7 def skip_action9(self): return self.action_type == 9 or self.label == 0 class UnconditionalSkipAction(SkipAction): def __init__(self, action_type, label): SkipAction.__init__(self, action_type, 0x9A, 1, (0, r"\71"), 0, label) def op_to_cond_op(op): # The operators are reversed as we want to skip if the expression is true # while the nml-syntax wants to execute the block if the expression is true if op == nmlop.CMP_NEQ: return (2, r"\7=") if op == nmlop.CMP_EQ: return (3, r"\7!") if op == nmlop.CMP_GE: return (4, r"\7<") if op == nmlop.CMP_LE: return (5, r"\7>") # Not reached raise ValueError("Unexpected operator '{}'".format(op.token)) def parse_conditional(expr): """ Parse an expression and return enough information to use that expression as a conditional statement. Return value is a tuple with the following elements: - Parameter number (as integer) to use in comparison or None for unconditional skip - List of actions needed to set the given parameter to the correct value - The type of comparison to be done - The value to compare against (as integer) - The size of the value (as integer) """ if expr is None: return (None, [], (2, r"\7="), 0, 4) if isinstance(expr, expression.BinOp): if expr.op == nmlop.HASBIT or expr.op == nmlop.NOTHASBIT: if isinstance(expr.expr1, expression.Parameter) and isinstance(expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) if isinstance(expr.expr2, expression.ConstantNumeric): bit_num = expr.expr2.value else: if isinstance(expr.expr2, expression.Parameter) and isinstance( expr.expr2.num, expression.ConstantNumeric ): param = expr.expr2.num.value else: param, tmp_action_list = actionD.get_tmp_parameter(expr.expr2) actions.extend(tmp_action_list) act6 = action6.Action6() act6.modify_bytes(param, 1, 4) actions.append(act6) bit_num = 0 comp_type = (1, r"\70") if expr.op == nmlop.HASBIT else (0, r"\71") return (param, actions, comp_type, bit_num, 1) elif expr.op in (nmlop.CMP_EQ, nmlop.CMP_NEQ, nmlop.CMP_LE, nmlop.CMP_GE) and isinstance( expr.expr2, expression.ConstantNumeric ): if isinstance(expr.expr1, expression.Parameter) and isinstance(expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) op = op_to_cond_op(expr.op) return (param, actions, op, expr.expr2.value, 4) if isinstance(expr, expression.Boolean): expr = expr.expr if isinstance(expr, expression.Not): param, actions = actionD.get_tmp_parameter(expr.expr) return (param, actions, (3, r"\7!"), 0, 4) param, actions = actionD.get_tmp_parameter(expr) return (param, actions, (2, r"\7="), 0, 4) def cond_skip_actions(action_list, param, condtype, value, value_size, pos): if len(action_list) == 0: return [] actions = [] start, length = 0, 0 # Whether to allow not-skipping, using action7 or using action9 skip_opts = (True, True, True) # Add a sentinel value to the list to avoid code duplication for action in action_list + [None]: assert any(skip_opts) if action is not None: # Options allowed by the next action act_opts = (not action.skip_needed(), action.skip_action7(), action.skip_action9()) else: # There are no further actions, so be sure to finish the current block act_opts = (False, False, False) # Options still allowed, when including this action in the previous block new_opts = tuple(all(tpl) for tpl in zip(skip_opts, act_opts)) # End this block in two cases: # - There are no options to skip this action and the preceding block in one go # - The existing block (with length > 0) needed no skipping, but this action does if any(new_opts) and not (length > 0 and skip_opts[0] and not new_opts[0]): # Append this action to the existing block length += 1 skip_opts = new_opts continue # We need to create a new block if skip_opts[0]: # We can just choose to not skip the preceeding actions without harm actions.extend(action_list[start : start + length]) else: action_type = 7 if skip_opts[1] else 9 if length < 0x10: # Lengths under 0x10 are handled without labels, to avoid excessive label usage target = length label = None else: target = free_labels.pop(pos) label = action10.Action10(target) actions.append(SkipAction(action_type, param, value_size, condtype, value, target)) actions.extend(action_list[start : start + length]) if label is not None: actions.append(label) start = start + length length = 1 skip_opts = act_opts assert start == len(action_list) return actions recursive_cond_blocks = 0 def parse_conditional_block(cond_list): global recursive_cond_blocks recursive_cond_blocks += 1 if recursive_cond_blocks == 1: # We only save a single state (at toplevel nml-blocks) because # we don't know at the start of the block how many labels we need. # Getting the same label for a block that was already used in a # sub-block would be very bad, since the action7/9 would skip # to the action10 of the sub-block. free_labels.save() blocks = [] for cond in cond_list.statements: if isinstance(cond.expr, expression.ConstantNumeric): if cond.expr.value == 0: continue else: blocks.append({"expr": None, "statements": cond.statements}) break blocks.append({"expr": cond.expr, "statements": cond.statements}) if blocks: blocks[-1]["last_block"] = True if len(blocks) == 1 and blocks[0]["expr"] is None: action_list = [] for stmt in blocks[0]["statements"]: action_list.extend(stmt.get_action_list()) return action_list action6.free_parameters.save() if len(blocks) > 1: # the skip all parameter is used to skip all blocks after one # of the conditionals was true. We can't always skip directly # to the end of the blocks since action7/action9 can't always # be mixed param_skip_all, action_list = actionD.get_tmp_parameter(expression.ConstantNumeric(0xFFFFFFFF)) else: action_list = [] # use parse_conditional here, we also need to know if all generated # actions (like action6) can be skipped safely for block in blocks: ( block["param_dst"], block["cond_actions"], block["cond_type"], block["cond_value"], block["cond_value_size"], ) = parse_conditional(block["expr"]) if "last_block" not in block: block["action_list"] = [ actionD.ActionD( # If this isn't the last block, len(blocks) > 1 so param_skip_all is initialized. expression.ConstantNumeric(param_skip_all), # lgtm [py/uninitialized-local-variable] expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0), expression.ConstantNumeric(0), ) ] else: block["action_list"] = [] for stmt in block["statements"]: block["action_list"].extend(stmt.get_action_list()) # Main problem: action10 can't be skipped by action9, so we're # nearly forced to use action7, but action7 can't safely skip action6 # Solution: use temporary parameter, set to 0 for not skip, !=0 for skip. # then skip every block of actions (as large as possible) with either # action7 or action9, depending on which of the two works. for i, block in enumerate(blocks): param = block["param_dst"] if i == 0: action_list.extend(block["cond_actions"]) else: action_list.extend( cond_skip_actions(block["cond_actions"], param_skip_all, (2, r"\7="), 0, 4, cond_list.pos) ) if param is None: param = param_skip_all else: action_list.append( actionD.ActionD( expression.ConstantNumeric(block["param_dst"]), expression.ConstantNumeric(block["param_dst"]), nmlop.AND, expression.ConstantNumeric(param_skip_all), ) ) action_list.extend( cond_skip_actions( block["action_list"], param, block["cond_type"], block["cond_value"], block["cond_value_size"], cond_list.pos, ) ) if recursive_cond_blocks == 1: free_labels.restore() recursive_cond_blocks -= 1 action6.free_parameters.restore() return action_list def parse_loop_block(loop): global recursive_cond_blocks recursive_cond_blocks += 1 if recursive_cond_blocks == 1: free_labels.save() action6.free_parameters.save() begin_label = free_labels.pop_unique(loop.pos) action_list = [action10.Action10(begin_label)] cond_param, cond_actions, cond_type, cond_value, cond_value_size = parse_conditional(loop.expr) block_actions = [] for stmt in loop.statements: block_actions.extend(stmt.get_action_list()) action_list.extend(cond_actions) block_actions.append(UnconditionalSkipAction(9, begin_label)) action_list.extend(cond_skip_actions(block_actions, cond_param, cond_type, cond_value, cond_value_size, loop.pos)) if recursive_cond_blocks == 1: free_labels.restore() recursive_cond_blocks -= 1 action6.free_parameters.restore() return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/action8.py0000644000175100001660000000267114754345605016105 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import grfstrings from nml.actions import base_action class Action8(base_action.BaseAction): def __init__(self, grfid, name, description): self.grfid = grfid self.name = name self.description = description def write(self, file): name = grfstrings.get_translation(self.name) desc = grfstrings.get_translation(self.description) size = 6 + grfstrings.get_string_size(name) + grfstrings.get_string_size(desc) file.start_sprite(size) file.print_bytex(8) # Write the grf version file.print_bytex(8) file.print_string(self.grfid.value, False, True) file.print_string(name) file.print_string(desc) file.end_sprite() def skip_action7(self): return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/actionA.py0000644000175100001660000000534714754345605016121 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import nmlop from nml.actions import action6, actionD, base_action, real_sprite class ActionA(base_action.BaseAction): """ Action class for Action A (sprite replacement) @ivar sets: List of sprite collections to be replaced. @type sets: C{list} of (C{int}, C{int})-tuples """ def __init__(self, sets): self.sets = sets def write(self, file): # * 0A [ ]+ size = 2 + 3 * len(self.sets) file.start_sprite(size) file.print_bytex(0x0A) file.print_byte(len(self.sets)) for num, first in self.sets: file.print_byte(num) file.print_word(first) file.newline() file.end_sprite() def parse_actionA(replaces): """ Parse replace-block to ActionA. @param replaces: Replace-block to parse. @type replaces: L{ReplaceSprite} """ action_list = [] action6.free_parameters.save() act6 = action6.Action6() real_sprite_list = real_sprite.parse_sprite_data(replaces) block_list = [] total_sprites = len(real_sprite_list) offset = 2 # Skip 0A and sprite_offset = 0 # Number of sprites already covered by previous [ ]-pairs while total_sprites > 0: this_block = min(total_sprites, 255) # number of sprites in this block total_sprites -= this_block offset += 1 # Skip first_sprite = replaces.start_id # number of first sprite if sprite_offset != 0: first_sprite = nmlop.ADD(first_sprite, sprite_offset).reduce() first_sprite, offset = actionD.write_action_value(first_sprite, action_list, act6, offset, 2) block_list.append((this_block, first_sprite.value)) sprite_offset += this_block # increase first-sprite for next block if len(act6.modifications) > 0: action_list.append(act6) action6.free_parameters.restore() action_list.append(ActionA(block_list)) action_list.extend(real_sprite_list) return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/actionB.py0000644000175100001660000001025014754345605016107 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, grfstrings from nml.actions import action6, actionD, base_action class ActionB(base_action.BaseAction): def __init__(self, severity, lang, msg, data, extra_params): self.severity = severity self.lang = lang self.msg = msg self.data = data self.extra_params = extra_params def write(self, file): size = 4 if not isinstance(self.msg, int): size += grfstrings.get_string_size(self.msg) if self.data is not None: size += grfstrings.get_string_size(self.data) + len(self.extra_params) file.start_sprite(size) file.print_bytex(0x0B) self.severity.write(file, 1) file.print_bytex(self.lang) if isinstance(self.msg, int): file.print_bytex(self.msg) else: file.print_bytex(0xFF) file.print_string(self.msg) if self.data is not None: file.print_string(self.data) for param in self.extra_params: param.write(file, 1) file.newline() file.end_sprite() def skip_action7(self): return False default_error_msg = { "REQUIRES_TTDPATCH": 0, "REQUIRES_DOS_WINDOWS": 1, "USED_WITH": 2, "INVALID_PARAMETER": 3, "MUST_LOAD_BEFORE": 4, "MUST_LOAD_AFTER": 5, "REQUIRES_OPENTTD": 6, } error_severity = { "NOTICE": 0, "WARNING": 1, "ERROR": 2, "FATAL": 3, } def parse_error_block(error): action6.free_parameters.save() action_list = [] act6 = action6.Action6() severity = actionD.write_action_value(error.severity, action_list, act6, 1, 1)[0] langs = [0x7F] if isinstance(error.msg, expression.String): custom_msg = True msg_string = error.msg grfstrings.validate_string(msg_string) langs.extend(grfstrings.get_translations(msg_string)) for lang in langs: assert lang is not None else: custom_msg = False msg = error.msg.reduce_constant().value if error.data is not None: error.data = error.data.reduce() if isinstance(error.data, expression.String): grfstrings.validate_string(error.data) langs.extend(grfstrings.get_translations(error.data)) for lang in langs: assert lang is not None elif not isinstance(error.data, expression.StringLiteral): raise generic.ScriptError( "Error parameter 3 'data' should be the identifier of a custom sting", error.data.pos ) params = [] for expr in error.params: if isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): params.append(expr.num) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) action_list.extend(tmp_param_actions) params.append(expression.ConstantNumeric(tmp_param)) langs = list(set(langs)) langs.sort() for lang in langs: if custom_msg: msg = grfstrings.get_translation(msg_string, lang) if error.data is None: data = None elif isinstance(error.data, expression.StringLiteral): data = error.data.value else: data = grfstrings.get_translation(error.data, lang) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionB(severity, lang, msg, data, params)) action6.free_parameters.restore() return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/actionD.py0000644000175100001660000004715514754345605016127 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.actions import action6, action7, base_action from nml.ast import base_statement, conditional class ActionD(base_action.BaseAction): """ ActionD class General procedure: target = param1 op param2 If one of the params is 0xFF, the value of 'data' is read instead. @ivar target: Number of the target parameter @ivar target: L{ConstantNumeric} @ivar param1: Paramter number of the first operand @type param1: L{ConstantNumeric} @ivar op: (Binary) operator to use. @type op: L{Operator} @ivar param2: Paramter number of the second operand @type param2: L{ConstantNumeric} @ivar data: Numerical data that will be used instead of parameter value, if the parameter number is 0xFF. None if n/a. @type data: L{ConstantNumeric} or C{None} """ def __init__(self, target, param1, op, param2, data=None): self.target = target self.param1 = param1 self.op = op self.param2 = param2 self.data = data def write(self, file): size = 5 if self.data is not None: size += 4 # print the statement for easier debugging str1 = "param[{}]".format(self.param1) if self.param1.value != 0xFF else str(self.data) str2 = "param[{}]".format(self.param2) if self.param2.value != 0xFF else str(self.data) str_total = self.op.to_string(str1, str2) if self.op != nmlop.ASSIGN else str1 file.comment("param[{}] = {}".format(self.target, str_total)) file.start_sprite(size) file.print_bytex(0x0D) self.target.write(file, 1) file.print_bytex(self.op.actd_num, self.op.actd_str) self.param1.write(file, 1) self.param2.write(file, 1) if self.data is not None: self.data.write(file, 4) file.newline() file.end_sprite() def skip_action7(self): return False class ParameterAssignment(base_statement.BaseStatement): """ AST-node for a parameter assignment. NML equivalent: param[$num] = $expr; @ivar param: Target expression to assign (must evaluate to a parameter) @type param: L{Expression} @ivar value: Value to assign to this parameter @type value: L{Expression} """ def __init__(self, param, value): base_statement.BaseStatement.__init__(self, "parameter assignment", param.pos) self.param = param self.value = value def register_names(self): self.param = self.param.reduce(global_constants.const_list, unknown_id_fatal=False) if isinstance(self.param, expression.Identifier): if global_constants.identifier_refcount[self.param.value] == 0: generic.print_warning( generic.Warning.OPTIMISATION, "Named parameter '{}' is not referenced, ignoring.".format(self.param.value), self.param.pos, ) return num = action6.free_parameters.pop_unique(self.pos) global_constants.named_parameters[self.param.value] = num def pre_process(self): self.value = self.value.reduce(global_constants.const_list) if isinstance(self.param, expression.SpecialParameter): if not self.param.can_assign(): raise generic.ScriptError( "Trying to assign a value to the read-only variable '{}'".format(self.param.name), self.param.pos ) elif isinstance(self.param, expression.Identifier): return elif not isinstance(self.param, expression.Parameter): raise generic.ScriptError("Left side of an assignment must be a parameter.", self.param.pos) def debug_print(self, indentation): generic.print_dbg(indentation, "Parameter assignment") self.param.debug_print(indentation + 2) self.value.debug_print(indentation + 2) def get_action_list(self): return parse_actionD(self) def __str__(self): return "{} = {};\n".format(self.param, self.value) # prevent evaluating common sub-expressions multiple times def parse_subexpression(expr, action_list): if isinstance(expr, expression.ConstantNumeric) or ( isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric) ): return expr else: tmp_param, tmp_param_actions = get_tmp_parameter(expr) action_list.extend(tmp_param_actions) return expression.Parameter(expression.ConstantNumeric(tmp_param)) # returns a (param_num, action_list) tuple. def get_tmp_parameter(expr): param = action6.free_parameters.pop(expr.pos) actions = parse_actionD(ParameterAssignment(expression.Parameter(expression.ConstantNumeric(param)), expr)) return (param, actions) def write_action_value(expr, action_list, act6, offset, size): """ Write a single numeric value that is part of an action Possibly use action6 and/or actionD if needed @param expr: Expression to write @type expr: L{Expression} @param action_list: Action list to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to append any modifications to @type act6: L{Action6} @param offset: Offset to use for the action 6 (if applicable) @type offset: C{int} @param size: Size to use for the action 6 (if applicable) @type size: C{int} @return: 2-tuple containing - - the value to be used when writing the action (0 if overwritten by action 6) - the offset just past this numeric value (=offset + size) @rtype: C{tuple} of (L{ConstantNumeric}, C{int}) """ if isinstance(expr, expression.ConstantNumeric): result = expr elif isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): act6.modify_bytes(expr.num.value, size, offset) result = expression.ConstantNumeric(0) else: tmp_param, tmp_param_actions = get_tmp_parameter(expr) action_list.extend(tmp_param_actions) act6.modify_bytes(tmp_param, size, offset) result = expression.ConstantNumeric(0) return result, offset + size def parse_ternary_op(assignment): assert isinstance(assignment.value, expression.TernaryOp) actions = parse_actionD(ParameterAssignment(assignment.param, assignment.value.expr2)) cond_block = conditional.Conditional( assignment.value.guard, [ParameterAssignment(assignment.param, assignment.value.expr1)], None ) actions.extend(conditional.ConditionalList([cond_block]).get_action_list()) return actions def parse_abs_op(assignment): assert isinstance(assignment.value, expression.AbsOp) actions = parse_actionD(ParameterAssignment(assignment.param, assignment.value.expr)) cond_block = conditional.Conditional( nmlop.CMP_LT(assignment.value.expr, 0), [ParameterAssignment(assignment.param, nmlop.SUB(0, assignment.value.expr))], None, ) actions.extend(conditional.ConditionalList([cond_block]).get_action_list()) return actions def parse_special_check(assignment): check = assignment.value assert isinstance(check, expression.SpecialCheck) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(check.results[0]))) value = check.value if check.mask is not None: value &= check.mask value += check.mask << 32 assert check.varsize == 8 else: assert check.varsize <= 4 actions.append(action7.SkipAction(9, check.varnum, check.varsize, check.op, value, 1)) actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(check.results[1])))) return actions def parse_grm(assignment): assert isinstance(assignment.value, expression.GRMOp) action6.free_parameters.save() action_list = [] act6 = action6.Action6() assert isinstance(assignment.param, expression.Parameter) target = assignment.param.num if isinstance(target, expression.Parameter) and isinstance(target.num, expression.ConstantNumeric): act6.modify_bytes(target.num.value, 1, 1) target = expression.ConstantNumeric(0) elif not isinstance(target, expression.ConstantNumeric): tmp_param, tmp_param_actions = get_tmp_parameter(target) act6.modify_bytes(tmp_param, 1, 1) target = expression.ConstantNumeric(0) action_list.extend(tmp_param_actions) op = nmlop.ASSIGN param1 = assignment.value.op param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(0xFF | (assignment.value.feature << 8) | (assignment.value.count << 16)) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionD(target, param1, op, param2, data)) action6.free_parameters.restore() return action_list def parse_hasbit(assignment): assert isinstance(assignment.value, expression.BinOp) and ( assignment.value.op == nmlop.HASBIT or assignment.value.op == nmlop.NOTHASBIT ) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(0))) cond_block = conditional.Conditional( assignment.value, [ParameterAssignment(assignment.param, expression.ConstantNumeric(1))], None ) actions.extend(conditional.ConditionalList([cond_block]).get_action_list()) return actions def parse_min_max(assignment): assert isinstance(assignment.value, expression.BinOp) and assignment.value.op in (nmlop.MIN, nmlop.MAX) # min(a, b) ==> a < b ? a : b. # max(a, b) ==> a > b ? a : b. action6.free_parameters.save() action_list = [] expr1 = parse_subexpression(assignment.value.expr1, action_list) expr2 = parse_subexpression(assignment.value.expr2, action_list) guard = expression.BinOp(nmlop.CMP_LT if assignment.value.op == nmlop.MIN else nmlop.CMP_GT, expr1, expr2) action_list.extend( parse_actionD(ParameterAssignment(assignment.param, expression.TernaryOp(guard, expr1, expr2, None))) ) action6.free_parameters.restore() return action_list def parse_boolean(assignment): assert isinstance(assignment.value, expression.Boolean) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(0))) expr = nmlop.CMP_NEQ(assignment.value.expr, 0) cond_block = conditional.Conditional( expr, [ParameterAssignment(assignment.param, expression.ConstantNumeric(1))], None ) actions.extend(conditional.ConditionalList([cond_block]).get_action_list()) return actions def transform_bin_op(assignment): op = assignment.value.op expr1 = assignment.value.expr1 expr2 = assignment.value.expr2 extra_actions = [] if op == nmlop.CMP_GE: expr1, expr2 = expr2, expr1 op = nmlop.CMP_LE if op == nmlop.CMP_LE: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2)))) op = nmlop.CMP_LT expr1 = assignment.param expr2 = expression.ConstantNumeric(1) if op == nmlop.CMP_GT: expr1, expr2 = expr2, expr1 op = nmlop.CMP_LT if op == nmlop.CMP_LT: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2)))) op = nmlop.SHIFTU_LEFT # shift left by negative number = shift right expr1 = assignment.param expr2 = expression.ConstantNumeric(-31) elif op == nmlop.CMP_NEQ: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2)))) op = nmlop.DIV # We rely here on the (ondocumented) behavior of both OpenTTD and TTDPatch # that expr/0==expr. What we do is compute A/A, which will result in 1 if # A != 0 and in 0 if A == 0 expr1 = assignment.param expr2 = assignment.param elif op == nmlop.CMP_EQ: # We compute A==B by doing not(A - B) which will result in a value != 0 # if A is equal to B extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, nmlop.SUB(expr1, expr2)))) # Clamp the value to 0/1, see above for details extra_actions.extend( parse_actionD(ParameterAssignment(assignment.param, nmlop.DIV(assignment.param, assignment.param))) ) op = nmlop.SUB expr1 = expression.ConstantNumeric(1) expr2 = assignment.param if op == nmlop.SHIFT_RIGHT or op == nmlop.SHIFTU_RIGHT: if isinstance(expr2, expression.ConstantNumeric): expr2.value *= -1 else: expr2 = nmlop.SUB(0, expr2) op = nmlop.SHIFT_LEFT if op == nmlop.SHIFT_RIGHT else nmlop.SHIFTU_LEFT elif op == nmlop.XOR: # a ^ b ==> (a | b) - (a & b) expr1 = parse_subexpression(expr1, extra_actions) expr2 = parse_subexpression(expr2, extra_actions) tmp_param1, tmp_action_list1 = get_tmp_parameter(nmlop.OR(expr1, expr2)) tmp_param2, tmp_action_list2 = get_tmp_parameter(nmlop.AND(expr1, expr2)) extra_actions.extend(tmp_action_list1) extra_actions.extend(tmp_action_list2) expr1 = expression.Parameter(expression.ConstantNumeric(tmp_param1)) expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param2)) op = nmlop.SUB return op, expr1, expr2, extra_actions def parse_actionD(assignment): assignment.value.supported_by_actionD(True) if isinstance(assignment.param, expression.SpecialParameter): assignment.param, assignment.value = assignment.param.to_assignment(assignment.value) elif isinstance(assignment.param, expression.Identifier): if global_constants.identifier_refcount[assignment.param.value] == 0: # Named parameter is not referenced, ignoring return [] assignment.param = expression.Parameter( expression.ConstantNumeric(global_constants.named_parameters[assignment.param.value]), assignment.param.pos ) assert isinstance(assignment.param, expression.Parameter) if isinstance(assignment.value, expression.SpecialParameter): assignment.value = assignment.value.to_reading() if isinstance(assignment.value, expression.TernaryOp): return parse_ternary_op(assignment) if isinstance(assignment.value, expression.AbsOp): return parse_abs_op(assignment) if isinstance(assignment.value, expression.SpecialCheck): return parse_special_check(assignment) if isinstance(assignment.value, expression.GRMOp): return parse_grm(assignment) if isinstance(assignment.value, expression.BinOp): op = assignment.value.op if op == nmlop.HASBIT or op == nmlop.NOTHASBIT: return parse_hasbit(assignment) elif op == nmlop.MIN or op == nmlop.MAX: return parse_min_max(assignment) if isinstance(assignment.value, expression.Boolean): return parse_boolean(assignment) if isinstance(assignment.value, expression.Not): expr = nmlop.SUB(1, assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) if isinstance(assignment.value, expression.BinNot): expr = nmlop.SUB(0xFFFFFFFF, assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) action6.free_parameters.save() action_list = [] act6 = action6.Action6() assert isinstance(assignment.param, expression.Parameter) target = assignment.param.num if isinstance(target, expression.Parameter) and isinstance(target.num, expression.ConstantNumeric): act6.modify_bytes(target.num.value, 1, 1) target = expression.ConstantNumeric(0) elif not isinstance(target, expression.ConstantNumeric): tmp_param, tmp_param_actions = get_tmp_parameter(target) act6.modify_bytes(tmp_param, 1, 1) target = expression.ConstantNumeric(0) action_list.extend(tmp_param_actions) data = None # print assignment.value if isinstance(assignment.value, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0xFF) param2 = expression.ConstantNumeric(0) data = assignment.value elif isinstance(assignment.value, expression.Parameter): if isinstance(assignment.value.num, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0) elif isinstance(assignment.value, expression.OtherGRFParameter): op = nmlop.ASSIGN if isinstance(assignment.value.num, expression.ConstantNumeric): param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(expression.parse_string_to_dword(assignment.value.grfid)) elif isinstance(assignment.value, expression.PatchVariable): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(assignment.value.num) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(0xFFFF) elif isinstance(assignment.value, expression.BinOp): op, expr1, expr2, extra_actions = transform_bin_op(assignment) action_list.extend(extra_actions) if isinstance(expr1, expression.ConstantNumeric): param1 = expression.ConstantNumeric(0xFF) data = expr1 elif isinstance(expr1, expression.Parameter) and isinstance(expr1.num, expression.ConstantNumeric): param1 = expr1.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr1) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(tmp_param) # We can use the data only for one for the parameters. # If the first parameter uses "data" we need a temp parameter for this one if isinstance(expr2, expression.ConstantNumeric) and data is None: param2 = expression.ConstantNumeric(0xFF) data = expr2 elif isinstance(expr2, expression.Parameter) and isinstance(expr2.num, expression.ConstantNumeric): param2 = expr2.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr2) action_list.extend(tmp_param_actions) param2 = expression.ConstantNumeric(tmp_param) else: raise generic.ScriptError("Invalid expression in argument assignment", assignment.value.pos) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionD(target, param1, op, param2, data)) action6.free_parameters.restore() return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/actionE.py0000644000175100001660000000232114754345605016112 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import base_action class ActionE(base_action.BaseAction): def __init__(self, grfid_list): self.grfid_list = grfid_list def write(self, file): size = 2 + 4 * len(self.grfid_list) file.start_sprite(size) file.print_bytex(0x0E) file.print_byte(len(self.grfid_list)) for grfid in self.grfid_list: file.newline() file.print_dwordx(grfid) file.newline() file.end_sprite() def parse_deactivate_block(block): return [ActionE(block.grfid_list)] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/actionF.py0000644000175100001660000002060014754345605016113 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Code for storing and generating action F """ from nml import expression, generic, grfstrings from nml.actions import base_action # Helper functions to allocate townname IDs # # Numbers available are from 0 to 0x7f (inclusive). # These numbers can be in five states: # - free: Number is available for use. # - named: Number is allocated to represent a name. # - safe numbered: Number is allocated by the user, and is safe to refer to. # - unsafe numbered: Number is allocated by the user, and is not safe to refer to # (that is, it is below the point of 'prepare_output') # - invisible: Number is allocated by a final town_name, without attaching a name to it. # It is not accessible any more. # Instances of the TownNames class have a 'name' attribute, which can be 'None' (for an invisible number), # a string (for a named number), or a (constant numeric) expression (for a safe/unsafe number). # total_numbers = 0x80 free_numbers = set(range(total_numbers)) #: Free numbers. first_free_id = 0 #: All numbers before this are allocated. named_numbers = {} #: Mapping of names to named numbers. Note that these are always safe to refer to. numbered_numbers = set() #: Safe numbers introduced by the user (without name). def get_free_id(): """Allocate a number from the free_numbers.""" global first_free_id while first_free_id not in free_numbers: first_free_id = first_free_id + 1 if first_free_id >= total_numbers: raise generic.ScriptError( "Too many town name blocks. Some of these are autogenerated. Please use less town names." ) number = first_free_id free_numbers.remove(number) first_free_id = first_free_id + 1 return number town_names_blocks = {} # Mapping of town_names ID number to TownNames instance. def print_stats(): """ Print statistics about used ids. """ num_used = total_numbers - len(free_numbers) if num_used > 0: generic.print_info("Town names: {}/{}".format(num_used, total_numbers)) class ActionF(base_action.BaseAction): """ Town names action. @ivar name: Name ID of the town_name. @type name: C{None}, L{Identifier}, or L{ConstantNumeric} @ivar id_number: Allocated ID number for this town_name action F node. @type id_number: C{None} or C{int} @ivar style_name: Name of the translated string containing the name of the style, if any. @type style_name: C{None} or L{String} @ivar style_names: List translations of L{style_name}, pairs (languageID, text). @type style_names: C{list} of (C{int}, L{Identifier}) @ivar parts: Parts of the names. @type parts: C{list} of L{TownNamesPart} @ivar pos: Position information of the 'town_names' block. @type pos: L{Position} @ivar deps: List of ID numbers of action F dependant on this node. @type deps: C{list} of C{int} """ def __init__(self, name, id_number, style_name, parts, pos): self.name = name self.id_number = id_number self.style_name = style_name self.style_names = [] self.parts = parts self.pos = pos self.deps = [] def prepare_output(self, sprite_num): # Resolve references to earlier townname actions referenced_blocks = [] for part in self.parts: part_blocks = part.resolve_townname_id() if part_blocks: referenced_blocks.append(part_blocks) # Allocate a number for this action F. if self.name is None or isinstance(self.name, expression.Identifier): self.id_number = get_free_id() if isinstance(self.name, expression.Identifier): if self.name.value in named_numbers: raise generic.ScriptError( 'Cannot define town name "{}", it is already in use'.format(self.name), self.pos ) named_numbers[self.name.value] = self.id_number # Add name to the set 'safe' names. else: numbered_numbers.add(self.id_number) # Add number to the set of 'safe' numbers. town_names_blocks[self.id_number] = self # Add self to the available blocks. def assign_bits(part, startbit): num_bits = part.assign_bits(startbit) # Prevent overlap if really needed if len(part.pieces) > 1: startbit += num_bits return startbit def update_block(block, startbit): for part in block.parts: startbit = assign_bits(part, startbit) # Update dependant Action F for dep in block.deps: update_block(town_names_blocks[dep], startbit) return startbit # Determine lowest available bit, and prevent overlap when needed. # referenced_blocks is a list of sets, each set contains references that can share the same bits, # but different sets can't share bits. startbit = 0 for part_blocks in referenced_blocks: freebit = startbit for part_block in part_blocks: block = town_names_blocks[part_block] startbit = max(startbit, update_block(block, freebit)) if self.id_number not in block.deps: block.deps.append(self.id_number) # Allocate random bits to all parts. for part in self.parts: startbit = assign_bits(part, startbit) if startbit > 32: raise generic.ScriptError( "Not enough random bits for the town name generation ({:d} needed, 32 available)".format(startbit), self.pos, ) # Pull style names if needed. if self.style_name is not None: grfstrings.validate_string(self.style_name) self.style_names = [ (lang_id, grfstrings.get_translation(self.style_name, lang_id)) for lang_id in grfstrings.get_translations(self.style_name) ] self.style_names.append((0x7F, grfstrings.get_translation(self.style_name))) self.style_names.sort() if len(self.style_names) == 0: raise generic.ScriptError( 'Style "{}" defined, but no translations found for it'.format(self.style_name.name.value), self.pos ) else: self.style_names = [] # Style names def get_length_styles(self): if len(self.style_names) == 0: return 0 size = 0 for _lang, txt in self.style_names: size += 1 + grfstrings.get_string_size(txt) # Language ID, text return size + 1 # Terminating 0 def write_styles(self, handle): if len(self.style_names) == 0: return for lang, txt in self.style_names: handle.print_bytex(lang) handle.print_string(txt, final_zero=True) handle.newline() handle.print_bytex(0) handle.newline() # Parts def get_length_parts(self): size = 1 # num_parts byte return size + sum(part.get_length() for part in self.parts) def write_parts(self, handle): handle.print_bytex(len(self.parts)) for part in self.parts: part.write(handle) handle.newline() def write(self, handle): handle.start_sprite(2 + self.get_length_styles() + self.get_length_parts()) handle.print_bytex(0xF) handle.print_bytex(self.id_number | (0x80 if len(self.style_names) > 0 else 0)) handle.newline(str(self.name) if self.name is not None else "") self.write_styles(handle) self.write_parts(handle) handle.end_sprite() def skip_action7(self): return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/base_action.py0000644000175100001660000000364314754345605017007 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class BaseAction: def prepare_output(self, sprite_num): """ Called just before L{write}, this function can be used to do some last modifications to the action (like resolving some IDs that can't be resolved earlier). @param sprite_num: The sprite number in the nfo/grf output. @type sprite_num: C{int} """ pass def write(self, file): """ Write this action to the given outputfile. @param file: The outputfile to write the data to. @type file: L{SpriteOutputBase} """ raise NotImplementedError("write is not implemented in {!r}".format(type(self))) def skip_action7(self): """ Can this action be skipped safely by an action7? @return True iff this action can be skipped by action7. """ return True def skip_action9(self): """ Can this action be skipped safely by an action9? @return True iff this action can be skipped by action9. """ return True def skip_needed(self): """ Do we need to skip this action at all? @return False when it doesn't matter whether this action is skipped or not. """ return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/real_sprite.py0000644000175100001660000006322614754345605017054 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" try: from PIL import Image except ImportError: # Image is required only when using graphics pass from nml import expression, generic from nml.actions import base_action from nml.ast import assignment FLAG_NOCROP = 0x0040 FLAG_NOALPHA = 0x0100 FLAG_WHITE = 0x0200 FLAG_ANIM = 0x0400 real_sprite_flags = { "CROP": 0, # Allow cropping "NOCROP": FLAG_NOCROP, # Disallow cropping "ALPHA": 0, # Allow semi-transparency "NOALPHA": FLAG_NOALPHA, # Warn about semi-transparency "WHITE": FLAG_WHITE, # Allow pure-white "NOWHITE": 0, # Warn about pure-white "ANIM": FLAG_ANIM, # Allow anim colours "NOANIM": 0, # Warn about anim colours } # fmt: off palmap_d2w = [ 0, 215, 216, 136, 88, 106, 32, 33, # 0..7 40, 245, 10, 11, 12, 13, 14, 15, # 8..15 16, 17, 18, 19, 20, 21, 22, 23, # 16..23 24, 25, 26, 27, 28, 29, 30, 31, # 24..31 53, 54, 34, 35, 36, 37, 38, 39, # 32..39 178, 41, 42, 43, 44, 45, 46, 47, # 40..47 48, 49, 50, 51, 52, 53, 54, 55, # 48..55 56, 57, 58, 59, 60, 61, 62, 63, # 56..63 64, 65, 66, 67, 68, 69, 70, 71, # 64..71 72, 73, 74, 75, 76, 77, 78, 79, # 72..79 80, 81, 82, 83, 84, 85, 86, 87, # 80..87 96, 89, 90, 91, 92, 93, 94, 95, # 88..95 96, 97, 98, 99, 100, 101, 102, 103, # 96..103 104, 105, 53, 107, 108, 109, 110, 111, # 104..111 112, 113, 114, 115, 116, 117, 118, 119, # 112..119 120, 121, 122, 123, 124, 125, 126, 127, # 120..127 128, 129, 130, 131, 132, 133, 134, 135, # 128..135 170, 137, 138, 139, 140, 141, 142, 143, # 136..143 144, 145, 146, 147, 148, 149, 150, 151, # 144..151 152, 153, 154, 155, 156, 157, 158, 159, # 152..159 160, 161, 162, 163, 164, 165, 166, 167, # 160..167 168, 169, 170, 171, 172, 173, 174, 175, # 168..175 176, 177, 178, 179, 180, 181, 182, 183, # 176..183 184, 185, 186, 187, 188, 189, 190, 191, # 184..191 192, 193, 194, 195, 196, 197, 198, 199, # 192..199 200, 201, 202, 203, 204, 205, 206, 207, # 200..207 208, 209, 210, 211, 212, 213, 214, 215, # 208..215 216, 217, 246, 247, 248, 249, 250, 251, # 216..223 252, 253, 254, 227, 228, 229, 230, 231, # 224..231 232, 233, 234, 235, 236, 237, 238, 239, # 232..239 240, 241, 242, 243, 244, 217, 218, 219, # 240..247 220, 221, 222, 223, 224, 225, 226, 255, # 248..255 ] palmap_w2d = [ 0, 1, 2, 3, 4, 5, 6, 7, # 0..7 8, 9, 10, 11, 12, 13, 14, 15, # 8..15 16, 17, 18, 19, 20, 21, 22, 23, # 16..23 24, 25, 26, 27, 28, 29, 30, 31, # 24..31 6, 7, 34, 35, 36, 37, 38, 39, # 32..39 8, 41, 42, 43, 44, 45, 46, 47, # 40..47 48, 49, 50, 51, 52, 53, 54, 55, # 48..55 56, 57, 58, 59, 60, 61, 62, 63, # 56..63 64, 65, 66, 67, 68, 69, 70, 71, # 64..71 72, 73, 74, 75, 76, 77, 78, 79, # 72..79 80, 81, 82, 83, 84, 85, 86, 87, # 80..87 4, 89, 90, 91, 92, 93, 94, 95, # 88..95 96, 97, 98, 99, 100, 101, 102, 103, # 96..103 104, 105, 5, 107, 108, 109, 110, 111, # 104..111 112, 113, 114, 115, 116, 117, 118, 119, # 112..119 120, 121, 122, 123, 124, 125, 126, 127, # 120..127 128, 129, 130, 131, 132, 133, 134, 135, # 128..135 3, 137, 138, 139, 140, 141, 142, 143, # 136..143 144, 145, 146, 147, 148, 149, 150, 151, # 144..151 152, 153, 154, 155, 156, 157, 158, 159, # 152..159 160, 161, 162, 163, 164, 165, 166, 167, # 160..167 168, 169, 170, 171, 172, 173, 174, 175, # 168..175 176, 177, 178, 179, 180, 181, 182, 183, # 176..183 184, 185, 186, 187, 188, 189, 190, 191, # 184..191 192, 193, 194, 195, 196, 197, 198, 199, # 192..199 200, 201, 202, 203, 204, 205, 206, 207, # 200..207 208, 209, 210, 211, 212, 213, 214, 1, # 208..215 2, 245, 246, 247, 248, 249, 250, 251, # 216..223 252, 253, 254, 229, 230, 231, 227, 228, # 224..231 232, 233, 234, 235, 236, 237, 238, 239, # 232..239 240, 241, 242, 243, 244, 9, 218, 219, # 240..247 220, 221, 222, 223, 224, 225, 226, 255, # 248..255 ] # fmt: on translate_w2d = bytearray(v for v in palmap_w2d) translate_d2w = bytearray(v for v in palmap_d2w) def convert_palette(pal): ret = 256 * [0] for idx, colour in enumerate(pal): if 0xD7 <= idx <= 0xE2: if idx != colour: raise generic.ScriptError( "Indices 0xD7..0xE2 are not allowed in recolour sprites when the output is in the WIN palette" ) continue ret[palmap_d2w[idx]] = palmap_d2w[colour] return ret class RealSprite: """ @ivar param_list: Original parameters from NML source file. @type param_list: List of L{expression.Expression}, or C{None} @ivar label: Optional label from NML source file. @type label: L{expression.Identifier} or C{None} @ivar is_empty: True, if this is an empty sprite with zero dimension. @type is_empty: C{bool} @ivar file: Filename of primary image file. @type file: L{expression.StringLiteral} @ivar xpos: X position of sprite in source image file. @type xpos: L{expression.ConstantNumeric} or C{None} @ivar ypos: Y position of sprite in source image file. @type ypos: L{expression.ConstantNumeric} or C{None} @ivar mask_file: Filename of additional mask image file. @type mask_file: L{expression.StringLiteral} or C{None} @ivar mask_pos: Position of sprite in source mask image file. @type mask_pos: Pair of L{expression.ConstantNumeric}, or C{None} @ivar xsize: X size of sprite in both source image and mask file. @type xsize: L{expression.ConstantNumeric} or C{None} @ivar ysize: Y position of sprite in both source image and mask file. @type ysize: L{expression.ConstantNumeric} or C{None} @ivar xrel: X sprite offset. @type xrel: L{expression.ConstantNumeric} @ivar yrel: Y sprite offset. @type yrel: L{expression.ConstantNumeric} @ivar flags: Cropping/warning flags. @type flags: L{expression.ConstantNumeric} @ivar poslist: Position of creation of the sprite, if available. @type poslist: C{list} of L{Position} """ def __init__(self, param_list=None, label=None, poslist=None): self.param_list = param_list self.label = label self.is_empty = False self.xpos = None self.ypos = None self.xsize = None self.ysize = None if poslist is None: self.poslist = [] else: self.poslist = poslist def debug_print(self, indentation): generic.print_dbg(indentation, "Real sprite, parameters:") for param in self.param_list: param.debug_print(indentation + 2) def get_labels(self): labels = {} if self.label is not None: labels[self.label.value] = 0 return labels, 1 def expand(self, default_file, default_mask_file, poslist, id_dict): return [parse_real_sprite(self, default_file, default_mask_file, poslist, id_dict)] def check_sprite_size(self): generic.check_range(self.xpos.value, 0, 0x7FFFFFFF, "Real sprite paramater 'xpos'", self.xpos.pos) generic.check_range(self.ypos.value, 0, 0x7FFFFFFF, "Real sprite paramater 'ypos'", self.ypos.pos) generic.check_range(self.xsize.value, 1, 0xFFFF, "Real sprite paramater 'xsize'", self.xsize.pos) generic.check_range(self.ysize.value, 1, 0xFFFF, "Real sprite paramater 'ysize'", self.ysize.pos) def validate_size(self): """ Check if xpos/ypos/xsize/ysize are already set and if not, set them to 0,0,image_width,image_height. """ if self.xpos is None: with Image.open(generic.find_file(self.file.value)) as im: self.xpos = expression.ConstantNumeric(0) self.ypos = expression.ConstantNumeric(0) self.xsize = expression.ConstantNumeric(im.size[0]) self.ysize = expression.ConstantNumeric(im.size[1]) self.check_sprite_size() if self.mask_pos is None: self.mask_pos = (self.xpos, self.ypos) def __str__(self): ret = "" if self.label is not None: ret += str(self.label) + ": " ret += "[" ret += ", ".join([str(param) for param in self.param_list]) ret += "]" return ret def get_cache_key(self, crop_sprites): """ Assemble the sprite meta data into a key, able to identify the sprite. @param crop_sprites: Whether to crop sprites, which allow it. @type crop_sprites: C{bool} @return: Key @rtype: C{tuple} """ filename_8bpp = None filename_32bpp = None if self.bit_depth == 8: filename_8bpp = self.file else: filename_32bpp = self.file filename_8bpp = self.mask_file x = self.xpos.value y = self.ypos.value size_x = self.xsize.value size_y = self.ysize.value if self.bit_depth == 8 or self.mask_pos is None: mask_x, mask_y = x, y else: mask_x = self.mask_pos[0].value mask_y = self.mask_pos[1].value rgb_file, rgb_rect = ( (filename_32bpp.value, (x, y, size_x, size_y)) if filename_32bpp is not None else (None, None) ) mask_file, mask_rect = ( (filename_8bpp.value, (mask_x, mask_y, size_x, size_y)) if filename_8bpp is not None else (None, None) ) do_crop = crop_sprites and ((self.flags.value & FLAG_NOCROP) == 0) return (rgb_file, rgb_rect, mask_file, mask_rect, do_crop) class SpriteAction(base_action.BaseAction): """ @ivar sprite_num: Number of the sprite, or C{None} if not decided yet. @type sprite_num: C{int} or C{None} @ivar last: Whether this sprite action is the last of a series. @type last: C{bool} """ def __init__(self): self.sprite_num = None self.last = False def prepare_output(self, sprite_num): if self.sprite_num is not None and self.sprite_num.value != sprite_num: msg = "Sprite number {:d} given in base_graphics-block, but it doesn't match output sprite number {:d}" msg = msg.format(self.sprite_num.value, sprite_num) raise generic.ScriptError(msg) class RealSpriteAction(SpriteAction): def __init__(self): SpriteAction.__init__(self) self.sprite_list = [] def add_sprite(self, sprite): self.sprite_list.append(sprite) def write(self, file): if len(self.sprite_list) == 0 or self.sprite_list[0].is_empty: file.print_empty_realsprite() else: file.print_sprite([s for s in self.sprite_list if not s.is_empty]) if self.last: file.newline() class RecolourSprite: def __init__(self, mapping, label=None, poslist=None): self.mapping = mapping self.label = label if poslist is None: self.poslist = [] else: self.poslist = poslist def debug_print(self, indentation): generic.print_dbg(indentation, "Recolour sprite, mapping:") for recolour in self.mapping: generic.print_dbg(indentation + 2, "{}: {};".format(recolour.name, recolour.value)) def get_labels(self): labels = {} if self.label is not None: labels[self.label.value] = 0 return labels, 1 def expand(self, default_file, default_mask_file, poslist, id_dict): # create new struct, needed for template expansion new_mapping = [] for old_assignment in self.mapping: from_min_value = old_assignment.name.min.reduce_constant([id_dict]) from_max_value = ( from_min_value if old_assignment.name.max is None else old_assignment.name.max.reduce_constant([id_dict]) ) to_min_value = old_assignment.value.min.reduce_constant([id_dict]) to_max_value = ( None if old_assignment.value.max is None else old_assignment.value.max.reduce_constant([id_dict]) ) new_mapping.append( assignment.Assignment( assignment.Range(from_min_value, from_max_value), assignment.Range(to_min_value, to_max_value), old_assignment.pos, ) ) return [RecolourSprite(new_mapping, poslist=poslist)] def __str__(self): ret = "" if self.label is None else str(self.label) + ": " ret += "recolour_sprite {\n" for recolour in self.mapping: ret += "{}: {};".format(recolour.name, recolour.value) ret += "}" return ret class RecolourSpriteAction(SpriteAction): def __init__(self, sprite): SpriteAction.__init__(self) self.sprite = sprite self.output_table = [] def prepare_output(self, sprite_num): SpriteAction.prepare_output(self, sprite_num) colour_mapping = {} for recolour in self.sprite.mapping: if ( recolour.value.max is not None and recolour.name.max.value - recolour.name.min.value != recolour.value.max.value - recolour.value.min.value ): raise generic.ScriptError( "From and to ranges in a recolour block need to have the same size", recolour.pos ) for i in range(recolour.name.max.value - recolour.name.min.value + 1): idx = recolour.name.min.value + i val = recolour.value.min.value if recolour.value.max is not None: val += i colour_mapping[idx] = val for i in range(256): if i in colour_mapping: colour = colour_mapping[i] else: colour = i self.output_table.append(colour) def write(self, file): file.start_sprite(257) file.print_bytex(0) if file.palette not in ("DEFAULT", "LEGACY"): raise generic.ScriptError( "Recolour sprites are only supported when writing to the DEFAULT (DOS) or LEGACY (WIN) palette." " If you don't have any real sprites use the commandline option -p to set a palette." ) colour_table = self.output_table if file.palette == "DEFAULT" else convert_palette(self.output_table) for idx, colour in enumerate(colour_table): if idx % 16 == 0: file.newline() file.print_bytex(colour) if self.last: file.newline() file.end_sprite() class TemplateUsage: def __init__(self, name, param_list, label, pos): self.name = name self.param_list = param_list self.label = label self.pos = pos def debug_print(self, indentation): generic.print_dbg(indentation, "Template used:", self.name.value) generic.print_dbg(indentation + 2, "Parameters:") for param in self.param_list: param.debug_print(indentation + 4) def get_labels(self): # Load labels from the template definition if self.name.value not in sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + self.name.value, self.name.pos) labels, offset = sprite_template_map[self.name.value].get_labels() # Add (possibly) label applied to ourselves if self.label is not None: if self.label.value in labels: raise generic.ScriptError( "Duplicate label encountered; '{}' already exists.".format(self.label.value), self.pos ) labels[self.label.value] = 0 return labels, offset def expand(self, default_file, default_mask_file, poslist, parameters): if self.name.value not in sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + self.name.value, self.name.pos) template = sprite_template_map[self.name.value] if len(self.param_list) != len(template.param_list): raise generic.ScriptError( "Incorrect number of template arguments. Expected " + str(len(template.param_list)) + ", got " + str(len(self.param_list)), self.pos, ) param_dict = {} for i, param in enumerate(self.param_list): param = param.reduce([real_sprite_flags, parameters]) if not isinstance(param, (expression.ConstantNumeric, expression.StringLiteral)): raise generic.ScriptError("Template parameters should be compile-time constants", param.pos) param_dict[template.param_list[i].value] = param.value return parse_sprite_list( template.sprite_list, default_file, default_mask_file, poslist + [self.pos], param_dict ) def __str__(self): return "{}({})".format(self.name, ", ".join(str(param) for param in self.param_list)) def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict): # check the number of parameters num_param = len(sprite.param_list) if num_param == 0: sprite.is_empty = True return sprite elif not (2 <= num_param <= 9): raise generic.ScriptError( "Invalid number of arguments for real sprite. Expected 2..9.", sprite.param_list[0].pos ) # create new sprite struct, needed for template expansion new_sprite = RealSprite(poslist=poslist + sprite.poslist) param_offset = 0 if num_param >= 6: # xpos, ypos, xsize and ysize are all optional. If not specified they'll default # to 0, 0, image_width, image_height new_sprite.xpos = sprite.param_list[0].reduce_constant([id_dict]) new_sprite.ypos = sprite.param_list[1].reduce_constant([id_dict]) new_sprite.xsize = sprite.param_list[2].reduce_constant([id_dict]) new_sprite.ysize = sprite.param_list[3].reduce_constant([id_dict]) new_sprite.check_sprite_size() param_offset += 4 new_sprite.xrel = sprite.param_list[param_offset].reduce_constant([id_dict]) new_sprite.yrel = sprite.param_list[param_offset + 1].reduce_constant([id_dict]) generic.check_range( new_sprite.xrel.value, -0x8000, 0x7FFF, "Real sprite paramater {:d} 'xrel'".format(param_offset + 1), new_sprite.xrel.pos, ) generic.check_range( new_sprite.yrel.value, -0x8000, 0x7FFF, "Real sprite paramater {:d} 'yrel'".format(param_offset + 2), new_sprite.yrel.pos, ) param_offset += 2 # Next may follow any combination of (flags, filename, mask), but always in that order new_sprite.flags = expression.ConstantNumeric(0) if num_param > param_offset: try: new_sprite.flags = sprite.param_list[param_offset].reduce_constant([real_sprite_flags, id_dict]) param_offset += 1 except generic.ConstError: # No flags pass new_sprite.file = default_file if num_param > param_offset and not isinstance(sprite.param_list[param_offset], expression.Array): new_sprite.file = sprite.param_list[param_offset].reduce([id_dict]) param_offset += 1 if not isinstance(new_sprite.file, expression.StringLiteral): raise generic.ScriptError( "Real sprite parameter {:d} 'file' should be a string literal".format(param_offset + 1), new_sprite.file.pos, ) if new_sprite.file is None: raise generic.ScriptError("No image file specified for real sprite", sprite.param_list[0].pos) new_sprite.mask_file = default_mask_file new_sprite.mask_pos = None if num_param > param_offset: mask = sprite.param_list[param_offset] param_offset += 1 # Mask may be either string (file only) # or array (empty => no mask, 1 value => file only, 2 => offsets only, 3 => file + offsets) if isinstance(mask, expression.Array): if not (0 <= len(mask.values) <= 3): raise generic.ScriptError( "Real sprite mask should be an array with 0 to 3 values, encountered {:d}".format(len(mask.values)), mask.pos, ) if len(mask.values) == 0: # disable any default mask new_sprite.mask_file = None else: if len(mask.values) & 1: new_sprite.mask_file = mask.values[0].reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError( "Real sprite parameter 'mask_file' should be a string literal", new_sprite.file.pos ) if len(mask.values) & 2: new_sprite.mask_pos = tuple(mask.values[i].reduce_constant([id_dict]) for i in range(-2, 0)) # Check that there is also a mask specified, else the offsets make no sense if new_sprite.mask_file is None: raise generic.ScriptError( "Mask offsets are specified, but there is no mask file set.", new_sprite.mask_pos[0].pos ) else: new_sprite.mask_file = mask.reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError( "Real sprite parameter {:d} 'mask' should be an array or string literal".format(param_offset + 1), new_sprite.file.pos, ) if num_param > param_offset: raise generic.ScriptError( "Real sprite has too many parameters, the last {:d} parameter(s) cannot be parsed.".format( num_param - param_offset ), sprite.param_list[param_offset].pos, ) return new_sprite sprite_template_map = {} def parse_sprite_list(sprite_list, default_file, default_mask_file, poslist, parameters=None): real_sprite_list = [] for sprite in sprite_list: real_sprite_list.extend(sprite.expand(default_file, default_mask_file, poslist, parameters or {})) return real_sprite_list def parse_sprite_data(sprite_container): """ @param sprite_container: AST node that contains the sprite data @type sprite_container: L{SpriteContainer} @return: List of real sprite actions @rtype: C{list} of L{BaseAction} """ all_sprite_data = sprite_container.get_all_sprite_data() action_list = [] first = True for sprite_data in all_sprite_data: sprite_list, default_file, default_mask_file, pos, zoom_level, bit_depth = sprite_data new_sprite_list = parse_sprite_list(sprite_list, default_file, default_mask_file, [pos]) if not first and len(new_sprite_list) != len(action_list): msg = "Expected {:d} alternative sprites for {} '{}', got {:d}." msg = msg.format( len(action_list), sprite_container.block_type, sprite_container.block_name.value, len(new_sprite_list) ) raise generic.ScriptError(msg, sprite_container.pos) for i, sprite in enumerate(new_sprite_list): sprite.zoom_level = zoom_level sprite.bit_depth = bit_depth if ( bit_depth == 8 and isinstance(sprite, RealSprite) and (not sprite.is_empty) and sprite.mask_file is not None ): raise generic.ScriptError("Mask file may only be specified for 32bpp sprites.", sprite.mask_file.pos) if first: if isinstance(sprite, RealSprite): action_list.append(RealSpriteAction()) else: assert isinstance(sprite, RecolourSprite) action_list.append(RecolourSpriteAction(sprite)) else: # Not the first sprite, so an alternative sprite if isinstance(sprite, RecolourSprite) or isinstance(action_list[i], RecolourSpriteAction): raise generic.ScriptError( "Alternative sprites may only be provided for and contain real sprites, not recolour sprites.", sprite_container.pos, ) if action_list[i].sprite_list[0].is_empty and not sprite.is_empty: # if the first sprite is empty, all others are ignored generic.print_warning( generic.Warning.OPTIMISATION, "Alternative sprites for an empty real sprite are ignored.", sprite_container.pos, ) if isinstance(sprite, RealSprite): action_list[i].add_sprite(sprite) first = False if len(action_list) != 0: action_list[-1].last = True return action_list ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/actions/sprite_count.py0000644000175100001660000000172014754345605017250 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class SpriteCountAction: def __init__(self, count): self.count = count def prepare_output(self, sprite_num): assert sprite_num == 0 def write(self, file): file.start_sprite(4) file.print_dword(self.count) file.newline() file.end_sprite() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0634625 nml-0.7.6/nml/ast/0000755000175100001660000000000014754345610013303 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/__init__.py0000644000175100001660000000124214754345605015417 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/alt_sprites.py0000644000175100001660000001374414754345605016223 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.ast import base_statement, sprite_container class AltSpritesBlock(base_statement.BaseStatement): """ AST Node for alternative graphics. These are normally 32bpp graphics, possible for a higher zoom-level than the default sprites. Syntax: alternative_sprites(name, zoom_level, bit_depth[, image_file]) @ivar name: The name of the replace/font_glyph/replace_new/spriteset/base_graphics-block this block contains alternative graphics for. @type name: L{expression.Identifier} @ivar zoom_level: The zoomlevel these graphics are for. @type zoom_level: C{int} @ivar bit_depth: Bit depth these graphics are for @type bit_depth: C{int} @ivar image_file: Default graphics file for the sprites in this block. @type image_file: L{expression.StringLiteral} or C{None} @ivar mask_file: Default graphics file for the mask sprites in this block. @type mask_file: L{expression.StringLiteral} or C{None} @ivar sprite_list: List of real sprites or templates expanding to real sprites. @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ def __init__(self, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "alt_sprites-block", pos) if not (3 <= len(param_list) <= 5): raise generic.ScriptError( "alternative_sprites-block requires 3 or 4 parameters, encountered " + str(len(param_list)), pos ) self.name = param_list[0] if not isinstance(self.name, expression.Identifier): raise generic.ScriptError("alternative_sprites parameter 1 'name' must be an identifier", self.name.pos) if isinstance(param_list[1], expression.Identifier) and param_list[1].value in global_constants.zoom_levels: self.zoom_level = global_constants.zoom_levels[param_list[1].value] else: raise generic.ScriptError( "value for alternative_sprites parameter 2 'zoom level' is not a valid zoom level", param_list[1].pos ) if isinstance(param_list[2], expression.Identifier) and param_list[2].value in global_constants.bit_depths: self.bit_depth = global_constants.bit_depths[param_list[2].value] else: raise generic.ScriptError( "value for alternative_sprites parameter 3 'bit depth' is not a valid bit depthl", param_list[2].pos ) if self.bit_depth == 32: global_constants.any_32bpp_sprites = global_constants.allow_32bpp if len(param_list) >= 4: self.image_file = param_list[3].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "alternative_sprites-block parameter 4 'file' must be a string literal", self.image_file.pos ) else: self.image_file = None if len(param_list) >= 5: self.mask_file = param_list[4].reduce() if not isinstance(self.mask_file, expression.StringLiteral): raise generic.ScriptError( "alternative_sprites-block parameter 5 'mask_file' must be a string literal", self.mask_file.pos ) if not self.bit_depth == 32: raise generic.ScriptError("A mask file may only be specified for 32 bpp sprites.", self.mask_file.pos) else: self.mask_file = None self.sprite_list = sprite_list def pre_process(self): if (self.bit_depth == 32 and not global_constants.allow_32bpp) or ( self.zoom_level != 0 and not global_constants.allow_extra_zoom ): return block = sprite_container.SpriteContainer.resolve_sprite_block(self.name) block.add_sprite_data( self.sprite_list, self.image_file, self.pos, self.zoom_level, self.bit_depth, self.mask_file ) def debug_print(self, indentation): generic.print_dbg(indentation, "Alternative sprites") generic.print_dbg(indentation + 2, "Replacement for sprite:", self.name) generic.print_dbg(indentation + 2, "Zoom level:", self.zoom_level) generic.print_dbg(indentation + 2, "Bit depth:", self.bit_depth) generic.print_dbg(indentation + 2, "Source:", self.image_file.value if self.image_file is not None else "None") generic.print_dbg( indentation + 2, "Mask source:", self.mask_file.value if self.mask_file is not None else "None" ) generic.print_dbg(indentation + 2, "Sprites:") for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): params = [ self.name, generic.reverse_lookup(global_constants.zoom_levels, self.zoom_level), generic.reverse_lookup(global_constants.bit_depths, self.bit_depth), ] if self.image_file is not None: params.append(self.image_file) if self.mask_file is not None: params.append(self.mask_file) ret = "alternative_sprites({}) {{\n".format(", ".join(str(p) for p in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/assignment.py0000644000175100001660000000577414754345605016046 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class Assignment: """ Simple storage container for a name / value pair. This class does not enforce any type information. Create a subclass to do this, or hard-code it in the parser. @ivar name: Name of the parameter / property / whatever to be set. @type name: Anything @ivar value: Value to assign to @type value: Anything @ivar pos: Position information of the assignment @type pos: L{Position} """ def __init__(self, name, value, pos): self.name = name self.value = value self.pos = pos def debug_print(self, indentation): generic.print_dbg(indentation, "Assignment") generic.print_dbg(indentation + 2, "Name:") self.name.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Value:") self.value.debug_print(indentation + 4) def __str__(self): return "{}: {};".format(self.name, self.value) class UnitAssignment(Assignment): """ Storage container where the value can have a unit. @ivar unit: Unit of the value, or not C{None} @type unit: L{Unit} """ def __init__(self, name, value, unit, pos): Assignment.__init__(self, name, value, pos) self.unit = unit def debug_print(self, indentation): Assignment.debug_print(self, indentation) generic.print_dbg(indentation + 2, "Unit:") if self.unit is None: generic.print_dbg(indentation + 4, "None") else: generic.print_dbg(indentation + 4, self.unit) def __str__(self): if self.unit is None: return Assignment.__str__(self) else: return "{}: {} {};".format(self.name, self.value, self.unit.name) class Range: """ Storage container for a range of values (inclusive). This Contains a minimum value and optionally also a maximum value. If the maximum values is omitted, the minimum is also used as maximum. @ivar min: The minimum value of this range. @type min: L{Expression} @ivar max: The maximum value of this range. @type max: L{Expression} or C{None} """ def __init__(self, min, max): self.min = min self.max = max def __str__(self): if self.max is None: return str(self.min) return "{} .. {}".format(self.min, self.max) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/base_graphics.py0000644000175100001660000000723614754345605016463 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import real_sprite from nml.ast import base_statement, sprite_container class BaseGraphics(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST node for a 'base_graphics' block. NML syntax: base_graphics [block_name]([[sprite_num ,]default_file]) { ..real sprites.. } @ivar image_file: Default image file to use for sprites. @type image_file: C{None} if not specified, else L{StringLiteral} @ivar sprite_num: Sprite number of the first sprite (if provided explicitly) @type sprite_num: L{Expression} or C{None} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "base_graphics-block", pos) sprite_container.SpriteContainer.__init__(self, "base_graphics-block", name) num_params = len(param_list) if not (0 <= num_params <= 2): raise generic.ScriptError( "base_graphics-block requires 0 to 2 parameters, encountered {:d}".format(num_params), pos ) if num_params >= 2: self.sprite_num = param_list[0].reduce_constant() else: self.sprite_num = None if num_params >= 1: self.image_file = param_list[-1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "The last base_graphics-block parameter 'file' must be a string literal", self.image_file.pos ) else: self.image_file = None self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos) def debug_print(self, indentation): generic.print_dbg(indentation, "base_graphics-block") generic.print_dbg(indentation + 2, "Source:", self.image_file.value if self.image_file is not None else "None") if self.block_name: generic.print_dbg(indentation + 2, "Name:", self.block_name) if self.sprite_num is not None: generic.print_dbg(indentation + 2, "Sprite number:", self.sprite_num) generic.print_dbg(indentation + 2, "Sprites:") for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): actions = real_sprite.parse_sprite_data(self) actions[0].sprite_num = self.sprite_num return actions def __str__(self): name = str(self.block_name) if self.block_name is not None else "" params = [] if self.sprite_num is None else [self.sprite_num] if self.image_file is not None: params.append(self.image_file) ret = "base_graphics {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/base_statement.py0000644000175100001660000001412514754345605016662 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class BaseStatement: """ Base class for a statement (AST node) in NML. Note: All instance variables (except 'pos') are prefixed with "bs_" to avoid naming conflicts @ivar bs_name: Name of the statement type @type bs_name: C{str} @ivar pos: Position information @type pos: L{Position} @ivar bs_skipable: Whether the statement may be skipped using an if-block (default: True) @type bs_skipable: C{bool} @ivar bs_loopable: Whether the statement may be executed multiple times using a while-block (default: True) @type bs_loopable: C{bool} @pre: bs_skipable or not bs_loopable @ivar bs_in_item: Whether the statement may be part of an item block (default: False) @type bs_in_item: C{bool} @ivar bs_out_item: Whether the statement may appear outside of item blocks (default: True) @type bs_out_item: C{bool} """ def __init__(self, name, pos, skipable=True, loopable=True, in_item=False, out_item=True): assert skipable or not loopable self.bs_name = name self.pos = pos self.bs_skipable = skipable self.bs_loopable = loopable self.bs_in_item = in_item self.bs_out_item = out_item def validate(self, scope_list): """ Validate that this statement is in a valid location @param scope_list: List of nested blocks containing this statement @type scope_list: C{list} of L{BaseStatementList} """ seen_item = False for scope in scope_list: if scope.list_type == BaseStatementList.LIST_TYPE_SKIP: if not self.bs_skipable: raise generic.ScriptError( "{} may not appear inside a conditional block.".format(self.bs_name), self.pos ) if scope.list_type == BaseStatementList.LIST_TYPE_LOOP: if not self.bs_loopable: raise generic.ScriptError("{} may not appear inside a loop.".format(self.bs_name), self.pos) if scope.list_type == BaseStatementList.LIST_TYPE_ITEM: seen_item = True if not self.bs_in_item: raise generic.ScriptError("{} may not appear inside an item block.".format(self.bs_name), self.pos) if not (seen_item or self.bs_out_item): raise generic.ScriptError("{} must appear inside an item block.".format(self.bs_name), self.pos) def register_names(self): """ Called to register identifiers, that must be available before their definition. """ pass def pre_process(self): """ Called to do any pre-processing before the actual action generation. For example, to remove identifiesr """ pass def debug_print(self, indentation): """ Print all AST information to the standard output @param indentation: Print all lines with at least C{indentation} spaces @type indentation: C{int} """ raise NotImplementedError("debug_print must be implemented in BaseStatement-subclass {!r}".format(type(self))) def get_action_list(self): """ Generate a list of NFO actions associated with this statement @return: A list of action @rtype: C{list} of L{BaseAction} """ raise NotImplementedError( "get_action_list must be implemented in BaseStatement-subclass {!r}".format(type(self)) ) def __str__(self): """ Generate a string representing this statement in valid NML-code. @return: An NML string representing this action @rtype: C{str} """ raise NotImplementedError("__str__ must be implemented in BaseStatement-subclass {!r}".format(type(self))) class BaseStatementList(BaseStatement): """ Base class for anything that contains a list of statements @ivar list_type: Type of this list, used for validation logic @type list_type: C{int}, see constants below @pre list_type in (LIST_TYPE_NONE, LIST_TYPE_SKIP, LIST_TYPE_LOOP, LIST_TYPE_ITEM) @ivar statements: List of sub-statements in this block @type statements: C{list} of L{BaseStatement} """ LIST_TYPE_NONE = 0 LIST_TYPE_SKIP = 1 LIST_TYPE_LOOP = 2 LIST_TYPE_ITEM = 3 def __init__(self, name, pos, list_type, statements, skipable=True, loopable=True, in_item=False, out_item=True): BaseStatement.__init__(self, name, pos, skipable, loopable, in_item, out_item) assert list_type in (self.LIST_TYPE_NONE, self.LIST_TYPE_SKIP, self.LIST_TYPE_LOOP, self.LIST_TYPE_ITEM) self.list_type = list_type self.statements = statements def validate(self, scope_list): new_list = scope_list + [self] for stmt in self.statements: stmt.validate(new_list) def register_names(self): for stmt in self.statements: stmt.register_names() def pre_process(self): for stmt in self.statements: stmt.pre_process() def debug_print(self, indentation): for stmt in self.statements: stmt.debug_print(indentation) def get_action_list(self): action_list = [] for stmt in self.statements: action_list.extend(stmt.get_action_list()) return action_list def __str__(self): res = "" for stmt in self.statements: res += "\t" + str(stmt).replace("\n", "\n\t")[0:-1] return res ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/basecost.py0000644000175100001660000001553114754345605015471 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.actions import action0 from nml.ast import assignment, base_statement class BaseCost(base_statement.BaseStatement): """ AST Node for a base costs table. @ivar costs: List of base cost values to set. @type costs: C{list} of L{Assignment} """ def __init__(self, costs, pos): base_statement.BaseStatement.__init__(self, "basecost-block", pos) self.costs = costs def pre_process(self): new_costs = [] for cost in self.costs: cost.value = cost.value.reduce(global_constants.const_list) if isinstance(cost.value, expression.ConstantNumeric): generic.check_range(cost.value.value, -8, 16, "Base cost value", cost.value.pos) cost.value = nmlop.ADD(cost.value, 8).reduce() if isinstance(cost.name, expression.Identifier): if cost.name.value in base_cost_table: cost.name = expression.ConstantNumeric(base_cost_table[cost.name.value][0]) new_costs.append(cost) elif cost.name.value in generic_base_costs: # create temporary list, so it can be sorted for efficiency tmp_list = [] for num, type in base_cost_table.values(): if type == cost.name.value: tmp_list.append( assignment.Assignment(expression.ConstantNumeric(num), cost.value, cost.name.pos) ) tmp_list.sort(key=lambda x: x.name.value) new_costs.extend(tmp_list) else: raise generic.ScriptError( "Unrecognized base cost identifier '{}' encountered".format(cost.name.value), cost.name.pos ) else: cost.name = cost.name.reduce() if isinstance(cost.name, expression.ConstantNumeric): generic.check_range(cost.name.value, 0, len(base_cost_table), "Base cost number", cost.name.pos) new_costs.append(cost) self.costs = new_costs def debug_print(self, indentation): generic.print_dbg(indentation, "Base costs") for cost in self.costs: cost.debug_print(indentation + 2) def get_action_list(self): return action0.get_basecost_action(self) def __str__(self): ret = "basecost {\n" for cost in self.costs: ret += "\t{}: {};\n".format(cost.name, cost.value) ret += "}\n" return ret base_cost_table = { "PR_STATION_VALUE": (0, ""), "PR_BUILD_RAIL": (1, "PR_CONSTRUCTION"), "PR_BUILD_ROAD": (2, "PR_CONSTRUCTION"), "PR_BUILD_SIGNALS": (3, "PR_CONSTRUCTION"), "PR_BUILD_BRIDGE": (4, "PR_CONSTRUCTION"), "PR_BUILD_DEPOT_TRAIN": (5, "PR_CONSTRUCTION"), "PR_BUILD_DEPOT_ROAD": (6, "PR_CONSTRUCTION"), "PR_BUILD_DEPOT_SHIP": (7, "PR_CONSTRUCTION"), "PR_BUILD_TUNNEL": (8, "PR_CONSTRUCTION"), "PR_BUILD_STATION_RAIL": (9, "PR_CONSTRUCTION"), "PR_BUILD_STATION_RAIL_LENGTH": (10, "PR_CONSTRUCTION"), "PR_BUILD_STATION_AIRPORT": (11, "PR_CONSTRUCTION"), "PR_BUILD_STATION_BUS": (12, "PR_CONSTRUCTION"), "PR_BUILD_STATION_TRUCK": (13, "PR_CONSTRUCTION"), "PR_BUILD_STATION_DOCK": (14, "PR_CONSTRUCTION"), "PR_BUILD_VEHICLE_TRAIN": (15, "PR_BUILD_VEHICLE"), "PR_BUILD_VEHICLE_WAGON": (16, "PR_BUILD_VEHICLE"), "PR_BUILD_VEHICLE_AIRCRAFT": (17, "PR_BUILD_VEHICLE"), "PR_BUILD_VEHICLE_ROAD": (18, "PR_BUILD_VEHICLE"), "PR_BUILD_VEHICLE_SHIP": (19, "PR_BUILD_VEHICLE"), "PR_BUILD_TREES": (20, "PR_CONSTRUCTION"), "PR_TERRAFORM": (21, "PR_CONSTRUCTION"), "PR_CLEAR_GRASS": (22, "PR_CONSTRUCTION"), "PR_CLEAR_ROUGH": (23, "PR_CONSTRUCTION"), "PR_CLEAR_ROCKS": (24, "PR_CONSTRUCTION"), "PR_CLEAR_FIELDS": (25, "PR_CONSTRUCTION"), "PR_CLEAR_TREES": (26, "PR_CONSTRUCTION"), "PR_CLEAR_RAIL": (27, "PR_CONSTRUCTION"), "PR_CLEAR_SIGNALS": (28, "PR_CONSTRUCTION"), "PR_CLEAR_BRIDGE": (29, "PR_CONSTRUCTION"), "PR_CLEAR_DEPOT_TRAIN": (30, "PR_CONSTRUCTION"), "PR_CLEAR_DEPOT_ROAD": (31, "PR_CONSTRUCTION"), "PR_CLEAR_DEPOT_SHIP": (32, "PR_CONSTRUCTION"), "PR_CLEAR_TUNNEL": (33, "PR_CONSTRUCTION"), "PR_CLEAR_WATER": (34, "PR_CONSTRUCTION"), "PR_CLEAR_STATION_RAIL": (35, "PR_CONSTRUCTION"), "PR_CLEAR_STATION_AIRPORT": (36, "PR_CONSTRUCTION"), "PR_CLEAR_STATION_BUS": (37, "PR_CONSTRUCTION"), "PR_CLEAR_STATION_TRUCK": (38, "PR_CONSTRUCTION"), "PR_CLEAR_STATION_DOCK": (39, "PR_CONSTRUCTION"), "PR_CLEAR_HOUSE": (40, "PR_CONSTRUCTION"), "PR_CLEAR_ROAD": (41, "PR_CONSTRUCTION"), "PR_RUNNING_TRAIN_STEAM": (42, "PR_RUNNING"), "PR_RUNNING_TRAIN_DIESEL": (43, "PR_RUNNING"), "PR_RUNNING_TRAIN_ELECTRIC": (44, "PR_RUNNING"), "PR_RUNNING_AIRCRAFT": (45, "PR_RUNNING"), "PR_RUNNING_ROADVEH": (46, "PR_RUNNING"), "PR_RUNNING_SHIP": (47, "PR_RUNNING"), "PR_BUILD_INDUSTRY": (48, "PR_CONSTRUCTION"), "PR_CLEAR_INDUSTRY": (49, "PR_CONSTRUCTION"), "PR_BUILD_UNMOVABLE": (50, "PR_CONSTRUCTION"), "PR_CLEAR_UNMOVABLE": (51, "PR_CONSTRUCTION"), "PR_BUILD_WAYPOINT_RAIL": (52, "PR_CONSTRUCTION"), "PR_CLEAR_WAYPOINT_RAIL": (53, "PR_CONSTRUCTION"), "PR_BUILD_WAYPOINT_BUOY": (54, "PR_CONSTRUCTION"), "PR_CLEAR_WAYPOINT_BUOY": (55, "PR_CONSTRUCTION"), "PR_TOWN_ACTION": (56, "PR_CONSTRUCTION"), "PR_BUILD_FOUNDATION": (57, "PR_CONSTRUCTION"), "PR_BUILD_INDUSTRY_RAW": (58, "PR_CONSTRUCTION"), "PR_BUILD_TOWN": (59, "PR_CONSTRUCTION"), "PR_BUILD_CANAL": (60, "PR_CONSTRUCTION"), "PR_CLEAR_CANAL": (61, "PR_CONSTRUCTION"), "PR_BUILD_AQUEDUCT": (62, "PR_CONSTRUCTION"), "PR_CLEAR_AQUEDUCT": (63, "PR_CONSTRUCTION"), "PR_BUILD_LOCK": (64, "PR_CONSTRUCTION"), "PR_CLEAR_LOCK": (65, "PR_CONSTRUCTION"), "PR_MAINTENANCE_RAIL": (66, "PR_RUNNING"), "PR_MAINTENANCE_ROAD": (67, "PR_RUNNING"), "PR_MAINTENANCE_CANAL": (68, "PR_RUNNING"), "PR_MAINTENANCE_STATION": (69, "PR_RUNNING"), "PR_MAINTENANCE_AIRPORT": (70, "PR_RUNNING"), } generic_base_costs = ["PR_CONSTRUCTION", "PR_RUNNING", "PR_BUILD_VEHICLE"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/cargotable.py0000644000175100001660000000443014754345605015765 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action0 from nml.ast import base_statement class CargoTable(base_statement.BaseStatement): def __init__(self, cargo_list, pos): base_statement.BaseStatement.__init__(self, "cargo table", pos, False, False) self.cargo_list = cargo_list def register_names(self): generic.OnlyOnce.enforce(self, "cargo table") for i, cargo in enumerate(self.cargo_list): if isinstance(cargo, expression.Identifier): self.cargo_list[i] = expression.StringLiteral(cargo.value, cargo.pos) expression.parse_string_to_dword( self.cargo_list[i] ) # we don't care about the result, only validate the input if self.cargo_list[i].value in global_constants.cargo_numbers: generic.print_warning( generic.Warning.GENERIC, "Duplicate entry in cargo table: {}".format(self.cargo_list[i].value), cargo.pos, ) else: global_constants.cargo_numbers[self.cargo_list[i].value] = i def debug_print(self, indentation): generic.print_dbg(indentation, "Cargo table") for cargo in self.cargo_list: generic.print_dbg(indentation, "Cargo:", cargo.value) def get_action_list(self): return action0.get_cargolist_action(self.cargo_list) def __str__(self): ret = "cargotable {\n" ret += ", ".join([expression.identifier_to_print(cargo.value) for cargo in self.cargo_list]) ret += "\n}\n" return ret ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/conditional.py0000644000175100001660000000560014754345605016165 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants from nml.actions import action7 from nml.ast import base_statement class ConditionalList(base_statement.BaseStatementList): """ Wrapper for a complete if/else if/else if/else block. """ def __init__(self, conditionals): assert len(conditionals) > 0 base_statement.BaseStatementList.__init__( self, "if/else-block", conditionals[0].pos, base_statement.BaseStatementList.LIST_TYPE_SKIP, conditionals, in_item=True, ) def get_action_list(self): return action7.parse_conditional_block(self) def debug_print(self, indentation): generic.print_dbg(indentation, "Conditional") base_statement.BaseStatementList.debug_print(self, indentation + 2) def __str__(self): ret = "" ret += " else ".join([str(stmt) for stmt in self.statements]) ret += "\n" return ret class Conditional(base_statement.BaseStatementList): """ Condition along with the code that has to be executed if the condition evaluates to some value not equal to 0. @ivar expr: The expression where the execution of code in this block depends on. @type expr: L{Expression} """ def __init__(self, expr, block, pos): base_statement.BaseStatementList.__init__( self, "if/else-block", pos, base_statement.BaseStatementList.LIST_TYPE_SKIP, block, in_item=True ) self.expr = expr def pre_process(self): if self.expr is not None: self.expr = self.expr.reduce(global_constants.const_list) base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): if self.expr is not None: generic.print_dbg(indentation, "Expression:") self.expr.debug_print(indentation + 2) generic.print_dbg(indentation, "Block:") base_statement.BaseStatementList.debug_print(self, indentation + 2) def __str__(self): ret = "" if self.expr is not None: ret += "if ({})".format(self.expr) ret += " {\n" ret += base_statement.BaseStatementList.__str__(self) ret += "}\n" return ret ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/constant.py0000644000175100001660000000360514754345605015516 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.ast import base_statement class Constant(base_statement.BaseStatement): def __init__(self, name, value): base_statement.BaseStatement.__init__(self, "constant", name.pos, False, False) self.name = name self.value = value def register_names(self): if not isinstance(self.name, expression.Identifier): raise generic.ScriptError("Constant name should be an identifier", self.name.pos) if self.name.value in global_constants.constant_numbers: raise generic.ScriptError("Redefinition of constant '{}'.".format(self.name.value), self.name.pos) global_constants.constant_numbers[self.name.value] = self.value.reduce_constant( global_constants.const_list ).value def debug_print(self, indentation): generic.print_dbg(indentation, "Constant") generic.print_dbg(indentation + 2, "Name:") self.name.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Value:") self.value.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): return "const {} = {};".format(self.name, self.value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/deactivate.py0000644000175100001660000000303314754345605015771 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import actionE from nml.ast import base_statement class DeactivateBlock(base_statement.BaseStatement): def __init__(self, grfid_list, pos): base_statement.BaseStatement.__init__(self, "deactivate()", pos) self.grfid_list = grfid_list def pre_process(self): # Parse (string-)expressions to integers self.grfid_list = [expression.parse_string_to_dword(grfid.reduce()) for grfid in self.grfid_list] def debug_print(self, indentation): generic.print_dbg(indentation, "Deactivate other newgrfs:") for grfid in self.grfid_list: grfid.debug_print(indentation + 2) def get_action_list(self): return actionE.parse_deactivate_block(self) def __str__(self): return "deactivate({});\n".format(", ".join(str(grfid) for grfid in self.grfid_list)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/disable_item.py0000644000175100001660000000566214754345605016313 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants from nml.actions import action0 from nml.ast import base_statement, general class DisableItem(base_statement.BaseStatement): """ Class representing a 'disable_item' statement in the AST. @ivar feature: Feature of the items to disable @type feature: L{ConstantNumeric} @ivar first_id: First item ID to disable @type first_id: L{ConstantNumeric}, or C{None} if not set @ivar last_id: Last item ID to disable @type last_id: L{ConstantNumeric}, or C{None} if not set """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "disable_item()", pos) if not (1 <= len(param_list) <= 3): raise generic.ScriptError( "disable_item() requires between 1 and 3 parameters, encountered {:d}.".format(len(param_list)), pos ) self.feature = general.parse_feature(param_list[0]) self.first_id = param_list[1] if len(param_list) > 1 else None self.last_id = param_list[2] if len(param_list) > 2 else None def pre_process(self): if self.first_id is not None: self.first_id = self.first_id.reduce_constant(global_constants.const_list) if self.last_id is not None: self.last_id = self.last_id.reduce_constant(global_constants.const_list) if self.last_id.value < self.first_id.value: raise generic.ScriptError("Last id to disable may not be lower than the first id.", self.last_id.pos) def debug_print(self, indentation): generic.print_dbg(indentation, "Disable items, feature=" + str(self.feature.value)) if self.first_id is not None: generic.print_dbg(indentation + 2, "First ID:") self.first_id.debug_print(indentation + 4) if self.last_id is not None: generic.print_dbg(indentation + 2, "Last ID:") self.last_id.debug_print(indentation + 4) def __str__(self): ret = str(self.feature) if self.first_id is not None: ret += ", " + str(self.first_id) if self.last_id is not None: ret += ", " + str(self.last_id) return "disable_item({});\n".format(ret) def get_action_list(self): return action0.get_disable_actions(self) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/error.py0000644000175100001660000000772414754345605015024 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import actionB from nml.ast import base_statement class Error(base_statement.BaseStatement): """ An error has occured while parsing the GRF. This can be anything ranging from an imcompatible GRF file that was found or a game setting that is set to the wrong value to a wrong combination of parameters. The action taken by the host depends on the severity level of the error. NML equivalent: error(level, message[, extra_text[, parameter1[, parameter2]]]). @ivar params: Extra expressions whose value can be used in the error string. @type params: C{list} of L{Expression} @ivar severity: Severity level of this error, value between 0 and 3. @type severity: L{Expression} @ivar msg: The string to be used for this error message. This can be either one of the predifined error strings or a custom string from the language file. @type msg: L{Expression} @ivar data: Optional extra message that is inserted in place of the second {STRING}-code of msg. @type data: C{None} or L{String} or L{StringLiteral} """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "error()", pos) if not 2 <= len(param_list) <= 5: raise generic.ScriptError( "'error' expects between 2 and 5 parameters, got " + str(len(param_list)), self.pos ) self.severity = param_list[0] self.msg = param_list[1] self.data = param_list[2] if len(param_list) >= 3 else None self.params = param_list[3:] def pre_process(self): self.severity = self.severity.reduce([actionB.error_severity]) self.msg = self.msg.reduce([actionB.default_error_msg]) if self.data: self.data = self.data.reduce() self.params = [x.reduce() for x in self.params] def debug_print(self, indentation): generic.print_dbg(indentation, "Error message") generic.print_dbg(indentation + 2, "Message:") self.msg.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Severity:") self.severity.debug_print(indentation + 4) generic.print_dbg(indentation, "Data: ") if self.data is not None: self.data.debug_print(indentation + 4) if len(self.params) > 0: generic.print_dbg(indentation + 2, "Param1: ") self.params[0].debug_print(indentation + 4) if len(self.params) > 1: generic.print_dbg(indentation + 2, "Param2: ") self.params[1].debug_print(indentation + 4) def get_action_list(self): return actionB.parse_error_block(self) def __str__(self): sev = str(self.severity) if isinstance(self.severity, expression.ConstantNumeric): for s in actionB.error_severity: if self.severity.value == actionB.error_severity[s]: sev = s break res = "error({}, {}".format(sev, self.msg) if self.data is not None: res += ", {}".format(self.data) if len(self.params) > 0: res += ", {}".format(self.params[0]) if len(self.params) > 1: res += ", {}".format(self.params[1]) res += ");\n" return res ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/font.py0000644000175100001660000000644714754345605014642 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import action12 from nml.ast import base_statement, sprite_container class FontGlyphBlock(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST class for a font_glyph block Syntax: font_glyph( @ivar font_size: Size of the font to provide characters for (NORMAL/SMALL/LARGE/MONO) @type font_size: L{Expression} @ivar base_char: First character to replace @type base_char: L{Expression} @ivar image_file: Default file to use for the contained real sprites (none if N/A) @type image_file: L{StringLiteral} or C{None} @ivar sprite_list: List of real sprites @type sprite_list: C{list} of L{RealSprite} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "font_glyph-block", pos) sprite_container.SpriteContainer.__init__(self, "font_glyph-block", name) if not (2 <= len(param_list) <= 3): raise generic.ScriptError( "font_glyph-block requires 2 or 3 parameters, encountered " + str(len(param_list)), pos ) self.font_size = param_list[0] self.base_char = param_list[1] self.image_file = param_list[2].reduce() if len(param_list) >= 3 else None if self.image_file is not None and not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "font_glyph-block parameter 3 'file' must be a string literal", self.image_file.pos ) self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos) def debug_print(self, indentation): generic.print_dbg(indentation, "Load font glyphs, starting at", self.base_char) generic.print_dbg(indentation + 2, "Font size: ", self.font_size) generic.print_dbg( indentation + 2, "Source: ", self.image_file.value if self.image_file is not None else "None" ) generic.print_dbg(indentation + 2, "Sprites:") for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return action12.parse_action12(self) def __str__(self): name = str(self.block_name) if self.block_name is not None else "" params = [self.font_size, self.base_char] if self.image_file is not None: params.append(self.image_file) ret = "font_glyph {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/general.py0000644000175100001660000000542214754345605015301 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.ast import base_statement feature_ids = { "FEAT_TRAINS": 0x00, "FEAT_ROADVEHS": 0x01, "FEAT_SHIPS": 0x02, "FEAT_AIRCRAFT": 0x03, "FEAT_STATIONS": 0x04, "FEAT_CANALS": 0x05, "FEAT_BRIDGES": 0x06, "FEAT_HOUSES": 0x07, "FEAT_GLOBALVARS": 0x08, "FEAT_INDUSTRYTILES": 0x09, "FEAT_INDUSTRIES": 0x0A, "FEAT_CARGOS": 0x0B, "FEAT_SOUNDEFFECTS": 0x0C, "FEAT_AIRPORTS": 0x0D, "FEAT_SIGNALS": 0x0E, "FEAT_OBJECTS": 0x0F, "FEAT_RAILTYPES": 0x10, "FEAT_AIRPORTTILES": 0x11, "FEAT_ROADTYPES": 0x12, "FEAT_TRAMTYPES": 0x13, "FEAT_ROADSTOPS": 0x14, } def feature_name(feature): """ Given a feature number, return the identifier as normally used for that feature in nml files. @param feature: The feature number to convert to a string. @type feature: C{int} @return: String with feature as used in nml files. @rtype: C{str} """ for name, num in feature_ids.items(): if num == feature: return name raise AssertionError("Invalid feature number '{}'.".format(feature)) def parse_feature(expr): """ Parse an expression into a valid feature number. @param expr: Expression to parse @type expr: L{Expression} @return: A constant number representing the parsed feature @rtype: L{ConstantNumeric} """ expr = expr.reduce_constant([feature_ids]) if expr.value not in feature_ids.values(): raise generic.ScriptError("Invalid feature '{:02X}' encountered.".format(expr.value), expr.pos) return expr class MainScript(base_statement.BaseStatementList): def __init__(self, statements): assert len(statements) > 0 base_statement.BaseStatementList.__init__( self, "main script", statements[0].pos, base_statement.BaseStatementList.LIST_TYPE_NONE, statements, False, False, False, False, ) def __str__(self): res = "" for stmt in self.statements: res += str(stmt) + "\n" return res ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/grf.py0000644000175100001660000003270314754345605014444 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, grfstrings from nml.actions import action8, action14 from nml.ast import base_statement palette_node = None blitter_node = None """ Statistics about registers used for parameters. The 1st field is the largest parameter register used. The 2nd field is the maximum amount of parameter registers available. This is where L{action6.free_parameters} begins. """ param_stats = [0, 0x40] def print_stats(): """ Print statistics about used ids. """ if param_stats[0] > 0: generic.print_info("GRF parameter registers: {}/{}".format(param_stats[0], param_stats[1])) def set_palette_used(pal): """ Set the used palette in the action 14 node, if applicable @param pal: Palette to use @type pal: C{str} of length 1 """ if palette_node: palette_node.pal = pal def set_preferred_blitter(blitter): """ Set the preferred blitter in the action14 node, if applicable @param blitter: The blitter to set @type blitter: C{str} of length 1 """ if blitter_node: blitter_node.blitter = blitter class GRF(base_statement.BaseStatement): """ AST Node for a grf block, that supplies (static) information about the GRF This is equivalent to actions 8 and 14 @ivar name: Name of the GRF (short) @type name: L{Expression}, should be L{String} else user error @ivar desc: Description of the GRF (longer) @type name: L{Expression}, should be L{String} else user error @ivar grfid: Globally unique identifier of the GRF @type grfid: L{Expression}, should be L{StringLiteral} of 4 bytes else user error @ivar version: Version of this GRF @type version: L{Expression} @ivar min_compatible_version: Minimum (older) version of the same GRF that it is compatible with @type min_compatible_version: L{Expression} @ivar params: List of user-configurable GRF parameters @type params: C{list} of L{ParameterDescription} """ def __init__(self, alist, pos): base_statement.BaseStatement.__init__(self, "grf-block", pos, False, False) self.name = None self.desc = None self.url = None self.grfid = None self.version = None self.min_compatible_version = None self.params = [] for assignment in alist: if isinstance(assignment, ParameterDescription): self.params.append(assignment) elif assignment.name.value == "name": self.name = assignment.value elif assignment.name.value == "desc": self.desc = assignment.value elif assignment.name.value == "url": self.url = assignment.value elif assignment.name.value == "grfid": self.grfid = assignment.value elif assignment.name.value == "version": self.version = assignment.value elif assignment.name.value == "min_compatible_version": self.min_compatible_version = assignment.value else: raise generic.ScriptError("Unknown item in GRF-block: " + str(assignment.name), assignment.name.pos) def register_names(self): generic.OnlyOnce.enforce(self, "GRF-block") def pre_process(self): if None in (self.name, self.desc, self.grfid, self.version, self.min_compatible_version): raise generic.ScriptError( "A GRF-block requires the" " 'name', 'desc', 'grfid', 'version' and 'min_compatible_version' properties to be set.", self.pos, ) self.grfid = self.grfid.reduce() global_constants.constant_numbers["GRFID"] = expression.parse_string_to_dword(self.grfid) self.name = self.name.reduce() if not isinstance(self.name, expression.String): raise generic.ScriptError("GRF-name must be a string", self.name.pos) grfstrings.validate_string(self.name) self.desc = self.desc.reduce() if not isinstance(self.desc, expression.String): raise generic.ScriptError("GRF-description must be a string", self.desc.pos) grfstrings.validate_string(self.desc) if self.url is not None: self.url = self.url.reduce() if not isinstance(self.url, expression.String): raise generic.ScriptError("URL must be a string", self.url.pos) grfstrings.validate_string(self.url) self.version = self.version.reduce_constant() self.min_compatible_version = self.min_compatible_version.reduce_constant() global param_stats param_num = 0 for param in self.params: param.pre_process(expression.ConstantNumeric(param_num)) param_num = param.num.value + 1 if param_num > param_stats[1]: raise generic.ScriptError( "No free parameters available." " Consider assigning manually and combine multiple bool parameters into" " a single bitmask parameter using .", self.pos, ) if param_num > param_stats[0]: param_stats[0] = param_num def debug_print(self, indentation): generic.print_dbg(indentation, "GRF") generic.print_dbg(indentation + 2, "grfid:") self.grfid.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Name:") self.name.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Description:") self.desc.debug_print(indentation + 4) if self.url is not None: generic.print_dbg(indentation + 2, "URL:") self.url.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Version:") self.version.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Minimal compatible version:") self.min_compatible_version.debug_print(indentation + 4) if len(self.params) > 0: generic.print_dbg(indentation + 2, "Params:") for param in self.params: param.debug_print(indentation + 4) def get_action_list(self): global palette_node, blitter_node palette_node = action14.UsedPaletteNode("A") blitter_node = action14.BlitterNode("8") action14_root = action14.BranchNode("INFO") action14.grf_name_desc_actions( action14_root, self.name, self.desc, self.url, self.version, self.min_compatible_version ) action14.param_desc_actions(action14_root, self.params) action14_root.subnodes.append(palette_node) action14_root.subnodes.append(blitter_node) return action14.get_actions(action14_root) + [action8.Action8(self.grfid, self.name, self.desc)] def __str__(self): ret = "grf {\n" ret += "\tgrfid: {};\n".format(str(self.grfid)) ret += "\tname: {};\n".format(str(self.name)) ret += "\tdesc: {};\n".format(str(self.desc)) if self.url is not None: ret += "\turl: {};\n".format(self.url) ret += "\tversion: {};\n".format(self.version) ret += "\tmin_compatible_version: {};\n".format(self.min_compatible_version) for param in self.params: ret += str(param) ret += "}\n" return ret class ParameterSetting: def __init__(self, name, value_list): self.name = name self.value_list = value_list self.type = "int" self.name_string = None self.desc_string = None self.min_val = None self.max_val = None self.def_val = None self.bit_num = None self.val_names = [] self.properties_set = set() def pre_process(self): for set_val in self.value_list: self.set_property(set_val.name.value, set_val.value) def __str__(self): ret = "\t\t{} {{\n".format(self.name) for val in self.value_list: if val.name.value == "names": ret += "\t\t\tnames: {\n" for name in val.value.values: ret += "\t\t\t\t{}: {};\n".format(name.name, name.value) ret += "\t\t\t};\n" else: ret += "\t\t\t{}: {};\n".format(val.name, val.value) ret += "\t\t}\n" return ret def debug_print(self, indentation): self.name.debug_print(indentation) for val in self.value_list: val.name.debug_print(indentation + 2) if val.name.value == "names": for name in val.value.values: name.name.debug_print(indentation + 4) name.value.debug_print(indentation + 4) else: val.value.debug_print(indentation + 4) def set_property(self, name, value): """ Set a single parameter property @param name: Name of the property to be set @type name: C{str} @param value: Value of the property (note: may be an array) @type value: L{Expression} """ if name in self.properties_set: raise generic.ScriptError( "You cannot set the same property twice in a parameter description block", value.pos ) self.properties_set.add(name) if name == "names": for name_value in value.values: num = name_value.name.reduce_constant().value desc = name_value.value if not isinstance(desc, expression.String): raise generic.ScriptError("setting name description must be a string", desc.pos) self.val_names.append((num, desc)) return value = value.reduce(unknown_id_fatal=False) if name == "type": if not isinstance(value, expression.Identifier) or (value.value != "int" and value.value != "bool"): raise generic.ScriptError("setting-type must be either 'int' or 'bool'", value.pos) self.type = value.value elif name == "name": if not isinstance(value, expression.String): raise generic.ScriptError("setting-name must be a string", value.pos) self.name_string = value elif name == "desc": if not isinstance(value, expression.String): raise generic.ScriptError("setting-description must be a string", value.pos) self.desc_string = value elif name == "bit": if self.type != "bool": raise generic.ScriptError("setting-bit is only valid for 'bool' settings", value.pos) self.bit_num = value.reduce_constant() elif name == "min_value": if self.type != "int": raise generic.ScriptError("setting-min_value is only valid for 'int' settings", value.pos) self.min_val = value.reduce_constant() elif name == "max_value": if self.type != "int": raise generic.ScriptError("setting-max_value is only valid for 'int' settings", value.pos) self.max_val = value.reduce_constant() elif name == "def_value": self.def_val = value.reduce_constant() if self.type == "bool" and self.def_val.value != 0 and self.def_val.value != 1: raise generic.ScriptError("setting-def_value must be either 0 or 1 for 'bool' settings", value.pos) else: raise generic.ScriptError("Unknown setting-property " + name, value.pos) class ParameterDescription: def __init__(self, setting_list, num=None, pos=None): self.setting_list = setting_list self.num = num self.pos = pos def __str__(self): ret = "\tparam" if self.num: ret += " " + str(self.num) ret += " {\n" for setting in self.setting_list: ret += str(setting) ret += "\t}\n" return ret def debug_print(self, indentation): if self.num is not None: self.num.debug_print(indentation) for setting in self.setting_list: setting.debug_print(indentation + 2) def pre_process(self, num): if self.num is None: self.num = num self.num = self.num.reduce_constant() for setting in self.setting_list: setting.pre_process() for setting in self.setting_list: if setting.type == "int": if len(self.setting_list) > 1: raise generic.ScriptError( "When packing multiple settings in one parameter only bool settings are allowed", self.pos ) global_constants.settings[setting.name.value] = {"num": self.num.value, "size": 4} else: bit = 0 if setting.bit_num is None else setting.bit_num.value global_constants.misc_grf_bits[setting.name.value] = {"param": self.num.value, "bit": bit} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/item.py0000644000175100001660000002750414754345605014627 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action0, action2, action2var, action3 from nml.ast import base_statement, general item_feature = None item_id = None item_size = None class Item(base_statement.BaseStatementList): """ AST-node representing an item block @ivar feature: Feature of the item @type feature: L{ConstantNumeric} @ivar name: Name of the item @type name: L{Identifier} or C{None} if N/A. @ivar id: Numeric ID of the item @type id: L{ConstantNumeric} or C{None} if N/A @ivar size: Size, used by houses only @type size: L{ConstantNumeric} or C{None} if N/A """ def __init__(self, params, body, pos): base_statement.BaseStatementList.__init__( self, "item-block", pos, base_statement.BaseStatementList.LIST_TYPE_ITEM, body ) if not (1 <= len(params) <= 4): raise generic.ScriptError( "Item block requires between 1 and 4 parameters, found {:d}.".format(len(params)), self.pos ) self.feature = general.parse_feature(params[0]) if self.feature.value in (0x08, 0x0C, 0x0E): raise generic.ScriptError("Defining item blocks for this feature is not allowed.", self.pos) self.name = params[1] if len(params) >= 2 else None self.id = params[2] if len(params) >= 3 else None if len(params) >= 4: if self.feature.value != 0x07: raise generic.ScriptError("item-block parameter 4 'size' may only be set for houses", params[3].pos) self.size = params[3].reduce_constant(global_constants.const_list) if self.size.value not in action0.house_sizes: raise generic.ScriptError("item-block parameter 4 'size' does not have a valid value", self.size.pos) else: self.size = None def register_names(self): if self.id is not None: self.id = self.id.reduce_constant(global_constants.const_list) if isinstance(self.id, expression.ConstantNumeric) and self.id.value == -1: self.id = None # id == -1 means default if self.name: if not isinstance(self.name, expression.Identifier): raise generic.ScriptError("Item parameter 2 'name' should be an identifier", self.pos) if self.name.value in global_constants.item_names: existing_id = global_constants.item_names[self.name.value].id if self.id is not None and existing_id.value != self.id.value: raise generic.ScriptError( ( "Duplicate item with name '{}'." " This item has already been assigned to id {:d}, cannot reassign to id {:d}" ).format(self.name.value, existing_id.value, self.id.value), self.pos, ) self.id = existing_id # We may have to reserve multiple item IDs for houses num_ids = action0.house_sizes[self.size.value] if self.size is not None else 1 if self.id is None: self.id = expression.ConstantNumeric(action0.get_free_id(self.feature.value, num_ids, self.pos)) elif not action0.check_id_range(self.feature.value, self.id.value, num_ids, self.id.pos): action0.mark_id_used(self.feature.value, self.id.value, num_ids) if self.name is not None: global_constants.item_names[self.name.value] = self base_statement.BaseStatementList.register_names(self) def pre_process(self): global item_feature, item_id, item_size item_id = self.id item_feature = self.feature.value item_size = self.size base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): generic.print_dbg(indentation, "Item, feature", hex(self.feature.value)) base_statement.BaseStatementList.debug_print(self, indentation + 2) def get_action_list(self): global item_feature, item_id, item_size item_id = self.id item_feature = self.feature.value item_size = self.size return base_statement.BaseStatementList.get_action_list(self) def __str__(self): ret = "item({:d}".format(self.feature.value) if self.name is not None: ret += ", {}".format(self.name) ret += ", {}".format(str(self.id) if self.id is not None else "-1") if self.size is not None: sizes = ["1X1", None, "2X1", "1X2", "2X2"] ret += ", HOUSE_SIZE_{}".format(sizes[self.size.value]) ret += ") {\n" ret += base_statement.BaseStatementList.__str__(self) ret += "}\n" return ret class Property: """ AST-node representing a single property. These are only valid insde a PropertyBlock. @ivar name: The name (or number) of this property. @type name: L{Identifier} or L{ConstantNumeric}. @ivar value: The value that will be assigned to this property. @type value: L{Expression}. @ivar unit: The unit of the value. @type unit: L{Unit} @ivar pos: Position information. @type pos: L{Position} """ def __init__(self, name, value, unit, pos): self.pos = pos self.name = name self.value = value self.unit = unit def pre_process(self): self.value = self.value.reduce(global_constants.const_list, unknown_id_fatal=False) def debug_print(self, indentation): generic.print_dbg(indentation, "Property:", self.name.value) self.value.debug_print(indentation + 2) def __str__(self): unit = "" if self.unit is None else " " + str(self.unit) return "\t{}: {}{};".format(self.name, self.value, unit) class PropertyBlock(base_statement.BaseStatement): """ Block that contains a list of property/value pairs to be assigned to the current item. @ivar prop_list: List of properties. @type prop_list: C{list} of L{Property} """ def __init__(self, prop_list, pos): base_statement.BaseStatement.__init__(self, "property-block", pos, in_item=True, out_item=False) self.prop_list = prop_list def pre_process(self): for prop in self.prop_list: prop.pre_process() def debug_print(self, indentation): generic.print_dbg(indentation, "Property block:") for prop in self.prop_list: prop.debug_print(indentation + 2) def get_action_list(self): return action0.parse_property_block(self.prop_list, item_feature, item_id, item_size) def __str__(self): ret = "property {\n" for prop in self.prop_list: ret += "{}\n".format(prop) ret += "}\n" return ret class LiveryOverride(base_statement.BaseStatement): def __init__(self, wagon_id, graphics_block, pos): base_statement.BaseStatement.__init__(self, "livery override", pos, in_item=True, out_item=False) self.graphics_block = graphics_block self.wagon_id = wagon_id def pre_process(self): self.graphics_block.pre_process() def debug_print(self, indentation): generic.print_dbg(indentation, "Livery override, wagon id:") self.wagon_id.debug_print(indentation + 2) for graphics in self.graphics_block.graphics_list: graphics.debug_print(indentation + 2) generic.print_dbg(indentation + 2, "Default graphics:", self.graphics_block.default_graphics) def get_action_list(self): wagon_id = self.wagon_id.reduce_constant([(global_constants.item_names, global_constants.item_to_id)]) return action3.parse_graphics_block(self.graphics_block, item_feature, wagon_id, item_size, True) def __str__(self): ret = "livery_override({}) {{\n".format(self.wagon_id) for graphics in self.graphics_block.graphics_list: ret += "\t{}\n".format(graphics) if self.graphics_block.default_graphics is not None: ret += "\t{}\n".format(self.graphics_block.default_graphics) ret += "}\n" return ret graphics_base_class = action2.make_sprite_group_class(False, False, True) class GraphicsBlock(graphics_base_class): def __init__(self, graphics_list, default_graphics, pos): base_statement.BaseStatement.__init__(self, "graphics-block", pos, in_item=True, out_item=False) self.graphics_list = graphics_list self.default_graphics = default_graphics def pre_process(self): for graphics_def in self.graphics_list: graphics_def.reduce_expressions(action2var.get_scope(item_feature)) if self.default_graphics is not None: if self.default_graphics.value is None: raise generic.ScriptError( "Returning the computed value is not possible in a graphics-block, as there is no computed value.", self.pos, ) self.default_graphics.value = action2var.reduce_varaction2_expr( self.default_graphics.value, action2var.get_scope(item_feature) ) # initialize base class and pre_process it as well (in that order) self.initialize(None, item_feature) graphics_base_class.pre_process(self) def collect_references(self): all_refs = [] for result in [g.result for g in self.graphics_list] + ( [self.default_graphics] if self.default_graphics is not None else [] ): all_refs += result.value.collect_references() return all_refs def debug_print(self, indentation): generic.print_dbg(indentation, "Graphics block:") for graphics in self.graphics_list: graphics.debug_print(indentation + 2) if self.default_graphics is not None: generic.print_dbg(indentation + 2, "Default graphics:") self.default_graphics.debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action3.parse_graphics_block(self, item_feature, item_id, item_size) return [] def __str__(self): ret = "graphics {\n" for graphics in self.graphics_list: ret += "\t{}\n".format(graphics) if self.default_graphics is not None: ret += "\t{}\n".format(self.default_graphics) ret += "}\n" return ret class GraphicsDefinition: def __init__(self, cargo_id, result, unit=None): self.cargo_id = cargo_id self.result = result self.unit = unit def reduce_expressions(self, var_scope): # Do not reduce cargo-id (yet) if self.result.value is None: raise generic.ScriptError( "Returning the computed value is not possible in a graphics-block, as there is no computed value.", self.result.pos, ) self.result.value = action2var.reduce_varaction2_expr(self.result.value, var_scope) def debug_print(self, indentation): generic.print_dbg(indentation, "Cargo ID:") self.cargo_id.debug_print(indentation + 2) generic.print_dbg(indentation, "Result:") self.result.debug_print(indentation + 2) def __str__(self): return "{}: {}".format(self.cargo_id, self.result) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/loop.py0000644000175100001660000000363514754345605014641 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants from nml.actions import action7 from nml.ast import base_statement class Loop(base_statement.BaseStatementList): """ AST node for a while-loop. @ivar expr: The conditional to check whether the loop continues. @type expr: L{Expression} """ def __init__(self, expr, block, pos): base_statement.BaseStatementList.__init__( self, "while-loop", pos, base_statement.BaseStatementList.LIST_TYPE_LOOP, block, in_item=True ) self.expr = expr def pre_process(self): self.expr = self.expr.reduce(global_constants.const_list) base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): generic.print_dbg(indentation, "While loop") generic.print_dbg(indentation + 2, "Expression:") self.expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Block:") base_statement.BaseStatementList.debug_print(self, indentation + 4) def get_action_list(self): return action7.parse_loop_block(self) def __str__(self): ret = "while({}) {{\n".format(self.expr) ret += base_statement.BaseStatementList.__str__(self) ret += "}\n" return ret ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/override.py0000644000175100001660000000457414754345605015512 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action0 from nml.ast import base_statement class EngineOverride(base_statement.BaseStatement): """ AST Node for an engine override. @ivar grfid: GRFid of the grf to override the engines from. @type grfid: C{int{ @ivar source_grfid: GRFid of the grf that overrides the engines. @type source_grfid: C{int} """ def __init__(self, args, pos): base_statement.BaseStatement.__init__(self, "engine_override()", pos) self.args = args def pre_process(self): if len(self.args) not in (1, 2): raise generic.ScriptError("engine_override expects 1 or 2 parameters", self.pos) if len(self.args) == 1: try: self.source_grfid = expression.Identifier("GRFID").reduce(global_constants.const_list).value assert isinstance(self.source_grfid, int) except generic.ScriptError: raise generic.ScriptError("GRFID of this grf is required, but no grf-block is defined.", self.pos) else: self.source_grfid = expression.parse_string_to_dword(self.args[0].reduce(global_constants.const_list)) self.grfid = expression.parse_string_to_dword(self.args[-1].reduce(global_constants.const_list)) def debug_print(self, indentation): generic.print_dbg(indentation, "Engine override") generic.print_dbg(indentation, "Source:", self.source_grfid) generic.print_dbg(indentation, "Target:", self.grfid) def get_action_list(self): return action0.get_engine_override_action(self) def __str__(self): return "engine_override({});\n".format(", ".join(str(x) for x in self.args)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/produce.py0000644000175100001660000001466714754345605015340 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import action2, action2production, action2var from nml.ast import base_statement produce_base_class = action2.make_sprite_group_class(False, True, True) class ProduceOld(produce_base_class): """ AST node for a 'produce'-block, which is basically equivalent to the production callback. Syntax: produce(name, sub1, sub2, sub3, add1, add2[, again]) @ivar param_list: List of parameters supplied to the produce-block. - 0..2: Amounts of cargo to subtract from input - 3..4: Amounts of cargo to add to output - 5: Run the production CB again if nonzero @type param_list: C{list} of L{Expression} """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "produce-block", pos, False, False) if not (6 <= len(param_list) <= 7): raise generic.ScriptError( "produce-block requires 6 or 7 parameters, encountered {:d}".format(len(param_list)), self.pos ) name = param_list[0] if not isinstance(name, expression.Identifier): raise generic.ScriptError("produce parameter 1 'name' should be an identifier.", name.pos) self.initialize(name, 0x0A) self.param_list = param_list[1:] if len(self.param_list) < 6: self.param_list.append(expression.ConstantNumeric(0)) def pre_process(self): generic.print_warning( generic.Warning.DEPRECATION, "Consider using the new produce() syntax for '{}'".format(self.name), self.name.pos, ) var_scope = action2var.get_scope(0x0A) for i, param in enumerate(self.param_list): self.param_list[i] = action2var.reduce_varaction2_expr(param, var_scope) produce_base_class.pre_process(self) def collect_references(self): all_refs = [] for expr in self.param_list: all_refs += expr.collect_references() return all_refs def __str__(self): return "produce({});\n".format(", ".join(str(x) for x in [self.name] + self.param_list)) def debug_print(self, indentation): generic.print_dbg(indentation, "Produce, name =", self.name) generic.print_dbg(indentation + 2, "Subtract from input:") for expr in self.param_list[0:3]: expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Add to output:") for expr in self.param_list[3:5]: expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Again:") self.param_list[5].debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action2production.get_production_actions(self) return [] class Produce(produce_base_class): """ AST node for a 'produce'-block for version 2 industry production callback. Syntax: produce(name, [INP1: expression; ...], [OUT1: expression; ...], again) or: produce(name, [INP1: expression; ...], [OUT1: expression; ...]) @ivar name: name given to this produce block @type name: L{Identifier} @ivar subtract_in: List of input cargos that are consumed for this production (cargo label to register-expression mapping) @type subtract_in: C{list} of L{Assignment} @ivar add_out: List of output cargos that are generated for this production (cargo label to register-expression mapping) @type add_out: C{list} of L{Assignment} @ivar again: Production CB is repeated if this is a register evaluating to nonzero @type again: L{Expression} """ def __init__(self, name, subtract_in, add_out, again, pos): if not isinstance(name, expression.Identifier): raise generic.ScriptError("produce parameter 1 'name' should be an identifier.", name.pos) base_statement.BaseStatement.__init__(self, "produce-block", pos, False, False) self.initialize(name, 0x0A) self.subtract_in = subtract_in self.add_out = add_out self.again = again def pre_process(self): var_scope = action2var.get_scope(0x0A) for i, param in enumerate(self.subtract_in): self.subtract_in[i].value = action2var.reduce_varaction2_expr(param.value, var_scope) for i, param in enumerate(self.add_out): self.add_out[i].value = action2var.reduce_varaction2_expr(param.value, var_scope) self.again = action2var.reduce_varaction2_expr(self.again, var_scope) produce_base_class.pre_process(self) def collect_references(self): all_refs = [] for cargopair in self.subtract_in + self.add_out: all_refs += cargopair.value.collect_references() return all_refs def __str__(self): return "produce({0}, [{1}], [{2}], {3})\n".format( str(self.name), " ".join(str(x) for x in self.subtract_in), " ".join(str(x) for x in self.add_out), str(self.again), ) def debug_print(self, indentation): generic.print_dbg(indentation, "Produce, name =", self.name) for cargopair in self.subtract_in: generic.print_dbg(indentation + 2, "Subtract from input {0}:".format(cargopair.name.value)) cargopair.value.debug_print(indentation + 4) for cargopair in self.add_out: generic.print_dbg(indentation + 2, "Add to output {0}:".format(cargopair.name.value)) cargopair.value.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Again:") self.again.debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action2production.get_production_v2_actions(self) return [] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/replace.py0000644000175100001660000001560414754345605015302 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action5, actionA from nml.ast import base_statement, sprite_container class ReplaceSprite(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST node for a 'replace' block. NML syntax: replace [name] (start_id[, default_file]) { ..real sprites.. } @ivar start_id: First sprite to replace. Extracted from C{param_list} during pre-processing. @type start_id: C{Expression} @ivar image_file: Default image file to use for sprites. @type image_file: C{None} if not specified, else L{StringLiteral} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "replace-block", pos) sprite_container.SpriteContainer.__init__(self, "replace-block", name) num_params = len(param_list) if not (1 <= num_params <= 2): raise generic.ScriptError("replace-block requires 1 or 2 parameters, encountered " + str(num_params), pos) self.start_id = param_list[0] if num_params >= 2: self.image_file = param_list[1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "replace-block parameter 2 'file' must be a string literal", self.image_file.pos ) else: self.image_file = None self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos) def pre_process(self): self.start_id = self.start_id.reduce(global_constants.const_list) if isinstance(self.start_id, expression.ConstantNumeric): generic.check_range( self.start_id.value, 0, 4895 - len(self.sprite_list), "replace-block parameter 1 'start'", self.start_id.pos, ) def debug_print(self, indentation): generic.print_dbg(indentation, "Replace sprites starting at") self.start_id.debug_print(indentation + 2) generic.print_dbg(indentation + 2, "Source:", self.image_file.value if self.image_file is not None else "None") if self.block_name is not None: generic.print_dbg(indentation + 2, "Name:", self.block_name.value) generic.print_dbg(indentation + 2, "Sprites:") for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return actionA.parse_actionA(self) def __str__(self): name = str(self.block_name) if self.block_name is not None else "" def_file = "" if self.image_file is None else ", " + str(self.image_file) ret = "replace {}({}{}) {{\n".format(name, self.start_id, def_file) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret class ReplaceNewSprite(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST node for a 'replacenew' block. NML syntax: replacenew [name](type[, default_file[, offset]]) { ..real sprites.. } @ivar type: Type of sprites to replace. @type type: L{Identifier} @ivar image_file: Default image file to use for sprites. @type image_file: C{None} if not specified, else L{StringLiteral} @ivar offset: Offset into the block of sprites. @type offset: C{int} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "replacenew-block", pos) sprite_container.SpriteContainer.__init__(self, "replacenew-block", name) num_params = len(param_list) if not (1 <= num_params <= 3): raise generic.ScriptError( "replacenew-block requires 1 to 3 parameters, encountered " + str(num_params), pos ) self.type = param_list[0] if not isinstance(self.type, expression.Identifier): raise generic.ScriptError( "replacenew parameter 'type' must be an identifier of a sprite replacement type", self.type.pos ) if num_params >= 2: self.image_file = param_list[1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "replacenew-block parameter 2 'file' must be a string literal", self.image_file.pos ) else: self.image_file = None if num_params >= 3: self.offset = param_list[2].reduce_constant().value generic.check_range(self.offset, 0, 0xFFFF, "replacenew-block parameter 3 'offset'", param_list[2].pos) else: self.offset = 0 self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos) def debug_print(self, indentation): generic.print_dbg(indentation, "Replace sprites for new features of type", self.type) generic.print_dbg(indentation + 2, "Offset: ", self.offset) generic.print_dbg( indentation + 2, "Source: ", self.image_file.value if self.image_file is not None else "None" ) if self.block_name is not None: generic.print_dbg(indentation + 2, "Name:", self.block_name.value) generic.print_dbg(indentation + 2, "Sprites:") for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return action5.parse_action5(self) def __str__(self): name = str(self.block_name) if self.block_name is not None else "" params = [self.type] if self.image_file is not None: params.append(self.image_file) if self.offset != 0: params.append(self.offset) ret = "replacenew {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/skipall.py0000644000175100001660000000226414754345605015324 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import action7 from nml.ast import base_statement class SkipAll(base_statement.BaseStatement): """ Skip everything after this statement. """ def __init__(self, pos): base_statement.BaseStatement.__init__(self, "exit-statement", pos) def get_action_list(self): return [action7.UnconditionalSkipAction(9, 0)] def debug_print(self, indentation): generic.print_dbg(indentation, "Skip all") def __str__(self): return "exit;\n" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/snowline.py0000644000175100001660000001501214754345605015516 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import datetime from nml import expression, generic, nmlop from nml.actions import action0 from nml.ast import base_statement class Snowline(base_statement.BaseStatement): """ Snowline curve throughout the year. @ivar type: Type of snowline. @type type: C{str} @ivar date_heights: Height of the snow line at given days in the year. @type date_heights: C{list} of L{UnitAssignment} """ def __init__(self, line_type, height_data, pos): base_statement.BaseStatement.__init__(self, "snowline-block", pos) if line_type.value not in ("equal", "linear"): raise generic.ScriptError( 'Unknown type of snow line (only "equal" and "linear" are supported)', line_type.pos ) self.type = line_type.value self.date_heights = height_data def debug_print(self, indentation): generic.print_dbg(indentation, "Snowline (type={})".format(self.type)) for dh in self.date_heights: dh.debug_print(indentation + 2) def __str__(self): return "snowline ({}) {{\n\t{}\n}}\n".format(str(self.type), "\n\t".join(str(x) for x in self.date_heights)) def get_action_list(self): return action0.get_snowlinetable_action(compute_table(self)) def compute_table(snowline): """ Compute the table with snowline height for each day of the year. @param snowline: Snowline definition. @type snowline: L{Snowline} @return: Table of 12*32 entries with snowline heights. @rtype: C{str} """ day_table = [None] * 365 # Height at each day, starting at day 0 for dh in snowline.date_heights: doy = dh.name.reduce() if not isinstance(doy, expression.ConstantNumeric): raise generic.ScriptError("Day of year is not a compile-time constant", doy.pos) if doy.value < 1 or doy.value > 365: raise generic.ScriptError("Day of the year must be between 1 and 365", doy.pos) height = dh.value.reduce() if isinstance(height, expression.ConstantNumeric) and height.value < 0: raise generic.ScriptError("Height must be at least 0", height.pos) if dh.unit is None: if isinstance(height, expression.ConstantNumeric) and height.value > 255: raise generic.ScriptError("Height must be at most 255", height.pos) else: unit = dh.unit if unit.type != "snowline": raise generic.ScriptError('Expected a snowline percentage ("snow%")', height.pos) if isinstance(height, expression.ConstantNumeric) and height.value > 100: raise generic.ScriptError("Height must be at most 100 snow%", height.pos) mul, div = unit.convert, 1 if isinstance(mul, tuple): mul, div = mul # Factor out common factors gcd = generic.greatest_common_divisor(mul, div) mul //= gcd div //= gcd if isinstance(height, (expression.ConstantNumeric, expression.ConstantFloat)): # Even if mul == div == 1, we have to round floats and adjust value height = expression.ConstantNumeric(int(float(height.value) * mul / div + 0.5), height.pos) elif mul != div: # Compute (value * mul + div/2) / div height = nmlop.MUL(height, mul) height = nmlop.ADD(height, int(div / 2)) height = nmlop.DIV(height, div) # For 'linear' snow-line, only accept integer constants. if snowline.type != "equal" and not isinstance(height, expression.ConstantNumeric): raise generic.ScriptError("Height is not a compile-time constant", height.pos) day_table[doy.value - 1] = height # Find first specified point. start = 0 while start < 365 and day_table[start] is None: start = start + 1 if start == 365: raise generic.ScriptError("No heights given for the snowline table", snowline.pos) first_point = start while True: # Find second point from start end = start + 1 if end == 365: end = 0 while end != first_point and day_table[end] is None: end = end + 1 if end == 365: end = 0 # Fill the days between start and end (exclusive both border values) startvalue = day_table[start] endvalue = day_table[end] unwrapped_end = end if end < start: unwrapped_end += 365 if snowline.type == "equal": for day in range(start + 1, unwrapped_end): if day >= 365: day -= 365 day_table[day] = startvalue else: assert snowline.type == "linear" if start != end: dhd = float(endvalue.value - startvalue.value) / float(unwrapped_end - start) else: assert startvalue.value == endvalue.value dhd = 0 for day in range(start + 1, unwrapped_end): uday = day if uday >= 365: uday -= 365 height = startvalue.value + int(round(dhd * (day - start))) day_table[uday] = expression.ConstantNumeric(height) if end == first_point: # All days done break start = end table = [None] * (12 * 32) for dy in range(365): today = datetime.date.fromordinal(dy + 1) if day_table[dy]: expr = day_table[dy].reduce() else: expr = None table[(today.month - 1) * 32 + today.day - 1] = expr for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] # Second loop is needed because we need make sure the first item is also set. for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] return table ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/sort_vehicles.py0000644000175100001660000000452014754345605016533 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action0 from nml.ast import base_statement, general class SortVehicles(base_statement.BaseStatement): """ AST-node representing a sort-vehicles block. @ivar feature: Feature of the item @type feature: L{ConstantNumeric} @ivar vehid_list: List of vehicle ids. @type vehid_list: L{Array}. """ def __init__(self, params, pos): base_statement.BaseStatement.__init__(self, "sort-block", pos) if len(params) != 2: raise generic.ScriptError( "Sort-block requires exactly two parameters, got {:d}".format(len(params)), self.pos ) self.feature = general.parse_feature(params[0]) self.vehid_list = params[1] def pre_process(self): self.vehid_list = self.vehid_list.reduce(global_constants.const_list) if not isinstance(self.vehid_list, expression.Array) or not all( isinstance(x, expression.ConstantNumeric) for x in self.vehid_list.values ): raise generic.ScriptError( "Second parameter is not an array of one of the items in it could not be reduced to a constant number", self.pos, ) def debug_print(self, indentation): generic.print_dbg(indentation, "Sort, feature", hex(self.feature.value)) for id in self.vehid_list.values: generic.print_dbg(indentation + 2, "Vehicle id:", id) def get_action_list(self): return action0.parse_sort_block(self.feature.value, self.vehid_list.values) def __str__(self): return "sort({:d}, {});\n".format(self.feature.value, self.vehid_list) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/sprite_container.py0000644000175100001660000000732014754345605017233 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class SpriteContainer: """ Base class for all AST Nodes that contain real sprites Note that this does not inherit from BaseStatement, to (amongst other things) avoid various multiple inheritance issues with Spritesets @ivar block_type: Type of block (e.g. 'spriteset' ,'replace') @type block_type: C{str} @ivar block_name: Block-specific name @type block_name: L{Identifier}, or C{None} if N/A @ivar sprite_data: Mapping of (zoom level, bit-depth) to (sprite list, default file) @type sprite_data: C{dict} that maps (C{tuple} of (C{int}, C{int})) to (C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position})) """ sprite_blocks = {} def __init__(self, block_type, block_name): self.block_type = block_type self.block_name = block_name self.sprite_data = {} if block_name is not None: if block_name.value in SpriteContainer.sprite_blocks: raise generic.ScriptError( "Block with name '{}' is already defined.".format(block_name.value), block_name.pos ) SpriteContainer.sprite_blocks[block_name.value] = self def add_sprite_data(self, sprite_list, default_file, pos, zoom_level=0, bit_depth=8, default_mask_file=None): assert zoom_level in range(0, 6) assert bit_depth in (8, 32) key = (zoom_level, bit_depth) if key in self.sprite_data: msg = ( "Sprites are already defined for {} '{}' for this zoom " + "level / bit depth combination. This data will be overridden." ) msg = msg.format(self.block_type, self.block_name.value) generic.print_warning(generic.Warning.GENERIC, msg, pos) self.sprite_data[key] = (sprite_list, default_file, default_mask_file, pos) def get_all_sprite_data(self): """ Get all sprite data. Sorting makes sure that the order is consistent, and that the normal zoom, 8bpp sprites appear first. @return: List of 6-tuples (sprite_list, default_file, default_mask_file, position, zoom_level, bit_depth). @rtype: C{list} of C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position}, C{int}, C{int}) """ return [val + key for key, val in sorted(self.sprite_data.items())] @classmethod def resolve_sprite_block(cls, block_name): if block_name.value in cls.sprite_blocks: return cls.sprite_blocks[block_name.value] raise generic.ScriptError( "Undeclared block identifier '{}' encountered".format(block_name.value), block_name.pos ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/spriteblock.py0000644000175100001660000003273714754345605016216 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action2, action2layout, action2real, real_sprite from nml.ast import base_statement, sprite_container class TemplateDeclaration(base_statement.BaseStatement): def __init__(self, name, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "template declaration", pos, False, False) self.name = name self.param_list = param_list self.sprite_list = sprite_list def pre_process(self): # check that all templates that are referred to exist at this point # This prevents circular dependencies for sprite in self.sprite_list: if isinstance(sprite, real_sprite.TemplateUsage): if sprite.name.value == self.name.value: raise generic.ScriptError( "Sprite template '{}' includes itself.".format(sprite.name.value), self.pos ) elif sprite.name.value not in real_sprite.sprite_template_map: raise generic.ScriptError( "Encountered unknown template identifier: " + sprite.name.value, sprite.pos ) # Register template if self.name.value not in real_sprite.sprite_template_map: real_sprite.sprite_template_map[self.name.value] = self else: raise generic.ScriptError( "Template named '{}' is already defined, first definition at {}".format( self.name.value, real_sprite.sprite_template_map[self.name.value].pos ), self.pos, ) def get_labels(self): labels = {} offset = 0 for sprite in self.sprite_list: sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in sprite_labels.items(): if lbl in labels: raise generic.ScriptError("Duplicate label encountered; '{}' already exists.".format(lbl), self.pos) labels[lbl] = lbl_offset + offset offset += num_sprites return labels, offset def debug_print(self, indentation): generic.print_dbg(indentation, "Template declaration:", self.name.value) generic.print_dbg(indentation + 2, "Parameters:") for param in self.param_list: param.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Sprites:") for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): ret = "template {}({}) {{\n".format(str(self.name), ", ".join([str(param) for param in self.param_list])) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret spriteset_base_class = action2.make_sprite_group_class(True, True, False, cls_is_relocatable=True) class SpriteSet(spriteset_base_class, sprite_container.SpriteContainer): def __init__(self, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "spriteset", pos, False, False) if not (1 <= len(param_list) <= 5): raise generic.ScriptError("Spriteset requires 1 to 5 parameters, encountered " + str(len(param_list)), pos) name = param_list[0] if not isinstance(name, expression.Identifier): raise generic.ScriptError("Spriteset parameter 1 'name' should be an identifier", name.pos) sprite_container.SpriteContainer.__init__(self, "spriteset", name) self.initialize(name) self.zoom_level = 0 self.bit_depth = 8 self.image_file = None if len(param_list) >= 3: if isinstance(param_list[1], expression.Identifier) and param_list[1].value in global_constants.zoom_levels: self.zoom_level = global_constants.zoom_levels[param_list[1].value] else: raise generic.ScriptError( "value for Spriteset-block parameter 2 'zoom level' is not a valid zoom level", param_list[1].pos ) if isinstance(param_list[2], expression.Identifier) and param_list[2].value in global_constants.bit_depths: self.bit_depth = global_constants.bit_depths[param_list[2].value] else: raise generic.ScriptError( "value for Spriteset-block parameter 3 'bit depth' is not a valid bit depth", param_list[2].pos ) if len(param_list) >= 4: self.image_file = param_list[3].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "Spriteset-block parameter 4 'file' must be a string literal", self.image_file.pos ) elif len(param_list) >= 2: self.image_file = param_list[1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "Spriteset-block parameter 2 'file' must be a string literal", self.image_file.pos ) if self.bit_depth == 32: global_constants.any_32bpp_sprites = global_constants.allow_32bpp if len(param_list) >= 5: self.mask_file = param_list[4].reduce() if not isinstance(self.mask_file, expression.StringLiteral): raise generic.ScriptError( "Spriteset-block parameter 5 'mask_file' must be a string literal", self.mask_file.pos ) if not self.bit_depth == 32: raise generic.ScriptError("A mask file may only be specified for 32 bpp sprites.", self.mask_file.pos) else: self.mask_file = None self.sprite_list = sprite_list self.action1_num = None # set number in action1 self.labels = {} # mapping of real sprite labels to offsets self.add_sprite_data(self.sprite_list, self.image_file, pos, self.zoom_level, self.bit_depth, self.mask_file) def pre_process(self): spriteset_base_class.pre_process(self) offset = 0 for sprite in self.sprite_list: sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in sprite_labels.items(): if lbl in self.labels: raise generic.ScriptError("Duplicate label encountered; '{}' already exists.".format(lbl), self.pos) self.labels[lbl] = lbl_offset + offset offset += num_sprites def collect_references(self): return [] def debug_print(self, indentation): generic.print_dbg(indentation, "Sprite set:", self.name.value) generic.print_dbg( indentation + 2, "Source: ", self.image_file.value if self.image_file is not None else "None" ) generic.print_dbg(indentation + 2, "Sprites:") for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): # Actions are created when parsing the action2s, not here return [] def __str__(self): params = [ self.name, generic.reverse_lookup(global_constants.zoom_levels, self.zoom_level), generic.reverse_lookup(global_constants.bit_depths, self.bit_depth), ] if self.image_file is not None: params.append(self.image_file) if self.mask_file is not None: params.append(self.mask_file) ret = "spriteset({}) {{\n".format(", ".join(str(p) for p in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(str(sprite)) ret += "}\n" return ret spritegroup_base_class = action2.make_sprite_group_class(False, True, False) class SpriteGroup(spritegroup_base_class): def __init__(self, name, spriteview_list, pos=None): base_statement.BaseStatement.__init__(self, "spritegroup", pos, False, False) self.initialize(name) self.spriteview_list = spriteview_list def pre_process(self): for spriteview in self.spriteview_list: spriteview.pre_process() spritegroup_base_class.pre_process(self) def collect_references(self): return [] def debug_print(self, indentation): generic.print_dbg(indentation, "Sprite group:", self.name.value) for spriteview in self.spriteview_list: spriteview.debug_print(indentation + 2) def get_action_list(self): action_list = [] if self.prepare_act2_output(): for feature in sorted(self.feature_set): action_list.extend(action2real.get_real_action2s(self, feature)) return action_list def __str__(self): ret = "spritegroup {} {{\n".format(self.name) for spriteview in self.spriteview_list: ret += "\t{}\n".format(spriteview) ret += "}\n" return ret class SpriteView: def __init__(self, name, spriteset_list, pos): self.name = name self.spriteset_list = spriteset_list self.pos = pos def pre_process(self): self.spriteset_list = [x.reduce(global_constants.const_list) for x in self.spriteset_list] for sg_ref in self.spriteset_list: if not ( isinstance(sg_ref, expression.SpriteGroupRef) and action2.resolve_spritegroup(sg_ref.name).is_spriteset() ): raise generic.ScriptError("Expected a sprite set reference", sg_ref.pos) if len(sg_ref.param_list) != 0: raise generic.ScriptError( "Spritesets referenced from a spritegroup may not have parameters.", sg_ref.pos ) def debug_print(self, indentation): generic.print_dbg(indentation, "Sprite view:", self.name.value) generic.print_dbg(indentation + 2, "Sprite sets:") for spriteset in self.spriteset_list: spriteset.debug_print(indentation + 4) def __str__(self): return "{}: [{}];".format(self.name, ", ".join([str(spriteset) for spriteset in self.spriteset_list])) spritelayout_base_class = action2.make_sprite_group_class(False, True, False) class SpriteLayout(spritelayout_base_class): def __init__(self, name, param_list, layout_sprite_list, pos=None): base_statement.BaseStatement.__init__(self, "spritelayout", pos, False, False) self.initialize(name, None, len(param_list)) self.param_list = param_list self.register_map = {} # Set during action generation for easier referencing self.layout_sprite_list = layout_sprite_list # Do not reduce expressions here as they may contain variables # And the feature is not known yet def pre_process(self): # Check parameter names seen_names = set() for param in self.param_list: if not isinstance(param, expression.Identifier): raise generic.ScriptError("spritelayout parameter names must be identifiers.", param.pos) if param.value in seen_names: raise generic.ScriptError("Duplicate parameter name '{}' encountered.".format(param.value), param.pos) seen_names.add(param.value) spritelayout_base_class.pre_process(self) def collect_references(self): return [] def debug_print(self, indentation): generic.print_dbg(indentation, "Sprite layout:", self.name.value) generic.print_dbg(indentation + 2, "Parameters:") for param in self.param_list: param.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Sprites:") for layout_sprite in self.layout_sprite_list: layout_sprite.debug_print(indentation + 4) def __str__(self): params = "" if not self.param_list else "({})".format(", ".join(str(x) for x in self.param_list)) return "spritelayout {}{} {{\n{}\n}}\n".format( str(self.name), params, "\n".join(str(x) for x in self.layout_sprite_list) ) def get_action_list(self): action_list = [] if self.prepare_act2_output(): for feature in sorted(self.feature_set): if feature == 0x04: continue action_list.extend(action2layout.get_layout_action2s(self, feature)) return action_list class LayoutSprite: def __init__(self, ls_type, param_list, pos): self.type = ls_type self.param_list = param_list self.pos = pos def debug_print(self, indentation): generic.print_dbg(indentation, "Tile layout sprite of type:", self.type) for layout_param in self.param_list: layout_param.debug_print(indentation + 2) def __str__(self): return "\t{} {{\n\t\t{}\n\t}}".format( self.type, "\n\t\t".join(str(layout_param) for layout_param in self.param_list) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/switch.py0000644000175100001660000005031214754345605015163 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action2, action2random, action2var from nml.ast import base_statement, general var_ranges = {"SELF": 0x89, "PARENT": 0x8A} # Used by Switch and RandomSwitch switch_base_class = action2.make_sprite_group_class(False, True, True) class Switch(switch_base_class): def __init__(self, param_list, body, pos): base_statement.BaseStatement.__init__(self, "switch-block", pos, False, False) if len(param_list) < 4: raise generic.ScriptError( "Switch-block requires at least 4 parameters, encountered " + str(len(param_list)), pos ) if not isinstance(param_list[1], expression.Identifier): raise generic.ScriptError( "Switch-block parameter 2 'variable range' must be an identifier.", param_list[1].pos ) if param_list[1].value in var_ranges: self.var_range = var_ranges[param_list[1].value] else: raise generic.ScriptError( "Unrecognized value for switch parameter 2 'variable range': '{}'".format(param_list[1].value), param_list[1].pos, ) if not isinstance(param_list[2], expression.Identifier): raise generic.ScriptError("Switch-block parameter 3 'name' must be an identifier.", param_list[2].pos) self.initialize(param_list[2], general.parse_feature(param_list[0]).value, len(param_list) - 4) self.expr = param_list[-1] self.body = body self.param_list = param_list[3:-1] # register_map is a dict to be duck-compatible with spritelayouts. # But because feature_set has only one item for switches, register_map also has at most one item. self.register_map = {} def pre_process(self): # Check parameter names seen_names = set() for param in self.param_list: if not isinstance(param, expression.Identifier): raise generic.ScriptError("switch parameter names must be identifiers.", param.pos) if param.value in seen_names: raise generic.ScriptError("Duplicate parameter name '{}' encountered.".format(param.value), param.pos) seen_names.add(param.value) feature = next(iter(self.feature_set)) var_scope = action2var.get_scope(feature, self.var_range) if var_scope is None: raise generic.ScriptError("Requested scope not available for this feature.", self.pos) # Allocate registers param_map = {} param_registers = [] for param in self.param_list: reg = action2var.VarAction2CallParam(param.value) param_registers.append(reg) param_map[param.value] = reg param_map = (param_map, lambda name, value, pos: action2var.VarAction2LoadCallParam(value, name)) self.register_map[feature] = param_registers self.expr = action2var.reduce_varaction2_expr(self.expr, var_scope, [param_map]) self.body.reduce_expressions(var_scope, [param_map]) switch_base_class.pre_process(self) def optimise(self): if self.optimised: return self.optimised is not self # Constant condition, pick the right result if isinstance(self.expr, expression.ConstantNumeric): for r in self.body.ranges[:]: if r.min.value <= self.expr.value <= r.max.value: self.optimised = r.result.value if r.result.value else self.expr if not self.optimised and self.body.default: self.optimised = self.body.default.value if self.body.default.value else self.expr # Default result only if not self.optimised and self.expr.is_read_only() and self.body.default and len(self.body.ranges) == 0: self.optimised = self.body.default.value # If we return an expression, just rewrite ourself to keep the correct scope # so a return action with the wrong scope doesn't need to be created later if ( self.optimised and not isinstance(self.optimised, expression.ConstantNumeric) and not (isinstance(self.optimised, expression.SpriteGroupRef) and not self.optimised.is_procedure) and not isinstance(self.optimised, expression.String) ): self.expr = self.optimised self.body.ranges = [] self.body.default = SwitchValue(None, True, self.optimised.pos) self.optimised = self if self.optimised: generic.print_warning( generic.Warning.OPTIMISATION, "Block '{}' returns a constant, optimising.".format(self.name.value), self.pos, ) return self.optimised is not self self.optimised = self # Prevent multiple run on the same non optimisable Switch return False def collect_references(self): all_refs = self.expr.collect_references() for result in [r.result for r in self.body.ranges] + [self.body.default]: if result is not None and result.value is not None: all_refs += result.value.collect_references() return all_refs def is_read_only(self): for result in [r.result for r in self.body.ranges] + [self.body.default]: if result is not None and result.value is not None: if not result.value.is_read_only(): return False return self.expr.is_read_only() def debug_print(self, indentation): generic.print_dbg( indentation, "Switch, Feature = {:d}, name = {}".format(next(iter(self.feature_set)), self.name.value) ) if self.param_list: generic.print_dbg(indentation + 2, "Parameters:") for param in self.param_list: param.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Expression:") self.expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Body:") self.body.debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action2var.parse_varaction2(self) return [] def __str__(self): var_range = "SELF" if self.var_range == 0x89 else "PARENT" params = "" if not self.param_list else "{}, ".format(", ".join(str(x) for x in self.param_list)) return "switch({}, {}, {}, {}{}) {{\n{}}}\n".format( next(iter(self.feature_set)), var_range, self.name, params, self.expr, self.body ) class SwitchBody: """ AST-node representing the body of a switch block This contains the various ranges as well as the default value @ivar ranges: List of ranges @type ranges: C{list} of L{SwitchRange} @ivar default: Default result to use if no range matches @type default: L{SwitchValue} or C{None} if N/A """ def __init__(self, ranges, default): self.ranges = ranges self.default = default def reduce_expressions(self, var_scope, extra_dicts=None): if extra_dicts is None: extra_dicts = [] for r in self.ranges[:]: if r.min is r.max and isinstance(r.min, expression.Identifier) and r.min.value == "default": if self.default is not None: raise generic.ScriptError( "Switch-block has more than one default, which is impossible.", r.result.pos ) self.default = r.result self.ranges.remove(r) else: r.reduce_expressions(var_scope, extra_dicts) if self.default is not None: if self.default.value is not None: self.default.value = action2var.reduce_varaction2_expr(self.default.value, var_scope, extra_dicts) if len(self.ranges) != 0: if any(self.default.value != r.result.value for r in self.ranges): return generic.print_warning( generic.Warning.OPTIMISATION, "Switch-Block ranges are the same as default, optimising.", self.default.pos, ) self.ranges = [] def debug_print(self, indentation): for r in self.ranges: r.debug_print(indentation) if self.default is not None: generic.print_dbg(indentation, "Default:") self.default.debug_print(indentation + 2) def __str__(self): ret = "".join("\t{}\n".format(r) for r in self.ranges) if self.default is not None: ret += "\t{}\n".format(str(self.default)) return ret class SwitchRange: def __init__(self, min, max, result, unit=None): self.min = min self.max = max self.result = result self.unit = unit def reduce_expressions(self, var_scope, extra_dicts=None): if extra_dicts is None: extra_dicts = [] self.min = self.min.reduce(global_constants.const_list) self.max = self.max.reduce(global_constants.const_list) if self.result.value is not None: self.result.value = action2var.reduce_varaction2_expr(self.result.value, var_scope, extra_dicts) def debug_print(self, indentation): generic.print_dbg(indentation, "Min:") self.min.debug_print(indentation + 2) generic.print_dbg(indentation, "Max:") self.max.debug_print(indentation + 2) generic.print_dbg(indentation, "Result:") self.result.debug_print(indentation + 2) def __str__(self): ret = str(self.min) if self.min is not self.max and ( not isinstance(self.min, expression.ConstantNumeric) or not isinstance(self.max, expression.ConstantNumeric) or self.max.value != self.min.value ): ret += ".." + str(self.max) ret += ": " + str(self.result) return ret class SwitchValue: """ Class representing a single returned value or sprite group in a switch-block Also used for random-switch and graphics blocks @ivar value: Value to return @type value: L{Expression} or C{None} @ivar is_return: Whether the return keyword was present @type is_return: C{bool} @ivar pos: Position information @type pos: L{Position} """ def __init__(self, value, is_return, pos): self.value = value self.is_return = is_return self.pos = pos def debug_print(self, indentation): if self.value is None: assert self.is_return generic.print_dbg(indentation, "Return computed value") else: generic.print_dbg(indentation, "Return value:" if self.is_return else "Go to block:") self.value.debug_print(indentation + 2) def __str__(self): if self.value is None: assert self.is_return return "return;" elif self.is_return: return "return {};".format(self.value) else: return "{};".format(self.value) class RandomSwitch(switch_base_class): def __init__(self, param_list, choices, pos): base_statement.BaseStatement.__init__(self, "random_switch-block", pos, False, False) if not (3 <= len(param_list) <= 4): raise generic.ScriptError( "random_switch requires 3 or 4 parameters, encountered {:d}".format(len(param_list)), pos ) # feature feature = general.parse_feature(param_list[0]).value # type self.type = param_list[1] # Extract type name and possible argument if isinstance(self.type, expression.Identifier): self.type_count = None elif isinstance(self.type, expression.FunctionCall): if len(self.type.params) == 0: self.type_count = None elif len(self.type.params) == 1: # var_scope is really weird for type=BACKWARD/FORWARD. var_scope = action2var.get_scope(feature, 0x89) self.type_count = action2var.reduce_varaction2_expr(self.type.params[0], var_scope) else: raise generic.ScriptError( "Value for random_switch parameter 2 'type' can have only one parameter.", self.type.pos ) self.type = self.type.name else: raise generic.ScriptError( "random_switch parameter 2 'type' should be an identifier, possibly with a parameter.", self.type.pos ) # name if not isinstance(param_list[2], expression.Identifier): raise generic.ScriptError("random_switch parameter 3 'name' should be an identifier", pos) name = param_list[2] # triggers self.triggers = param_list[3] if len(param_list) == 4 else expression.ConstantNumeric(0) # body self.choices = [] self.dependent = [] self.independent = [] for choice in choices: if isinstance(choice.probability, expression.Identifier): if choice.probability.value == "dependent": self.dependent.append(choice.result) continue elif choice.probability.value == "independent": self.independent.append(choice.result) continue self.choices.append(choice) if len(self.choices) == 0: raise generic.ScriptError("random_switch requires at least one possible choice", pos) self.initialize(name, feature) self.random_act2 = None # Set during action generation to resolve dependent/independent chains def pre_process(self): feature = next(iter(self.feature_set)) # var_scope is really weird for type=BACKWARD/FORWARD. # Expressions in cases will still refer to the origin vehicle. var_scope = action2var.get_scope(feature, 0x8A if self.type.value == "PARENT" else 0x89) for choice in self.choices: choice.reduce_expressions(var_scope) for dep_list in (self.dependent, self.independent): for i, dep in enumerate(dep_list[:]): if dep.is_return: raise generic.ScriptError( "Expected a random_switch identifier after (in)dependent, not a return.", dep.pos ) dep_list[i] = dep.value.reduce(global_constants.const_list) # Make sure, all [in]dependencies refer to existing random switch blocks if (not isinstance(dep_list[i], expression.SpriteGroupRef)) or len(dep_list[i].param_list) > 0: raise generic.ScriptError("Value for (in)dependent should be an identifier", dep_list[i].pos) spritegroup = action2.resolve_spritegroup(dep_list[i].name) if not isinstance(spritegroup, RandomSwitch): raise generic.ScriptError( "Value of (in)dependent '{}' should refer to a random_switch.".format(dep_list[i].name.value), dep_list[i].pos, ) self.triggers = self.triggers.reduce_constant(global_constants.const_list) if not (0 <= self.triggers.value <= 255): raise generic.ScriptError( "random_switch parameter 4 'triggers' out of range 0..255, encountered " + str(self.triggers.value), self.triggers.pos, ) switch_base_class.pre_process(self) def optimise(self): if self.optimised: return self.optimised is not self # Triggers have side-effects, and can't be skipped. # Scope for expressions can be different in referencing location, so don't optimise them. if self.triggers.value == 0 and len(self.choices) == 1: optimised = self.choices[0].result.value if ( isinstance(optimised, expression.ConstantNumeric) or (isinstance(optimised, expression.SpriteGroupRef) and not optimised.is_procedure) or isinstance(optimised, expression.String) ): generic.print_warning( generic.Warning.OPTIMISATION, "Block '{}' returns a constant, optimising.".format(self.name.value), self.pos, ) self.optimised = optimised return True self.optimised = self # Prevent multiple run on the same non optimisable RandomSwitch return False def collect_references(self): all_refs = [] for choice in self.choices: all_refs += choice.result.value.collect_references() return all_refs def is_read_only(self): for choice in self.choices: if not choice.result.value.is_read_only(): return False return True def debug_print(self, indentation): generic.print_dbg(indentation, "Random") generic.print_dbg(indentation + 2, "Feature:", next(iter(self.feature_set))) generic.print_dbg(indentation + 2, "Type:") self.type.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Name:", self.name.value) generic.print_dbg(indentation + 2, "Triggers:") self.triggers.debug_print(indentation + 4) for dep in self.dependent: generic.print_dbg(indentation + 2, "Dependent on:") dep.debug_print(indentation + 4) for indep in self.independent: generic.print_dbg(indentation + 2, "Independent from:") indep.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Choices:") for choice in self.choices: choice.debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action2random.parse_randomswitch(self) return [] def __str__(self): ret = "random_switch({}, {}, {}, {}) {{\n".format( str(next(iter(self.feature_set))), str(self.type), str(self.name), str(self.triggers) ) for dep in self.dependent: ret += "dependent: {};\n".format(dep) for indep in self.independent: ret += "independent: {};\n".format(indep) for choice in self.choices: ret += str(choice) + "\n" ret += "}\n" return ret class RandomChoice: """ Class to hold one of the possible choices in a random_switch @ivar probability: Relative chance for this choice to be chosen @type probability: L{Expression} @ivar result: Result of this choice, either another action2 or a return value @type result: L{SwitchValue} """ def __init__(self, probability, result): self.probability = probability if result.value is None: raise generic.ScriptError( "Returning the computed value is not possible in a random_switch, as there is no computed value.", result.pos, ) self.result = result def reduce_expressions(self, var_scope): self.probability = self.probability.reduce_constant(global_constants.const_list) if self.probability.value <= 0: raise generic.ScriptError("Random probability must be higher than 0", self.probability.pos) self.result.value = action2var.reduce_varaction2_expr(self.result.value, var_scope) def debug_print(self, indentation): generic.print_dbg(indentation, "Probability:") self.probability.debug_print(indentation + 2) generic.print_dbg(indentation, "Result:") self.result.debug_print(indentation + 2) def __str__(self): return "{}: {}".format(self.probability, self.result) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/tilelayout.py0000644000175100001660000001304214754345605016054 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action0properties from nml.ast import assignment, base_statement class TileLayout(base_statement.BaseStatement): """ 'tile_layout' AST node. A TileLayout is a list of x,y-offset/tileID pairs. The x and y offsets are from the northernmost tile of the industry/airport. Additionally some extra properties can be stored in the TileLayout, like the orientation of the airport. @ivar name: The name of this layout by which it can be referenced later. @type name: C{str} @ivar tile_prop_list: List of offset/tileID and properties. @type tile_prop_list: C{list} of L{LayoutTile} and L{Assignment} @ivar tile_list: List of tile-offsets/tileIDs. @type tile_list: C{list} of C{LayoutTile} with constant x and y values. @ivar properties: table of all properties. Unknown property names are accepted and ignored. @type properties: C{dict} with C{str} keys and L{ConstantNumeric} values """ def __init__(self, name, tile_list, pos): base_statement.BaseStatement.__init__(self, "tile layout", pos, False, False) self.name = name.value self.tile_prop_list = tile_list self.tile_list = [] self.properties = {} def pre_process(self): for tileprop in self.tile_prop_list: if isinstance(tileprop, assignment.Assignment): name = tileprop.name.value if name in self.properties: raise generic.ScriptError("Duplicate property {} in tile layout".format(name), tileprop.name.pos) self.properties[name] = tileprop.value.reduce_constant(global_constants.const_list) else: assert isinstance(tileprop, LayoutTile) x = tileprop.x.reduce_constant() y = tileprop.y.reduce_constant() tile = tileprop.tiletype.reduce(unknown_id_fatal=False) if isinstance(tile, expression.Identifier) and tile.value == "clear": tile = expression.ConstantNumeric(0xFF) self.tile_list.append(LayoutTile(x, y, tile)) if self.name in action0properties.tilelayout_names: raise generic.ScriptError( "A tile layout with name '{}' has already been defined.".format(self.name), self.pos ) action0properties.tilelayout_names[self.name] = self def debug_print(self, indentation): generic.print_dbg(indentation, "TileLayout") for tile in self.tile_list: generic.print_dbg(indentation + 2, "At {:d},{:d}:".format(tile.x, tile.y)) tile.tiletype.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): return "tilelayout {} {{\n\t{}\n}}\n".format(self.name, "\n\t".join(str(x) for x in self.tile_prop_list)) def get_size(self): size = 2 for tile in self.tile_list: size += 3 if not isinstance(tile.tiletype, expression.ConstantNumeric): size += 2 return size def write(self, file): for tile in self.tile_list: file.print_bytex(tile.x.value) file.print_bytex(tile.y.value) if isinstance(tile.tiletype, expression.ConstantNumeric): file.print_bytex(tile.tiletype.value) else: if not isinstance(tile.tiletype, expression.Identifier): raise generic.ScriptError("Invalid expression type for layout tile", tile.tiletype.pos) if tile.tiletype.value not in global_constants.item_names: raise generic.ScriptError("Unknown tile name", tile.tiletype.pos) file.print_bytex(0xFE) tile_id = global_constants.item_names[tile.tiletype.value].id if not isinstance(tile_id, expression.ConstantNumeric): raise generic.ScriptError( "Tile '{}' cannot be used in a tilelayout, as its ID is not a constant.".format( tile.tiletype.value ), tile.tiletype.pos, ) file.print_wordx(tile_id.value) file.newline() file.print_bytex(0) file.print_bytex(0x80) file.newline() class LayoutTile: """ Single tile that is part of a L{TileLayout}. @ivar x: X-offset from the northernmost tile of the industry/airport. @type x: L{Expression} @ivar y: Y-offset from the northernmost tile of the industry/airport. @type y: L{Expression} @ivar tiletype: TileID of the tile to draw on the given offset. @type tiletype: L{Expression} """ def __init__(self, x, y, tiletype): self.x = x self.y = y self.tiletype = tiletype def __str__(self): return "{}, {}: {};".format(self.x, self.y, self.tiletype) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/townnames.py0000644000175100001660000004103214754345605015674 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import heapq from nml import expression, generic, grfstrings from nml.actions import actionF from nml.ast import base_statement townname_serial = 1 class TownNames(base_statement.BaseStatement): """ 'town_names' ast node. @ivar name: Name ID of the town_name. @type name: C{None}, L{Identifier}, or L{ConstantNumeric} @ivar id_number: Allocated ID number for this town_name action F node. @type id_number: C{None} or C{int} @ivar style_name: Name of the translated string containing the name of the style, if any. @type style_name: C{None} or L{String} @ivar actFs: Action F instance needed before this one. @type actFs: C{list} of L{ActionF} @ivar parts: Parts of the names. @type parts: C{list} of L{TownNamesPart} @ivar param_list: Stored parameter list. @type param_list: C{list} of (L{TownNamesPart} or L{TownNamesParam}) """ def __init__(self, name, param_list, pos): base_statement.BaseStatement.__init__(self, "town_names-block", pos, False, False) self.name = name self.param_list = param_list self.id_number = None self.style_name = None self.actFs = [] self.parts = [] def debug_print(self, indentation): generic.print_dbg(indentation, "Town name") if self.name is not None: generic.print_dbg(indentation + 2, "Name:") self.name.debug_print(indentation + 4) for param in self.param_list: param.debug_print(indentation + 2) def pre_process(self): self.actFs = [] self.parts = [] for param in self.param_list: if isinstance(param, TownNamesPart): actFs, part = param.make_actions() self.actFs.extend(actFs) self.parts.append(part) else: if param.key.value != "styles": raise generic.ScriptError("Expected 'styles' keyword.", param.pos) if len(param.value.params) > 0: raise generic.ScriptError("Parameters of the 'styles' were not expected.", param.pos) if self.style_name is not None: raise generic.ScriptError("'styles' is already defined.", self.pos) self.style_name = param.value if len(self.parts) == 0: raise generic.ScriptError("Missing name parts in a town_names item.", self.pos) # 'name' is actually a number. # Allocate it now, before the self.prepare_output() call (to prevent names to grab it). if self.name is not None and not isinstance(self.name, expression.Identifier): value = self.name.reduce_constant() if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("ID should be an integer number.", self.pos) self.id_number = value.value if self.id_number < 0 or self.id_number > 0x7F: raise generic.ScriptError("ID must be a number between 0 and 0x7f (inclusive)", self.pos) if self.id_number not in actionF.free_numbers: raise generic.ScriptError("town names ID 0x{:x} is already used.".format(self.id_number), self.pos) actionF.free_numbers.remove(self.id_number) def __str__(self): ret = "town_names" if self.name is not None: ret += "({})".format(self.name) ret += "{{\n{}}}\n".format("".join(str(x) for x in self.param_list)) return ret def get_action_list(self): return self.actFs + [actionF.ActionF(self.name, self.id_number, self.style_name, self.parts, self.pos)] class TownNamesPart: """ A class containing a town name part. @ivar pieces: Pieces of the town name part. @type pieces: C{list} of (L{TownNamesEntryDefinition} or L{TownNamesEntryText}) @ivar pos: Position information of the parts block. @type pos: L{Position} @ivar startbit: First bit to use for this part, if defined. @type startbit: C{int} or C{None} @ivar num_bits: Number of bits to use, if defined. @type num_bits: C{int} or C{None} """ def __init__(self, pieces, pos): self.pos = pos self.pieces = pieces self.startbit = None self.num_bits = None def make_actions(self): """ Construct new actionF instances to store all pieces of this part, if needed. @return: Action F that should be defined before, and the processed part. @rtype: C{list} of L{ActionF}, L{TownNamesPart} """ new_pieces = [] for piece in self.pieces: piece.pre_process() if piece.probability.value == 0: generic.print_warning( generic.Warning.OPTIMISATION, "Dropping town name piece with 0 probability.", piece.pos ) else: new_pieces.append(piece) self.pieces = new_pieces actFs = self.move_pieces() if len(self.pieces) == 0: raise generic.ScriptError("Expected names and/or town_name references in the part.", self.pos) if len(self.pieces) > 255: raise generic.ScriptError( "Too many values in a part, found {:d}, maximum is 255".format(len(self.pieces)), self.pos ) return actFs, self def move_pieces(self): """ Move pieces to new action F instances to make it fit, if needed. @return: Created action F instances. @rtype: C{list} of L{ActionF} @note: Function may change L{pieces}. """ global townname_serial if len(self.pieces) <= 255: return [] # Trivially correct. # There are too many pieces. number_action_f = (len(self.pieces) + 254) // 255 pow2 = 1 while pow2 < number_action_f: pow2 = pow2 * 2 if pow2 < 255: number_action_f = pow2 heap = [] # Heap of (summed probability, subset-of-pieces) i = 0 while i < number_action_f: # Index 'i' is added to have a unique sorting when lists have equal total probabilities. heapq.heappush(heap, (0, i, [])) i = i + 1 finished_actions = [] # Index 'idx' is added to have a unique sorting when pieces have equal probabilities. rev_pieces = sorted(((p.probability.value, idx, p) for idx, p in enumerate(self.pieces)), reverse=True) for prob, _idx, piece in rev_pieces: while True: sub = heapq.heappop(heap) if len(sub[2]) < 255: break # If a subset already has the max number of parts, consider it finished. finished_actions.append(sub) sub[2].append(piece) sub = (sub[0] + prob, sub[1], sub[2]) heapq.heappush(heap, sub) finished_actions.extend(heap) # To ensure the chances do not get messed up due to one part needing less bits for its # selection, all parts are forced to use the same number of bits. max_prob = max(sub[0] for sub in finished_actions) num_bits = 1 while (1 << num_bits) < max_prob: num_bits = num_bits + 1 # Assign to action F actFs = [] for _prob, _idx, sub in finished_actions: actF_name = expression.Identifier("**townname #{:d}**".format(townname_serial), None) townname_serial = townname_serial + 1 town_part = TownNamesPart(sub, self.pos) town_part.set_numbits(num_bits) actF = actionF.ActionF(actF_name, None, None, [town_part], self.pos) actFs.append(actF) # Remove pieces of 'sub' from self.pieces counts = len(self.pieces), len(sub) sub_set = set(sub) self.pieces = [piece for piece in self.pieces if piece not in sub_set] assert len(self.pieces) == counts[0] - counts[1] self.pieces.append(TownNamesEntryDefinition(actF_name, expression.ConstantNumeric(1), self.pos)) # update self.parts return actFs def assign_bits(self, startbit): """ Assign bits for this piece. @param startbit: First bit free for use. @type startbit: C{int} @return: Number of bits needed for this piece. @rtype: C{int} """ assert len(self.pieces) <= 255 total = sum(piece.probability.value for piece in self.pieces) if self.startbit is None or self.startbit < startbit: self.startbit = startbit if self.num_bits is None: n = 1 while total > (1 << n): n = n + 1 self.num_bits = n assert (1 << self.num_bits) >= total return self.num_bits def set_numbits(self, numbits): """ Set the number of bits that this part should use. """ assert self.num_bits is None self.num_bits = numbits def debug_print(self, indentation): total = sum(piece.probability.value for piece in self.pieces) generic.print_dbg(indentation, "Town names part (total {:d})".format(total)) for piece in self.pieces: piece.debug_print(indentation + 2, total) def __str__(self): return "{{\n\t{}\n}}\n".format("\n\t".join(str(piece) for piece in self.pieces)) def get_length(self): size = 3 # textcount, firstbit, bitcount bytes. size += sum(piece.get_length() for piece in self.pieces) return size def resolve_townname_id(self): """ Resolve the reference numbers to previous C{town_names} blocks. @return: Set of referenced C{town_names} block numbers. """ blocks = set() for piece in self.pieces: block = piece.resolve_townname_id() if block is not None: blocks.add(block) return blocks def write(self, file): file.print_bytex(len(self.pieces)) file.print_bytex(self.startbit) file.print_bytex(self.num_bits) for piece in self.pieces: piece.write(file) file.newline() class TownNamesParam: """ Class containing a parameter of a town name. Currently known key/values: - 'styles' / string expression """ def __init__(self, key, value, pos): self.key = key self.value = value self.pos = pos def debug_print(self, indentation): generic.print_dbg(indentation, "Town names param") generic.print_dbg(indentation + 2, "Key:") self.key.debug_print(indentation + 4) generic.print_dbg(indentation + 2, "Value:") self.value.debug_print(indentation + 4) def __str__(self): return "{}: {};\n".format(self.key, self.value) class TownNamesEntryDefinition: """ An entry in a part referring to a non-final town name, with a given probability. @ivar def_number: Name or number referring to a previous town_names node. @type def_number: L{Identifier} or L{ConstantNumeric} @ivar number: Actual ID to use. @type number: C{None} or C{int} @ivar probability: Probability of picking this reference. @type probability: C{ConstantNumeric} @ivar pos: Position information of the parts block. @type pos: L{Position} """ def __init__(self, def_number, probability, pos): self.def_number = def_number self.number = None self.probability = probability self.pos = pos def pre_process(self): self.number = None if not isinstance(self.def_number, expression.Identifier): self.def_number = self.def_number.reduce_constant() if not isinstance(self.def_number, expression.ConstantNumeric): raise generic.ScriptError("Reference to other town name ID should be an integer number.", self.pos) if self.def_number.value < 0 or self.def_number.value > 0x7F: raise generic.ScriptError( "Reference number out of range (must be between 0 and 0x7f inclusive).", self.pos ) self.probability = self.probability.reduce_constant() if not isinstance(self.probability, expression.ConstantNumeric): raise generic.ScriptError("Probability should be an integer number.", self.pos) if self.probability.value < 0 or self.probability.value > 0x7F: raise generic.ScriptError("Probability out of range (must be between 0 and 0x7f inclusive).", self.pos) def debug_print(self, indentation, total): if isinstance(self.def_number, expression.Identifier): name_text = "name '" + self.def_number.value + "'" else: name_text = "number 0x{:x}".format(self.def_number.value) generic.print_dbg( indentation, "Insert town_name ID {} with probability {:d}/{:d}".format(name_text, self.probability.value, total), ) def __str__(self): return "town_names({}, {:d}),".format(str(self.def_number), self.probability.value) def get_length(self): return 2 def resolve_townname_id(self): """ Resolve the reference number to a previous C{town_names} block. @return: Number of the referenced C{town_names} block. """ if isinstance(self.def_number, expression.Identifier): self.number = actionF.named_numbers.get(self.def_number.value) if self.number is None: raise generic.ScriptError( 'Town names name "{}" is not defined or points to a next town_names node'.format( self.def_number.value ), self.pos, ) else: self.number = self.def_number.value if self.number not in actionF.numbered_numbers: raise generic.ScriptError( 'Town names number "{}" is not defined or points to a next town_names node'.format(self.number), self.pos, ) return self.number def write(self, file): file.print_bytex(self.probability.value | 0x80) file.print_bytex(self.number) class TownNamesEntryText: """ An entry in a part, a text-string with a given probability. @ivar pos: Position information of the parts block. @type pos: L{Position} """ def __init__(self, id, text, probability, pos): self.id = id self.text = text self.probability = probability self.pos = pos def pre_process(self): if self.id.value != "text": raise generic.ScriptError("Expected 'text' prefix.", self.pos) if not isinstance(self.text, expression.StringLiteral): raise generic.ScriptError("Expected string literal for the name.", self.pos) self.probability = self.probability.reduce_constant() if not isinstance(self.probability, expression.ConstantNumeric): raise generic.ScriptError("Probability should be an integer number.", self.pos) if self.probability.value < 0 or self.probability.value > 0x7F: raise generic.ScriptError("Probability out of range (must be between 0 and 0x7f inclusive).", self.pos) def debug_print(self, indentation, total): generic.print_dbg( indentation, "Text {} with probability {:d}/{:d}".format(self.text.value, self.probability.value, total) ) def __str__(self): return "text({}, {:d}),".format(self.text, self.probability.value) def get_length(self): return 1 + grfstrings.get_string_size(self.text.value) # probability, text def resolve_townname_id(self): """ Resolve the reference number to a previous C{town_names} block. @return: C{None}, as being the block number of a referenced previous C{town_names} block. """ return None def write(self, file): file.print_bytex(self.probability.value) file.print_string(self.text.value, final_zero=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/ast/tracktypetable.py0000644000175100001660000001114614754345605016702 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action0 from nml.ast import assignment, base_statement class BaseTracktypeTable(base_statement.BaseStatement): """Base class for RailtypeTable, RoadtypeTable etc.""" def __init__(self, tracktype_list, pos): base_statement.BaseStatement.__init__(self, self.track_kind + "type table", pos, False, False) generic.OnlyOnce.enforce(self, self.track_kind + "type table") self.tracktype_table.clear() self.tracktype_list = tracktype_list def register_names(self): for i, tracktype in enumerate(self.tracktype_list): if isinstance(tracktype, assignment.Assignment): name = tracktype.name val_list = [] for rt in tracktype.value: if isinstance(rt, expression.Identifier): val_list.append(expression.StringLiteral(rt.value, rt.pos)) else: val_list.append(rt) expression.parse_string_to_dword( val_list[-1] ) # we don't care about the result, only validate the input self.tracktype_list[i] = val_list if len(val_list) > 1 else val_list[0] else: name = tracktype if isinstance(tracktype, expression.Identifier): self.tracktype_list[i] = expression.StringLiteral(tracktype.value, tracktype.pos) expression.parse_string_to_dword( self.tracktype_list[i] ) # we don't care about the result, only validate the input self.tracktype_table[name.value] = i def pre_process(self): pass def debug_print(self, indentation): generic.print_dbg(indentation, self.track_kind.title() + "type table") for tracktype in self.tracktype_list: if isinstance(tracktype, assignment.Assignment): generic.print_dbg(indentation + 2, self.track_kind.title() + ":", tracktype.name.value) for v in tracktype.value: generic.print_dbg(indentation + 4, "Try:", v.value) else: generic.print_dbg(indentation + 2, self.track_kind.title() + ":", tracktype.value) def get_action_list(self): return action0.get_tracktypelist_action( self.table_prop_id, self.cond_tracktype_not_defined, self.tracktype_list ) def __str__(self): lines = [] for tracktype in self.tracktype_list: if isinstance(tracktype, assignment.Assignment): ids = [expression.identifier_to_print(v.value) for v in tracktype.value] lines.append("{}: [{}]".format(str(tracktype.name), ", ".join(ids))) else: lines.append(expression.identifier_to_print(tracktype.value)) ret = self.track_kind + "typetable {\n " ret += ", ".join(lines) ret += "\n}\n" return ret class RailtypeTable(BaseTracktypeTable): track_kind = "rail" tracktype_table = global_constants.railtype_table table_prop_id = 0x12 cond_tracktype_not_defined = 0x0D def __init__(self, *args, **kwargs): global_constants.is_default_railtype_table = False super().__init__(*args, **kwargs) class RoadtypeTable(BaseTracktypeTable): track_kind = "road" tracktype_table = global_constants.roadtype_table table_prop_id = 0x16 cond_tracktype_not_defined = 0x0F def __init__(self, *args, **kwargs): global_constants.is_default_roadtype_table = False super().__init__(*args, **kwargs) class TramtypeTable(BaseTracktypeTable): track_kind = "tram" tracktype_table = global_constants.tramtype_table table_prop_id = 0x17 cond_tracktype_not_defined = 0x11 def __init__(self, *args, **kwargs): global_constants.is_default_tramtype_table = False super().__init__(*args, **kwargs) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0644624 nml-0.7.6/nml/editors/0000755000175100001660000000000014754345610014165 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/editors/__init__.py0000644000175100001660000000124214754345605016301 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/editors/extract_tables.py0000644000175100001660000001342014754345605017547 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import global_constants, tokens, unit from nml.actions import ( action0properties, action2layout, action2var_variables, action3_callbacks, action5, action12, actionB, real_sprite, ) from nml.ast import basecost, general, switch from nml.expression import functioncall # Create list of blocks, functions and units units = set(unit.units.keys()) keywords = set(tokens.reserved.keys()) functions = set(functioncall.function_table.keys()) layouts = set(action2layout.layout_sprite_types.keys()) # No easy way to get action14 stuff temp1 = units | keywords | functions | layouts | {"int", "bool"} block_names_table = sorted(temp1) # Create list of properties and variables var_tables = [ action2var_variables.varact2_globalvars, action2var_variables.varact2vars_vehicles, action2var_variables.varact2vars_trains, action2var_variables.varact2vars_roadvehs, action2var_variables.varact2vars_ships, action2var_variables.varact2vars_aircraft, action2var_variables.varact2vars60x_vehicles, action2var_variables.varact2vars_base_stations, action2var_variables.varact2vars60x_base_stations, action2var_variables.varact2vars_stations, action2var_variables.varact2vars60x_stations, action2var_variables.varact2vars_canals, action2var_variables.varact2vars_houses, action2var_variables.varact2vars60x_houses, action2var_variables.varact2vars_industrytiles, action2var_variables.varact2vars60x_industrytiles, action2var_variables.varact2vars_industries, action2var_variables.varact2vars60x_industries, action2var_variables.varact2vars_airports, action2var_variables.varact2vars_objects, action2var_variables.varact2vars60x_objects, action2var_variables.varact2vars_railtype, action2var_variables.varact2vars_roadtype, action2var_variables.varact2vars_tramtype, action2var_variables.varact2vars_airporttiles, action2var_variables.varact2vars60x_airporttiles, action2var_variables.varact2vars_towns, ] variables = set() for d in var_tables: for key in d.keys(): variables.add(key) prop_tables = [ action0properties.general_veh_props, action0properties.properties[0x00], action0properties.properties[0x01], action0properties.properties[0x02], action0properties.properties[0x03], action0properties.properties[0x05], action0properties.properties[0x07], action0properties.properties[0x09], action0properties.properties[0x0A], action0properties.properties[0x0B], action0properties.properties[0x0D], action0properties.properties[0x0F], action0properties.properties[0x10], action0properties.properties[0x11], action0properties.properties[0x12], action0properties.properties[0x13], ] properties = set() for d in prop_tables: for key in d.keys(): properties.add(key) dummy = action2layout.Action2LayoutSprite(None, None) layout_sprites = set(dummy.params.keys()) cb_tables = [ action3_callbacks.general_vehicle_cbs, action3_callbacks.callbacks[0x00], action3_callbacks.callbacks[0x01], action3_callbacks.callbacks[0x02], action3_callbacks.callbacks[0x03], action3_callbacks.callbacks[0x04], action3_callbacks.callbacks[0x05], action3_callbacks.callbacks[0x07], action3_callbacks.callbacks[0x09], action3_callbacks.callbacks[0x0A], action3_callbacks.callbacks[0x0B], action3_callbacks.callbacks[0x0D], action3_callbacks.callbacks[0x0F], action3_callbacks.callbacks[0x10], action3_callbacks.callbacks[0x11], action3_callbacks.callbacks[0x12], action3_callbacks.callbacks[0x13], ] callbacks = set() for d in cb_tables: for key in d.keys(): callbacks.add(key) # No easy way to get action14 stuff act14_vars = { "grfid", "name", "desc", "version", "min_compatible_version", "type", "bit", "min_value", "max_value", "def_value", "names", } temp2 = variables | properties | layout_sprites | callbacks | act14_vars variables_names_table = sorted(temp2) # Create list of features features = set(general.feature_ids.keys()) switch_names = set(switch.var_ranges.keys()) temp3 = features | switch_names feature_names_table = sorted(temp3) # Create list of callbacks constants const_tables = [ global_constants.constant_numbers, global_constants.global_parameters, global_constants.misc_grf_bits, global_constants.patch_variables, global_constants.config_flags, global_constants.unified_maglev_var, global_constants.railtype_table, global_constants.roadtype_table, global_constants.tramtype_table, ] constant_names = set() for d in const_tables: for key in d.keys(): constant_names.add(key) act5_names = set(action5.action5_table.keys()) act12_names = set(action12.font_sizes.keys()) actB_names = set(actionB.default_error_msg.keys()) | set(actionB.error_severity.keys()) sprite_names = set(real_sprite.real_sprite_flags.keys()) cost_names = set(basecost.base_cost_table.keys()) | set(basecost.generic_base_costs) temp4 = constant_names | act5_names | act12_names | actB_names | sprite_names | cost_names callback_names_table = sorted(temp4) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/editors/kate.py0000644000175100001660000001672314754345605015500 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.editors import extract_tables output_file = "nml_kate.xml" header_text = """\ """ feature_text = """\ """ builtin_text = """\ """ constant_text = """\ """ tail_text = """\ """ def write_file(fname): with open(fname, "w") as file: file.write(header_text) for word in extract_tables.keywords: file.write(" {} \n".format(word)) file.write(feature_text) for word in extract_tables.features: file.write(" {} \n".format(word)) file.write(builtin_text) for word in extract_tables.functions: file.write(" {} \n".format(word)) file.write(constant_text) for word in extract_tables.callback_names_table: file.write(" {} \n".format(word)) file.write(tail_text) def run(): write_file("nml_kate.xml") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/editors/notepadpp.py0000644000175100001660000000767414754345605016553 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.editors import extract_tables # Define parts of np++ xml file string1 = """\ 000000 { } ( ) , : ; [ ] 1/* 2*/ 0// """ string2 = """\ """ string3 = """\ """ string4 = """\ """ string5 = """\ """ # Build np++ xml file def write_file(fname): with open(fname, "w") as file: file.write(string1) file.write(" ".join(extract_tables.block_names_table)) file.write(string2) file.write(" ".join(extract_tables.variables_names_table)) file.write(string3) file.write(" ".join(extract_tables.feature_names_table)) file.write(string4) file.write(" ".join(extract_tables.callback_names_table)) file.write(string5) def run(): write_file("nml_notepadpp.xml") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/editors/visualstudiocode.py0000644000175100001660000001302614754345605020133 0ustar00runnerdocker__license__ = """ nmlL is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. nmlL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with nmlL; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import json from nml.editors import extract_tables text1 = """\ { "name": "newgrfml", "scopeName": "source.newgrfml", "fileTypes": [ ".nml", ".pnml" ], "patterns": [ { "include": "#comment" }, { "include": "#numeric-literal" }, { "include": "#string" }, { "include": "#block" }, { "include": "#variable" }, { "include": "#feature" }, { "include": "#callback" } ], "repository": { "comment": { "patterns": [ { "name": "comment.line.newgrfml", "begin": "//", "end": "$" }, { "name": "comment.line.newgrfml", "begin": "#", "end": "$" }, { "name": "comment.block.newgrfml", "begin": "/\\\\*", "end": "\\\\*/" } ] }, "numeric-literal": { "patterns": [ { "name": "constant.numeric.decimal.newgrfml", "match": "\\\\b[0-9]+\\\\b" }, { "name": "constant.numeric.binary.newgrfml", "match": "\\\\b0(b|B)[01]*\\\\b" }, { "name": "constant.numeric.hex.newgrfml", "match": "\\\\b0(x|X)[0-9a-fA-F]*\\\\b" } ] }, "string": { "patterns": [ { "name": "string.quoted.double.newgrfml", "begin": "\\"", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.newgrfml" } }, "end": "\\"", "endCaptures": { "0": { "name": "punctuation.definition.string.end.newgrfml" } } }, { "name": "string.quoted.single.newgrfml", "begin": "'", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.newgrfml" } }, "end": "'", "endCaptures": { "0": { "name": "punctuation.definition.string.end.newgrfml" } } } ] }, """ text2 = """\ "block": { "patterns": [ { "name": "storage.type.primitive.newgrfml", "match": "blocks" } ] }, """ text3 = """\ "variable": { "patterns": [ { "name": "variable.other.property.newgrfml", "match": "variables" } ] }, """ text4 = """\ "feature": { "patterns": [ { "name": "support.class.newgrfml", "match": "features" } ] }, """ text5 = """\ "callback": { "patterns": [ { "name": "constant.numeric.newgrfml", "match": "callbacks" } ] } """ text6 = """\ } } """ def run(): line = r"(?= 0 and expr1.shift.value >= 0 and (expr1.shift.value + expr2.value) < 32 ): expr1.shift.value += expr2.value expr1.mask = nmlop.SHIFTU_RIGHT(expr1.mask, expr2).reduce() return expr1 # - Try to merge multiple additions/subtractions with constant numbers if ( op in (nmlop.ADD, nmlop.SUB) and isinstance(expr2, ConstantNumeric) and isinstance(expr1, BinOp) and expr1.op in (nmlop.ADD, nmlop.SUB) and isinstance(expr1.expr2, ConstantNumeric) ): val = expr2.value if op == nmlop.ADD else -expr2.value if expr1.op == nmlop.ADD: return nmlop.ADD(expr1.expr1, (expr1.expr2.value + val), self.pos).reduce() if expr1.op == nmlop.SUB: return nmlop.SUB(expr1.expr1, (expr1.expr2.value - val), self.pos).reduce() if op == nmlop.OR and isinstance(expr1, Boolean) and isinstance(expr2, Boolean): return Boolean(nmlop.OR(expr1.expr, expr2.expr, self.pos)).reduce(id_dicts) return BinOp(op, expr1, expr2, self.pos) def supported_by_action2(self, raise_error): if not self.op.act2_supports: token = " '{}'".format(self.op.token) if self.op.token else "" if raise_error: raise generic.ScriptError("Operator{} not supported in a switch-block".format(token), self.pos) return False return self.expr1.supported_by_action2(raise_error) and self.expr2.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): if not self.op.actd_supports: if raise_error: if self.op == nmlop.STO_PERM: raise generic.ScriptError("STORE_PERM is only available in switch-blocks.", self.pos) elif self.op == nmlop.STO_TMP: raise generic.ScriptError("STORE_TEMP is only available in switch-blocks.", self.pos) # default case token = " '{}'".format(self.op.token) if self.op.token else "" raise generic.ScriptError("Operator{} not supported in parameter assignment".format(token), self.pos) return False return self.expr1.supported_by_actionD(raise_error) and self.expr2.supported_by_actionD(raise_error) def collect_references(self): return self.expr1.collect_references() + self.expr2.collect_references() def is_read_only(self): return self.expr1.is_read_only() and self.expr2.is_read_only() def is_boolean(self): if self.op in (nmlop.AND, nmlop.OR, nmlop.XOR): return self.expr1.is_boolean() and self.expr2.is_boolean() return self.op.returns_boolean def __eq__(self, other): return ( other is not None and isinstance(other, BinOp) and self.op == other.op and self.expr1 == other.expr1 and self.expr2 == other.expr2 ) def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.op, self.expr1, self.expr2)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/bitmask.py0000644000175100001660000000417214754345605016727 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, nmlop from .base_expression import ConstantNumeric, Expression, Type class BitMask(Expression): def __init__(self, values, pos): Expression.__init__(self, pos) self.values = values def debug_print(self, indentation): generic.print_dbg(indentation, "Get bitmask:") for value in self.values: value.debug_print(indentation + 2) def reduce(self, id_dicts=None, unknown_id_fatal=True): ret = ConstantNumeric(0, self.pos) for orig_expr in self.values: val = orig_expr.reduce(id_dicts) if val.type() != Type.INTEGER: if val.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(val.name, val.pos) raise generic.ScriptError("Parameters of 'bitmask' must be integers.", orig_expr.pos) if isinstance(val, ConstantNumeric) and val.value >= 32: raise generic.ScriptError("Parameters of 'bitmask' cannot be greater than 31", orig_expr.pos) val = nmlop.SHIFT_LEFT(1, val) ret = nmlop.OR(ret, val) return ret.reduce() def collect_references(self): from itertools import chain return list(chain.from_iterable(v.collect_references() for v in self.values)) def is_read_only(self): return all(v.is_read_only() for v in self.values) def __str__(self): return "bitmask(" + ", ".join(str(e) for e in self.values) + ")" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/boolean.py0000644000175100001660000000406714754345605016717 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression, Type class Boolean(Expression): """ Convert to boolean truth value. @ivar expr: (Integer) expression to convert. @type expr: C{Expression} """ def __init__(self, expr, pos=None): Expression.__init__(self, pos) self.expr = expr def debug_print(self, indentation): generic.print_dbg(indentation, "Force expression to boolean:") self.expr.debug_print(indentation + 2) def reduce(self, id_dicts=None, unknown_id_fatal=True): expr = self.expr.reduce(id_dicts) if expr.type() != Type.INTEGER: if expr.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr.name, expr.pos) raise generic.ScriptError("Only integers can be converted to a boolean value.", expr.pos) if expr.is_boolean(): return expr return Boolean(expr) def supported_by_action2(self, raise_error): return self.expr.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.expr.supported_by_actionD(raise_error) def collect_references(self): return self.expr.collect_references() def is_read_only(self): return self.expr.is_read_only() def is_boolean(self): return True def __str__(self): return "!!({})".format(self.expr) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/cargo.py0000644000175100001660000000370014754345605016364 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants from .base_expression import Expression class CargoExpression(Expression): def __init__(self, cargotype, value, pos): Expression.__init__(self, pos) assert isinstance(value, list) self.cargotype = cargotype self.value = value def cargolabel(self): for label, number in global_constants.cargo_numbers.items(): if number == self.cargotype: return label raise AssertionError("Cargo expression with unregistered cargotype at " + str(self.pos)) def debug_print(self, indentation): if self.value is None: generic.print_dbg(indentation, "{0} cargo {1}".format(self._debugname, self.cargolabel())) else: generic.print_dbg(indentation, "{0} cargo {1} with result:".format(self._debugname, self.cargolabel())) self.value.debug_print(indentation + 2) def __str__(self): return "{0}({1}, {2})".format(self._fnname, self.cargolabel(), str(self.value)) def reduce(self, id_dicts=None, unknown_id_fatal=True): return self class AcceptCargo(CargoExpression): _fnname = "accept_cargo" _debugname = "Accept" class ProduceCargo(CargoExpression): _fnname = "produce_cargo" _debugname = "Produce" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/functioncall.py0000644000175100001660000006557014754345605017767 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import calendar import datetime import math from functools import reduce from nml import generic, global_constants, nmlop from . import identifier from .base_expression import ConstantFloat, ConstantNumeric, Expression, Type from .bitmask import BitMask from .cargo import AcceptCargo, ProduceCargo from .parameter import parse_string_to_dword from .storage_op import StorageOp from .string_literal import StringLiteral from .abs_op import AbsOp class FunctionCall(Expression): def __init__(self, name, params, pos): Expression.__init__(self, pos) self.name = name self.params = params def debug_print(self, indentation): generic.print_dbg(indentation, "Call function: " + self.name.value) for param in self.params: generic.print_dbg(indentation + 2, "Parameter:") param.debug_print(indentation + 4) def __str__(self): ret = "{}({})".format(self.name, ", ".join(str(param) for param in self.params)) return ret def reduce(self, id_dicts=None, unknown_id_fatal=True): # At this point we don't care about invalid arguments, they'll be handled later. identifier.ignore_all_invalid_ids = True params = [param.reduce(id_dicts, unknown_id_fatal=False) for param in self.params] identifier.ignore_all_invalid_ids = False if self.name.value in function_table: func = function_table[self.name.value] val = func(self.name.value, params, self.pos) return val.reduce(id_dicts) else: # try user-defined functions func_ptr = self.name.reduce(id_dicts, unknown_id_fatal=False, search_func_ptr=True) if func_ptr != self.name: # we found something! if func_ptr.type() == Type.SPRITEGROUP_REF: func_ptr.param_list = params func_ptr.is_procedure = True return func_ptr if func_ptr.type() != Type.FUNCTION_PTR: raise generic.ScriptError( "'{}' is defined, but it is not a function.".format(self.name.value), self.pos ) return func_ptr.call(params) if unknown_id_fatal: raise generic.ScriptError("'{}' is not defined as a function.".format(self.name.value), self.pos) return FunctionCall(self.name, params, self.pos) class SpecialCheck(Expression): """ Action7/9 special check (e.g. to see whether a cargo is defined) @ivar op: Action7/9 operator to use @type op: (C{int}, C{str})-tuple @ivar varnum: Variable number to read @type varnum: C{int} @ivar results: Result of the check when skipping (0) or not skipping (1) @type results: (C{int}, C{int})-tuple @ivar value: Value to test @type value: C{int} @ivar varsize: Varsize for the action7/9 check @type varsize: C{int} @ivar mask: Mask to to test only certain bits of the value @type mask: C{int} @ivar pos: Position information @type pos: L{Position} """ def __init__(self, op, varnum, results, value, to_string, varsize=4, mask=None, pos=None): Expression.__init__(self, pos) self.op = op self.varnum = varnum self.results = results self.value = value self.to_string = to_string self.varsize = varsize self.mask = mask def reduce(self, id_dicts=None, unknown_id_fatal=True): return self def __str__(self): return self.to_string def supported_by_actionD(self, raise_error): return True class GRMOp(Expression): def __init__(self, op, feature, count, to_string, pos=None): Expression.__init__(self, pos) self.op = op self.feature = feature self.count = count self.to_string = to_string def reduce(self, id_dicts=None, unknown_id_fatal=True): return self def __str__(self): return self.to_string(self) def supported_by_actionD(self, raise_error): return True function_table = {} def builtin(func): """ Decorator that adds a function named `builtin_func` to the function table as `func`. """ assert func.__name__.startswith("builtin_") name = func.__name__[8:] # Strip the "builtin_". str.removeprefix() is only added in py3.9. function_table[name] = func return func def builtins(*names): """ Decorator that adds a function to the function table with one or more custom names. """ def dec(func): for name in names: function_table[name] = func return func return dec # { Builtin functions @builtin def builtin_min(name, args, pos): """ min(...) builtin function. @return Lowest value of the given arguments. """ if len(args) < 2: raise generic.ScriptError("min() requires at least 2 arguments", pos) return reduce(lambda x, y: nmlop.MIN(x, y, pos), args) @builtin def builtin_max(name, args, pos): """ max(...) builtin function. @return Heighest value of the given arguments. """ if len(args) < 2: raise generic.ScriptError("max() requires at least 2 arguments", pos) return reduce(lambda x, y: nmlop.MAX(x, y, pos), args) @builtin def builtin_date(name, args, pos): """ date(year, month, day) builtin function. @return Days since 1 jan 1 of the given date. """ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if len(args) != 3: raise generic.ScriptError("date() requires exactly 3 arguments", pos) identifier.ignore_all_invalid_ids = True year = args[0].reduce(global_constants.const_list) identifier.ignore_all_invalid_ids = False try: month = args[1].reduce_constant().value day = args[2].reduce_constant().value except generic.ConstError: raise generic.ScriptError("Month and day parameters of date() should be compile-time constants", pos) generic.check_range(month, 1, 12, "month", args[1].pos) generic.check_range(day, 1, days_in_month[month - 1], "day", args[2].pos) if not isinstance(year, ConstantNumeric): if month != 1 or day != 1: raise generic.ScriptError( "when the year parameter of date() is not a compile time constant month and day should be 1", pos ) # num_days = year*365 + year/4 - year/100 + year/400 part1 = nmlop.MUL(year, 365) part2 = nmlop.DIV(year, 4) part3 = nmlop.DIV(year, 100) part4 = nmlop.DIV(year, 400) res = nmlop.ADD(part1, part2) res = nmlop.SUB(res, part3) res = nmlop.ADD(res, part4) return res generic.check_range(year.value, 0, 5000000, "year", year.pos) day_in_year = 0 for i in range(month - 1): day_in_year += days_in_month[i] day_in_year += day if month >= 3 and (year.value % 4 == 0) and ((not year.value % 100 == 0) or (year.value % 400 == 0)): day_in_year += 1 return ConstantNumeric(year.value * 365 + calendar.leapdays(0, year.value) + day_in_year - 1, pos) @builtin def builtin_day_of_year(name, args, pos): """ day_of_year(month, day) builtin function. @return Day of the year, assuming February has 28 days. """ if len(args) != 2: raise generic.ScriptError(name + "() must have a month and a day parameter", pos) month = args[0].reduce() if not isinstance(month, ConstantNumeric): raise generic.ScriptError("Month should be a compile-time constant.", month.pos) if month.value < 1 or month.value > 12: raise generic.ScriptError("Month should be a value between 1 and 12.", month.pos) day = args[1].reduce() if not isinstance(day, ConstantNumeric): raise generic.ScriptError("Day should be a compile-time constant.", day.pos) # Mapping of month to number of days in that month. number_days = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31} if day.value < 1 or day.value > number_days[month.value]: raise generic.ScriptError("Day should be value between 1 and {:d}.".format(number_days[month.value]), day.pos) return ConstantNumeric(datetime.date(1, month.value, day.value).toordinal(), pos) @builtins("STORE_TEMP", "STORE_PERM", "LOAD_TEMP", "LOAD_PERM") def builtin_storage(name, args, pos): """ Accesses to temporary / persistent storage """ return StorageOp(name, args, pos) @builtin def builtin_UCMP(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return nmlop.VACT2_UCMP(args[0], args[1], pos) @builtin def builtin_CMP(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return nmlop.VACT2_CMP(args[0], args[1], pos) @builtin def builtin_rotate(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return nmlop.ROT_RIGHT(args[0], args[1], pos) @builtin def builtin_bitmask(name, args, pos): return BitMask(args, pos) @builtin def builtin_hasbit(name, args, pos): """ hasbit(value, bit_num) builtin function. @return C{1} if and only if C{value} has bit C{bit_num} set, C{0} otherwise. """ if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return nmlop.HASBIT(args[0], args[1], pos) @builtin def builtin_getbits(name, args, pos): """ getbits(value, first, amount) builtin function. @return Extract C{amount} bits starting at C{first} from C{value}, that is (C{value} >> C{first}) & (1 << C{amount} - 1) """ if len(args) != 3: raise generic.ScriptError(name + "() must have exactly three parameters", pos) # getbits(value, first, amount) = (value >> first) & ((0xFFFFFFFF << amount) ^ 0xFFFFFFFF) part1 = nmlop.SHIFTU_RIGHT(args[0], args[1], pos) part2 = nmlop.SHIFT_LEFT(0xFFFFFFFF, args[2], pos) part3 = nmlop.XOR(part2, 0xFFFFFFFF, pos) return nmlop.AND(part1, part3, pos) @builtin def builtin_version_openttd(name, args, pos): """ version_openttd(major, minor[, revision[, build]]) builtin function. For OpenTTD >= 12.0 only 2 parameters may be passed. For OpenTTD < 12.0 3 to 4 parameters may be passed. @return The version information encoded in a double-word. """ if len(args) < 2: raise generic.ScriptError(name + "() must have at least 2 parameters", pos) major = args[0].reduce_constant().value minor = args[1].reduce_constant().value if major >= 12: if len(args) != 2: raise generic.ScriptError(name + "() must have at exactly 2 parameters for OpenTTD >= 12.0", pos) return ConstantNumeric(((16 + major) << 24) | (minor << 20)) else: if len(args) > 4: raise generic.ScriptError(name + "() must have at most 4 parameters", pos) revision = args[2].reduce_constant().value if len(args) >= 3 else 0 build = args[3].reduce_constant().value if len(args) >= 4 else 0x80000 return ConstantNumeric((major << 28) | (minor << 24) | (revision << 20) | build) @builtins("cargotype_available", "railtype_available", "roadtype_available", "tramtype_available") def builtin_typelabel_available(name, args, pos): """ {cargo|rail|road|tram}type_available(label) builtin functions. @return 1 if the label is available, 0 otherwise. """ op = { "cargotype_available": (0x0B, r"\7c"), "railtype_available": (0x0D, None), "roadtype_available": (0x0F, None), "tramtype_available": (0x11, None), }[name] if len(args) != 1: raise generic.ScriptError(name + "() must have exactly 1 parameter", pos) label = args[0].reduce() return SpecialCheck(op, 0, (0, 1), parse_string_to_dword(label), "{}({})".format(name, label), pos=args[0].pos) @builtins("grf_current_status", "grf_future_status", "grf_order_behind") def builtin_grf_status(name, args, pos): """ grf_{current_status|future_status|order_behind}(grfid[, mask]) builtin functions. @return 1 if the grf is, or will be, active, 0 otherwise. """ op, results = { # can't use \7g (0, 1), because that's false when the queried grf isn't present at all. "grf_current_status": ((0x06, r"\7G"), (1, 0)), "grf_future_status": ((0x0A, r"\7gg"), (0, 1)), "grf_order_behind": ((0x08, r"\7gG"), (0, 1)), }[name] if len(args) == 1: grfid = args[0].reduce() mask = None string = "{}({})".format(name, grfid) varsize = 4 elif len(args) == 2: grfid = args[0].reduce() mask = parse_string_to_dword(args[1].reduce()) string = "{}({}, {})".format(name, grfid, mask) varsize = 8 else: raise generic.ScriptError(name + "() must have 1 or 2 parameters", pos) return SpecialCheck(op, 0x88, results, parse_string_to_dword(grfid), string, varsize, mask, args[0].pos) @builtins("visual_effect", "visual_effect_and_powered") def builtin_visual_effect_and_powered(name, args, pos): """ Builtin function, used in two forms: visual_effect_and_powered(effect, offset, powered) visual_effect(effect, offset) Use this to set the vehicle property visual_effect[_and_powered] and for the callback VEH_CB_VISUAL_EFFECT[_AND_POWERED] """ arg_len = 2 if name == "visual_effect" else 3 if len(args) != arg_len: raise generic.ScriptError(name + "() must have {:d} parameters".format(arg_len), pos) effect = args[0].reduce_constant(global_constants.const_list).value offset = nmlop.ADD(args[1], 8).reduce_constant().value generic.check_range(offset, 0, 0x0F, "offset in function " + name, pos) if arg_len == 3: powered = args[2].reduce_constant(global_constants.const_list).value if powered != 0 and powered != 0x80: raise generic.ScriptError( "3rd argument to visual_effect_and_powered (powered) must be" " either ENABLE_WAGON_POWER or DISABLE_WAGON_POWER", pos, ) else: powered = 0 return ConstantNumeric(effect | offset | powered) @builtin def builtin_create_effect(name, args, pos): """ Builtin function: create_effect(effect_sprite, l_x_offset, t_y_offset, z_offset) Use this to set the values for temporary storages 100+x in the callback create_effect """ if len(args) != 4: raise generic.ScriptError(name + "() must have 4 parameters", pos) sprite = args[0].reduce_constant(global_constants.const_list).value offset1 = args[1].reduce_constant().value offset2 = args[2].reduce_constant().value offset3 = args[3].reduce_constant().value generic.check_range(sprite, 0, 255, "effect_sprite in function " + name, args[0].pos) generic.check_range(offset1, -128, 127, "l_x_offset in function " + name, args[1].pos) generic.check_range(offset2, -128, 127, "t_y_offset in function " + name, args[2].pos) generic.check_range(offset3, -128, 127, "z_offset in function " + name, args[3].pos) return ConstantNumeric(sprite | (offset1 & 0xFF) << 8 | (offset2 & 0xFF) << 16 | (offset3 & 0xFF) << 24) @builtin def builtin_str2number(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) return ConstantNumeric(parse_string_to_dword(args[0])) @builtins("cargotype", "railtype", "roadtype", "tramtype") def builtin_resolve_typelabel(name, args, pos, table_name=None): """ {cargo,rail,road,tram}type(label) builtin functions. Also used from some Action2Var variables to resolve cargo labels. """ tracktype_funcs = { "cargotype": global_constants.cargo_numbers, "railtype": global_constants.railtype_table, "roadtype": global_constants.roadtype_table, "tramtype": global_constants.tramtype_table, } if not table_name: table_name = name table = tracktype_funcs[table_name] if table_name == "cargotype": table_name = "cargo" # NML syntax uses "cargotable" and "railtypetable" if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if not isinstance(args[0], StringLiteral) or args[0].value not in table: raise generic.ScriptError( "Parameter for {}() must be a string literal that is also in your {} table".format(name, table_name), pos ) return ConstantNumeric(table[args[0].value]) @builtin def builtin_reserve_sprites(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) count = args[0].reduce_constant() return GRMOp(nmlop.GRM_RESERVE, 0x08, count.value, lambda x: "{}({:d})".format(name, count.value), pos) @builtin def builtin_industry_type(name, args, pos): """ industry_type(IND_TYPE_OLD | IND_TYPE_NEW, id) builtin function @return The industry type in the format used by grfs (industry prop 0x16 and var 0x64) """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) type = args[0].reduce_constant(global_constants.const_list).value if type not in (0, 1): raise generic.ScriptError("First argument of industry_type() must be IND_TYPE_OLD or IND_TYPE_NEW", pos) # Industry ID uses 7 bits (0 .. 6), bit 7 is for old/new id = args[1].reduce_constant(global_constants.const_list).value if not 0 <= id <= 127: raise generic.ScriptError("Second argument 'id' of industry_type() must be in range 0..127", pos) return ConstantNumeric(type << 7 | id) @builtins("accept_cargo", "produce_cargo") def builtin_cargoexpr(name, args, pos): if len(args) < 1: raise generic.ScriptError(name + "() must have 1 or more parameters", pos) if not isinstance(args[0], StringLiteral) or args[0].value not in global_constants.cargo_numbers: raise generic.ScriptError( "First argument of " + name + "() must be a string literal that is also in your cargo table", pos ) cargotype = global_constants.cargo_numbers[args[0].value] if name == "produce_cargo": return ProduceCargo(cargotype, args[1:], pos) elif name == "accept_cargo": return AcceptCargo(cargotype, args[1:], pos) else: raise AssertionError() @builtins("acos", "asin", "atan", "cos", "sin", "sqrt", "tan") def builtin_math(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) val = args[0].reduce() if not isinstance(val, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Parameter for " + name + "() must be a constant", pos) math_func_table = { "acos": math.acos, "asin": math.asin, "atan": math.atan, "cos": math.cos, "sin": math.sin, "sqrt": math.sqrt, "tan": math.tan, } return ConstantFloat(math_func_table[name](val.value), val.pos) @builtin def builtin_round(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) val = args[0].reduce() if not isinstance(val, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Parameter for " + name + "() must be a constant", pos) return ConstantNumeric(round(val.value), pos) @builtin def builtin_int(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) val = args[0].reduce() if not isinstance(val, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Parameter for " + name + "() must be a constant", pos) return ConstantNumeric(int(val.value), val.pos) @builtin def builtin_abs(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) return AbsOp(args[0], args[0].pos).reduce() @builtin def builtin_sound(name, args, pos): if len(args) not in (1, 2): raise generic.ScriptError(name + "() must have 1 or 2 parameters", pos) if not isinstance(args[0], StringLiteral): raise generic.ScriptError("Parameter for " + name + "() must be a string literal", pos) volume = args[1].reduce_constant().value if len(args) >= 2 else 100 generic.check_range(volume, 0, 100, "sound volume", pos) from nml.actions import action11 return ConstantNumeric(action11.add_sound((args[0].value, volume), pos), pos) @builtin def builtin_import_sound(name, args, pos): if len(args) not in (2, 3): raise generic.ScriptError(name + "() must have 2 or 3 parameters", pos) grfid = parse_string_to_dword(args[0].reduce()) sound_num = args[1].reduce_constant().value volume = args[2].reduce_constant().value if len(args) >= 3 else 100 generic.check_range(volume, 0, 100, "sound volume", pos) from nml.actions import action11 return ConstantNumeric(action11.add_sound((grfid, sound_num, volume), pos), pos) @builtin def builtin_relative_coord(name, args, pos): """ relative_coord(x, y) builtin function. @return Coordinates in 0xYYXX format. """ if len(args) != 2: raise generic.ScriptError(name + "() must have x and y coordinates as parameters", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 255, "Argument of '{}'".format(name), args[0].pos) if isinstance(args[1], ConstantNumeric): generic.check_range(args[1].value, 0, 255, "Argument of '{}'".format(name), args[1].pos) x_coord = nmlop.AND(args[0], 0xFF) y_coord = nmlop.AND(args[1], 0xFF) # Shift Y to its position. y_coord = nmlop.SHIFT_LEFT(y_coord, 8) return nmlop.OR(x_coord, y_coord, pos) @builtin def builtin_num_corners_raised(name, args, pos): """ num_corners_raised(slope) builtin function. slope is a 5-bit value @return Number of raised corners in a slope (4 for steep slopes) """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) slope = args[0] # The returned value is ((slope x 0x8421) & 0x11111) % 0xF # Explanation in steps: (numbers in binary) # - Masking constrains the slope to 5 bits, just to be sure (a|bcde) # - Multiplication creates 4 copies of those bits (abcd|eabc|deab|cdea|bcde) # - And-masking leaves only the lowest bit in each nibble (000d|000c|000b|000a|000e) # - The modulus operation adds one to the output for each set bit # - We now have the count of bits in the slope, which is wat we want. yay! slope = nmlop.AND(slope, 0x1F, pos) slope = nmlop.MUL(slope, 0x8421) slope = nmlop.AND(slope, 0x11111) return nmlop.MOD(slope, 0xF) @builtin def builtin_slope_to_sprite_offset(name, args, pos): """ builtin function slope_to_sprite_offset(slope) @return sprite offset to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '{}'".format(name), args[0].pos) # step 1: ((slope >= 0) & (slope <= 14)) * slope # This handles all non-steep slopes expr = nmlop.AND(nmlop.CMP_LE(args[0], 14, pos), nmlop.CMP_GE(args[0], 0, pos)) expr = nmlop.MUL(expr, args[0]) # Now handle the steep slopes separately # So add (slope == SLOPE_XX) * offset_of_SLOPE_XX for each steep slope steep_slopes = [(23, 16), (27, 17), (29, 15), (30, 18)] for slope, offset in steep_slopes: to_add = nmlop.MUL(nmlop.CMP_EQ(args[0], slope, pos), offset) expr = nmlop.ADD(expr, to_add) return expr @builtin def builtin_palette_1cc(name, args, pos): """ palette_1cc(colour) builtin function. @return Recolour sprite to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '{}'".format(name), args[0].pos) return nmlop.ADD(args[0], 775, pos) @builtin def builtin_palette_2cc(name, args, pos): """ palette_2cc(colour1, colour2) builtin function. @return Recolour sprite to use """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for i in range(0, 2): if isinstance(args[i], ConstantNumeric): generic.check_range(args[i].value, 0, 15, "Argument of '{}'".format(name), args[i].pos) col2 = nmlop.MUL(args[1], 16, pos) col12 = nmlop.ADD(col2, args[0]) # Base sprite is not a constant base = global_constants.patch_variable("base_sprite_2cc", global_constants.patch_variables["base_sprite_2cc"], pos) return nmlop.ADD(col12, base) @builtin def builtin_vehicle_curv_info(name, args, pos): """ vehicle_curv_info(prev_cur, cur_next) builtin function @return Value to use with vehicle var curv_info """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for arg in args: if isinstance(arg, ConstantNumeric): generic.check_range(arg.value, -2, 2, "Argument of '{}'".format(name), arg.pos) args = [nmlop.AND(arg, 0xF, pos) for arg in args] cur_next = nmlop.SHIFT_LEFT(args[1], 8) return nmlop.OR(args[0], cur_next) @builtin def builtin_format_string(name, args, pos): """ format_string(format, ... args ..) builtin function @return Formatted string """ if len(args) < 1: raise generic.ScriptError(name + "() must have at least one parameter", pos) format = args[0].reduce() if not isinstance(format, StringLiteral): raise generic.ScriptError(name + "() parameter 1 'format' must be a literal string", format.pos) # Validate other args format_args = [] for i, arg in enumerate(args[1:]): arg = arg.reduce() if not isinstance(arg, (StringLiteral, ConstantFloat, ConstantNumeric)): raise generic.ScriptError( name + "() parameter {:d} is not a constant number of literal string".format(i + 1), arg.pos ) format_args.append(arg.value) try: result = format.value % tuple(format_args) return StringLiteral(result, pos) except Exception as ex: raise generic.ScriptError("Invalid combination of format / arguments for {}: {}".format(name, str(ex)), pos) # } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/functionptr.py0000644000175100001660000000441714754345605017652 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression, Type class FunctionPtr(Expression): """ Pointer to a function. If this appears inside an expression, the user has made an error. @ivar name: Identifier that has been resolved to this function pointer. @type name: L{Identifier} @ivar func: Function that will be called to resolve this function call. Arguments: Name of the function (C{str}) List of passed arguments (C{list} of L{Expression}) Position information (L{Position}) Any extra arguments passed to the constructor of this class @type func: C{function} @ivar extra_args List of arguments that should be passed to the function that is to be called. @type extra_args C{list} """ def __init__(self, name, func, *extra_args): super().__init__(pos=None) self.name = name self.func = func self.extra_args = extra_args def debug_print(self, indentation): raise AssertionError("Function pointers should not appear inside expressions.") def __str__(self): raise AssertionError("Function pointers should not appear inside expressions.") def reduce(self, id_dicts=None, unknown_id_fatal=True): raise generic.ScriptError( "'{}' is a function and should be called using the function call syntax.".format(str(self.name)), self.name.pos, ) def type(self): return Type.FUNCTION_PTR def call(self, args): return self.func(self.name.value, args, self.name.pos, *self.extra_args) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/identifier.py0000644000175100001660000000576514754345605017430 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants from .base_expression import ConstantNumeric, Expression from .string_literal import StringLiteral ignore_all_invalid_ids = False def default_id_func(name, x, pos): """ Default id conversion function. @param name: Key in the containing dictionary. @type name: Any, unused (to match other id_dicts callbacks) @param x: Value to convert. @type x: C{str}, C{int}, or C{float} @param pos: Position of the id. @type pos: L{Position} @return: Expression of the id. @rtype: L{Expression} """ del name # unused if isinstance(x, str): return StringLiteral(x, pos) else: return ConstantNumeric(x, pos) class Identifier(Expression): def __init__(self, value, pos=None): Expression.__init__(self, pos) self.value = value if value in global_constants.identifier_refcount: global_constants.identifier_refcount[value] += 1 else: global_constants.identifier_refcount[value] = 0 def debug_print(self, indentation): generic.print_dbg(indentation, "ID:", self.value) def __str__(self): return self.value def reduce(self, id_dicts=None, unknown_id_fatal=True, search_func_ptr=False): for id_dict in id_dicts or []: if isinstance(id_dict, tuple): id_d, func = id_dict else: id_d, func = id_dict, default_id_func if self.value in id_d: if search_func_ptr: # Do not reduce function pointers, since they have no (numerical) value return func(self.value, id_d[self.value], self.pos) else: return func(self.value, id_d[self.value], self.pos).reduce(id_dicts) if unknown_id_fatal and not ignore_all_invalid_ids: raise generic.ScriptError("Unrecognized identifier '" + self.value + "' encountered", self.pos) return self def supported_by_actionD(self, raise_error): if raise_error: raise generic.ScriptError("Unknown identifier '{}'".format(self.value), self.pos) return False def __eq__(self, other): return other is not None and isinstance(other, Identifier) and self.value == other.value def __hash__(self): return hash(self.value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/parameter.py0000644000175100001660000001074714754345605017262 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, grfstrings from .base_expression import ConstantNumeric, Expression, Type from .string_literal import StringLiteral class Parameter(Expression): def __init__(self, num, pos=None, by_user=False): Expression.__init__(self, pos) self.num = num if by_user and isinstance(num, ConstantNumeric) and not (0 <= num.value <= 63): generic.print_warning( generic.Warning.GENERIC, "Accessing parameters out of the range 0..63 is not supported and may lead to unexpected behaviour.", pos, ) def debug_print(self, indentation): generic.print_dbg(indentation, "Parameter:") self.num.debug_print(indentation + 2) def __str__(self): return "param[{}]".format(self.num) def reduce(self, id_dicts=None, unknown_id_fatal=True): num = self.num.reduce(id_dicts) if num.type() != Type.INTEGER: raise generic.ScriptError("Parameter number must be an integer.", num.pos) return Parameter(num, self.pos) def supported_by_action2(self, raise_error): supported = isinstance(self.num, ConstantNumeric) if not supported and raise_error: raise generic.ScriptError( "Parameter acessess with non-constant numbers are not supported in a switch-block.", self.pos ) return supported def supported_by_actionD(self, raise_error): return True def __eq__(self, other): return other is not None and isinstance(other, Parameter) and self.num == other.num def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.num,)) class OtherGRFParameter(Expression): def __init__(self, grfid, num, pos=None): Expression.__init__(self, pos) self.grfid = grfid self.num = num def debug_print(self, indentation): generic.print_dbg(indentation, "OtherGRFParameter:") self.grfid.debug_print(indentation + 2) self.num.debug_print(indentation + 2) def __str__(self): return "param[{}, {}]".format(self.grfid, self.num) def reduce(self, id_dicts=None, unknown_id_fatal=True): grfid = self.grfid.reduce(id_dicts) # Test validity parse_string_to_dword(grfid) num = self.num.reduce(id_dicts) if num.type() != Type.INTEGER: raise generic.ScriptError("Parameter number must be an integer.", num.pos) return OtherGRFParameter(grfid, num, self.pos) def supported_by_action2(self, raise_error): if raise_error: raise generic.ScriptError( "Reading parameters from another GRF is not supported in a switch-block.", self.pos ) return False def supported_by_actionD(self, raise_error): return True def parse_string_to_dword(string): """ Convert string literal expression of length 4 to it's equivalent 32 bit number. @param string: Expression to convert. @type string: L{Expression} @return: Value of the converted expression (a 32 bit integer number, little endian). @rtype: C{int} """ if not isinstance(string, StringLiteral) or grfstrings.get_string_size(string.value, False, True) != 4: raise generic.ScriptError("Expected a string literal of length 4", string.pos) pos = string.pos string = string.value bytes = [] i = 0 try: while len(bytes) < 4: if string[i] == "\\": bytes.append(int(string[i + 1 : i + 3], 16)) i += 3 else: bytes.append(ord(string[i])) i += 1 except ValueError: raise generic.ScriptError("Cannot convert string to integer id", pos) return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/patch_variable.py0000644000175100001660000000304214754345605020234 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression class PatchVariable(Expression): """ Class for reading so-called 'patch variables' via a special ActionD @ivar num: Variable number to read @type num: C{int} """ def __init__(self, num, pos=None): Expression.__init__(self, pos) self.num = num def debug_print(self, indentation): generic.print_dbg(indentation, "PatchVariable:", self.num) def __str__(self): return "PatchVariable({:d})".format(self.num) def reduce(self, id_dicts=None, unknown_id_fatal=True): return self def supported_by_action2(self, raise_error): if raise_error: raise generic.ScriptError("Reading patch variables is not supported in a switch-block.", self.pos) return False def supported_by_actionD(self, raise_error): return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/special_parameter.py0000644000175100001660000000626314754345605020760 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression class SpecialParameter(Expression): """ Class for handling special grf parameters. These can be assigned special, custom methods for reading / writing to them. @ivar name: Name of the parameter, for debugging purposes. @type name: C{str} @ivar info: Information about the parameter. @type info: C{dict} @ivar write_func: Function that will be called when the parameter is the target of an assignment Arguments: Dictionary with parameter information (self.info) Target expression to assign Position information Return value is a 2-tuple: Left side of the assignment (must be a parameter) Right side of the assignment (may be any expression) @type write_func: C{function} @ivar read_func: Function that will be called to read out the parameter value Arguments: Dictionary with parameter information (self.info) Position information Return value: Expression that should be evaluated to get the parameter value @type read_func: C{function} @ivar is_bool: Does read_func return a boolean value? @type is_bool: C{bool} """ def __init__(self, name, info, write_func, read_func, is_bool, pos=None): Expression.__init__(self, pos) self.name = name self.info = info self.write_func = write_func self.read_func = read_func self.is_bool = is_bool def debug_print(self, indentation): generic.print_dbg(indentation, "Special parameter '{}'".format(self.name)) def __str__(self): return self.name def reduce(self, id_dicts=None, unknown_id_fatal=True): return self def is_boolean(self): return self.is_bool def can_assign(self): return self.write_func is not None def to_assignment(self, expr): param, expr = self.write_func(self.info, expr, self.pos) param = param.reduce() expr = expr.reduce() return (param, expr) def to_reading(self): param = self.read_func(self.info, self.pos) param = param.reduce() return param def supported_by_actionD(self, raise_error): return True def supported_by_action2(self, raise_error): return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/spritegroup_ref.py0000644000175100001660000000741214754345605020514 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from itertools import chain from nml import generic from nml.actions import action2 from .base_expression import Expression, Type class SpriteGroupRef(Expression): """ Container for a reference to a sprite group / layout @ivar name: Name of the referenced item @type name: L{Identifier} @ivar param_list: List of parameters to be passed @type param_list: C{list} of L{Expression} @ivar pos: Position of this reference @type pos: L{Position} @ivar act2: Action2 that is the target of this reference To be used for action2s that have no direct equivalent in the AST @type act2: L{Action2} """ def __init__(self, name, param_list, pos, act2=None): super().__init__(pos) self.name = name self.param_list = param_list self.act2 = act2 self.is_procedure = False def debug_print(self, indentation): generic.print_dbg(indentation, "Reference to:", self.name) if len(self.param_list) != 0: generic.print_dbg(indentation, "Parameters:") for p in self.param_list: p.debug_print(indentation + 2) def __str__(self): if self.param_list: return "{}({})".format(self.name, ", ".join(str(x) for x in self.param_list)) return str(self.name) def get_action2_id(self, feature): """ Get the action2 set-ID that this reference maps to @param feature: Feature of the action2 @type feature: C{int} @return: The set ID @rtype: C{int} """ if self.act2 is not None: return self.act2.id if self.name.value == "CB_FAILED": return 0 # 0 serves as a failed CB result because it is never used try: spritegroup = action2.resolve_spritegroup(self.name) except generic.ScriptError: raise AssertionError("Illegal action2 reference '{}' encountered.".format(self.name.value)) return spritegroup.get_action2(feature).id def reduce(self, id_dicts=None, unknown_id_fatal=True): if self.name.value != "CB_FAILED" and not self.is_procedure: spritegroup = action2.resolve_spritegroup(self.name) if spritegroup.optimise(): return spritegroup.optimised return self def supported_by_action2(self, raise_error): return True def collect_references(self): return list(chain([self], *(p.collect_references() for p in self.param_list))) def is_read_only(self): if self.name.value != "CB_FAILED": spritegroup = action2.resolve_spritegroup(self.name) return spritegroup.is_read_only() return True def type(self): if self.is_procedure: return Type.INTEGER return Type.SPRITEGROUP_REF def __eq__(self, other): return ( other is not None and isinstance(other, SpriteGroupRef) and other.name == self.name and other.param_list == self.param_list ) def __hash__(self): return hash(self.name) ^ hash(tuple(self.param_list)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/storage_op.py0000644000175100001660000001301614754345605017434 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import ConstantNumeric, Expression, Type from .parameter import parse_string_to_dword storage_op_info = { "STORE_PERM": {"store": True, "perm": True, "grfid": False, "max": 0xFF, "reserved": ()}, "STORE_TEMP": {"store": True, "perm": False, "grfid": False, "max": 0x10F, "reserved": range(0x80, 0xFF + 1)}, "LOAD_PERM": {"store": False, "perm": True, "grfid": True, "max": 0xFF, "reserved": ()}, "LOAD_TEMP": {"store": False, "perm": False, "grfid": False, "max": 0xFF, "reserved": range(0x80, 0xFF + 1)}, } class StorageOp(Expression): """ Class for reading/writing to (temporary or permanent) storage @ivar name: Name of the called storage function @type name: C{str} @ivar info: Dictionary containting information about the operation to perform @type info: C{dict} @ivar value: Value to store, or C{None} for loading operations @type value: L{Expression} @ivar register: Register to access @type register: L{Expression} @ivar grfid: GRFID of the register to access @type grfid: L{Expression} """ def __init__(self, name, args, pos=None): Expression.__init__(self, pos) self.name = name assert name in storage_op_info self.info = storage_op_info[name] arg_len = (2,) if self.info["store"] else (1,) if self.info["grfid"]: arg_len += (arg_len[0] + 1,) if len(args) not in arg_len: argstr = "{:d}".format(arg_len[0]) if len(arg_len) == 1 else "{}..{}".format(arg_len[0], arg_len[1]) raise generic.ScriptError( "{} requires {} argument(s), encountered {:d}".format(name, argstr, len(args)), pos ) i = 0 if self.info["store"]: self.value = args[i] i += 1 else: self.value = None self.register = args[i] i += 1 if i < len(args): self.grfid = args[i] assert self.info["grfid"] else: self.grfid = None def debug_print(self, indentation): generic.print_dbg(indentation, self.name) generic.print_dbg(indentation + 2, "Register:") self.register.debug_print(indentation + 4) if self.value is not None: generic.print_dbg(indentation + 2, "Value:") self.value.debug_print(indentation + 4) if self.grfid is not None: generic.print_dbg(indentation + 2, "GRFID:") self.grfid.debug_print(indentation + 4) def __str__(self): args = [] if self.value is not None: args.append(str(self.value)) args.append(str(self.register)) if self.grfid is not None: args.append(str(self.grfid)) return "{}({})".format(self.name, ", ".join(args)) def reduce(self, id_dicts=None, unknown_id_fatal=True): args = [] if self.value is not None: value = self.value.reduce(id_dicts) if value.type() != Type.INTEGER: if value.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(value.name, value.pos) raise generic.ScriptError("Value to store must be an integer.", value.pos) args.append(value) register = self.register.reduce(id_dicts) if register.type() != Type.INTEGER: if register.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(register.name, register.pos) raise generic.ScriptError("Register to access must be an integer.", register.pos) if isinstance(register, ConstantNumeric) and register.value > self.info["max"]: raise generic.ScriptError("Maximum register for {} is {:d}".format(self.name, self.info["max"]), self.pos) if isinstance(register, ConstantNumeric) and register.value in self.info["reserved"]: raise generic.ScriptError( "Temporary registers from 128 to 255 are reserved for NML's internal calculations.", self.pos ) args.append(register) if self.grfid is not None: grfid = self.grfid.reduce(id_dicts) # Test validity parse_string_to_dword(grfid) args.append(grfid) return StorageOp(self.name, args, self.pos) def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): if raise_error: raise generic.ScriptError("{}() may only be used inside switch-blocks".format(self.name), self.pos) return False def collect_references(self): return self.register.collect_references() + (self.value.collect_references() if self.value is not None else []) def is_read_only(self): assert self.info["store"] == (self.value is not None) return (not self.info["store"]) and self.register.is_read_only() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/string.py0000644000175100001660000000413514754345605016602 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from functools import reduce from nml import generic from .base_expression import Expression from .identifier import Identifier class String(Expression): def __init__(self, params, pos): Expression.__init__(self, pos) if len(params) == 0: raise generic.ScriptError("string() requires at least one parameter.", pos) self.name = params[0] if not isinstance(self.name, Identifier): raise generic.ScriptError("First parameter of string() must be an identifier.", pos) self.params = params[1:] def debug_print(self, indentation): generic.print_dbg(indentation, "String:") self.name.debug_print(indentation + 2) for param in self.params: generic.print_dbg(indentation + 2, "Parameter:") param.debug_print(indentation + 4) def __str__(self): ret = "string(" + self.name.value for p in self.params: ret += ", " + str(p) ret += ")" return ret def reduce(self, id_dicts=None, unknown_id_fatal=True): params = [p.reduce(id_dicts) for p in self.params] return String([self.name] + params, self.pos) def __eq__(self, other): return ( other is not None and isinstance(other, String) and self.name == other.name and self.params == other.params ) def __hash__(self): return hash(self.name) ^ reduce(lambda x, y: x ^ hash(y), self.params, 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/string_literal.py0000644000175100001660000000276214754345605020322 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, grfstrings from .base_expression import Expression, Type class StringLiteral(Expression): """ String literal expression. @ivar value: Value of the string literal. @type value: C{str} """ def __init__(self, value, pos): Expression.__init__(self, pos) self.value = value def debug_print(self, indentation): generic.print_dbg(indentation, 'String literal: "{}"'.format(self.value)) def __str__(self): return '"{}"'.format(self.value) def write(self, file, size): assert grfstrings.get_string_size(self.value, False, True) == size file.print_string(self.value, final_zero=False, force_ascii=True) def reduce(self, id_dicts=None, unknown_id_fatal=True): return self def type(self): return Type.STRING_LITERAL ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/ternaryop.py0000644000175100001660000000727614754345605017330 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import ConstantNumeric, Expression, Type class TernaryOp(Expression): def __init__(self, guard, expr1, expr2, pos): Expression.__init__(self, pos) self.guard = guard self.expr1 = expr1 self.expr2 = expr2 def debug_print(self, indentation): generic.print_dbg(indentation, "Ternary operator") generic.print_dbg(indentation, "Guard:") self.guard.debug_print(indentation + 2) generic.print_dbg(indentation, "Expression 1:") self.expr1.debug_print(indentation + 2) generic.print_dbg(indentation, "Expression 2:") self.expr2.debug_print(indentation + 2) def reduce(self, id_dicts=None, unknown_id_fatal=True): guard = self.guard.reduce(id_dicts) expr1 = self.expr1.reduce(id_dicts) expr2 = self.expr2.reduce(id_dicts) if isinstance(guard, ConstantNumeric): if guard.value != 0: return expr1 else: return expr2 if guard.type() != Type.INTEGER or expr1.type() != Type.INTEGER or expr2.type() != Type.INTEGER: if guard.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(guard.name, guard.pos) if expr1.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr1.name, expr1.pos) if expr2.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr2.name, expr2.pos) raise generic.ScriptError("All parts of the ternary operator (?:) must be integers.", self.pos) return TernaryOp(guard, expr1, expr2, self.pos) def supported_by_action2(self, raise_error): return ( self.guard.supported_by_action2(raise_error) and self.expr1.supported_by_action2(raise_error) and self.expr2.supported_by_action2(raise_error) ) def supported_by_actionD(self, raise_error): return ( self.guard.supported_by_actionD(raise_error) and self.expr1.supported_by_actionD(raise_error) and self.expr2.supported_by_actionD(raise_error) ) def collect_references(self): return self.guard.collect_references() + self.expr1.collect_references() + self.expr2.collect_references() def is_read_only(self): return self.guard.is_read_only() and self.expr1.is_read_only() and self.expr2.is_read_only() def is_boolean(self): return self.expr1.is_boolean() and self.expr2.is_boolean() def __eq__(self, other): return ( other is not None and isinstance(other, TernaryOp) and self.guard == other.guard and self.expr1 == other.expr1 and self.expr2 == other.expr2 ) def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.guard, self.expr1, self.expr2)) def __str__(self): return "({} ? {} : {})".format(str(self.guard), str(self.expr1), str(self.expr2)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/expression/variable.py0000644000175100001660000000775014754345605017067 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import ConstantNumeric, Expression, Type class Variable(Expression): def __init__(self, num, shift=None, mask=None, param=None, pos=None): Expression.__init__(self, pos) self.num = num self.shift = shift if shift is not None else ConstantNumeric(0) self.mask = mask if mask is not None else ConstantNumeric(0xFFFFFFFF) self.param = param self.add = None self.div = None self.mod = None self.extra_params = [] def debug_print(self, indentation): generic.print_dbg(indentation, "Action2 variable") self.num.debug_print(indentation + 2) if self.param is not None: generic.print_dbg(indentation + 2, "Parameter:") if isinstance(self.param, str): generic.print_dbg(indentation + 4, "Procedure call:", self.param) else: self.param.debug_print(indentation + 4) if len(self.extra_params) > 0: generic.print_dbg(indentation + 2, "Extra parameters:") for extra_param in self.extra_params: extra_param.debug_print(indentation + 4) def __str__(self): num = "0x{:02X}".format(self.num.value) if isinstance(self.num, ConstantNumeric) else str(self.num) ret = "var[{}, {}, {}".format(num, self.shift, self.mask) if self.param is not None: ret += ", {}".format(self.param) ret += "]" if self.add is not None: ret = "({} + {})".format(ret, self.add) if self.div is not None: ret = "({} / {})".format(ret, self.div) if self.mod is not None: ret = "({} % {})".format(ret, self.mod) return ret def reduce(self, id_dicts=None, unknown_id_fatal=True): num = self.num.reduce(id_dicts) shift = self.shift.reduce(id_dicts) mask = self.mask.reduce(id_dicts) param = self.param.reduce(id_dicts) if self.param is not None else None if ( num.type() != Type.INTEGER or shift.type() != Type.INTEGER or mask.type() != Type.INTEGER or (param is not None and param.type() != Type.INTEGER) ): raise generic.ScriptError("All parts of a variable access must be integers.", self.pos) var = Variable(num, shift, mask, param, self.pos) var.add = None if self.add is None else self.add.reduce(id_dicts) var.div = None if self.div is None else self.div.reduce(id_dicts) var.mod = None if self.mod is None else self.mod.reduce(id_dicts) var.extra_params = [(extra_param[0], extra_param[1].reduce(id_dicts)) for extra_param in self.extra_params] return var def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): if raise_error: if isinstance(self.num, ConstantNumeric): if self.num.value == 0x7C: raise generic.ScriptError("LOAD_PERM is only available in switch-blocks.", self.pos) if self.num.value == 0x7D: raise generic.ScriptError("LOAD_TEMP is only available in switch-blocks.", self.pos) raise generic.ScriptError("Variable accesses are not supported outside of switch-blocks.", self.pos) return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/free_number_list.py0000644000175100001660000001117414754345605016422 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class FreeNumberList: """ Contains a list with numbers and functions to pop one number from the list, to save the current state and to restore to the previous state. @ivar total_amount: Amount of numbers in the beginning. @type total_amount: C{int} @ivar stats: Statistics about id usage. @type stats: Tuple of C{int} and L{Position} refering to the amount on location of most concurrently used ids. @ivar free_numbers: The list with currently unused numbers. @type free_numbers: C{list} @ivar states: A list of lists. Each sublist contains all numbers that were L{popped} between the last call to L{save} and the next call to L{save}. Every time L{save} is called one sublist is added, every time L{restore} is called the topmost sublist is removed and it's values are added to the free number list again. @type states: C{list} @ivar used_numbers: A set with all numbers that have been used at some time. This is used by L{pop_unique}. @type used_numbers: C{set} @ivar exception: Exception to be thrown when there is no number available when requested via pop() or pop_global(). @type exception: C{str} @ivar exception_unique: Exception to be thrown when there is no unique number available when it is requested via pop_unique(). @type exception_unique: C{str} """ def __init__(self, free_numbers, exception, exception_unique): self.total_amount = len(free_numbers) self.stats = (0, None) self.free_numbers = free_numbers self.states = [] self.used_numbers = set() self.exception = exception self.exception_unique = exception_unique def pop(self, pos): """ Pop a free number from the list. You have to call L{save} at least once before calling L{pop}. @param pos: Positional context. @type pos: L{Position} @return: Some free number. """ assert len(self.states) > 0 if len(self.free_numbers) == 0: raise generic.ScriptError(self.exception, pos) num = self.free_numbers.pop() self.states[-1].append(num) self.used_numbers.add(num) num_used = len(self.used_numbers) if num_used > self.stats[0]: self.stats = (num_used, pos) return num def pop_global(self, pos): """ Pop a free number from the list. The number may have been used before and already been restored but it'll never be given out again. @param pos: Positional context. @type pos: L{Position} @return: Some free number. """ if len(self.free_numbers) == 0: raise generic.ScriptError(self.exception, pos) return self.free_numbers.pop() def pop_unique(self, pos): """ Pop a free number from the list. The number has not been used before and will not be used again. @param pos: Positional context. @type pos: L{Position} @return: A unique free number. """ for num in reversed(self.free_numbers): if num in self.used_numbers: continue self.free_numbers.remove(num) self.used_numbers.add(num) num_used = len(self.used_numbers) if num_used > self.stats[0]: self.stats = (num_used, pos) return num raise generic.ScriptError(self.exception_unique, pos) def save(self): """ Save the current state. All calls to L{pop} will be saved and can be reverted by calling L{restore} """ self.states.append([]) def restore(self): """ Add all numbers given out via L{pop} since the last L{save} to the free number list. """ assert len(self.states) > 0 self.states[-1].reverse() self.free_numbers.extend(self.states[-1]) self.states.pop() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0674624 nml-0.7.6/nml/generated/0000755000175100001660000000000014754345610014452 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/generated/__init__.py0000644000175100001660000000047314754345605016573 0ustar00runnerdocker# This package may contain the parsetab.py and lextab.py # files generated by PLY to speed up parser initialization. # # DO NOT EDIT THEM # # PLY will attempt to generate them at runtime if they aren't # present or the NML grammar/tokens have changed. # # The setup.py script should generate them at build time. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/generic.py0000644000175100001660000004077214754345605014520 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import os import sys import time # Enable VT100 sequences on windows consoles if os.name == "nt": class Break(Exception): pass try: from ctypes import byref, windll from ctypes.wintypes import DWORD, HANDLE kernel32 = windll.kernel32 h = kernel32.GetStdHandle(-11) # stdout if h is None or h == HANDLE(-1): raise Break() FILE_TYPE_CHAR = 0x0002 if (kernel32.GetFileType(h) & 3) != FILE_TYPE_CHAR: raise Break() mode = DWORD() kernel32.GetConsoleMode(h, byref(mode)) ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: kernel32.SetConsoleMode(h, mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) except Break: # Early exit pass def truncate_int32(value): """ Truncate the given value so it can be stored in exactly 4 bytes. The sign will be kept. Too high or too low values will be cut off, not clamped to the valid range. @param value: The value to truncate. @type value: C{int} @return: The truncated value. @rtype: C{int}. """ # source: http://www.tiac.net/~sw/2010/02/PureSalsa20/index.html return int((value & 0x7FFFFFFF) | -(value & 0x80000000)) def check_range(value, min_value, max_value, name, pos): """ Check if a value is within a certain range and raise an error if it's not. @param value: The value to check. @type value: C{int} @param min_value: Minimum valid value. @type min_value: C{int} @param max_value: Maximum valid value. @type max_value: C{int} @param name: Name of the variable that is being tested. @type name: C{str} @param pos: Position information from the variable being tested. @type pos: L{Position} """ if not min_value <= value <= max_value: raise RangeError(value, min_value, max_value, name, pos) def greatest_common_divisor(a, b): """ Get the greatest common divisor of two numbers @param a: First number. @type a: C{int} @param b: Second number. @type b: C{int} @return: Greatest common divisor. @rtype: C{int} """ while b != 0: t = b b = a % b a = t return a def reverse_lookup(dic, val): """ Perform reverse lookup of any key that has the provided value. @param dic: Dictionary to perform reverse lookup on. @type dic: C{dict} @param val: Value being searched. @type val: an existing value. @return: A key such that C{dict[key] == val}. @rtype: Type of the matching key. """ for k, v in dic.items(): if v == val: return k raise AssertionError("Value not found in the dictionary.") def build_position(poslist): """ Construct a L{Position} object that takes the other positions as include list. @param poslist: Sequence of positions to report. First entry is the innermost position, last entry is the nml statement that started it all. @type poslist: C{list} of L{Position} @return: Position to attach to an error. @rtype: L{Position} """ if poslist is None or len(poslist) == 0: return None if len(poslist) == 1: return poslist[0] pos = poslist[-1] pos.includes = pos.includes + poslist[:-1] return pos class Position: """ Base class representing a position in a file. @ivar filename: Name of the file. @type filename: C{str} @ivar includes: List of file includes @type includes: C{list} of L{Position} """ def __init__(self, filename, includes): self.filename = filename self.includes = includes class LinePosition(Position): """ Line in a file. @ivar line_start: Line number (starting with 1) where the position starts. @type line_start: C{int} """ def __init__(self, filename, line_start, includes=None): Position.__init__(self, filename, includes or []) self.line_start = line_start def __str__(self): return '"{}", line {:d}'.format(self.filename, self.line_start) class PixelPosition(Position): """ Position of a pixel (in a file with graphics). @ivar xpos: Horizontal position of the pixel. @type xpos: C{int} @ivar ypos: Vertical position of the pixel. @type ypos: C{int} """ def __init__(self, filename, xpos, ypos): Position.__init__(self, filename, []) self.xpos = xpos self.ypos = ypos def __str__(self): return '"{}" at [x: {:d}, y: {:d}]'.format(self.filename, self.xpos, self.ypos) class ImageFilePosition(Position): """ Generic (not position-dependant) error with an image file """ def __init__(self, filename, pos=None): poslist = [] if pos is not None: poslist.append(pos) Position.__init__(self, filename, poslist) def __str__(self): return 'Image file "{}"'.format(self.filename) class LanguageFilePosition(Position): """ Generic (not position-dependant) error with a language file. """ def __init__(self, filename): Position.__init__(self, filename, []) def __str__(self): return 'Language file "{}"'.format(self.filename) class ScriptError(Exception): def __init__(self, value, pos=None): self.value = value self.pos = pos def __str__(self): if self.pos is None: return self.value else: ret = str(self.pos) + ": " + self.value for inc in reversed(self.pos.includes): ret += "\nIncluded from: " + str(inc) return ret class ConstError(ScriptError): """ Error to denote a compile-time integer constant was expected but not found. """ def __init__(self, pos=None): ScriptError.__init__(self, "Expected a compile-time integer constant", pos) class RangeError(ScriptError): def __init__(self, value, min_value, max_value, name, pos=None): ScriptError.__init__( self, name + " out of range " + str(min_value) + ".." + str(max_value) + ", encountered " + str(value), pos ) class ProcCallSyntaxError(ScriptError): def __init__(self, name, pos=None): ScriptError.__init__(self, "Missing '()' after '{}'.".format(name), pos) class ImageError(ScriptError): def __init__(self, value, filename, pos=None): ScriptError.__init__(self, value, ImageFilePosition(filename, pos)) class OnlyOnceError(ScriptError): """ An error denoting two elements in a single grf were found, where only one is allowed. """ def __init__(self, typestr, pos=None): """ @param typestr: Description of the type of element encountered. @type typestr: C{str} @param pos: Position of the error, if provided. @type pos: C{None} or L{Position} """ ScriptError.__init__(self, "A grf may contain only one {}.".format(typestr), pos) class OnlyOnce: """ Class to enforce that certain objects / constructs appear only once. """ seen = {} @classmethod def enforce(cls, obj, typestr): """ If this method is called more than once for an object of the exact same class, an OnlyOnceError is raised. """ objtype = obj.__class__ if objtype in cls.seen: raise OnlyOnceError(typestr, obj.pos) cls.seen[objtype] = None @classmethod def clear(cls): cls.seen = {} class Warning: GENERIC = 0 DEPRECATION = 1 OPTIMISATION = 2 disabled = None VERBOSITY_WARNING = 1 # Verbosity level for warnings VERBOSITY_INFO = 2 # Verbosity level for info messages VERBOSITY_PROGRESS = 3 # Verbosity level for progress feedback VERBOSITY_TIMING = 4 # Verbosity level for timing information VERBOSITY_MAX = 4 # Maximum verbosity level """ Verbosity level for console output. """ verbosity_level = VERBOSITY_PROGRESS def set_verbosity(level): global verbosity_level verbosity_level = level def print_eol(msg): """ Clear current line and print message without linefeed. """ if not os.isatty(sys.stdout.fileno()): return print("\r" + msg + "\033[K", end="") """ Current progress message. """ progress_message = None """ Timestamp when the current processing step started. """ progress_start_time = None """ Timestamp of the last incremental progress update. """ progress_update_time = None def hide_progress(): if progress_message is not None: print_eol("") def show_progress(): if progress_message is not None: print_eol(progress_message) def clear_progress(): global progress_message global progress_start_time global progress_update_time hide_progress() if (progress_message is not None) and (verbosity_level >= VERBOSITY_TIMING): print("{} {:.1f} s".format(progress_message, time.process_time() - progress_start_time)) progress_message = None progress_start_time = None progress_update_time = None def print_progress(msg, incremental=False): """ Output progess information to the user. @param msg: Progress message. @type msg: C{str} @param incremental: True if this message is updated incrementally (that is, very often). @type incremental: C{bool} """ if verbosity_level < VERBOSITY_PROGRESS: return global progress_message global progress_start_time global progress_update_time if (not incremental) and (progress_message is not None): clear_progress() progress_message = msg if incremental: t = time.process_time() if (progress_update_time is not None) and (t - progress_update_time < 1): return progress_update_time = t else: progress_start_time = time.process_time() print_eol(msg) def print_info(msg): """ Output a pure informational message to th euser. """ if verbosity_level < VERBOSITY_INFO: return hide_progress() print(" nmlc info: " + msg) show_progress() def print_warning(type, msg, pos=None): """ Output a warning message to the user. """ if verbosity_level < VERBOSITY_WARNING: return if Warning.disabled and type in Warning.disabled: return if pos: msg = str(pos) + ": " + msg msg = " nmlc warning: " + msg if sys.stderr.isatty(): msg = "\033[93m" + msg + "\033[0m" hide_progress() print(msg, file=sys.stderr) show_progress() def print_error(msg, pos=None): """ Output an error message to the user. """ if pos: msg = str(pos) + ": " + msg else: clear_progress() msg = " nmlc ERROR: " + msg if sys.stderr.isatty(): msg = "\033[91m" + msg + "\033[0m" hide_progress() print(msg, file=sys.stderr) show_progress() def print_dbg(indent, *args): """ Output debug text. @param indent: Indentation to add to the output. @type indent: C{int} @param args: Arguments to print. An additional space is printed between them. @type args: C{Tuple} of C{str} """ hide_progress() print(indent * " " + " ".join(str(arg) for arg in args)) show_progress() """ Paths already resolved to correct paths on the system. The key is the path as specified in the sources. The value is the validated path on the system. """ _paths = {} def find_file(filepath): """ Verify whether L{filepath} exists. If not, try to find a similar one with a different case. @param filepath: Path to the file to open. @type filepath: C{str} @return: Path name to a file that exists at the file system. @rtype: C{str} """ # Split the filepath in components (directory parts and the filename). drive, filepath = os.path.splitdrive(os.path.normpath(filepath)) # 'splitdrive' above does not remove the leading / of absolute Unix paths. # The 'split' below splits on os.sep, which means that loop below fails for "/". # To prevent that, handle the leading os.sep separately. if filepath.startswith(os.sep): drive = drive + os.sep filepath = filepath[len(os.sep) :] components = [] # Path stored in reverse order (filename at index[0]) while filepath != "": filepath, filepart = os.path.split(filepath) components.append(filepart) # Re-build the absolute path. path = drive if path == "": path = os.getcwd() while len(components) > 0: comp = components.pop() childpath = os.path.join(path, comp) if childpath in _paths: path = _paths[childpath] continue if os.access(path, os.R_OK): # Path is readable, compare provided path with the file system. entries = os.listdir(path) + [os.curdir, os.pardir] lcomp = comp.lower() matches = [entry for entry in entries if lcomp == entry.lower()] if len(matches) == 0: raise ScriptError( 'Path "{}" does not exist (even after case conversions)'.format(os.path.join(path, comp)) ) elif len(matches) > 1: raise ScriptError( 'Path "{}" is not unique (case conversion gave {:d} solutions)'.format( os.path.join(path, comp), len(matches) ) ) if matches[0] != comp: given_path = os.path.join(path, comp) real_path = os.path.join(path, matches[0]) msg = ( 'Path "{}" at the file system does not match path "{}" given in the input' " (case mismatch in the last component)" ).format(real_path, given_path) print_warning(Warning.GENERIC, msg) elif os.access(path, os.X_OK): # Path is only accessible, cannot inspect the file system. matches = [comp] else: raise ScriptError('Path "{}" does not exist or is not accessible'.format(path)) path = os.path.join(path, matches[0]) if len(components) > 0: _paths[childpath] = path return path cache_root_dir = ".nmlcache" def set_cache_root_dir(dir): global cache_root_dir cache_root_dir = None if dir is None else os.path.abspath(dir) def _cache_file_path(sources, extension): """ Compose a filename for a cache file. @param sources: List of source files, the cache file depends on / belongs to. @type sources: C{list} or C{tuple} of C{str} or similar. @param extension: File extension for the cache file including leading ".". @type extension: C{str} @return: Filename for cache file. @rtype: C{str} """ result = "" for part in sources: if part is not None: path, name = os.path.split(part) if len(result) == 0: # Make sure that the path does not leave the cache dir path = os.path.normpath(path).replace(os.path.pardir, "__") path = os.path.join(cache_root_dir, path) result = os.path.join(path, name) else: # In case of multiple soure files, ignore the path component for all but the first result += "_" + name return result + extension def open_cache_file(sources, extension, mode): if cache_root_dir is None: raise FileNotFoundError("No cache directory") if not any(sources): raise FileNotFoundError("Can't create cache file with no sources") path = _cache_file_path(sources, extension) try: if "w" in mode: os.makedirs(os.path.dirname(path), exist_ok=True) return open(path, mode) except OSError: if "w" in mode: print_warning( Warning.GENERIC, "Can't create cache file {}. Check permissions, or use --cache-dir or --no-cache.".format(path), ) raise ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/global_constants.py0000644000175100001660000017676214754345605016451 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, nmlop def constant_number(name, info, pos): if isinstance(info, str): generic.print_warning( generic.Warning.DEPRECATION, "'{}' is deprecated, consider using '{}' instead".format(name, info), pos ) info = constant_numbers[info] return expression.ConstantNumeric(info, pos) # fmt: off constant_numbers = { # climates "CLIMATE_TEMPERATE" : 0, "CLIMATE_ARCTIC" : 1, "CLIMATE_TROPIC" : 2, "CLIMATE_TROPICAL" : 2, "CLIMATE_TOYLAND" : 3, "ABOVE_SNOWLINE" : 11, # Only for house property available_mask "NO_CLIMATE" : 0x00, "ALL_CLIMATES" : 0x0F, # never expire "VEHICLE_NEVER_EXPIRES" : 0xFF, # default_cargo "DEFAULT_CARGO_FIRST_REFITTABLE" : 0xFF, # cargo classes "CC_PASSENGERS" : 0, "CC_MAIL" : 1, "CC_EXPRESS" : 2, "CC_ARMOURED" : 3, "CC_BULK" : 4, "CC_PIECE_GOODS" : 5, "CC_LIQUID" : 6, "CC_REFRIGERATED" : 7, "CC_HAZARDOUS" : 8, "CC_COVERED" : 9, "CC_OVERSIZED" : 10, "CC_POWDERIZED" : 11, "CC_NON_POURABLE" : 12, "CC_NEO_BULK" : 12, "CC_POTABLE" : 13, "CC_NON_POTABLE" : 14, "CC_SPECIAL" : 15, "NO_CARGO_CLASS" : 0, "ALL_NORMAL_CARGO_CLASSES" : 0x1FFF, "ALL_CARGO_CLASSES" : 0x9FFF, # engine class "ENGINE_CLASS_STEAM" : 0x00, "ENGINE_CLASS_DIESEL" : 0x08, "ENGINE_CLASS_ELECTRIC" : 0x28, "ENGINE_CLASS_MONORAIL" : 0x32, "ENGINE_CLASS_MAGLEV" : 0x38, # running costs "RUNNING_COST_STEAM" : 0x4C30, "RUNNING_COST_DIESEL" : 0x4C36, "RUNNING_COST_ELECTRIC" : 0x4C3C, "RUNNING_COST_ROADVEH" : 0x4C48, "RUNNING_COST_NONE" : 0x0000, # vehicle length "VEHICLE_LENGTH" : 8, # visual effect "VISUAL_EFFECT_DEFAULT" : 0x00, "VISUAL_EFFECT_STEAM" : 0x10, "VISUAL_EFFECT_DIESEL" : 0x20, "VISUAL_EFFECT_ELECTRIC" : 0x30, "VISUAL_EFFECT_DISABLE" : 0x40, # effect spawn model "EFFECT_SPAWN_MODEL_NONE" : 0x40, "EFFECT_SPAWN_MODEL_STEAM" : 0x41, "EFFECT_SPAWN_MODEL_DIESEL" : 0x42, "EFFECT_SPAWN_MODEL_ELECTRIC": 0x43, # visual effect / effect spawn model "ENABLE_WAGON_POWER" : 0x00, "DISABLE_WAGON_POWER" : 0x80, # create_effect "EFFECT_SPRITE_STEAM" : 0xF1, "EFFECT_SPRITE_DIESEL" : 0xF2, "EFFECT_SPRITE_ELECTRIC" : 0xF3, "EFFECT_SPRITE_AIRCRAFT_BREAKDOWN_SMOKE" : 0xFA, "EFFECT_SPRITE_NONE" : 0xFF, "CB_RESULT_CREATE_EFFECT_CENTER" : 0x2000, "CB_RESULT_CREATE_EFFECT_NO_ROTATION" : 0x4000, # train misc flags "TRAIN_FLAG_TILT" : 0, "TRAIN_FLAG_2CC" : 1, "TRAIN_FLAG_MU" : 2, "TRAIN_FLAG_FLIP" : 3, "TRAIN_FLAG_AUTOREFIT": 4, "TRAIN_FLAG_NO_BREAKDOWN_SMOKE": 6, "TRAIN_FLAG_SPRITE_STACK": 7, # roadveh misc flags "ROADVEH_FLAG_TRAM" : 0, "ROADVEH_FLAG_2CC" : 1, "ROADVEH_FLAG_AUTOREFIT": 4, "ROADVEH_FLAG_NO_BREAKDOWN_SMOKE": 6, "ROADVEH_FLAG_SPRITE_STACK": 7, # ship misc flags "SHIP_FLAG_2CC" : 1, "SHIP_FLAG_AUTOREFIT": 4, "SHIP_FLAG_NO_BREAKDOWN_SMOKE": 6, "SHIP_FLAG_SPRITE_STACK": 7, # aircraft misc flags "AIRCRAFT_FLAG_2CC" : 1, "AIRCRAFT_FLAG_AUTOREFIT": 4, "AIRCRAFT_FLAG_NO_BREAKDOWN_SMOKE": 6, "AIRCRAFT_FLAG_SPRITE_STACK": 7, # for those, who can't tell the difference between a train and an aircraft: "VEHICLE_FLAG_2CC" : 1, "VEHICLE_FLAG_AUTOREFIT": 4, "VEHICLE_FLAG_NO_BREAKDOWN_SMOKE": 6, # vehicle extra flags "VEHICLE_FLAG_DISABLE_NEW_VEHICLE_MESSAGE" : 0, "VEHICLE_FLAG_DISABLE_EXCLUSIVE_PREVIEW" : 1, "VEHICLE_FLAG_SYNC_VARIANT_EXCLUSIVE_PREVIEW" : 2, "VEHICLE_FLAG_SYNC_VARIANT_RELIABILITY" : 3, # Graphic flags for waterfeatures "WATERFEATURE_ALTERNATIVE_SPRITES" : 0, # IDs for waterfeatures "WF_WATERCLIFFS" : 0x00, "WF_LOCKS" : 0x01, "WF_DIKES" : 0x02, "WF_CANAL_GUI" : 0x03, "WF_FLAT_DOCKS" : 0x04, "WF_RIVER_SLOPE" : 0x05, "WF_RIVERBANKS" : 0x06, "WF_RIVER_GUI" : 0x07, "WF_BUOY" : 0x08, # ai flags "AI_FLAG_PASSENGER" : 0x01, "AI_FLAG_CARGO" : 0x00, # Callback results "CB_RESULT_ATTACH_ALLOW_IF_RAILTYPES" : 0x400, "CB_RESULT_ATTACH_ALLOW" : 0x401, "CB_RESULT_ATTACH_DISALLOW" : 0x402, "CB_RESULT_NO_MORE_ARTICULATED_PARTS" : 0x7FFF, "CB_RESULT_REVERSED_VEHICLE" : 0x4000, "CB_RESULT_32_DAYS_TRIGGER" : 0, "CB_RESULT_32_DAYS_COLOUR_MAPPING" : 1, "CB_RESULT_COLOUR_MAPPING_ADD_CC" : 0x4000, "CB_RESULT_AUTOREFIT" : 0x4000, "CB_RESULT_REFIT_COST_MASK" : 0x3FFF, "CB_RESULT_NO_SOUND" : 0x7EFF, # Never a valid sound id # Callback results in registers "CB_FLAG_MORE_SPRITES" : 0x80000000, # 1-based, not 0-based "SOUND_EVENT_START" : 1, "SOUND_EVENT_TUNNEL" : 2, "SOUND_EVENT_BREAKDOWN" : 3, "SOUND_EVENT_RUNNING" : 4, "SOUND_EVENT_TOUCHDOWN" : 5, "SOUND_EVENT_VISUAL_EFFECT" : 6, "SOUND_EVENT_RUNNING_16" : 7, "SOUND_EVENT_STOPPED" : 8, "SOUND_EVENT_LOAD_UNLOAD" : 9, # sound effects "SOUND_CONSTRUCTION_WATER" : 0x00, "SOUND_FACTORY" : 0x01, "SOUND_DEPARTURE_STEAM" : 0x02, "SOUND_TRAIN_THROUGH_TUNNEL" : 0x03, "SOUND_DEPARTURE_CARGO_SHIP" : 0x04, "SOUND_DEPARTURE_FERRY" : 0x05, "SOUND_TAKEOFF_PROPELLER" : 0x06, "SOUND_TAKEOFF_JET" : 0x07, "SOUND_DEPARTURE_TRAIN" : 0x08, "SOUND_MINE" : 0x09, "SOUND_POWER_STATION" : 0x0A, "SOUND_STEAM" : 0x0B, "SOUND_LEVEL_CROSSING" : 0x0C, "SOUND_BREAKDOWN_ROADVEHICLE" : 0x0D, "SOUND_BREAKDOWN_TRAIN_SHIP" : 0x0E, "SOUND_CRASH" : 0x0F, "SOUND_EXPLOSION" : 0x10, "SOUND_TRAIN_COLLISION" : 0x11, "SOUND_CASHTILL" : 0x12, "SOUND_BEEP" : 0x13, "SOUND_NEWS_TICKER" : 0x14, "SOUND_SKID_PLANE" : 0x15, "SOUND_TAKEOFF_HELICOPTER" : 0x16, "SOUND_DEPARTURE_OLD_RV_1" : 0x17, "SOUND_DEPARTURE_OLD_RV_2" : 0x18, "SOUND_DEPARTURE_MODERN_BUS" : 0x19, "SOUND_DEPARTURE_OLD_BUS" : 0x1A, "SOUND_APPLAUSE" : 0x1B, "SOUND_NEW_ENGINE" : 0x1C, "SOUND_CONSTRUCTION_OTHER" : 0x1D, "SOUND_CONSTRUCTION_RAIL" : 0x1E, "SOUND_ROAD_WORKS" : 0x1F, "SOUND_CAR_HORN" : 0x20, "SOUND_CAR_HORN_2" : 0x21, "SOUND_FARM_1" : 0x22, "SOUND_FARM_2" : 0x23, "SOUND_FARM_3" : 0x24, "SOUND_CONSTRUCTION_BRIDGE" : 0x25, "SOUND_SAWMILL" : 0x26, "SOUND_GOOD_YEAR" : 0x27, "SOUND_BAD_YEAR" : 0x28, "SOUND_SUGAR_MINE_2" : 0x29, "SOUND_TOY_FACTORY_3" : 0x2A, "SOUND_TOY_FACTORY_2" : 0x2B, "SOUND_TOY_FACTORY_1" : 0x2C, "SOUND_SUGAR_MINE_1" : 0x2D, "SOUND_BUBBLE_GENERATOR" : 0x2E, "SOUND_BUBBLE_GENERATOR_FAIL" : 0x2F, "SOUND_TOFFEE_QUARRY" : 0x30, "SOUND_BUBBLE_GENERATOR_SUCCESS" : 0x31, "SOUND_POP_2" : 0x32, "SOUND_PLASTIC_MINE" : 0x33, "SOUND_ARCTIC_SNOW_1" : 0x34, "SOUND_BREAKDOWN_ROADVEHICLE_TOYLAND" : 0x35, "SOUND_LUMBER_MILL_3" : 0x36, "SOUND_LUMBER_MILL_2" : 0x37, "SOUND_LUMBER_MILL_1" : 0x38, "SOUND_ARCTIC_SNOW_2" : 0x39, "SOUND_BREAKDOWN_TRAIN_SHIP_TOYLAND" : 0x3A, "SOUND_TAKEOFF_JET_FAST" : 0x3B, "SOUND_DEPARTURE_BUS_TOYLAND_1" : 0x3C, "SOUND_TAKEOFF_JET_BIG" : 0x3D, "SOUND_DEPARTURE_BUS_TOYLAND_2" : 0x3E, "SOUND_DEPARTURE_TRUCK_TOYLAND_1" : 0x3F, "SOUND_DEPARTURE_TRUCK_TOYLAND_2" : 0x40, "SOUND_DEPARTURE_MAGLEV" : 0x41, "SOUND_RAINFOREST_1" : 0x42, "SOUND_RAINFOREST_2" : 0x43, "SOUND_RAINFOREST_3" : 0x44, "SOUND_TAKEOFF_PROPELLER_TOYLAND_1" : 0x45, "SOUND_TAKEOFF_PROPELLER_TOYLAND_2" : 0x46, "SOUND_DEPARTURE_MONORAIL" : 0x47, "SOUND_RAINFOREST_4" : 0x48, # legacy compatibility - see https://github.com/OpenTTD/nml/issues/190 "SOUND_SPLAT" : "SOUND_CONSTRUCTION_WATER", "SOUND_FACTORY_WHISTLE" : "SOUND_FACTORY", "SOUND_TRAIN" : "SOUND_DEPARTURE_STEAM", "SOUND_SHIP_HORN" : "SOUND_DEPARTURE_CARGO_SHIP", "SOUND_FERRY_HORN" : "SOUND_DEPARTURE_FERRY", "SOUND_PLANE_TAKE_OFF" : "SOUND_TAKEOFF_PROPELLER", "SOUND_JET" : "SOUND_TAKEOFF_JET", "SOUND_TRAIN_HORN" : "SOUND_DEPARTURE_TRAIN", "SOUND_MINING_MACHINERY" : "SOUND_MINE", "SOUND_ELECTRIC_SPARK" : "SOUND_POWER_STATION", "SOUND_VEHICLE_BREAKDOWN" : "SOUND_BREAKDOWN_ROADVEHICLE", "SOUND_TRAIN_BREAKDOWN" : "SOUND_BREAKDOWN_TRAIN_SHIP", "SOUND_BIG_CRASH" : "SOUND_TRAIN_COLLISION", "SOUND_MORSE" : "SOUND_NEWS_TICKER", "SOUND_HELICOPTER" : "SOUND_TAKEOFF_HELICOPTER", "SOUND_BUS_START_PULL_AWAY" : "SOUND_DEPARTURE_OLD_RV_1", "SOUND_BUS_START_PULL_AWAY_WITH_HORN" : "SOUND_DEPARTURE_OLD_RV_2", "SOUND_TRUCK_START" : "SOUND_DEPARTURE_MODERN_BUS", "SOUND_TRUCK_START_2" : "SOUND_DEPARTURE_OLD_BUS", "SOUND_OOOOH" : "SOUND_NEW_ENGINE", "SOUND_SPLAT_2" : "SOUND_CONSTRUCTION_OTHER", "SOUND_SPLAT_3" : "SOUND_CONSTRUCTION_RAIL", "SOUND_JACKHAMMER" : "SOUND_ROAD_WORKS", "SOUND_SHEEP" : "SOUND_FARM_1", "SOUND_COW" : "SOUND_FARM_2", "SOUND_HORSE" : "SOUND_FARM_3", "SOUND_BLACKSMITH_ANVIL" : "SOUND_CONSTRUCTION_BRIDGE", "SOUND_RIP" : "SOUND_SUGAR_MINE_2", "SOUND_EXTRACT_AND_POP" : "SOUND_TOY_FACTORY_3", "SOUND_COMEDY_HIT" : "SOUND_TOY_FACTORY_2", "SOUND_MACHINERY" : "SOUND_TOY_FACTORY_1", "SOUND_RIP_2" : "SOUND_SUGAR_MINE_1", "SOUND_EXTRACT_AND_POP_2" : "SOUND_BUBBLE_GENERATOR", "SOUND_POP" : "SOUND_BUBBLE_GENERATOR_FAIL", "SOUND_CARTOON_SOUND" : "SOUND_TOFFEE_QUARRY", "SOUND_EXTRACT" : "SOUND_BUBBLE_GENERATOR_SUCCESS", "SOUND_WIND" : "SOUND_ARCTIC_SNOW_1", "SOUND_COMEDY_BREAKDOWN" : "SOUND_BREAKDOWN_ROADVEHICLE_TOYLAND", "SOUND_CARTOON_CRASH" : "SOUND_LUMBER_MILL_3", "SOUND_BALLOON_SQUEAK" : "SOUND_LUMBER_MILL_2", "SOUND_CHAINSAW" : "SOUND_LUMBER_MILL_1", "SOUND_HEAVY_WIND" : "SOUND_ARCTIC_SNOW_2", "SOUND_COMEDY_BREAKDOWN_2" : "SOUND_BREAKDOWN_TRAIN_SHIP_TOYLAND", "SOUND_JET_OVERHEAD" : "SOUND_TAKEOFF_JET_FAST", "SOUND_COMEDY_CAR" : "SOUND_DEPARTURE_BUS_TOYLAND_1", "SOUND_ANOTHER_JET_OVERHEAD" : "SOUND_TAKEOFF_JET_BIG", "SOUND_COMEDY_CAR_2" : "SOUND_DEPARTURE_BUS_TOYLAND_2", "SOUND_COMEDY_CAR_3" : "SOUND_DEPARTURE_TRUCK_TOYLAND_1", "SOUND_COMEDY_CAR_START_AND_PULL_AWAY" : "SOUND_DEPARTURE_TRUCK_TOYLAND_2", "SOUND_MAGLEV" : "SOUND_DEPARTURE_MAGLEV", "SOUND_LOON_BIRD" : "SOUND_RAINFOREST_1", "SOUND_LION" : "SOUND_RAINFOREST_2", "SOUND_MONKEYS" : "SOUND_RAINFOREST_3", "SOUND_PLANE_CRASHING" : "SOUND_TAKEOFF_PROPELLER_TOYLAND_1", "SOUND_PLANE_ENGINE_SPUTTERING" : "SOUND_TAKEOFF_PROPELLER_TOYLAND_2", "SOUND_MAGLEV_2" : "SOUND_DEPARTURE_MONORAIL", "SOUND_DISTANT_BIRD" : "SOUND_RAINFOREST_4", # sprite ids "SPRITE_ID_NEW_TRAIN" : 0xFD, "SPRITE_ID_NEW_ROADVEH" : 0xFF, "SPRITE_ID_NEW_SHIP" : 0xFF, "SPRITE_ID_NEW_AIRCRAFT" : 0xFF, "NEW_CARGO_SPRITE" : 0xFFFF, # aircraft type "AIRCRAFT_TYPE_HELICOPTER" : 0x00, "AIRCRAFT_TYPE_SMALL" : 0x02, "AIRCRAFT_TYPE_LARGE" : 0x03, # ground sprite IDs "GROUNDSPRITE_RAIL_Y" : 1011, "GROUNDSPRITE_RAIL_X" : 1012, "GROUNDSPRITE_ROAD_Y" : 1332, "GROUNDSPRITE_ROAD_X" : 1333, "GROUNDSPRITE_CONCRETE" : 1420, "GROUNDSPRITE_CLEARED" : 3924, "GROUNDSPRITE_GRASS_1_3" : 3943, "GROUNDSPRITE_GRASS_2_3" : 3962, "GROUNDSPRITE_GRASS_3_3" : 3981, "GROUNDSPRITE_GRASS" : 3981, "GROUNDSPRITE_NORMAL" : 3981, "GROUNDSPRITE_WATER" : 4061, "GROUNDSPRITE_SNOW_1_4" : 4493, "GROUNDSPRITE_SNOW_2_4" : 4512, "GROUNDSPRITE_SNOW_3_4" : 4531, "GROUNDSPRITE_SNOW_4_4" : 4550, "GROUNDSPRITE_SNOW" : 4550, "GROUNDSPRITE_DESERT_1_2" : 4512, "GROUNDSPRITE_DESERT_2_2" : 4550, "GROUNDSPRITE_DESERT" : 4550, # general CB for rerandomizing "CB_RANDOM_TRIGGER" : 0x01, # station general flags "STAT_FLAG_DISTRIBUTED_CARGO" : 1, "STAT_FLAG_RANDOM_ANIMATION" : 2, "STAT_FLAG_CUSTOM_FOUNDATIONS" : 3, "STAT_FLAG_EXTENDED_FOUNDATIONS" : 4, # station tile flags "STAT_TILE_PYLON" : 0, "STAT_TILE_NOWIRE" : 1, "STAT_TILE_BLOCKED" : 2, # station tiles "STAT_ALL_TILES" : 0xFF, # station animation triggers "STAT_ANIM_IS_BUILT" : 0, "STAT_ANIM_CARGO_ARRIVES" : 1, "STAT_ANIM_CARGO_REMOVED" : 2, "STAT_ANIM_TRAIN_ENTERS" : 3, "STAT_ANIM_TRAIN_LEAVES" : 4, "STAT_ANIM_TRAIN_LOAD_UNLOAD" : 5, "STAT_ANIM_250_TICKS" : 6, # house flags "HOUSE_FLAG_NOT_SLOPED" : 1, "HOUSE_FLAG_ANIMATE" : 5, "HOUSE_FLAG_CHURCH" : 6, "HOUSE_FLAG_STADIUM" : 7, "HOUSE_FLAG_ONLY_SE" : 8, "HOUSE_FLAG_PROTECTED" : 9, "HOUSE_FLAG_SYNC_CALLBACK" : 10, "HOUSE_FLAG_RANDOM_ANIMATION" : 11, # cargo acceptance "HOUSE_ACCEPT_GOODS" : 0x00, "HOUSE_ACCEPT_FOOD" : 0x10, # 0x80 / 8 "HOUSE_ACCEPT_FIZZY_DRINKS" : 0x10, # 0x80 / 8 # house sizes "HOUSE_SIZE_1X1" : 0, "HOUSE_SIZE_2X1" : 2, "HOUSE_SIZE_1X2" : 3, "HOUSE_SIZE_2X2" : 4, # house tiles "HOUSE_TILE_NORTH" : 0, "HOUSE_TILE_EAST" : 1, "HOUSE_TILE_WEST" : 2, "HOUSE_TILE_SOUTH" : 3, # house callback results "CB_RESULT_HOUSE_NO_MORE_PRODUCTION": 0x20FF, # town zones "TOWNZONE_EDGE" : 0, "TOWNZONE_OUTSKIRT" : 1, "TOWNZONE_OUTER_SUBURB" : 2, "TOWNZONE_INNER_SUBURB" : 3, "TOWNZONE_CENTRE" : 4, "ALL_TOWNZONES" : 0x1F, # industry tile special flags "INDTILE_FLAG_RANDOM_ANIMATION" : 0, "INDTILE_FLAG_ACCEPT_ALL" : 1, "CB_RESULT_IND_PROD_NO_CHANGE" : 0x00, "CB_RESULT_IND_PROD_HALF" : 0x01, "CB_RESULT_IND_PROD_DOUBLE" : 0x02, "CB_RESULT_IND_PROD_CLOSE" : 0x03, "CB_RESULT_IND_PROD_RANDOM" : 0x04, "CB_RESULT_IND_PROD_DIVIDE_BY_4" : 0x05, "CB_RESULT_IND_PROD_DIVIDE_BY_8" : 0x06, "CB_RESULT_IND_PROD_DIVIDE_BY_16" : 0x07, "CB_RESULT_IND_PROD_DIVIDE_BY_32" : 0x08, "CB_RESULT_IND_PROD_MULTIPLY_BY_4" : 0x09, "CB_RESULT_IND_PROD_MULTIPLY_BY_8" : 0x0A, "CB_RESULT_IND_PROD_MULTIPLY_BY_16" : 0x0B, "CB_RESULT_IND_PROD_MULTIPLY_BY_32" : 0x0C, "CB_RESULT_IND_PROD_DECREMENT_BY_1" : 0x0D, "CB_RESULT_IND_PROD_INCREMENT_BY_1" : 0x0E, "CB_RESULT_IND_PROD_SET_BY_0x100" : 0x0F, "CB_RESULT_IND_DO_NOT_USE_SPECIAL" : 0x00, "CB_RESULT_IND_USE_SPECIAL" : 0x01, "CB_RESULT_IND_NO_CONSTRUCTION" : 0x0000, "CB_RESULT_IND_PROBABILITY_FROM_PROPERTY" : 0x0100, "CB_RESULT_NO_TEXT" : 0x400, "CB_RESULT_IND_NO_TEXT_NO_AMOUNT" : 0x401, "CB_RESULT_LOCATION_ALLOW" : 0x400, "CB_RESULT_LOCATION_DISALLOW" : 0x401, "CB_RESULT_LOCATION_DISALLOW_ONLY_RAINFOREST" : 0x402, "CB_RESULT_LOCATION_DISALLOW_ONLY_DESERT" : 0x403, "CB_RESULT_LOCATION_DISALLOW_ONLY_ABOVE_SNOWLINE" : 0x404, "CB_RESULT_LOCATION_DISALLOW_ONLY_BELOW_SNOWLINE" : 0x405, "CB_RESULT_LOCATION_DISALLOW_NOT_ON_OPEN_SEA" : 0x406, "CB_RESULT_LOCATION_DISALLOW_NOT_ON_CANAL" : 0x407, "CB_RESULT_LOCATION_DISALLOW_NOT_ON_RIVER" : 0x408, # industry special flags "IND_FLAG_PLANT_FIELDS_PERIODICALLY" : 0, "IND_FLAG_CUT_TREES" : 1, "IND_FLAG_BUILT_ON_WATER" : 2, "IND_FLAG_ONLY_IN_LARGE_TOWNS" : 3, "IND_FLAG_ONLY_IN_TOWNS" : 4, "IND_FLAG_BUILT_NEAR_TOWN" : 5, "IND_FLAG_PLANT_FIELDS_WHEN_BUILT" : 6, "IND_FLAG_NO_PRODUCTION_INCREASE" : 7, "IND_FLAG_BUILT_ONLY_BEFORE_1950" : 8, "IND_FLAG_BUILT_ONLY_AFTER_1960" : 9, "IND_FLAG_AI_CREATES_AIR_AND_SHIP_ROUTES" : 10, "IND_FLAG_MILITARY_AIRPLANE_CAN_EXPLODE" : 11, "IND_FLAG_MILITARY_HELICOPTER_CAN_EXPLODE" : 12, "IND_FLAG_CAN_CAUSE_SUBSIDENCE" : 13, "IND_FLAG_AUTOMATIC_PRODUCTION_MULTIPLIER" : 14, "IND_FLAG_RANDOM_BITS_IN_PRODUCTION_CALLBACK" : 15, "IND_FLAG_DO_NOT_FORCE_INSTANCE_AT_MAP_GENERATION" : 16, "IND_FLAG_ALLOW_CLOSING_LAST_INSTANCE" : 17, "IND_FLAG_LONG_CARGO_TYPE_LISTS" : 18, "IND_FLAG_DO_NOT_CLAMP_PASSENGER_PRODUCTION" : 19, # flags for builtin function industry_type(..) "IND_TYPE_OLD" : 0, "IND_TYPE_NEW" : 1, # founder for industries (founder variable) "FOUNDER_GAME" : 16, # object flags "OBJ_FLAG_ONLY_SE" : 0, "OBJ_FLAG_IRREMOVABLE" : 1, "OBJ_FLAG_ANYTHING_REMOVE" : 2, "OBJ_FLAG_ON_WATER" : 3, "OBJ_FLAG_REMOVE_IS_INCOME": 4, "OBJ_FLAG_NO_FOUNDATIONS" : 5, "OBJ_FLAG_ANIMATED" : 6, "OBJ_FLAG_ONLY_INGAME" : 7, "OBJ_FLAG_2CC" : 8, "OBJ_FLAG_NOT_ON_LAND" : 9, "OBJ_FLAG_DRAW_WATER" : 10, "OBJ_FLAG_ALLOW_BRIDGE" : 11, "OBJ_FLAG_RANDOM_ANIMATION": 12, "OBJ_FLAG_SCALE_BY_WATER" : 13, # object animation triggers "OBJ_ANIM_IS_BUILT" : 0, "OBJ_ANIM_PERIODIC" : 1, "OBJ_ANIM_SYNC" : 2, # Special values for object var 0x60 "OBJECT_TYPE_OTHER_GRF" : 0xFFFE, "OBJECT_TYPE_NO_OBJECT" : 0xFFFF, # railtype flags "RAILTYPE_FLAG_CATENARY" : 0, "RAILTYPE_FLAG_NO_LEVEL_CROSSING" : 1, # for OpenTTD > r20049 "RAILTYPE_FLAG_HIDDEN" : 2, "RAILTYPE_FLAG_PRECOMBINED" : 3, "RAILTYPE_FLAG_ALLOW_90DEG" : 4, "RAILTYPE_FLAG_DISALLOW_90DEG" : 5, # roadtype flags "ROADTYPE_FLAG_CATENARY" : 0, "ROADTYPE_FLAG_NO_LEVEL_CROSSING" : 1, "ROADTYPE_FLAG_NO_HOUSES" : 2, "ROADTYPE_FLAG_HIDDEN" : 3, "ROADTYPE_FLAG_TOWN_BUILD" : 4, # tramtype flags "TRAMTYPE_FLAG_CATENARY" : 0, "TRAMTYPE_FLAG_NO_LEVEL_CROSSING" : 1, "TRAMTYPE_FLAG_NO_HOUSES" : 2, "TRAMTYPE_FLAG_HIDDEN" : 3, "TRAMTYPE_FLAG_TOWN_BUILD" : 4, # type of default station graphics used for a railtype "RAILTYPE_STATION_NORMAL" : 0, "RAILTYPE_STATION_MONORAIL" : 1, "RAILTYPE_STATION_MAGLEV" : 2, # ground tile types as returned by railtypes varaction2 0x40 "TILETYPE_NORMAL" : 0x00, "TILETYPE_DESERT" : 0x01, "TILETYPE_RAIN_FOREST" : 0x02, "TILETYPE_SNOW" : 0x04, # Water classes "WATER_CLASS_NONE" : 0, "WATER_CLASS_SEA" : 1, "WATER_CLASS_CANAL" : 2, "WATER_CLASS_RIVER" : 3, # level crossing status as returned by railtypes varaction2 0x42 "LEVEL_CROSSING_CLOSED" : 1, "LEVEL_CROSSING_OPEN" : 0, "LEVEL_CROSSING_NONE" : 0, # acceleration model for trains "ACC_MODEL_RAIL" : 0, "ACC_MODEL_MONORAIL" : 1, "ACC_MODEL_MAGLEV" : 2, # default industry IDs "INDUSTRYTYPE_COAL_MINE" : 0x00, "INDUSTRYTYPE_POWER_PLANT" : 0x01, "INDUSTRYTYPE_SAWMILL" : 0x02, "INDUSTRYTYPE_FOREST" : 0x03, "INDUSTRYTYPE_OIL_REFINERY" : 0x04, "INDUSTRYTYPE_OIL_RIG" : 0x05, "INDUSTRYTYPE_TEMPERATE_FACTORY" : 0x06, "INDUSTRYTYPE_PRINTING_WORKS" : 0x07, "INDUSTRYTYPE_STEEL_MILL" : 0x08, "INDUSTRYTYPE_TEMPERATE_ARCTIC_FARM" : 0x09, "INDUSTRYTYPE_COPPER_ORE_MINE" : 0x0A, "INDUSTRYTYPE_OIL_WELLS" : 0x0B, "INDUSTRYTYPE_TEMPERATE_BANK" : 0x0C, "INDUSTRYTYPE_FOOD_PROCESSING_PLANT" : 0x0D, "INDUSTRYTYPE_PAPER_MILL" : 0x0E, "INDUSTRYTYPE_GOLD_MINE" : 0x0F, "INDUSTRYTYPE_TROPICAL_ARCTIC_BANK" : 0x10, "INDUSTRYTYPE_DIAMOND_MINE" : 0x11, "INDUSTRYTYPE_IRON_ORE_MINE" : 0x12, "INDUSTRYTYPE_FRUIT_PLANTATION" : 0x13, "INDUSTRYTYPE_RUBBER_PLANTATION" : 0x14, "INDUSTRYTYPE_WATER_WELL" : 0x15, "INDUSTRYTYPE_WATER_TOWER" : 0x16, "INDUSTRYTYPE_TROPICAL_FACTORY" : 0x17, "INDUSTRYTYPE_TROPICAL_FARM" : 0x18, "INDUSTRYTYPE_LUMBER_MILL" : 0x19, "INDUSTRYTYPE_CANDYFLOSS_FOREST" : 0x1A, "INDUSTRYTYPE_SWEETS_FACTORY" : 0x1B, "INDUSTRYTYPE_BATTERY_FARM" : 0x1C, "INDUSTRYTYPE_COLA_WELLS" : 0x1D, "INDUSTRYTYPE_TOY_SHOP" : 0x1E, "INDUSTRYTYPE_TOY_FACTORY" : 0x1F, "INDUSTRYTYPE_PLASTIC_FOUNTAIN" : 0x20, "INDUSTRYTYPE_FIZZY_DRINKS_FACTORY" : 0x21, "INDUSTRYTYPE_BUBBLE_GENERATOR" : 0x22, "INDUSTRYTYPE_TOFFE_QUARRY" : 0x23, "INDUSTRYTYPE_SUGAR_MINE" : 0x24, "INDUSTRYTYPE_UNKNOWN" : 0xFE, "INDUSTRYTYPE_TOWN" : 0xFF, # industry life types (industry property 0x0B, life_type) "IND_LIFE_TYPE_BLACK_HOLE" : 0x00, "IND_LIFE_TYPE_EXTRACTIVE" : 0x01, "IND_LIFE_TYPE_ORGANIC" : 0x02, "IND_LIFE_TYPE_PROCESSING" : 0x04, # traffic side (bool, true = right hand side) "TRAFFIC_SIDE_LEFT" : 0, "TRAFFIC_SIDE_RIGHT" : 1, # which platform has loaded this grf "PLATFORM_TTDPATCH" : 0x00, "PLATFORM_OPENTTD" : 0x01, # player types (vehicle var 0x43) "PLAYERTYPE_HUMAN" : 0, "PLAYERTYPE_AI" : 1, "PLAYERTYPE_HUMAN_IN_AI" : 2, "PLAYERTYPE_AI_IN_HUMAN" : 3, # search criteria for house var 0x65 "SEARCH_HOUSE_BY_TYPE" : 0, "SEARCH_HOUSE_BY_CLASS" : 1, "SEARCH_HOUSE_BY_GRFID" : 2, # build types (industry var 0xB3) "BUILDTYPE_UNKNOWN" : 0, "BUILDTYPE_GAMEPLAY" : 1, "BUILDTYPE_GENERATION" : 2, "BUILDTYPE_EDITOR" : 3, # Creation types (several industry[tile] callbacks "IND_CREATION_GENERATION" : 0, "IND_CREATION_RANDOM" : 1, "IND_CREATION_FUND" : 2, "IND_CREATION_PROSPECT" : 3, # airport types (vehicle var 0x44, base station var airport_type) "AIRPORTTYPE_SMALL" : 0, "AIRPORTTYPE_LARGE" : 1, "AIRPORTTYPE_HELIPORT" : 2, "AIRPORTTYPE_OILRIG" : 3, # Direction for e.g. airport layouts, vehicle direction "DIRECTION_NORTH" : 0, "DIRECTION_NORTHEAST" : 1, "DIRECTION_EAST" : 2, "DIRECTION_SOUTHEAST" : 3, "DIRECTION_SOUTH" : 4, "DIRECTION_SOUTHWEST" : 5, "DIRECTION_WEST" : 6, "DIRECTION_NORTHWEST" : 7, "CANAL_DIRECTION_NORTH" : 7, "CANAL_DIRECTION_NORTHEAST" : 0, "CANAL_DIRECTION_EAST" : 4, "CANAL_DIRECTION_SOUTHEAST" : 1, "CANAL_DIRECTION_SOUTH" : 5, "CANAL_DIRECTION_SOUTHWEST" : 2, "CANAL_DIRECTION_WEST" : 6, "CANAL_DIRECTION_NORTHWEST" : 3, "CORNER_W" : 0, "CORNER_S" : 1, "CORNER_E" : 2, "CORNER_N" : 3, "IS_STEEP_SLOPE" : 4, "SLOPE_FLAT" : 0, "SLOPE_W" : 1, "SLOPE_S" : 2, "SLOPE_E" : 4, "SLOPE_N" : 8, "SLOPE_NW" : 9, "SLOPE_SW" : 3, "SLOPE_SE" : 6, "SLOPE_NE" : 12, "SLOPE_EW" : 5, "SLOPE_NS" : 10, "SLOPE_NWS" : 11, "SLOPE_WSE" : 7, "SLOPE_SEN" : 14, "SLOPE_ENW" : 13, "SLOPE_STEEP_W" : 27, "SLOPE_STEEP_S" : 23, "SLOPE_STEEP_E" : 30, "SLOPE_STEEP_N" : 29, # loading stages "LOADING_STAGE_INITIALIZE" : 0x0000, "LOADING_STAGE_RESERVE" : 0x0101, "LOADING_STAGE_ACTIVATE" : 0x0201, "LOADING_STAGE_TEST" : 0x0401, # palettes "PALETTE_DOS" : 0, "PALETTE_DEFAULT" : 0, "PALETTE_WIN" : 1, "PALETTE_LEGACY" : 1, # game mode "GAMEMODE_MENU" : 0, "GAMEMODE_GAME" : 1, "GAMEMODE_EDITOR" : 2, # difficulty "DIFFICULTY_EASY" : 0, "DIFFICULTY_MEDIUM" : 1, "DIFFICULTY_HARD" : 2, "DIFFICULTY_CUSTOM" : 3, # display options "DISPLAY_TOWN_NAMES" : 0, "DISPLAY_STATION_NAMES" : 1, "DISPLAY_SIGNS" : 2, "DISPLAY_ANIMATION" : 3, "DISPLAY_FULL_DETAIL" : 5, # map types (ttdp variable 0x13) "MAP_TYPE_X_BIGGER" : 0, # bit 0 and 1 clear "MAP_TYPE_RECTANGULAR" : "MAP_TYPE_SQUARE", "MAP_TYPE_SQUARE" : 1, # bit 0 set "MAP_TYPE_Y_BIGGER" : 2, # bit 0 clear, bit 1 set # Platform types (platform_xx station variables) "PLATFORM_SAME_STATION" : 0, "PLATFORM_SAME_SECTION" : 1, "PLATFORM_SAME_DIRECTION" : 2, # vehicle type (base station var "had_vehicle_of_type") "HAD_VEHICLE_OF_TYPE_TRAIN" : 0, "HAD_VEHICLE_OF_TYPE_BUS" : 1, "HAD_VEHICLE_OF_TYPE_TRUCK" : 2, "HAD_VEHICLE_OF_TYPE_AIRCRAFT" : 3, "HAD_VEHICLE_OF_TYPE_SHIP" : 4, # station facilities (base station var "facilities") "FACILITY_TRAIN" : 0, "FACILITY_TRUCK_STOP" : 1, "FACILITY_BUS_STOP" : 2, "FACILITY_AIRPORT" : 3, "FACILITY_DOCK" : 4, # Random triggers "TRIGGER_ALL_NEEDED" : 7, "TRIGGER_VEHICLE_NEW_LOAD" : 0, "TRIGGER_VEHICLE_SERVICE" : 1, "TRIGGER_VEHICLE_UNLOAD_ALL" : 2, "TRIGGER_VEHICLE_ANY_LOAD" : 3, "TRIGGER_VEHICLE_32_CALLBACK" : 4, "TRIGGER_STATION_NEW_CARGO" : 0, "TRIGGER_STATION_NO_MORE_CARGO" : 1, "TRIGGER_STATION_TRAIN_ARRIVES" : 2, "TRIGGER_STATION_TRAIN_LEAVES" : 3, "TRIGGER_STATION_TRAIN_LOADS_UNLOADS" : 4, "TRIGGER_STATION_TRAIN_RESERVES" : 5, "TRIGGER_HOUSE_TILELOOP" : 0, "TRIGGER_HOUSE_TOP_TILELOOP" : 1, "TRIGGER_INDUSTRYTILE_TILELOOP" : 0, "TRIGGER_INDUSTRYTILE_256_TICKS" : 1, "TRIGGER_INDUSTRYTILE_CARGO_DELIVERY" : 2, # Tile classes "TILE_CLASS_GROUND" : 0x00, "TILE_CLASS_RAIL" : 0x01, "TILE_CLASS_ROAD" : 0x02, "TILE_CLASS_HOUSE" : 0x03, "TILE_CLASS_TREES" : 0x04, "TILE_CLASS_STATION" : 0x05, "TILE_CLASS_WATER" : 0x06, "TILE_CLASS_VOID" : 0x07, "TILE_CLASS_INDUSTRY" : 0x08, "TILE_CLASS_TUNNEL_BRIDGE" : 0x09, "TILE_CLASS_OBJECTS" : 0x0A, # Land shape flags for industry tiles "LSF_CANNOT_LOWER_NW_EDGE" : 0, "LSF_CANNOT_LOWER_NE_EDGE" : 1, "LSF_CANNOT_LOWER_SW_EDGE" : 2, "LSF_CANNOT_LOWER_SE_EDGE" : 3, "LSF_ONLY_ON_FLAT_LAND" : 4, "LSF_ALLOW_ON_WATER" : 5, # Animation triggers "ANIM_TRIGGER_INDTILE_CONSTRUCTION_STATE" : 0, "ANIM_TRIGGER_INDTILE_TILE_LOOP" : 1, "ANIM_TRIGGER_INDTILE_INDUSTRY_LOOP" : 2, "ANIM_TRIGGER_INDTILE_RECEIVED_CARGO" : 3, "ANIM_TRIGGER_INDTILE_DISTRIBUTES_CARGO" : 4, "ANIM_TRIGGER_OBJ_BUILT" : 0, "ANIM_TRIGGER_OBJ_TILELOOP" : 1, "ANIM_TRIGGER_OBJ_256_TICKS" : 2, "ANIM_TRIGGER_APT_BUILT" : 0, "ANIM_TRIGGER_APT_TILELOOP" : 1, "ANIM_TRIGGER_APT_NEW_CARGO" : 2, "ANIM_TRIGGER_APT_CARGO_TAKEN" : 3, "ANIM_TRIGGER_APT_250_TICKS" : 4, "ANIM_TRIGGER_APT_AIRPLANE_LANDS" : 5, "ANIM_TRIGGER_ROAD_STOP_BUILT" : 0, "ANIM_TRIGGER_ROAD_STOP_NEW_CARGO" : 1, "ANIM_TRIGGER_ROAD_STOP_CARGO_TAKEN" : 2, "ANIM_TRIGGER_ROAD_STOP_VEH_ENTER" : 3, "ANIM_TRIGGER_ROAD_STOP_VEH_LEAVE" : 4, "ANIM_TRIGGER_ROAD_STOP_VEH_LOAD" : 5, "ANIM_TRIGGER_ROAD_STOP_250_TICKS" : 6, # Animation looping "ANIMATION_NON_LOOPING" : 0, "ANIMATION_LOOPING" : 1, # Animation callback results "CB_RESULT_STOP_ANIMATION" : 0xFF, # callback 0x25, 0x26 "CB_RESULT_START_ANIMATION" : 0xFE, # callback 0x25 "CB_RESULT_NEXT_FRAME" : 0xFE, # callback 0x26 "CB_RESULT_DO_NOTHING" : 0xFD, # callback 0x25 "CB_RESULT_FOUNDATIONS" : 0x01, # callback 0x30 "CB_RESULT_NO_FOUNDATIONS" : 0x00, # callback 0x30 "CB_RESULT_AUTOSLOPE" : 0x00, # callback 0x3C "CB_RESULT_NO_AUTOSLOPE" : 0x01, # callback 0x3C # Recolour modes for layout sprites "RECOLOUR_NONE" : 0, "RECOLOUR_TRANSPARENT" : 1, "RECOLOUR_REMAP" : 2, # Possible values for palette "PALETTE_USE_DEFAULT" : 0, "PALETTE_TILE_RED_PULSATING" : 771, "PALETTE_SEL_TILE_RED" : 772, "PALETTE_SEL_TILE_BLUE" : 773, "PALETTE_IDENTITY" : 775, "PALETTE_CC_FIRST" : 775, "PALETTE_CC_DARK_BLUE" : 775, # = first "PALETTE_CC_PALE_GREEN" : 776, "PALETTE_CC_PINK" : 777, "PALETTE_CC_YELLOW" : 778, "PALETTE_CC_RED" : 779, "PALETTE_CC_LIGHT_BLUE" : 780, "PALETTE_CC_GREEN" : 781, "PALETTE_CC_DARK_GREEN" : 782, "PALETTE_CC_BLUE" : 783, "PALETTE_CC_CREAM" : 784, "PALETTE_CC_MAUVE" : 785, "PALETTE_CC_PURPLE" : 786, "PALETTE_CC_ORANGE" : 787, "PALETTE_CC_BROWN" : 788, "PALETTE_CC_GREY" : 789, "PALETTE_CC_WHITE" : 790, "PALETTE_BARE_LAND" : 791, "PALETTE_STRUCT_BLUE" : 795, "PALETTE_STRUCT_BROWN" : 796, "PALETTE_STRUCT_WHITE" : 797, "PALETTE_STRUCT_RED" : 798, "PALETTE_STRUCT_GREEN" : 799, "PALETTE_STRUCT_CONCRETE" : 800, "PALETTE_STRUCT_YELLOW" : 801, "PALETTE_TRANSPARENT" : 802, "PALETTE_STRUCT_GREY" : 803, "PALETTE_CRASH" : 804, "PALETTE_CHURCH_RED" : 1438, "PALETTE_CHURCH_CREAM" : 1439, # Company colours "COLOUR_DARK_BLUE" : 0, "COLOUR_PALE_GREEN" : 1, "COLOUR_PINK" : 2, "COLOUR_YELLOW" : 3, "COLOUR_RED" : 4, "COLOUR_LIGHT_BLUE" : 5, "COLOUR_GREEN" : 6, "COLOUR_DARK_GREEN" : 7, "COLOUR_BLUE" : 8, "COLOUR_CREAM" : 9, "COLOUR_MAUVE" : 10, "COLOUR_PURPLE" : 11, "COLOUR_ORANGE" : 12, "COLOUR_BROWN" : 13, "COLOUR_GREY" : 14, "COLOUR_WHITE" : 15, # Town growth effect of cargo "TOWNGROWTH_PASSENGERS" : 0x00, "TOWNGROWTH_MAIL" : 0x02, "TOWNGROWTH_GOODS" : 0x05, "TOWNGROWTH_WATER" : 0x09, "TOWNGROWTH_FOOD" : 0x0B, "TOWNGROWTH_NONE" : 0xFF, # Town production effect on cargo "TOWNPRODUCTION_PASSENGERS" : 0x00, "TOWNPRODUCTION_MAIL" : 0x02, "TOWNPRODUCTION_NONE" : 0xFF, # Cargo callbacks "CARGO_CB_PROFIT" : 0x01, "CARGO_CB_STATION_RATING" : 0x02, # CMP and UCMP results "CMP_LESS" : 0, "CMP_EQUAL" : 1, "CMP_GREATER" : 2, # TTD Strings "TTD_STR_CARGO_PLURAL_NOTHING" : 0x000E, "TTD_STR_CARGO_PLURAL_PASSENGERS" : 0x000F, "TTD_STR_CARGO_PLURAL_COAL" : 0x0010, "TTD_STR_CARGO_PLURAL_MAIL" : 0x0011, "TTD_STR_CARGO_PLURAL_OIL" : 0x0012, "TTD_STR_CARGO_PLURAL_LIVESTOCK" : 0x0013, "TTD_STR_CARGO_PLURAL_GOODS" : 0x0014, "TTD_STR_CARGO_PLURAL_GRAIN" : 0x0015, "TTD_STR_CARGO_PLURAL_WOOD" : 0x0016, "TTD_STR_CARGO_PLURAL_IRON_ORE" : 0x0017, "TTD_STR_CARGO_PLURAL_STEEL" : 0x0018, "TTD_STR_CARGO_PLURAL_VALUABLES" : 0x0019, "TTD_STR_CARGO_PLURAL_COPPER_ORE" : 0x001A, "TTD_STR_CARGO_PLURAL_MAIZE" : 0x001B, "TTD_STR_CARGO_PLURAL_FRUIT" : 0x001C, "TTD_STR_CARGO_PLURAL_DIAMONDS" : 0x001D, "TTD_STR_CARGO_PLURAL_FOOD" : 0x001E, "TTD_STR_CARGO_PLURAL_PAPER" : 0x001F, "TTD_STR_CARGO_PLURAL_GOLD" : 0x0020, "TTD_STR_CARGO_PLURAL_WATER" : 0x0021, "TTD_STR_CARGO_PLURAL_WHEAT" : 0x0022, "TTD_STR_CARGO_PLURAL_RUBBER" : 0x0023, "TTD_STR_CARGO_PLURAL_SUGAR" : 0x0024, "TTD_STR_CARGO_PLURAL_TOYS" : 0x0025, "TTD_STR_CARGO_PLURAL_CANDY" : "TTD_STR_CARGO_PLURAL_SWEETS", "TTD_STR_CARGO_PLURAL_SWEETS" : 0x0026, "TTD_STR_CARGO_PLURAL_COLA" : 0x0027, "TTD_STR_CARGO_PLURAL_COTTON_CANDY" : "TTD_STR_CARGO_PLURAL_CANDYFLOSS", "TTD_STR_CARGO_PLURAL_CANDYFLOSS" : 0x0028, "TTD_STR_CARGO_PLURAL_BUBBLES" : 0x0029, "TTD_STR_CARGO_PLURAL_TOFFEE" : 0x002A, "TTD_STR_CARGO_PLURAL_BATTERIES" : 0x002B, "TTD_STR_CARGO_PLURAL_PLASTIC" : 0x002C, "TTD_STR_CARGO_PLURAL_FIZZY_DRINKS" : 0x002D, "TTD_STR_CARGO_SINGULAR_NOTHING" : 0x002E, "TTD_STR_CARGO_SINGULAR_PASSENGER" : 0x002F, "TTD_STR_CARGO_SINGULAR_COAL" : 0x0030, "TTD_STR_CARGO_SINGULAR_MAIL" : 0x0031, "TTD_STR_CARGO_SINGULAR_OIL" : 0x0032, "TTD_STR_CARGO_SINGULAR_LIVESTOCK" : 0x0033, "TTD_STR_CARGO_SINGULAR_GOODS" : 0x0034, "TTD_STR_CARGO_SINGULAR_GRAIN" : 0x0035, "TTD_STR_CARGO_SINGULAR_WOOD" : 0x0036, "TTD_STR_CARGO_SINGULAR_IRON_ORE" : 0x0037, "TTD_STR_CARGO_SINGULAR_STEEL" : 0x0038, "TTD_STR_CARGO_SINGULAR_VALUABLES" : 0x0039, "TTD_STR_CARGO_SINGULAR_COPPER_ORE" : 0x003A, "TTD_STR_CARGO_SINGULAR_MAIZE" : 0x003B, "TTD_STR_CARGO_SINGULAR_FRUIT" : 0x003C, "TTD_STR_CARGO_SINGULAR_DIAMOND" : 0x003D, "TTD_STR_CARGO_SINGULAR_FOOD" : 0x003E, "TTD_STR_CARGO_SINGULAR_PAPER" : 0x003F, "TTD_STR_CARGO_SINGULAR_GOLD" : 0x0040, "TTD_STR_CARGO_SINGULAR_WATER" : 0x0041, "TTD_STR_CARGO_SINGULAR_WHEAT" : 0x0042, "TTD_STR_CARGO_SINGULAR_RUBBER" : 0x0043, "TTD_STR_CARGO_SINGULAR_SUGAR" : 0x0044, "TTD_STR_CARGO_SINGULAR_TOY" : 0x0045, "TTD_STR_CARGO_SINGULAR_CANDY" : "TTD_STR_CARGO_SINGULAR_SWEETS", "TTD_STR_CARGO_SINGULAR_SWEETS" : 0x0046, "TTD_STR_CARGO_SINGULAR_COLA" : 0x0047, "TTD_STR_CARGO_SINGULAR_COTTON_CANDY" : "TTD_STR_CARGO_SINGULAR_CANDYFLOSS", "TTD_STR_CARGO_SINGULAR_CANDYFLOSS" : 0x0048, "TTD_STR_CARGO_SINGULAR_BUBBLE" : 0x0049, "TTD_STR_CARGO_SINGULAR_TOFFEE" : 0x004A, "TTD_STR_CARGO_SINGULAR_BATTERY" : 0x004B, "TTD_STR_CARGO_SINGULAR_PLASTIC" : 0x004C, "TTD_STR_CARGO_SINGULAR_FIZZY_DRINK" : 0x004D, "TTD_STR_PASSENGERS" : 0x004F, "TTD_STR_TONS" : 0x0050, "TTD_STR_BAGS" : 0x0051, "TTD_STR_LITERS" : 0x0052, "TTD_STR_ITEMS" : 0x0053, "TTD_STR_CRATES" : 0x0054, "TTD_STR_QUANTITY_NOTHING" : 0x006E, "TTD_STR_QUANTITY_PASSENGERS" : 0x006F, "TTD_STR_QUANTITY_COAL" : 0x0070, "TTD_STR_QUANTITY_MAIL" : 0x0071, "TTD_STR_QUANTITY_OIL" : 0x0072, "TTD_STR_QUANTITY_LIVESTOCK" : 0x0073, "TTD_STR_QUANTITY_GOODS" : 0x0074, "TTD_STR_QUANTITY_GRAIN" : 0x0075, "TTD_STR_QUANTITY_WOOD" : 0x0076, "TTD_STR_QUANTITY_IRON_ORE" : 0x0077, "TTD_STR_QUANTITY_STEEL" : 0x0078, "TTD_STR_QUANTITY_VALUABLES" : 0x0079, "TTD_STR_QUANTITY_COPPER_ORE" : 0x007A, "TTD_STR_QUANTITY_MAIZE" : 0x007B, "TTD_STR_QUANTITY_FRUIT" : 0x007C, "TTD_STR_QUANTITY_DIAMONDS" : 0x007D, "TTD_STR_QUANTITY_FOOD" : 0x007E, "TTD_STR_QUANTITY_PAPER" : 0x007F, "TTD_STR_QUANTITY_GOLD" : 0x0080, "TTD_STR_QUANTITY_WATER" : 0x0081, "TTD_STR_QUANTITY_WHEAT" : 0x0082, "TTD_STR_QUANTITY_RUBBER" : 0x0083, "TTD_STR_QUANTITY_SUGAR" : 0x0084, "TTD_STR_QUANTITY_TOYS" : 0x0085, "TTD_STR_QUANTITY_SWEETS" : 0x0086, "TTD_STR_QUANTITY_COLA" : 0x0087, "TTD_STR_QUANTITY_CANDYFLOSS" : 0x0088, "TTD_STR_QUANTITY_BUBBLES" : 0x0089, "TTD_STR_QUANTITY_TOFFEE" : 0x008A, "TTD_STR_QUANTITY_BATTERIES" : 0x008B, "TTD_STR_QUANTITY_PLASTIC" : 0x008C, "TTD_STR_QUANTITY_FIZZY_DRINKS" : 0x008D, "TTD_STR_ABBREV_NOTHING" : 0x008E, "TTD_STR_ABBREV_PASSENGERS" : 0x008F, "TTD_STR_ABBREV_COAL" : 0x0090, "TTD_STR_ABBREV_MAIL" : 0x0091, "TTD_STR_ABBREV_OIL" : 0x0092, "TTD_STR_ABBREV_LIVESTOCK" : 0x0093, "TTD_STR_ABBREV_GOODS" : 0x0094, "TTD_STR_ABBREV_GRAIN" : 0x0095, "TTD_STR_ABBREV_WOOD" : 0x0096, "TTD_STR_ABBREV_IRON_ORE" : 0x0097, "TTD_STR_ABBREV_STEEL" : 0x0098, "TTD_STR_ABBREV_VALUABLES" : 0x0099, "TTD_STR_ABBREV_COPPER_ORE" : 0x009A, "TTD_STR_ABBREV_MAIZE" : 0x009B, "TTD_STR_ABBREV_FRUIT" : 0x009C, "TTD_STR_ABBREV_DIAMONDS" : 0x009D, "TTD_STR_ABBREV_FOOD" : 0x009E, "TTD_STR_ABBREV_PAPER" : 0x009F, "TTD_STR_ABBREV_GOLD" : 0x00A0, "TTD_STR_ABBREV_WATER" : 0x00A1, "TTD_STR_ABBREV_WHEAT" : 0x00A2, "TTD_STR_ABBREV_RUBBER" : 0x00A3, "TTD_STR_ABBREV_SUGAR" : 0x00A4, "TTD_STR_ABBREV_TOYS" : 0x00A5, "TTD_STR_ABBREV_SWEETS" : 0x00A6, "TTD_STR_ABBREV_COLA" : 0x00A7, "TTD_STR_ABBREV_CANDYFLOSS" : 0x00A8, "TTD_STR_ABBREV_BUBBLES" : 0x00A9, "TTD_STR_ABBREV_TOFFEE" : 0x00AA, "TTD_STR_ABBREV_BATTERIES" : 0x00AB, "TTD_STR_ABBREV_PLASTIC" : 0x00AC, "TTD_STR_ABBREV_FIZZY_DRINKS" : 0x00AD, "TTD_STR_TOWN_BUILDING_NAME_TALL_OFFICE_BLOCK_1" : 0x200F, "TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_1" : 0x2010, "TTD_STR_TOWN_BUILDING_NAME_SMALL_BLOCK_OF_FLATS_1" : 0x2011, "TTD_STR_TOWN_BUILDING_NAME_CHURCH_1" : 0x2012, "TTD_STR_TOWN_BUILDING_NAME_LARGE_OFFICE_BLOCK_1" : 0x2013, "TTD_STR_TOWN_BUILDING_NAME_TOWN_HOUSES_1" : 0x2014, "TTD_STR_TOWN_BUILDING_NAME_HOTEL_1" : 0x2015, "TTD_STR_TOWN_BUILDING_NAME_STATUE_1" : 0x2016, "TTD_STR_TOWN_BUILDING_NAME_FOUNTAIN_1" : 0x2017, "TTD_STR_TOWN_BUILDING_NAME_PARK_1" : 0x2018, "TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_2" : 0x2019, "TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_1" : 0x201A, "TTD_STR_TOWN_BUILDING_NAME_MODERN_OFFICE_BUILDING_1" : 0x201B, "TTD_STR_TOWN_BUILDING_NAME_WAREHOUSE_1" : 0x201C, "TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_3" : 0x201D, "TTD_STR_TOWN_BUILDING_NAME_STADIUM_1" : 0x201E, "TTD_STR_TOWN_BUILDING_NAME_OLD_HOUSES_1" : 0x201F, "TTD_STR_TOWN_BUILDING_NAME_COTTAGES_1" : 0x2036, "TTD_STR_TOWN_BUILDING_NAME_HOUSES_1" : 0x2037, "TTD_STR_TOWN_BUILDING_NAME_FLATS_1" : 0x2038, "TTD_STR_TOWN_BUILDING_NAME_TALL_OFFICE_BLOCK_2" : 0x2039, "TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_2" : 0x203A, "TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_3" : 0x203B, "TTD_STR_TOWN_BUILDING_NAME_THEATER_1" : 0x203C, "TTD_STR_TOWN_BUILDING_NAME_STADIUM_2" : 0x203D, "TTD_STR_TOWN_BUILDING_NAME_OFFICES_1" : 0x203E, "TTD_STR_TOWN_BUILDING_NAME_HOUSES_2" : 0x203F, "TTD_STR_TOWN_BUILDING_NAME_CINEMA_1" : 0x2040, "TTD_STR_TOWN_BUILDING_NAME_SHOPPING_MALL_1" : 0x2041, "TTD_STR_TOWN_BUILDING_NAME_IGLOO_1" : 0x2059, "TTD_STR_TOWN_BUILDING_NAME_TEPEES_1" : 0x205A, "TTD_STR_TOWN_BUILDING_NAME_TEAPOT_HOUSE_1" : 0x205B, "TTD_STR_TOWN_BUILDING_NAME_PIGGY_BANK_1" : 0x205C, "TTD_STR_INDUSTRY_NAME_COAL_MINE" : 0x4802, "TTD_STR_INDUSTRY_NAME_POWER_STATION" : 0x4803, "TTD_STR_INDUSTRY_NAME_SAWMILL" : 0x4804, "TTD_STR_INDUSTRY_NAME_FOREST" : 0x4805, "TTD_STR_INDUSTRY_NAME_OIL_REFINERY" : 0x4806, "TTD_STR_INDUSTRY_NAME_OIL_RIG" : 0x4807, "TTD_STR_INDUSTRY_NAME_FACTORY" : 0x4808, "TTD_STR_INDUSTRY_NAME_PRINTING_WORKS" : 0x4809, "TTD_STR_INDUSTRY_NAME_STEEL_MILL" : 0x480A, "TTD_STR_INDUSTRY_NAME_FARM" : 0x480B, "TTD_STR_INDUSTRY_NAME_COPPER_ORE_MINE" : 0x480C, "TTD_STR_INDUSTRY_NAME_OIL_WELLS" : 0x480D, "TTD_STR_INDUSTRY_NAME_BANK" : 0x480E, "TTD_STR_INDUSTRY_NAME_FOOD_PROCESSING_PLANT" : 0x480F, "TTD_STR_INDUSTRY_NAME_PAPER_MILL" : 0x4810, "TTD_STR_INDUSTRY_NAME_GOLD_MINE" : 0x4811, "TTD_STR_INDUSTRY_NAME_BANK_TROPIC_ARCTIC" : 0x4812, "TTD_STR_INDUSTRY_NAME_DIAMOND_MINE" : 0x4813, "TTD_STR_INDUSTRY_NAME_IRON_ORE_MINE" : 0x4814, "TTD_STR_INDUSTRY_NAME_FRUIT_PLANTATION" : 0x4815, "TTD_STR_INDUSTRY_NAME_RUBBER_PLANTATION" : 0x4816, "TTD_STR_INDUSTRY_NAME_WATER_SUPPLY" : 0x4817, "TTD_STR_INDUSTRY_NAME_WATER_TOWER" : 0x4818, "TTD_STR_INDUSTRY_NAME_FACTORY_2" : 0x4819, "TTD_STR_INDUSTRY_NAME_FARM_2" : 0x481A, "TTD_STR_INDUSTRY_NAME_LUMBER_MILL" : 0x481B, "TTD_STR_INDUSTRY_NAME_COTTON_CANDY_FOREST" : 0x481C, "TTD_STR_INDUSTRY_NAME_CANDY_FACTORY" : 0x481D, "TTD_STR_INDUSTRY_NAME_BATTERY_FARM" : 0x481E, "TTD_STR_INDUSTRY_NAME_COLA_WELLS" : 0x481F, "TTD_STR_INDUSTRY_NAME_TOY_SHOP" : 0x4820, "TTD_STR_INDUSTRY_NAME_TOY_FACTORY" : 0x4821, "TTD_STR_INDUSTRY_NAME_PLASTIC_FOUNTAINS" : 0x4822, "TTD_STR_INDUSTRY_NAME_FIZZY_DRINK_FACTORY" : 0x4823, "TTD_STR_INDUSTRY_NAME_BUBBLE_GENERATOR" : 0x4824, "TTD_STR_INDUSTRY_NAME_TOFFEE_QUARRY" : 0x4825, "TTD_STR_INDUSTRY_NAME_SUGAR_MINE" : 0x4826, "TTD_STR_NEWS_INDUSTRY_CONSTRUCTION" : 0x482D, "TTD_STR_NEWS_INDUSTRY_PLANTED" : 0x482E, "TTD_STR_NEWS_INDUSTRY_CLOSURE_GENERAL" : 0x4832, "TTD_STR_NEWS_INDUSTRY_CLOSURE_SUPPLY_PROBLEMS" : 0x4833, "TTD_STR_NEWS_INDUSTRY_CLOSURE_LACK_OF_TREES" : 0x4834, "TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_GENERAL" : 0x4835, "TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_COAL" : 0x4836, "TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_OIL" : 0x4837, "TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_FARM" : 0x4838, "TTD_STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_GENERAL" : 0x4839, "TTD_STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_FARM" : 0x483A, "TTD_STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY" : 0x4830, "TTD_STR_ERROR_FOREST_CAN_ONLY_BE_PLANTED" : 0x4831, "TTD_STR_ERROR_CAN_ONLY_BE_POSITIONED" : 0x483B, # Road stops "RST_AVAILABILITY_TYPE_PASSENGER" : 0, "RST_AVAILABILITY_TYPE_FREIGHT" : 1, "RST_AVAILABILITY_TYPE_ALL" : 2, "RST_TYPE_BUS" : 0, "RST_TYPE_TRUCK" : 1, "RST_TYPE_WAYPOINT" : 2, "RST_DRAW_FLAG_BAY_ROAD" : 0, "RST_DRAW_FLAG_DRIVE_THROUGH_ROAD_OVERLAY" : 1, "RST_DRAW_FLAG_WAYPOINT_GROUND" : 2, "RST_GENERAL_FLAG_RANDOM_ANIMATION" : 0, "RST_GENERAL_FLAG_NO_CATENARY" : 2, "RST_GENERAL_FLAG_DRIVE_THROUGH_ONLY" : 3, "RST_GENERAL_FLAG_NO_AUTO_ROAD_CONNECTION" : 4, "RST_GENERAL_FLAG_BUILD_MENU_ROAD_ONLY" : 5, "RST_GENERAL_FLAG_BUILD_MENU_TRAM_ONLY" : 6, "RST_GENERAL_FLAG_DRAW_MODE_REGISTER" : 8, "RST_VIEW_BAY_NE" : 0, "RST_VIEW_BAY_SE" : 1, "RST_VIEW_BAY_SW" : 2, "RST_VIEW_BAY_NW" : 3, "RST_VIEW_DRIVE_THROUGH_X" : 4, "RST_VIEW_DRIVE_THROUGH_Y" : 5, } # fmt: on def signextend(param, info): # r = (x ^ m) - m; with m being (1 << (num_bits -1)) m = expression.ConstantNumeric(1 << (info["size"] * 8 - 1)) return nmlop.SUB(nmlop.XOR(param, m), m) def global_param_write(info, expr, pos): if not ("writable" in info and info["writable"]): raise generic.ScriptError("Target parameter is not writable.", pos) return expression.Parameter(expression.ConstantNumeric(info["num"]), pos), expr def global_param_read(info, pos): param = expression.Parameter(expression.ConstantNumeric(info["num"]), pos) if info["size"] == 1: mask = expression.ConstantNumeric(0xFF) param = nmlop.AND(param, mask) else: assert info["size"] == 4 if "function" in info: return info["function"](param, info) return param def param_from_info(name, info, pos): return expression.SpecialParameter(name, info, global_param_write, global_param_read, False, pos) # fmt: off global_parameters = { "climate": {"num": 0x83, "size": 1}, "loading_stage": {"num": 0x84, "size": 4}, "ttdpatch_version": {"num": 0x8B, "size": 4}, "current_palette": {"num": 0x8D, "size": 1}, "traininfo_y_offset": {"num": 0x8E, "size": 1, "writable": 1, "function": signextend}, "game_mode": {"num": 0x92, "size": 1}, "ttd_platform": {"num": 0x9D, "size": 4}, "openttd_version": {"num": 0xA1, "size": 4}, "difficulty_level": {"num": 0xA2, "size": 4}, "date_loaded": {"num": 0xA3, "size": 4}, "year_loaded": {"num": 0xA4, "size": 4}, } # fmt: on def misc_bit_write(info, expr, pos): param = expression.Parameter(expression.ConstantNumeric(info["param"], pos), pos) # param = (expr != 0) ? param | (1 << bit) : param & ~(1 << bit) expr = nmlop.CMP_NEQ(expr, 0, pos) or_expr = nmlop.OR(param, 1 << info["bit"], pos) and_expr = nmlop.AND(param, ~(1 << info["bit"]), pos) expr = expression.TernaryOp(expr, or_expr, and_expr, pos) return (param, expr) def misc_bit_read(info, pos): return nmlop.HASBIT(expression.Parameter(expression.ConstantNumeric(info["param"], pos), pos), info["bit"]) def misc_grf_bit(name, info, pos): return expression.SpecialParameter(name, info, misc_bit_write, misc_bit_read, True, pos) misc_grf_bits = { "traffic_side": {"param": 0x86, "bit": 4}, "desert_paved_roads": {"param": 0x9E, "bit": 1}, "train_width_32_px": {"param": 0x9E, "bit": 3}, "second_rocky_tileset": {"param": 0x9E, "bit": 6}, } def add_1920(expr, info): """ Create a new expression that adds 1920 to a given expression. @param expr: The expression to add 1920 to. @type expr: L{Expression} @param info: Ignored. @return: A new expression that adds 1920 to the given expression. @rtype: L{Expression} """ return nmlop.ADD(expr, 1920) def map_exponentiate(expr, info): """ Given a exponent, add an offset to it and compute the exponentiation with base 2. @param expr: The exponent. @type expr: L{Expression} @param info: Table with extra information, most notable 'log_offset', the value we need to add to the given expression before computing the exponentiation. @type info: C{dict} @return: An expression computing 2**(expr + info['log_offset']). @rtype: L{Expression} """ # map (log2(x) - a) to x, i.e. do 1 << (x + a) expr = nmlop.ADD(expr, info["log_offset"]) return nmlop.SHIFT_LEFT(1, expr) def patch_variable_read(info, pos): """ Helper function to read special patch variables. @param info: Generic information about the parameter to read, like parameter number and size. @type info: C{dict} @param pos: Position information in the source file. @type pos: L{Position} or C{None} @return: An expression that reads the special variables. @rtype: L{Expression} """ expr = expression.PatchVariable(info["num"], pos) if info["start"] != 0: expr = nmlop.SHIFT_RIGHT(expr, info["start"], pos) if info["size"] != 32: expr = nmlop.AND(expr, (1 << info["size"]) - 1, pos) if "function" in info: expr = info["function"](expr, info) return expr def patch_variable(name, info, pos): return expression.SpecialParameter(name, info, None, patch_variable_read, False, pos) # fmt: off patch_variables = { "starting_year": {"num": 0x0B, "start": 0, "size": 32, "function": add_1920}, "freight_trains": {"num": 0x0E, "start": 0, "size": 32}, "plane_speed": {"num": 0x10, "start": 0, "size": 32}, "base_sprite_2cc": {"num": 0x11, "start": 0, "size": 32}, "map_type": {"num": 0x13, "start": 24, "size": 2}, "map_min_edge": {"num": 0x13, "start": 20, "size": 4, "log_offset": 6, "function": map_exponentiate}, "map_max_edge": {"num": 0x13, "start": 16, "size": 4, "log_offset": 6, "function": map_exponentiate}, "map_x_edge": {"num": 0x13, "start": 12, "size": 4, "log_offset": 6, "function": map_exponentiate}, "map_y_edge": {"num": 0x13, "start": 8, "size": 4, "log_offset": 6, "function": map_exponentiate}, "map_size": {"num": 0x13, "start": 0, "size": 8, "log_offset": 12, "function": map_exponentiate}, "max_height_level": {"num": 0x14, "start": 0, "size": 32}, "base_sprite_foundations": {"num": 0x15, "start": 0, "size": 32}, "base_sprite_shores": {"num": 0x16, "start": 0, "size": 32}, "map_seed": {"num": 0x17, "start": 0, "size": 32}, } # fmt: on def config_flag_read(bit, pos): return expression.SpecialCheck((0x01, r"\70"), 0x85, (0, 1), bit, "PatchFlag({})".format(bit), varsize=1, pos=pos) def config_flag(name, info, pos): return expression.SpecialParameter(name, info, None, config_flag_read, True, pos) # fmt: off config_flags = { "long_bridges" : 0x0F, "gradual_loading" : 0x2C, "bridge_speed_limits" : 0x34, "newtrains" : 0x37, "newrvs" : 0x38, "newships" : 0x39, "newplanes" : 0x3A, "signals_on_traffic_side" : 0x3B, "electrified_railways" : 0x3C, "newhouses" : 0x59, "wagon_speed_limits" : 0x5D, "newindustries" : 0x67, "temperate_snowline" : 0x6A, "newcargos" : 0x6B, "dynamic_engines" : 0x78, "variable_runningcosts" : 0x7E, "256_persistent_registers": 0x80, "inflation" : 0x81, } # fmt: on def unified_maglev_read(info, pos): bit0 = nmlop.HASBIT(expression.Parameter(expression.ConstantNumeric(0x85), pos), 0x32) bit1 = nmlop.HASBIT(expression.Parameter(expression.ConstantNumeric(0x85), pos), 0x33) shifted_bit1 = nmlop.SHIFT_LEFT(bit1, 1) return nmlop.OR(shifted_bit1, bit0) def unified_maglev(name, info, pos): return expression.SpecialParameter(name, info, None, unified_maglev_read, False, pos) unified_maglev_var = { "unified_maglev": 0, } def setting_from_info(name, info, pos): return expression.SpecialParameter(name, info, global_param_write, global_param_read, False, pos) def item_to_id(name, item, pos): if not isinstance(item.id, expression.ConstantNumeric): raise generic.ScriptError("Referencing item '{}' with a non-constant id is not possible.".format(name), pos) return expression.ConstantNumeric(item.id.value, pos) def param_from_name(name, info, pos): del name # unused; to match other id_list callbacks return expression.Parameter(expression.ConstantNumeric(info), pos) def create_spritegroup_ref(name, info, pos): del name # unused; to match other id_list callbacks return expression.SpriteGroupRef(expression.Identifier(info), [], pos) cargo_numbers = {} is_default_railtype_table = True # if no railtype_table is provided, OpenTTD assumes these 3 railtypes railtype_table = {"RAIL": 0, "MONO": 1, "MGLV": 2} is_default_roadtype_table = True # if no roadtype_table is provided, OpenTTD sets all vehicles to ROAD roadtype_table = {"ROAD": 0} is_default_tramtype_table = True # if no tramtype_table is provided, OpenTTD sets all vehicles to ELRL tramtype_table = {"ELRL": 0} identifier_refcount = {} item_names = {} settings = {} named_parameters = {} spritegroups = {"CB_FAILED": "CB_FAILED"} zoom_levels = { "ZOOM_LEVEL_NORMAL": 0, "ZOOM_LEVEL_IN_4X": 1, "ZOOM_LEVEL_IN_2X": 2, "ZOOM_LEVEL_OUT_2X": 3, "ZOOM_LEVEL_OUT_4X": 4, "ZOOM_LEVEL_OUT_8X": 5, } bit_depths = {"BIT_DEPTH_8BPP": 8, "BIT_DEPTH_32BPP": 32.0} """ Store if there are any 32bpp sprites, if so ask to enable the 32bpp blitter via action14 """ any_32bpp_sprites = False allow_extra_zoom = True allow_32bpp = True const_list = [ (constant_numbers, constant_number), (global_parameters, param_from_info), (misc_grf_bits, misc_grf_bit), (patch_variables, patch_variable), (named_parameters, param_from_name), cargo_numbers, railtype_table, roadtype_table, tramtype_table, (item_names, item_to_id), (settings, setting_from_info), (config_flags, config_flag), (unified_maglev_var, unified_maglev), (spritegroups, create_spritegroup_ref), zoom_levels, bit_depths, ] def print_stats(): """ Print statistics about used ids. """ if len(cargo_numbers) > 0: # Ids FE and FF have special meanings in Action3, so we do not consider them valid ids. generic.print_info("Cargo translation table: {}/{}".format(len(cargo_numbers), 0xFE)) if not is_default_railtype_table: generic.print_info("Railtype translation table: {}/{}".format(len(railtype_table), 0x100)) if not is_default_roadtype_table: generic.print_info("Roadtype translation table: {}/{}".format(len(roadtype_table), 0x100)) if not is_default_tramtype_table: generic.print_info("Tramtype translation table: {}/{}".format(len(tramtype_table), 0x100)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/grfstrings.py0000644000175100001660000014066414754345605015275 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import glob import os import re from nml import generic def utf8_get_size(char): if char < 128: return 1 if char < 2048: return 2 if char < 65536: return 3 return 4 DEFAULT_LANGUAGE = 0x7F DEFAULT_LANGNAME = "english.lng" def validate_string(string): """ Check if a given string refers to a string that is translated in the language files and raise an error otherwise. @param string: The string to validate. @type string: L{expression.String} """ if string.name.value not in default_lang.strings: raise generic.ScriptError('Unknown string "{}"'.format(string.name.value), string.pos) def is_ascii_string(string): """ Check whether a given string can be written using the ASCII codeset or that we need unicode. @param string: The string to check. @type string: C{str} @return: True iff the string is ascii-only. @rtype: C{bool} """ assert isinstance(string, str) i = 0 while i < len(string): if string[i] != "\\": if ord(string[i]) >= 0x7B: return False i += 1 else: if string[i + 1] in ("\\", '"'): i += 2 elif string[i + 1] == "U": return False else: i += 3 return True def get_string_size(string, final_zero=True, force_ascii=False): """ Get the size (in bytes) of a given string. @param string: The string to check. @type string: C{str} @param final_zero: Whether or not to account for a zero-byte directly after the string. @type final_zero: C{bool} @param force_ascii: When true, make sure the string is written as ascii as opposed to unicode. @type force_ascii: C{bool} @return: The length (in bytes) of the given string. @rtype: C{int} @raise generic.ScriptError: force_ascii and not is_ascii_string(string). """ size = 0 if final_zero: size += 1 if not is_ascii_string(string): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") size += 2 i = 0 while i < len(string): if string[i] != "\\": size += utf8_get_size(ord(string[i])) i += 1 else: if string[i + 1] in ("\\", '"'): size += 1 i += 2 elif string[i + 1] == "U": size += utf8_get_size(int(string[i + 2 : i + 6], 16)) i += 6 else: size += 1 i += 3 return size def get_translation(string, lang_id=DEFAULT_LANGUAGE): """ Get the translation of a given string in a certain language. If there is no translation available in the given language return the default translation. @param string: the string to get the translation for. @type string: L{expression.String} @param lang_id: The language id of the language to translate the string into. @type lang_id: C{int} @return: Translation of the given string in the given language. @rtype: C{str} """ for lang_pair in langs: langid, lang = lang_pair if langid != lang_id: continue if string.name.value not in lang.strings: break return lang.get_string(string, lang_id) return default_lang.get_string(string, lang_id) def get_translations(string): """ Get a list of language ids that have a translation for the given string. @param string: the string to get the translations for. @type string: L{expression.String} @return: List of languages that translate the given string. @rtype: C{list} of C{int} """ translations = [] for lang_pair in langs: langid, lang = lang_pair assert langid is not None if string.name.value in lang.strings and lang.get_string(string, langid) != default_lang.get_string( string, langid ): translations.append(langid) # Also check for translated substrings import nml.expression for param in string.params: if not isinstance(param, nml.expression.String): continue param_translations = get_translations(param) translations.extend([langid for langid in param_translations if langid not in translations]) return translations def com_parse_comma(val, lang_id): val = val.reduce_constant() return str(val) def com_parse_hex(val, lang_id): val = val.reduce_constant() return "0x{:X}".format(val.value) def com_parse_string(val, lang_id): import nml.expression if not isinstance(val, (nml.expression.StringLiteral, nml.expression.String)): raise generic.ScriptError("Expected a (literal) string", val.pos) if isinstance(val, nml.expression.String): # Check that the string exists if val.name.value not in default_lang.strings: raise generic.ScriptError('Substring "{}" does not exist'.format(val.name.value), val.pos) return get_translation(val, lang_id) return val.value # fmt: off commands = { # Special characters / glyphs "": {"unicode": r"\0D", "ascii": r"\0D"}, "{": {"unicode": r"{", "ascii": r"{"}, "NBSP": {"unicode": r"\U00A0"}, # character A0 is used as up arrow in TTD, so don"t use ASCII here. "COPYRIGHT": {"unicode": r"\U00A9", "ascii": r"\A9"}, "TRAIN": {"unicode": r"\UE0B4", "ascii": r"\B4"}, "LORRY": {"unicode": r"\UE0B5", "ascii": r"\B5"}, "BUS": {"unicode": r"\UE0B6", "ascii": r"\B6"}, "PLANE": {"unicode": r"\UE0B7", "ascii": r"\B7"}, "SHIP": {"unicode": r"\UE0B8", "ascii": r"\B8"}, # Change the font size. "TINYFONT": {"unicode": r"\0E", "ascii": r"\0E"}, "BIGFONT": {"unicode": r"\0F", "ascii": r"\0F"}, "COMMA": {"unicode": r"\UE07B", "ascii": r"\7B", "size": 4, "parse": com_parse_comma}, "SIGNED_WORD": {"unicode": r"\UE07C", "ascii": r"\7C", "size": 2, "parse": com_parse_comma}, "UNSIGNED_WORD": {"unicode": r"\UE07E", "ascii": r"\7E", "size": 2, "parse": com_parse_comma}, "CURRENCY": {"unicode": r"\UE07F", "ascii": r"\7F", "size": 4}, "STRING": { "unicode": r"\UE080", "ascii": r"\80", "allow_case": True, "size": 2, "parse": com_parse_string }, "DATE1920_LONG": {"unicode": r"\UE082", "ascii": r"\82", "size": 2}, "DATE1920_SHORT": {"unicode": r"\UE083", "ascii": r"\83", "size": 2}, "VELOCITY": {"unicode": r"\UE084", "ascii": r"\84", "size": 2}, "SKIP": {"unicode": r"\UE085", "ascii": r"\85", "size": 2}, "VOLUME": {"unicode": r"\UE087", "ascii": r"\87", "size": 2}, "HEX": {"unicode": r"\UE09A\08", "ascii": r"\9A\08", "size": 4, "parse": com_parse_hex}, "STATION": {"unicode": r"\UE09A\0C", "ascii": r"\9A\0C", "size": 2}, "WEIGHT": {"unicode": r"\UE09A\0D", "ascii": r"\9A\0D", "size": 2}, "DATE_LONG": {"unicode": r"\UE09A\16", "ascii": r"\9A\16", "size": 4}, "DATE_SHORT": {"unicode": r"\UE09A\17", "ascii": r"\9A\17", "size": 4}, "POWER": {"unicode": r"\UE09A\18", "ascii": r"\9A\18", "size": 2}, "VOLUME_SHORT": {"unicode": r"\UE09A\19", "ascii": r"\9A\19", "size": 2}, "WEIGHT_SHORT": {"unicode": r"\UE09A\1A", "ascii": r"\9A\1A", "size": 2}, "CARGO_LONG": {"unicode": r"\UE09A\1B", "ascii": r"\9A\1B", "size": 2 * 2}, "CARGO_SHORT": {"unicode": r"\UE09A\1C", "ascii": r"\9A\1C", "size": 2 * 2}, "CARGO_TINY": {"unicode": r"\UE09A\1D", "ascii": r"\9A\1D", "size": 2 * 2}, "CARGO_NAME": {"unicode": r"\UE09A\1E", "ascii": r"\9A\1E", "size": 2}, "FORCE": {"unicode": r"\UE09A\21", "ascii": r"\9A\21", "size": 4}, # Colors "BLUE": {"unicode": r"\UE088", "ascii": r"\88"}, "SILVER": {"unicode": r"\UE089", "ascii": r"\89"}, "GOLD": {"unicode": r"\UE08A", "ascii": r"\8A"}, "RED": {"unicode": r"\UE08B", "ascii": r"\8B"}, "PURPLE": {"unicode": r"\UE08C", "ascii": r"\8C"}, "LTBROWN": {"unicode": r"\UE08D", "ascii": r"\8D"}, "ORANGE": {"unicode": r"\UE08E", "ascii": r"\8E"}, "GREEN": {"unicode": r"\UE08F", "ascii": r"\8F"}, "YELLOW": {"unicode": r"\UE090", "ascii": r"\90"}, "DKGREEN": {"unicode": r"\UE091", "ascii": r"\91"}, "CREAM": {"unicode": r"\UE092", "ascii": r"\92"}, "BROWN": {"unicode": r"\UE093", "ascii": r"\93"}, "WHITE": {"unicode": r"\UE094", "ascii": r"\94"}, "LTBLUE": {"unicode": r"\UE095", "ascii": r"\95"}, "GRAY": {"unicode": r"\UE096", "ascii": r"\96"}, "DKBLUE": {"unicode": r"\UE097", "ascii": r"\97"}, "BLACK": {"unicode": r"\UE098", "ascii": r"\98"}, "PUSH_COLOUR": {"unicode": r"\UE09A\1F", "ascii": r"\9A\1F"}, "POP_COLOUR": {"unicode": r"\UE09A\20", "ascii": r"\9A\20"}, # Deprecated string codes "DWORD_S": {"unicode": r"\UE07B", "ascii": r"\7B", "deprecated": True, "size": 4}, "PARAM": {"unicode": r"\UE07B", "ascii": r"\7B", "deprecated": True, "size": 4}, "WORD_S": {"unicode": r"\UE07C", "ascii": r"\7C", "deprecated": True, "size": 2}, "BYTE_S": {"unicode": r"\UE07D", "ascii": r"\7D", "deprecated": True}, "WORD_U": {"unicode": r"\UE07E", "ascii": r"\7E", "deprecated": True, "size": 2}, "POP_WORD": {"unicode": r"\UE085", "ascii": r"\85", "deprecated": True, "size": 2}, "CURRENCY_QWORD": {"unicode": r"\UE09A\01", "ascii": r"\9A\01", "deprecated": True}, "PUSH_WORD": {"unicode": r"\UE09A\03", "ascii": r"\9A\03", "deprecated": True}, "UNPRINT": {"unicode": r"\UE09A\04", "ascii": r"\9A\04", "deprecated": True}, "BYTE_HEX": {"unicode": r"\UE09A\06", "ascii": r"\9A\06", "deprecated": True}, "WORD_HEX": {"unicode": r"\UE09A\07", "ascii": r"\9A\07", "deprecated": True, "size": 2}, "DWORD_HEX": {"unicode": r"\UE09A\08", "ascii": r"\9A\08", "deprecated": True, "size": 4}, "QWORD_HEX": {"unicode": r"\UE09A\0B", "ascii": r"\9A\0B", "deprecated": True}, "WORD_S_TONNES": {"unicode": r"\UE09A\0D", "ascii": r"\9A\0D", "deprecated": True, "size": 2}, } # fmt: on special_commands = [ "P", "G", "G=", ] def read_extra_commands(custom_tags_file): """ @param custom_tags_file: Filename of the custom tags file. @type custom_tags_file: C{str} """ if not os.access(custom_tags_file, os.R_OK): # Failed to open custom_tags.txt, ignore this return line_no = 0 with open(generic.find_file(custom_tags_file), "r", encoding="utf-8") as fh: for line in fh: line_no += 1 line = line.strip() if len(line) == 0 or line[0] == "#": continue i = line.find(":") if i == -1: raise generic.ScriptError("Line has no ':' delimiter.", generic.LinePosition(custom_tags_file, line_no)) name = line[:i].strip() value = line[i + 1 :] if name in commands: generic.print_warning( generic.Warning.GENERIC, 'Overwriting existing tag "' + name + '".', generic.LinePosition(custom_tags_file, line_no), ) commands[name] = {"unicode": value} if is_ascii_string(value): commands[name]["ascii"] = value class StringCommand: """ Instantiated string command. @ivar name: Name of the string command. @type name: C{str} @ivar case: ??? @type case: ??? @ivar arguments: Arguments of the instantiated command. @type arguments: C{list} of ??? @ivar offset: Index to an argument of the P or G string command (ie "2" in {P 2 ...}), if available. @type offset: C{int} or C{None} @ivar str_pos: String argument index ("2" in {2:FOO..}, if available. @type str_pos: C{int} or C{None} @ivar pos: Position of the string. @type pos: L{Position} """ def __init__(self, name, str_pos, pos): assert name in commands or name in special_commands self.name = name self.case = None self.arguments = [] self.offset = None self.str_pos = str_pos self.pos = pos def set_arguments(self, arg_string): start = -1 cur = 0 quoted = False whitespace = " \t" while cur < len(arg_string): if start != -1: if (quoted and arg_string[cur] == '"') or (not quoted and arg_string[cur] in whitespace): if ( not quoted and self.offset is None and len(self.arguments) == 0 and isint(arg_string[start:cur]) and self.name in ("P", "G") ): self.offset = int(arg_string[start:cur]) else: self.arguments.append(arg_string[start:cur]) start = -1 elif arg_string[cur] not in whitespace: quoted = arg_string[cur] == '"' start = cur + 1 if quoted else cur cur += 1 if start != -1 and not quoted: self.arguments.append(arg_string[start:]) start = -1 return start == -1 def validate_arguments(self, lang): if lang.langid == DEFAULT_LANGUAGE: return if self.name == "P": if not lang.has_plural_pragma(): raise generic.ScriptError("Using {P} without a ##plural pragma", self.pos) if len(self.arguments) != lang.get_num_plurals(): raise generic.ScriptError( "Invalid number of arguments to plural command, expected {:d} but got {:d}".format( lang.get_num_plurals(), len(self.arguments) ), self.pos, ) elif self.name == "G": if not lang.has_gender_pragma(): raise generic.ScriptError("Using {G} without a ##gender pragma", self.pos) if len(self.arguments) != len(lang.genders): raise generic.ScriptError( "Invalid number of arguments to gender command, expected {:d} but got {:d}".format( len(lang.genders), len(self.arguments) ), self.pos, ) elif self.name == "G=": if not lang.has_gender_pragma(): raise generic.ScriptError("Using {G=} without a ##gender pragma", self.pos) if len(self.arguments) != 1: raise generic.ScriptError( "Invalid number of arguments to set-gender command, expected {:d} but got {:d}".format( 1, len(self.arguments) ), self.pos, ) elif len(self.arguments) != 0: raise generic.ScriptError('Unexpected arguments to command "{}"'.format(self.name), self.pos) def parse_string(self, str_type, lang, wanted_lang_id, prev_command, stack, static_args): """ Convert the string command to output text. @param str_type: Exptected type of result text, C{"unicode"} or C{"ascii"}. @type str_type: C{str} @param lang: Language of the string. @type lang: L{Language} @param wanted_lang_id: Language-id to use for interpreting the command (this string may be from another language, eg with missing strings). @param prev_command: Argument of previous string command (parameter number, size). @type prev_command: C{tuple} or C{None} @param stack: Stack of available arguments (list of (parameter number, size)). @type stack: C{list} of C{tuple} (C{int}, C{int}) or C{None} @param static_args: Static command arguments. """ if self.name in commands: if not self.is_important_command(): return commands[self.name][str_type] # Compute position of the argument in the stack. stack_pos = 0 for pos, size in stack: if pos == self.str_pos: break stack_pos += size self_size = commands[self.name]["size"] stack.remove((self.str_pos, self_size)) if self.str_pos < len(static_args): if "parse" not in commands[self.name]: raise generic.ScriptError( "Provided a static argument for string command '{}' which is invalid".format(self.name), self.pos, ) # Parse commands using the wanted (not current) lang id, so translations are used if present return commands[self.name]["parse"](static_args[self.str_pos], wanted_lang_id) prefix = "" suffix = "" if self.case: prefix += STRING_SELECT_CASE[str_type] + "\\{:02X}".format(self.case) if stack_pos + self_size > 8: raise generic.ScriptError( "Trying to read an argument from the stack without reading the arguments before", self.pos ) if self_size == 4 and stack_pos == 4: prefix += STRING_ROTATE[str_type] + STRING_ROTATE[str_type] elif self_size == 4 and stack_pos == 2: prefix += STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] elif self_size == 2 and stack_pos == 6: prefix += STRING_ROTATE[str_type] elif self_size == 2 and stack_pos == 4: prefix += STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] elif self_size == 2 and stack_pos == 2: prefix += STRING_PUSH_WORD[str_type] + STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] + STRING_SKIP[str_type] else: assert stack_pos == 0 return prefix + commands[self.name][str_type] + suffix assert self.name in special_commands # Create a local copy because we shouldn't modify the original offset = self.offset if offset is None: if self.name == "P": if not prev_command: raise generic.ScriptError( "A plural choice list {P} has to be preceded by another string code or provide an offset", self.pos, ) offset = prev_command[0] else: if not stack: raise generic.ScriptError( "A gender choice list {G} has to be followed by another string code or provide an offset", self.pos, ) offset = stack[0][0] offset -= len(static_args) if self.name == "P": if offset < 0: return self.arguments[lang.static_plural_form(static_args[offset]) - 1] ret = BEGIN_PLURAL_CHOICE_LIST[str_type] + "\\{:02X}".format(0x80 + offset) for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: ret += CHOICE_LIST_ITEM[str_type] + "\\{:02X}".format(idx + 1) ret += arg ret += CHOICE_LIST_END[str_type] return ret if self.name == "G": if offset < 0: return self.arguments[lang.static_gender(static_args[offset]) - 1] ret = BEGIN_GENDER_CHOICE_LIST[str_type] + "\\{:02X}".format(0x80 + offset) for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: ret += CHOICE_LIST_ITEM[str_type] + "\\{:02X}".format(idx + 1) ret += arg ret += CHOICE_LIST_END[str_type] return ret # Not reached raise ValueError("Unexpected string command '{}'".format(self.name)) def get_type(self): if self.name in commands: if "ascii" in commands[self.name]: return "ascii" else: return "unicode" if self.name == "P" or self.name == "G": for arg in self.arguments: if not is_ascii_string(arg): return "unicode" return "ascii" def is_important_command(self): if self.name in special_commands: return False return "size" in commands[self.name] def get_arg_size(self): return commands[self.name]["size"] # Characters that are valid in hex numbers VALID_HEX = "0123456789abcdefABCDEF" def is_valid_hex(string): return all(c in VALID_HEX for c in string) def validate_escapes(string, pos): """ Validate that all escapes (starting with a backslash) are correct. When an invalid escape is encountered, an error is thrown @param string: String to validate @type string: C{str} @param pos: Position information @type pos: L{Position} """ i = 0 while i < len(string): # find next '\' i = string.find("\\", i) if i == -1: break if i + 1 >= len(string): raise generic.ScriptError("Unexpected end-of-line encountered after '\\'", pos) if string[i + 1] in ("\\", '"'): i += 2 elif string[i + 1] == "U": if i + 5 >= len(string) or not is_valid_hex(string[i + 2 : i + 6]): raise generic.ScriptError("Expected 4 hexadecimal characters after '\\U'", pos) i += 6 else: if i + 2 >= len(string) or not is_valid_hex(string[i + 1 : i + 3]): raise generic.ScriptError("Expected 2 hexadecimal characters after '\\'", pos) i += 3 class NewGRFString: """ A string text in a language. @ivar string: String text. @type string: C{str} @ivar cases: Mapping of cases to ... @type cases: ??? @ivar components: Split string text. @type components: C{list} of (C{str} or L{StringCommand}) @ivar pos: Position of the string text. @type pos: L{Position} """ def __init__(self, string, lang, pos): """ Construct a L{NewGRFString}, and break down the string text into text literals and string commands. @param string: String text. @type string: C{str} @param lang: Language containing this string. @type lang: L{Language} @param pos: Position of the string text. @type pos: L{Position} """ validate_escapes(string, pos) self.string = string self.cases = {} self.components = [] self.pos = pos idx = 0 while idx < len(string): if string[idx] != "{": j = string.find("{", idx) if j == -1: self.components.append(string[idx:]) break self.components.append(string[idx:j]) idx = j start = idx + 1 end = start cmd_pos = None if start >= len(string): raise generic.ScriptError("Expected '}' before end-of-line.", pos) if string[start].isdigit(): while end < len(string) and string[end].isdigit(): end += 1 if end == len(string) or string[end] != ":": raise generic.ScriptError("Error while parsing position part of string command", pos) cmd_pos = int(string[start:end]) start = end + 1 end = start # Read the command name while end < len(string) and string[end] not in "} =.": end += 1 command_name = string[start:end] if end < len(string) and string[end] == "=": command_name += "=" if command_name not in commands and command_name not in special_commands: raise generic.ScriptError('Undefined command "{}"'.format(command_name), pos) if command_name in commands and "deprecated" in commands[command_name]: generic.print_warning( generic.Warning.DEPRECATION, "String code '{}' has been deprecated and will be removed soon".format(command_name), pos, ) del commands[command_name]["deprecated"] # command = StringCommand(command_name, cmd_pos, pos) if end >= len(string): raise generic.ScriptError("Missing '}}' from command \"{}\"".format(string[start:]), pos) if string[end] == ".": if command_name not in commands or "allow_case" not in commands[command_name]: raise generic.ScriptError('Command "{}" can\'t have a case'.format(command_name), pos) case_start = end + 1 end = case_start while end < len(string) and string[end] not in "} ": end += 1 case = string[case_start:end] if lang.cases is None or case not in lang.cases: raise generic.ScriptError('Invalid case-name "{}"'.format(case), pos) command.case = lang.cases[case] if string[end] != "}": command.argument_is_assigment = string[end] == "=" arg_start = end + 1 end = string.find("}", end + 1) if end == -1 or not command.set_arguments(string[arg_start:end]): raise generic.ScriptError("Missing '}}' from command \"{}\"".format(string[start:]), pos) command.validate_arguments(lang) if command_name == "G=" and self.components: raise generic.ScriptError("Set-gender command {G=} must be at the start of the string", pos) self.components.append(command) idx = end + 1 if ( len(self.components) > 0 and isinstance(self.components[0], StringCommand) and self.components[0].name == "G=" ): self.gender = self.components[0].arguments[0] if self.gender not in lang.genders: raise generic.ScriptError("Invalid gender name '{}'".format(self.gender), pos) self.components.pop(0) else: self.gender = None cmd_pos = 0 for cmd in self.components: if not (isinstance(cmd, StringCommand) and cmd.is_important_command()): continue if cmd.str_pos is None: cmd.str_pos = cmd_pos cmd_pos = cmd.str_pos + 1 def get_type(self): """ Get the type of string text. @return: C{"unicode"} if Unicode is required for expressing the string text, else C{"ascii"}. @rtype: C{str} """ for comp in self.components: if isinstance(comp, StringCommand): if comp.get_type() == "unicode": return "unicode" else: if not is_ascii_string(comp): return "unicode" for case in self.cases.values(): if case.get_type() == "unicode": return "unicode" return "ascii" def remove_non_default_commands(self): i = 0 while i < len(self.components): comp = self.components[i] if isinstance(comp, StringCommand): if comp.name == "P" or comp.name == "G": self.components[i] = comp.arguments[-1] if comp.arguments else "" i += 1 def parse_string(self, str_type, lang, wanted_lang_id, static_args): """ Convert the string text to output text. """ ret = "" stack = [(idx, size) for idx, size in enumerate(self.get_command_sizes())] prev_command = None for comp in self.components: if isinstance(comp, StringCommand): next_command = stack[0] if stack else None ret += comp.parse_string(str_type, lang, wanted_lang_id, prev_command, stack, static_args) if (comp.name in commands) and comp.is_important_command(): prev_command = next_command else: ret += comp return ret def get_command_sizes(self): """ Get the size of each string parameter. """ sizes = {} for cmd in self.components: if not (isinstance(cmd, StringCommand) and cmd.is_important_command()): continue if cmd.str_pos in sizes: raise generic.ScriptError("Two or more string commands are using the same argument", self.pos) sizes[cmd.str_pos] = cmd.get_arg_size() sizes_list = [] for idx in range(len(sizes)): if idx not in sizes: raise generic.ScriptError("String argument {:d} is not used".format(idx), self.pos) sizes_list.append(sizes[idx]) return sizes_list def match_commands(self, other_string): return self.get_command_sizes() == other_string.get_command_sizes() def isint(x, base=10): try: int(x, base) return True except ValueError: return False NUM_PLURAL_FORMS = 14 CHOICE_LIST_ITEM = {"unicode": r"\UE09A\10", "ascii": r"\9A\10"} CHOICE_LIST_DEFAULT = {"unicode": r"\UE09A\11", "ascii": r"\9A\11"} CHOICE_LIST_END = {"unicode": r"\UE09A\12", "ascii": r"\9A\12"} BEGIN_GENDER_CHOICE_LIST = {"unicode": r"\UE09A\13", "ascii": r"\9A\13"} BEGIN_CASE_CHOICE_LIST = {"unicode": r"\UE09A\14", "ascii": r"\9A\14"} BEGIN_PLURAL_CHOICE_LIST = {"unicode": r"\UE09A\15", "ascii": r"\9A\15"} SET_STRING_GENDER = {"unicode": r"\UE09A\0E", "ascii": r"\9A\0E"} STRING_SKIP = {"unicode": r"\UE085", "ascii": r"\85"} STRING_ROTATE = {"unicode": r"\UE086", "ascii": r"\86"} STRING_PUSH_WORD = {"unicode": r"\UE09A\03\20\20", "ascii": r"\9A\03\20\20"} STRING_SELECT_CASE = {"unicode": r"\UE09A\0F", "ascii": r"\9A\0F"} class Language: """ @ivar default: Whether the language is the default language. @type default: C{bool} @ivar langid: Language id of the language, if known. @type langid: C{None} or C{int} @ivar plural: Plural type. @type plural: C{None} or C{int} @ivar genders: @type genders: @ivar gender_map: @type gender_map: @ivar cases: @type cases: @ivar case_map: @type case_map: @ivar strings: Language strings of the file. @type strings: C{dict} of """ used_strings = [] def __init__(self, default): self.default = default self.langid = None self.plural = None self.genders = None self.gender_map = {} self.cases = None self.case_map = {} self.strings = {} def get_num_plurals(self): if self.plural is None: return 0 num_plurals = { 0: 2, 1: 1, 2: 2, 3: 3, 4: 5, 5: 3, 6: 3, 7: 3, 8: 4, 9: 2, 10: 3, 11: 2, 12: 4, 13: 4, 14: 3, } return num_plurals[self.plural] def has_plural_pragma(self): return self.plural is not None def has_gender_pragma(self): return self.genders is not None def static_gender(self, expr): import nml.expression if isinstance(expr, nml.expression.StringLiteral): return len(self.genders) if not isinstance(expr, nml.expression.String): raise generic.ScriptError("{G} can only refer to a string argument") parsed = self.get_string(expr, self.langid) if parsed.find(SET_STRING_GENDER["ascii"]) == 0: return int(parsed[len(SET_STRING_GENDER["ascii"]) + 1 : len(SET_STRING_GENDER["ascii"]) + 3], 16) if parsed.find(SET_STRING_GENDER["unicode"]) == 0: return int(parsed[len(SET_STRING_GENDER["unicode"]) + 1 : len(SET_STRING_GENDER["unicode"]) + 3], 16) return len(self.genders) def static_plural_form(self, expr): # Return values are the same as "Plural index" here: # http://newgrf-specs.tt-wiki.net/wiki/StringCodes#Using_plural_forms val = expr.reduce_constant().value if self.plural == 0: return 1 if val == 1 else 2 if self.plural == 1: return 1 if self.plural == 2: return 1 if val in (0, 1) else 2 if self.plural == 3: if val % 10 == 1 and val % 100 != 11: return 1 return 2 if val == 0 else 3 if self.plural == 4: if val == 1: return 1 if val == 2: return 2 if 3 <= val <= 6: return 3 if 7 <= val <= 10: return 4 return 5 if self.plural == 5: if val % 10 == 1 and val % 100 != 11: return 1 if 2 <= (val % 10) <= 9 and not 12 <= (val % 100) <= 19: return 2 return 3 if self.plural == 6: if val % 10 == 1 and val % 100 != 11: return 1 if 2 <= (val % 10) <= 4 and not 12 <= (val % 100) <= 14: return 2 return 3 if self.plural == 7: if val == 0: return 1 if 2 <= (val % 10) <= 4 and not 12 <= (val % 100) <= 14: return 2 return 3 if self.plural == 8: if val % 100 == 1: return 1 if val % 100 == 2: return 2 if val % 100 in (3, 4): return 3 return 4 if self.plural == 9: if val % 10 == 1 and val % 100 != 11: return 1 return 2 if self.plural == 10: if val == 1: return 1 if 2 <= val <= 4: return 2 return 3 if self.plural == 11: if val % 10 in (0, 1, 3, 6, 7, 8): return 1 return 2 if self.plural == 12: if val == 1: return 1 if val == 0 or 2 <= (val % 100) <= 10: return 2 if 11 <= (val % 100) <= 19: return 3 return 4 if self.plural == 13: if val == 1 or val == 11: return 1 if val == 2 or val == 12: return 2 if (val >= 3 and val <= 10) or (val >= 13 and val <= 19): return 3 return 4 if self.plural == 14: if val == 1: return 1 if val == 0 or 1 <= (val % 100) <= 19: return 2 return 3 raise AssertionError("Unknown plural type") def get_string(self, string, lang_id): """ Lookup up a string by name/params and return the actual created string @param string: String object @type string: L{expression.String} @param lang_id: Language ID we are actually looking for. This may differ from the ID of this language, if the string is missing from the target language. @type lang_id: C{int} @return: The created string @rtype: C{str} """ string_id = string.name.value assert isinstance(string_id, str) assert string_id in self.strings assert lang_id == self.langid or self.langid == DEFAULT_LANGUAGE if string_id not in Language.used_strings: Language.used_strings.append(string_id) str_type = self.strings[string_id].get_type() parsed_string = "" if self.strings[string_id].gender is not None: parsed_string += SET_STRING_GENDER[str_type] + "\\{:02X}".format( self.genders[self.strings[string_id].gender] ) if len(self.strings[string_id].cases) > 0: parsed_string += BEGIN_CASE_CHOICE_LIST[str_type] for case_name, case_string in sorted(self.strings[string_id].cases.items()): case_id = self.cases[case_name] parsed_string += ( CHOICE_LIST_ITEM[str_type] + "\\{:02X}".format(case_id) + case_string.parse_string(str_type, self, lang_id, string.params) ) parsed_string += CHOICE_LIST_DEFAULT[str_type] parsed_string += self.strings[string_id].parse_string(str_type, self, lang_id, string.params) if len(self.strings[string_id].cases) > 0: parsed_string += CHOICE_LIST_END[str_type] return parsed_string def handle_grflangid(self, data, pos): """ Handle a 'grflangid' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.langid is not None: raise generic.ScriptError("grflangid already set", pos) lang_text = data.strip() try: value = int(lang_text, 16) except ValueError: raise generic.ScriptError("Invalid grflangid {!r}".format(lang_text), pos) if value < 0 or value >= 0x7F: raise generic.ScriptError("Invalid grflangid", pos) self.langid = value def handle_plural(self, data, pos): """ Handle a 'plural' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.plural is not None: raise generic.ScriptError("plural form already set", pos) try: # Explicitly force base 10 like in OpenTTD's lang file value = int(data, 10) except ValueError: raise generic.ScriptError("Invalid plural form", pos) if value < 0 or value > NUM_PLURAL_FORMS: raise generic.ScriptError("Invalid plural form", pos) self.plural = value def handle_gender(self, data, pos): """ Handle a 'gender' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.genders is not None: raise generic.ScriptError("Genders already defined", pos) self.genders = {} for idx, gender in enumerate(data.split()): self.genders[gender] = idx + 1 self.gender_map[gender] = [] def handle_map_gender(self, data, pos): """ Handle a 'map_gender' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.genders is None: raise generic.ScriptError("##map_gender is not allowed before ##gender", pos) genders = data.split() if len(genders) != 2: raise generic.ScriptError("Invalid ##map_gender line", pos) if genders[0] not in self.genders: raise generic.ScriptError("Trying to map non-existing gender '{}'".format(genders[0]), pos) self.gender_map[genders[0]].append(genders[1]) def handle_case(self, data, pos): """ Handle a 'case' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.cases is not None: raise generic.ScriptError("Cases already defined", pos) self.cases = {} for idx, case in enumerate(data.split()): self.cases[case] = idx + 1 self.case_map[case] = [] def handle_map_case(self, data, pos): """ Handle a 'map_case' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.cases is None: raise generic.ScriptError("##map_case is not allowed before ##case", pos) cases = data.split() if len(cases) != 2: raise generic.ScriptError("Invalid ##map_case line", pos) if cases[0] not in self.cases: raise generic.ScriptError("Trying to map non-existing case '{}'".format(cases[0]), pos) self.case_map[cases[0]].append(cases[1]) def handle_text(self, string, case, value, pos): """ Handle a text string definition. @param string: Name of the string. @type string: C{str} @param case: The case being defined (optional). @type case: C{str} or C{None} @param value: Value of the string. @type value: C{str} """ if not re.match("[A-Za-z_0-9]+$", string): raise generic.ScriptError('Invalid string name "{}"'.format(string), pos) if string in self.strings and case is None: raise generic.ScriptError('String name "{}" is used multiple times'.format(string), pos) if self.default: self.strings[string] = NewGRFString(value, self, pos) self.strings[string].remove_non_default_commands() else: if string not in default_lang.strings: generic.print_warning( generic.Warning.GENERIC, 'String name "{}" does not exist in master file'.format(string), pos ) return newgrf_string = NewGRFString(value, self, pos) if not default_lang.strings[string].match_commands(newgrf_string): generic.print_warning( generic.Warning.GENERIC, 'String commands don\'t match with master file "{}"'.format(DEFAULT_LANGNAME), pos, ) return if case is None: self.strings[string] = newgrf_string else: if string not in self.strings: generic.print_warning(generic.Warning.GENERIC, "String with case used before the base string", pos) return if self.cases is None or case not in self.cases: generic.print_warning(generic.Warning.GENERIC, 'Invalid case name "{}"'.format(case), pos) return if case in self.strings[string].cases: raise generic.ScriptError('String name "{}.{}" is used multiple times'.format(string, case), pos) if newgrf_string.gender: generic.print_warning( generic.Warning.GENERIC, "Case-strings can't set the gender, only the base string can", pos ) return self.strings[string].cases[case] = newgrf_string def handle_string(self, line, pos): if len(line) == 0: # Silently ignore empty lines. return elif len(line) > 2 and line[:2] == "##" and not line[:3] == "###": # "##pragma" line, call relevant handler. if self.default: # Default language ignores all pragmas. return pragmas = { "##grflangid": self.handle_grflangid, "##plural": self.handle_plural, "##gender": self.handle_gender, "##map_gender": self.handle_map_gender, "##map_case": self.handle_map_case, "##case": self.handle_case, } try: pragma, value = line.split(" ", maxsplit=1) handler = pragmas[pragma] except (ValueError, KeyError): raise generic.ScriptError("Invalid pragma", pos) handler(value, pos) elif line[0] == "#": # Normal comment, ignore return else: # Must be a line defining a string. try: name, value = line.split(":", maxsplit=1) name = name.rstrip() except ValueError: raise generic.ScriptError("Line has no ':' delimiter", pos) if "." in name: name, case = name.rsplit(".", maxsplit=1) if not case: raise generic.ScriptError("No case after '.' delimiter", pos) else: case = None if self.default and case is not None: # Ignore cases for the default language return self.handle_text(name, case, value, pos) default_lang = Language(True) default_lang.langid = DEFAULT_LANGUAGE langs = [] def parse_file(filename, default): """ Read and parse a single language file. @param filename: The filename of the file to parse. @type filename: C{str} @param default: True iff this is the default language. @type default: C{bool} """ lang = Language(False) try: with open(generic.find_file(filename), "r", encoding="utf-8") as fh: for idx, line in enumerate(fh): pos = generic.LinePosition(filename, idx + 1) line = line.rstrip("\n\r").lstrip("\ufeff") # The default language is processed twice here. Once as fallback langauge # and once as normal language. if default: default_lang.handle_string(line, pos) lang.handle_string(line, pos) except UnicodeDecodeError: pos = generic.LanguageFilePosition(filename) if default: raise generic.ScriptError("The default language file contains non-utf8 characters.", pos) generic.print_warning( generic.Warning.GENERIC, "Language file contains non-utf8 characters. Ignoring (part of) the contents.", pos ) except generic.ScriptError as err: if default: raise generic.print_warning(generic.Warning.GENERIC, err.value, err.pos) else: if lang.langid is None: generic.print_warning( generic.Warning.GENERIC, "Language file does not contain a ##grflangid pragma", generic.LanguageFilePosition(filename), ) else: for lng in langs: if lng[0] == lang.langid: msg = "Language file has the same ##grflangid (with number {:d}) as another language file".format( lang.langid ) raise generic.ScriptError(msg, generic.LanguageFilePosition(filename)) langs.append((lang.langid, lang)) def read_lang_files(lang_dir, default_lang_file): """ Read the language files containing the translations for string constants used in the NML specification. @param lang_dir: Name of the directory containing the language files. @type lang_dir: C{str} @param default_lang_file: Filename of the language file that has the default translation which will be used as fallback for other languages. @type default_lang_file: C{str} """ global DEFAULT_LANGNAME DEFAULT_LANGNAME = default_lang_file if not os.path.exists(lang_dir + os.sep + default_lang_file): generic.print_warning( generic.Warning.GENERIC, 'Default language file "{}" doesn\'t exist'.format(os.path.join(lang_dir, default_lang_file)), ) return parse_file(lang_dir + os.sep + default_lang_file, True) for filename in glob.glob(lang_dir + os.sep + "*.lng"): if filename.endswith(default_lang_file): continue parse_file(filename, False) langs.sort() def list_unused_strings(): for string in default_lang.strings: if string in Language.used_strings: continue generic.print_warning(generic.Warning.GENERIC, 'String "{}" is unused'.format(string)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/lz77.py0000644000175100001660000000520714754345605013701 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import array def _encode(data): """ GRF compression algorithm. @param data: Uncompressed data. @type data: C{str}, C{bytearray}, C{bytes} or similar. @return: Compressed data. @rtype: C{bytearray} """ stream = data.tobytes() position = 0 output = array.array("B") literal_bytes = array.array("B") stream_len = len(stream) while position < stream_len: overlap_len = 0 start_pos = max(0, position - (1 << 11) + 1) # Loop through the lookahead buffer. for i in range(3, min(stream_len - position + 1, 16)): # Set pattern to find the longest match. pattern = stream[position : position + i] # Find the pattern match in the window. result = stream.find(pattern, start_pos, position) # If match failed, we've found the longest. if result < 0: break p = position - result overlap_len = i start_pos = result if overlap_len > 0: if len(literal_bytes) > 0: output.append(len(literal_bytes)) output.extend(literal_bytes) literal_bytes = array.array("B") val = ((-overlap_len) << 3) & 0xFF | (p >> 8) output.append(val) output.append(p & 0xFF) position += overlap_len else: literal_bytes.append(stream[position]) if len(literal_bytes) == 0x80: output.append(0) output.extend(literal_bytes) literal_bytes = array.array("B") position += 1 if len(literal_bytes) > 0: output.append(len(literal_bytes)) output.extend(literal_bytes) return output """ True if the encoding is provided by a native module. Used for verbose information. """ is_native = False try: from nml_lz77 import encode # lgtm[py/unused-import] is_native = True except ImportError: encode = _encode ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/main.py0000644000175100001660000005602014754345605014021 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import codecs import optparse import os import sys from nml import ( generic, global_constants, grfstrings, output_dep, output_grf, output_nfo, output_nml, palette, parser, spritecache, spriteencoder, version_info, ) from nml.actions import ( action0, action1, action2, action2layout, action2var, action4, action6, action7, action8, action11, actionF, real_sprite, sprite_count, ) from nml.ast import grf try: from PIL import Image except ImportError: # Image is required only when using graphics Image = None pass developmode = False # Give 'nice' error message instead of a stack dump. version = version_info.get_nml_version() def parse_cli(argv): """ Parse the command line, and process options. @return: Options, and input filename (if not supplied, use C{sys.stdin}). @rtype: C{tuple} (C{Object}, C{str} or C{None}) """ usage = "Usage: %prog [options] \n" "Where is the nml file to parse" opt_parser = optparse.OptionParser(usage=usage, version=version_info.get_cli_version()) opt_parser.set_defaults( debug=False, crop=False, compress=True, outputs=[], start_sprite_num=0, custom_tags="custom_tags.txt", lang_dir="lang", default_lang="english.lng", cache_dir=".nmlcache", forced_palette="ANY", quiet=False, md5_filename=None, keep_orphaned=True, verbosity=generic.verbosity_level, rebuild_parser=False, debug_parser=False, allow_extra_zoom=True, allow_32bpp=True, disable_palette_validation=False, list_unused_strings=False, ) opt_parser.add_option("-d", "--debug", action="store_true", dest="debug", help="write the AST to stdout") opt_parser.add_option("-s", "--stack", action="store_true", dest="stack", help="Dump stack when an error occurs") opt_parser.add_option("--grf", dest="grf_filename", metavar="", help="write the resulting grf to ") opt_parser.add_option( "--md5", dest="md5_filename", metavar="", help="Write an md5sum of the resulting grf to " ) opt_parser.add_option("--nfo", dest="nfo_filename", metavar="", help="write nfo output to ") opt_parser.add_option( "-M", action="store_true", dest="dep_check", help=( "output a rule suitable for make describing" " the graphics dependencies of the main grf file (requires input file or --grf)" ), ) opt_parser.add_option( "--MF", dest="dep_filename", metavar="", help="When used with -M, specifies a file to write the dependencies to", ) opt_parser.add_option( "--MT", dest="depgrf_filename", metavar="", help="target of the rule emitted by dependency generation (requires -M)", ) opt_parser.add_option( "-c", action="store_true", dest="crop", help="crop extraneous transparent blue from real sprites" ) opt_parser.add_option("-u", action="store_false", dest="compress", help="save uncompressed data in the grf file") opt_parser.add_option("--nml", dest="nml_filename", metavar="", help="write optimized nml to ") opt_parser.add_option( "-o", "--output", dest="outputs", action="append", metavar="", help="write output(nfo/grf) to " ) opt_parser.add_option( "-t", "--custom-tags", dest="custom_tags", metavar="", help="Load custom tags from [default: %default]", ) opt_parser.add_option( "-l", "--lang-dir", dest="lang_dir", metavar="

", help="Load language files from directory [default: %default]", ) opt_parser.add_option( "--default-lang", dest="default_lang", metavar="", help="The default language is stored in [default: %default]", ) opt_parser.add_option( "--start-sprite", action="store", type="int", dest="start_sprite_num", metavar="", help=( "Set the first sprite number to write" " (do not use except when you output nfo that you want to include in other files)" ), ) opt_parser.add_option( "-p", "--palette", dest="forced_palette", metavar="", choices=["DEFAULT", "LEGACY", "DOS", "WIN", "ANY"], help="Force nml to use the palette [default: %default]. Valid values are 'DEFAULT', 'LEGACY', 'ANY'", ) opt_parser.add_option( "--quiet", action="store_true", dest="quiet", help="Disable all warnings. Errors will be printed normally." ) opt_parser.add_option( "-n", "--no-cache", action="store_true", dest="no_cache", help="Disable caching of sprites in .cache[index] files, which may reduce compilation time.", ) opt_parser.add_option( "--cache-dir", dest="cache_dir", metavar="", help="Cache files are stored in directory [default: %default]", ) opt_parser.add_option( "--clear-orphaned", action="store_false", dest="keep_orphaned", help="Remove unused/orphaned items from cache files.", ) opt_parser.add_option( "--verbosity", type="int", dest="verbosity", metavar="", help="Set the verbosity level for informational output. [default: %default, max: {}]".format( generic.VERBOSITY_MAX ), ) opt_parser.add_option( "-R", "--rebuild-parser", action="store_true", dest="rebuild_parser", help="Force regeneration of parser tables.", ) opt_parser.add_option( "-D", "--debug-parser", action="store_true", dest="debug_parser", help="Enable debug mode for parser." ) opt_parser.add_option( "--no-optimisation-warning", action="append_const", dest="disable_warning", const=generic.Warning.OPTIMISATION, help="Disable optimisation warnings", ) opt_parser.add_option( "--no-deprecation-warning", action="append_const", dest="disable_warning", const=generic.Warning.DEPRECATION, help="Disable deprecation warnings", ) opt_parser.add_option( "--no-extra-zoom", action="store_false", dest="allow_extra_zoom", help="Skip extra zoom alternative sprites" ) opt_parser.add_option("--no-32bpp", action="store_false", dest="allow_32bpp", help="Skip 32bpp alternative sprites") opt_parser.add_option( "--no-palette-validation", action="store_true", dest="disable_palette_validation", help="Disable palette validation for sprites", ) opt_parser.add_option( "--list-unused-strings", action="store_true", dest="list_unused_strings", help="List unused strings" ) opts, args = opt_parser.parse_args(argv) generic.set_verbosity(0 if opts.quiet else opts.verbosity) generic.Warning.disabled = opts.disable_warning generic.set_cache_root_dir(None if opts.no_cache else opts.cache_dir) spritecache.keep_orphaned = opts.keep_orphaned global_constants.allow_extra_zoom = opts.allow_extra_zoom global_constants.allow_32bpp = opts.allow_32bpp opts.outputfile_given = ( opts.grf_filename or opts.nfo_filename or opts.nml_filename or opts.dep_filename or opts.outputs ) if not args: if not opts.outputfile_given: opt_parser.print_help() sys.exit(2) input_filename = None # Output filenames, but no input -> use stdin. elif len(args) > 1: opt_parser.error("Error: only a single nml file can be read per run") else: input_filename = args[0] if not os.access(input_filename, os.R_OK): raise generic.ScriptError('Input file "{}" does not exist'.format(input_filename)) return opts, input_filename def main(argv): global developmode opts, input_filename = parse_cli(argv) if opts.stack: developmode = True grfstrings.read_extra_commands(opts.custom_tags) generic.print_progress("Reading lang ...") grfstrings.read_lang_files(opts.lang_dir, opts.default_lang) generic.clear_progress() # We have to do the dependency check first or we might later have # more targets than we asked for outputs = [] if opts.dep_check: # First make sure we have a file to output the dependencies to: dep_filename = opts.dep_filename if dep_filename is None and opts.grf_filename is not None: dep_filename = filename_output_from_input(opts.grf_filename, ".dep") if dep_filename is None and input_filename is not None: dep_filename = filename_output_from_input(input_filename, ".dep") if dep_filename is None: raise generic.ScriptError( "-M requires a dependency file either via -MF, an input filename or a valid output via --grf" ) # Now make sure we have a file which is the target for the dependencies: depgrf_filename = opts.depgrf_filename if depgrf_filename is None and opts.grf_filename is not None: depgrf_filename = opts.grf_filename if depgrf_filename is None and input_filename is not None: depgrf_filename = filename_output_from_input(input_filename, ".grf") if depgrf_filename is None: raise generic.ScriptError( "-M requires either a target grf file via -MT, an input filename or a valid output via --grf" ) # Only append the dependency check to the output targets when we have both, # a target grf and a file to write to if dep_filename is not None and depgrf_filename is not None: outputs.append(output_dep.OutputDEP(dep_filename, depgrf_filename)) if input_filename is None: input = sys.stdin else: input = codecs.open(generic.find_file(input_filename), "r", "utf-8") # Only append an output grf name, if no ouput is given, also not implicitly via -M if not opts.outputfile_given and not outputs: opts.grf_filename = filename_output_from_input(input_filename, ".grf") # Translate the 'common' palette names as used by OpenTTD to the traditional ones being within NML's code if opts.forced_palette == "DOS": opts.forced_palette = "DEFAULT" elif opts.forced_palette == "WIN": opts.forced_palette = "LEGACY" if opts.grf_filename: outputs.append(output_grf.OutputGRF(opts.grf_filename)) if opts.nfo_filename: outputs.append(output_nfo.OutputNFO(opts.nfo_filename, opts.start_sprite_num)) if opts.nml_filename: outputs.append(output_nml.OutputNML(opts.nml_filename)) for output in opts.outputs: outroot, outext = os.path.splitext(output) outext = outext.lower() if outext == ".grf": outputs.append(output_grf.OutputGRF(output)) elif outext == ".nfo": outputs.append(output_nfo.OutputNFO(output, opts.start_sprite_num)) elif outext == ".nml": outputs.append(output_nml.OutputNML(output)) elif outext == ".dep": outputs.append(output_dep.OutputDEP(output, opts.grf_filename)) else: generic.print_error("Unknown output format {}".format(outext)) sys.exit(2) ret = nml( input, input_filename, opts.debug, outputs, opts.start_sprite_num, opts.compress, opts.crop, opts.forced_palette, opts.md5_filename, opts.rebuild_parser, opts.debug_parser, opts.disable_palette_validation, ) input.close() if opts.list_unused_strings: grfstrings.list_unused_strings() sys.exit(ret) def filename_output_from_input(name, ext): return os.path.splitext(name)[0] + ext def nml( inputfile, input_filename, output_debug, outputfiles, start_sprite_num, compress_grf, crop_sprites, forced_palette, md5_filename, rebuild_parser, debug_parser, disable_palette_validation, ): """ Compile an NML file. @param inputfile: File handle associated with the input file. @type inputfile: C{File} @param input_filename: Filename of the input file, C{None} if receiving from L{sys.stdin} @type input_filename: C{str} or C{None} @param outputfiles: Output streams to write to. @type outputfiles: C{List} of L{output_base.OutputBase} @param start_sprite_num: Number of the first sprite. @type start_sprite_num: C{int} @param compress_grf: Enable GRF sprite compression. @type compress_grf: C{bool} @param crop_sprites: Enable sprite cropping. @type crop_sprites: C{bool} @param forced_palette: Palette to use for the file. @type forced_palette: C{str} @param md5_filename: Filename to use for writing the md5 sum of the grf file. C{None} if the file should not be written. @type md5_filename: C{str} or C{None} """ generic.OnlyOnce.clear() generic.print_progress("Reading {} ...".format(input_filename or "standard input")) try: script = inputfile.read() except UnicodeDecodeError as ex: raise generic.ScriptError("Input file is not utf-8 encoded: {}".format(ex)) # Strip a possible BOM script = script.lstrip(str(codecs.BOM_UTF8, "utf-8")) if script.strip() == "": generic.print_error("Empty input file") return 4 generic.print_progress("Init parser ...") nml_parser = parser.NMLParser(rebuild_parser, debug_parser) if input_filename is None: input_filename = "input" generic.print_progress("Parsing ...") result = nml_parser.parse(script, input_filename) result.validate([]) if output_debug > 0: result.debug_print(0) for outputfile in outputfiles: if isinstance(outputfile, output_nml.OutputNML): with outputfile: outputfile.write(str(result)) generic.print_progress("Preprocessing ...") result.register_names() result.pre_process() tmp_actions = result.get_action_list() generic.print_progress("Generating actions ...") actions = [] for action in tmp_actions: if isinstance(action, action1.SpritesetCollection): actions.extend(action.get_action_list()) else: actions.append(action) actions.extend(action11.get_sound_actions()) generic.print_progress("Assigning Action2 registers ...") action8_index = -1 for i in range(len(actions) - 1, -1, -1): if isinstance(actions[i], (action2var.Action2Var, action2layout.Action2Layout)): actions[i].resolve_tmp_storage() elif isinstance(actions[i], action8.Action8): action8_index = i generic.print_progress("Generating strings ...") if action8_index != -1: lang_actions = [] # Add plural/gender/case tables for lang_pair in grfstrings.langs: lang_id, lang = lang_pair lang_actions.extend(action0.get_language_translation_tables(lang)) # Add global strings lang_actions.extend(action4.get_global_string_actions()) actions = actions[: action8_index + 1] + lang_actions + actions[action8_index + 1 :] generic.print_progress("Collecting real sprites ...") # Collect all sprite files, and put them into buckets of same image and mask files sprite_files = {} for action in actions: if isinstance(action, real_sprite.RealSpriteAction): for sprite in action.sprite_list: if sprite.is_empty: continue if not Image: generic.print_error("PIL (python-imaging) wasn't found, no support for using graphics") sys.exit(3) sprite.validate_size() file = sprite.file if file is not None: file = file.value mask_file = sprite.mask_file if mask_file is not None: mask_file = mask_file.value key = (file, mask_file) sprite_files.setdefault(key, []).append(sprite) # Check whether we can terminate sprite processing prematurely for # dependency checks skip_sprite_processing = True for outputfile in outputfiles: if isinstance(outputfile, output_dep.OutputDEP): with outputfile: for f in sprite_files: if f[0] is not None: outputfile.write(f[0]) if f[1] is not None: outputfile.write(f[1]) skip_sprite_processing &= outputfile.skip_sprite_checks() if skip_sprite_processing: generic.clear_progress() return 0 used_palette = forced_palette if not disable_palette_validation: generic.print_progress("Checking palette of source images ...") last_file = None for f_pair in sprite_files: # Palette is defined by mask_file, if present. Otherwise by the main file. f = f_pair[1] if f is None: f = f_pair[0] try: with Image.open(generic.find_file(f)) as im: # Verify the image is running in Palette mode, if not, skip this file. if im.mode != "P": continue pal = palette.validate_palette(im, f) except IOError as ex: raise generic.ImageError(str(ex), f) if ( forced_palette != "ANY" and pal != forced_palette and not (forced_palette == "DEFAULT" and pal == "LEGACY") ): raise generic.ImageError( "Image has '{}' palette, but you forced the '{}' palette".format(pal, used_palette), f ) if used_palette == "ANY": used_palette = pal elif pal != used_palette: if used_palette in ("LEGACY", "DEFAULT") and pal in ("LEGACY", "DEFAULT"): used_palette = "DEFAULT" else: raise generic.ImageError( "Image has '{}' palette, but \"{}\" has the '{}' palette".format(pal, last_file, used_palette), f, ) last_file = f palette_bytes = {"LEGACY": "W", "DEFAULT": "D", "ANY": "A"} if used_palette in palette_bytes: grf.set_palette_used(palette_bytes[used_palette]) encoder = None for outputfile in outputfiles: outputfile.palette = used_palette # used by RecolourSpriteAction if isinstance(outputfile, output_grf.OutputGRF): if encoder is None: encoder = spriteencoder.SpriteEncoder(compress_grf, crop_sprites, used_palette) outputfile.encoder = encoder generic.clear_progress() # Read all image data, compress, and store in sprite cache if encoder is not None: encoder.open(sprite_files) # If there are any 32bpp sprites hint to openttd that we'd like a 32bpp blitter if global_constants.any_32bpp_sprites: grf.set_preferred_blitter("3") generic.print_progress("Linking actions ...") if action8_index != -1: actions = [sprite_count.SpriteCountAction(len(actions))] + actions for idx, action in enumerate(actions): num = start_sprite_num + idx action.prepare_output(num) # Processing finished, print some statistics action0.print_stats() actionF.print_stats() action7.print_stats() action2.print_stats() action6.print_stats() grf.print_stats() global_constants.print_stats() action4.print_stats() action11.print_stats() generic.print_progress("Writing output ...") md5 = None for outputfile in outputfiles: if isinstance(outputfile, output_grf.OutputGRF): outputfile.open() for action in actions: action.write(outputfile) outputfile.close() md5 = outputfile.get_md5() if isinstance(outputfile, output_nfo.OutputNFO): outputfile.open() for action in actions: action.write(outputfile) outputfile.close() if md5 is not None and md5_filename is not None: with open(md5_filename, "w", encoding="utf-8") as f: f.write(md5 + "\n") if encoder is not None: encoder.close() generic.clear_progress() return 0 def run(): try: main(sys.argv[1:]) except generic.ScriptError as ex: generic.print_error(str(ex)) if developmode: raise # Reraise exception in developmode sys.exit(1) except SystemExit: raise except KeyboardInterrupt: generic.print_error("Application forcibly terminated by user.") if developmode: raise # Reraise exception in developmode sys.exit(1) except Exception as ex: # Other/internal error. if developmode: raise # Reraise exception in developmode # User mode: print user friendly error message. ex_msg = str(ex) if len(ex_msg) > 0: ex_msg = '"{}"'.format(ex_msg) traceback = sys.exc_info()[2] # Walk through the traceback object until we get to the point where the exception happened. while traceback.tb_next is not None: traceback = traceback.tb_next lineno = traceback.tb_lineno frame = traceback.tb_frame code = frame.f_code filename = code.co_filename name = code.co_name del traceback # Required according to Python docs. ex_data = { "class": ex.__class__.__name__, "version": version, "msg": ex_msg, "cli": sys.argv, "loc": 'File "{}", line {:d}, in {}'.format(filename, lineno, name), } msg = ( "nmlc: An internal error has occurred:\n" "nmlc-version: {version}\n" "Error: ({class}) {msg}.\n" "Command: {cli}\n" "Location: {loc}\n".format(**ex_data) ) generic.print_error(msg) sys.exit(1) sys.exit(0) if __name__ == "__main__": run() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/nmlop.py0000644000175100001660000003013714754345605014223 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import operator from nml import generic from .expression import binop from .expression.base_expression import ConstantFloat, ConstantNumeric, Type class Operator: """ Operator in an expression. @ivar act2_supports: Whether the operator is supported by action 2. @type act2_supports: C{bool} @ivar act2_str: NFO text representation of the operator in action 2. @type act2_str: C{str} or C{None} @ivar act2_num: Numeric value of the operator in action 2. @type act2_num: C{int} or C{None} @ivar actd_supports: Whether the operator is supported by action D. @type actd_supports: C{bool} @ivar actd_str: NFO text representation of the operator in action D. @type actd_str: C{str} or C{None} @ivar actd_num: Numeric value of the operator in action D. @type actd_num: C{int} or C{None} @ivar commutative: Whether swapping the operands will always maintain the same result. @type commutative: C{Bool} @ivar returns_boolean: Whether the operator gives a boolean result. @type returns_boolean: C{bool} @ivar token: Infix text to use for the string representation of the operator. @type token: C{None} or C{str} @ivar prefix_text: Prefix text to use for the string representation of the operator, if available. @type prefix_text: C{None} or C{str} """ def __init__( self, act2_supports=False, act2_str=None, act2_num=None, actd_supports=False, actd_str=None, actd_num=None, returns_boolean=False, commutative=False, token=None, prefix_text=None, compiletime_func=None, validate_func=None, ): self.act2_supports = act2_supports self.act2_str = act2_str self.act2_num = act2_num self.actd_supports = actd_supports self.actd_str = actd_str self.actd_num = actd_num self.commutative = commutative self.returns_boolean = returns_boolean self.token = token self.prefix_text = prefix_text self.compiletime_func = compiletime_func self.validate_func = validate_func def to_string(self, expr1, expr2): """ Convert expression to readable string form. @param expr1: Left child expression text. @type expr1: C{str} @param expr2: Right child expression text. @type expr2: C{str} @return: Text representation of the operator with its child expressions. @rtype: C{str} """ if self.prefix_text is not None: return "{}({}, {})".format(self.prefix_text, expr1, expr2) else: # Infix notation. return "({} {} {})".format(expr1, self.token, expr2) def __call__(self, expr1, expr2, pos=None): return binop.BinOp(self, expr1, expr2, pos) def unsigned_rshift(a, b): if a < 0: a += 0x100000000 return generic.truncate_int32(a >> b) def unsigned_rrotate(a, b): if a < 0: a += 0x100000000 return generic.truncate_int32((a >> b) | (a << (32 - b))) def validate_func_int(expr1, expr2, pos): if expr1.type() != Type.INTEGER or expr2.type() != Type.INTEGER: if expr1.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr1.name, expr1.pos) if expr2.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr2.name, expr2.pos) raise generic.ScriptError("Binary operator requires both operands to be integers.", pos) def validate_func_float(expr1, expr2, pos): if expr1.type() not in (Type.INTEGER, Type.FLOAT) or expr2.type() not in (Type.INTEGER, Type.FLOAT): if expr1.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr1.name, expr1.pos) if expr2.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr2.name, expr2.pos) raise generic.ScriptError("Binary operator requires both operands to be integers or floats.", pos) # If one is a float, the other must be constant since we can't handle floats at runtime if (expr1.type() == Type.FLOAT and not isinstance(expr2, (ConstantNumeric, ConstantFloat))) or ( expr2.type() == Type.FLOAT and not isinstance(expr1, (ConstantNumeric, ConstantFloat)) ): raise generic.ScriptError( "Floating-point operations are only possible when both operands are compile-time constants.", pos ) def validate_func_add(expr1, expr2, pos): if (expr1.type() == Type.STRING_LITERAL) ^ (expr2.type() == Type.STRING_LITERAL): raise generic.ScriptError("Concatenating a string literal and a number is not possible.", pos) if expr1.type() != Type.STRING_LITERAL: validate_func_float(expr1, expr2, pos) def validate_func_div_mod(expr1, expr2, pos): validate_func_float(expr1, expr2, pos) if isinstance(expr2, (ConstantNumeric, ConstantFloat)) and expr2.value == 0: raise generic.ScriptError("Division and modulo require the right hand side to be nonzero.", pos) def validate_func_rhs_positive(expr1, expr2, pos): validate_func_int(expr1, expr2, pos) if isinstance(expr2, ConstantNumeric) and expr2.value < 0: raise generic.ScriptError("Right hand side of the operator may not be a negative number.", pos) ADD = Operator( act2_supports=True, act2_str=r"\2+", act2_num=0, actd_supports=True, actd_str=r"\D+", actd_num=1, commutative=True, token="+", compiletime_func=operator.add, validate_func=validate_func_add, ) SUB = Operator( act2_supports=True, act2_str=r"\2-", act2_num=1, actd_supports=True, actd_str=r"\D-", actd_num=2, token="-", compiletime_func=operator.sub, validate_func=validate_func_float, ) DIV = Operator( act2_supports=True, act2_str=r"\2/", act2_num=6, actd_supports=True, actd_str=r"\D/", actd_num=10, token="/", compiletime_func=lambda a, b: a // b if isinstance(a, int) and isinstance(b, int) else a / b, validate_func=validate_func_div_mod, ) MOD = Operator( act2_supports=True, act2_str=r"\2%", act2_num=7, actd_supports=True, actd_str=r"\D%", actd_num=12, token="%", compiletime_func=operator.mod, validate_func=validate_func_div_mod, ) MUL = Operator( act2_supports=True, act2_str=r"\2*", act2_num=10, actd_supports=True, actd_str=r"\D*", actd_num=4, commutative=True, token="*", compiletime_func=operator.mul, validate_func=validate_func_float, ) AND = Operator( act2_supports=True, act2_str=r"\2&", act2_num=11, actd_supports=True, actd_str=r"\D&", actd_num=7, commutative=True, token="&", compiletime_func=operator.and_, validate_func=validate_func_int, ) OR = Operator( act2_supports=True, act2_str=r"\2|", act2_num=12, actd_supports=True, actd_str=r"\D|", actd_num=8, commutative=True, token="|", compiletime_func=operator.or_, validate_func=validate_func_int, ) XOR = Operator( act2_supports=True, act2_str=r"\2^", act2_num=13, actd_supports=True, commutative=True, token="^", compiletime_func=operator.xor, validate_func=validate_func_int, ) CMP_EQ = Operator( act2_supports=True, actd_supports=True, commutative=True, returns_boolean=True, token="==", compiletime_func=operator.eq, validate_func=validate_func_float, ) CMP_NEQ = Operator( act2_supports=True, actd_supports=True, commutative=True, returns_boolean=True, token="!=", compiletime_func=operator.ne, validate_func=validate_func_float, ) CMP_LE = Operator( act2_supports=True, actd_supports=True, returns_boolean=True, token="<=", compiletime_func=operator.le, validate_func=validate_func_float, ) CMP_GE = Operator( act2_supports=True, actd_supports=True, returns_boolean=True, token=">=", compiletime_func=operator.ge, validate_func=validate_func_float, ) CMP_LT = Operator( act2_supports=True, actd_supports=True, returns_boolean=True, token="<", compiletime_func=operator.lt, validate_func=validate_func_float, ) CMP_GT = Operator( act2_supports=True, actd_supports=True, returns_boolean=True, token=">", compiletime_func=operator.gt, validate_func=validate_func_float, ) MIN = Operator( act2_supports=True, act2_str=r"\2<", act2_num=2, actd_supports=True, commutative=True, compiletime_func=min, prefix_text="min", validate_func=validate_func_float, ) MAX = Operator( act2_supports=True, act2_str=r"\2>", act2_num=3, actd_supports=True, commutative=True, compiletime_func=max, prefix_text="max", validate_func=validate_func_float, ) STO_TMP = Operator( act2_supports=True, act2_str=r"\2sto", act2_num=14, prefix_text="STORE_TEMP", validate_func=validate_func_rhs_positive, ) STO_PERM = Operator( act2_supports=True, act2_str=r"\2psto", act2_num=16, prefix_text="STORE_PERM", validate_func=validate_func_rhs_positive, ) SHIFT_LEFT = Operator( act2_supports=True, act2_str=r"\2<<", act2_num=20, actd_supports=True, actd_str=r"\D<<", actd_num=6, token="<<", compiletime_func=operator.lshift, validate_func=validate_func_rhs_positive, ) SHIFT_RIGHT = Operator( act2_supports=True, act2_str=r"\2>>", act2_num=22, actd_supports=True, token=">>", compiletime_func=operator.rshift, validate_func=validate_func_rhs_positive, ) SHIFTU_RIGHT = Operator( act2_supports=True, act2_str=r"\2u>>", act2_num=21, actd_supports=True, token=">>>", compiletime_func=unsigned_rshift, validate_func=validate_func_rhs_positive, ) HASBIT = Operator( act2_supports=True, actd_supports=True, returns_boolean=True, prefix_text="hasbit", compiletime_func=lambda a, b: (a & (1 << b)) != 0, validate_func=validate_func_rhs_positive, ) # A few operators that are generated internally but can't be directly written in nml NOTHASBIT = Operator( act2_supports=True, actd_supports=True, returns_boolean=True, prefix_text="!hasbit", compiletime_func=lambda a, b: (a & (1 << b)) == 0, ) VAL2 = Operator( act2_supports=True, act2_str=r"\2r", act2_num=15, compiletime_func=lambda a, b: b, ) ASSIGN = Operator( actd_supports=True, actd_str=r"\D=", actd_num=0, ) SHIFTU_LEFT = Operator( actd_supports=True, actd_str=r"\Du<<", actd_num=5, token="<<", ) VACT2_CMP = Operator( act2_supports=True, act2_str=r"\2cmp", act2_num=18, prefix_text="CMP", ) VACT2_UCMP = Operator( act2_supports=True, act2_str=r"\2ucmp", act2_num=19, prefix_text="UCMP", ) MINU = Operator( act2_supports=True, act2_str=r"\2u<", act2_num=4, ) ROT_RIGHT = Operator( act2_supports=True, act2_str=r"\2ror", act2_num=17, prefix_text="rotate", compiletime_func=unsigned_rrotate, validate_func=validate_func_int, ) DIVU = Operator( act2_supports=True, act2_str=r"\2u/", act2_num=8, actd_supports=True, actd_str=r"\Du/", actd_num=9, ) class GRMOperator: def __init__(self, op_str, op_num): self.op_str = op_str self.op_num = op_num self.value = op_num def __str__(self): return self.op_str def write(self, file, size): assert size == 1 file.print_bytex(self.op_num, self.op_str) GRM_RESERVE = GRMOperator(r"\DR", 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/output_base.py0000644000175100001660000002343614754345605015434 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Abstract base classes that implements common functionality for output classes """ import array import io class OutputBase: """ Base class for output to a data file. The file is opened with L{open}. Once that is done, data can be written using the L{file} data member. When finished writing, the file should be closed with L{close}. Derived classes should implement L{open_file} to perform the actual opening of the file. L{assemble_file} is called to actually compose the output from possible multiple sources. @ivar filename: Name of the data file. @type filename: C{str} @ivar file: Memory output file handle, if opened. @type file: C{file} or C{None} """ def __init__(self, filename): self.filename = filename self.file = None def open(self): """ Open the output file. Data gets stored in-memory. """ raise NotImplementedError("Implement me in {}".format(type(self))) def open_file(self): """ Construct the file handle of the disk output file. @return: File handle of the opened file. @rtype: C{file} """ raise NotImplementedError("Implement me in {}".format(type(self))) def assemble_file(self, real_file): """ File is about to be closed, last chance to append data. @param real_file: Actual output stream. @type real_file: C{io.IOBase} """ real_file.write(self.file.getvalue()) def discard(self): """ Close the memory file, without writing to disk. """ if isinstance(self.file, io.IOBase): self.file.close() self.file = None def close(self): """ Close the memory file, copy collected output to the real file. """ with self.open_file() as real_file: self.assemble_file(real_file) self.discard() def skip_sprite_checks(self): """ Return whether sprites need detailed parsing. """ return False def __enter__(self): """ Allow `OutputBase` and subclasses to be used as context managers. """ self.open() return self def __exit__(self, type, value, traceback): """ Allow `OutputBase` and subclasses to be used as context managers. """ self.close() class SpriteOutputBase(OutputBase): """ Base class for output to a sprite file. @ivar in_sprite: Set to true if we are currently outputting a sprite. Outputting anything when not in a sprite causes an assert. @type in_sprite: C{bool} @ivar byte_count: Number of bytes written in the current sprite. @type byte_count: C{int} @ivar expected_count: Number of bytes expected in the current sprite. @type expected_count: C{int} """ def __init__(self, filename): OutputBase.__init__(self, filename) self.in_sprite = False self.expected_count = 0 self.byte_count = 0 def close(self): assert not self.in_sprite OutputBase.close(self) def prepare_byte(self, value): """ Normalize the provided value to an unsigned byte value. @param value: Value to normalize. @type value: C{int} @return: Normalized value (0..255). @rtype: C{int} @precond: Must be outputting a sprite. """ assert self.in_sprite if -0x80 <= value < 0: value += 0x100 assert value >= 0 and value <= 0xFF self.byte_count += 1 return value def prepare_word(self, value): """ Normalize the provided value to an unsigned word value. @param value: Value to normalize. @type value: C{int} @return: Normalized value (0..65535). @rtype: C{int} @precond: Must be outputting a sprite. """ assert self.in_sprite if -0x8000 <= value < 0: value += 0x10000 assert value >= 0 and value <= 0xFFFF self.byte_count += 2 return value def prepare_dword(self, value): """ Normalize the provided value to an unsigned double word value. @param value: Value to normalize. @type value: C{int} @return: Normalized value (0..0xFFFFFFFF). @rtype: C{int} @precond: Must be outputting a sprite. """ assert self.in_sprite if -0x80000000 <= value < 0: value += 0x100000000 assert value >= 0 and value <= 0xFFFFFFFF self.byte_count += 4 return value def print_varx(self, value, size): """ Print a variable sized value. @param value: Value to output. @type value: C{int} @param size: Size of the output (1..4), 3 means extended byte. @type size: C{int} """ if size == 1: self.print_bytex(value) elif size == 2: self.print_wordx(value) elif size == 3: self.print_bytex(0xFF) self.print_wordx(value) elif size == 4: self.print_dwordx(value) else: raise AssertionError() def print_bytex(self, byte, pretty_print=None): """ Output an unsigned byte. @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_bytex() in {}".format(type(self))) def print_wordx(self, byte): """ Output an unsigned word (2 bytes). @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_wordx() in {}".format(type(self))) def print_dwordx(self, byte): """ Output an unsigned double word (4 bytes). @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_dwordx() in {}".format(type(self))) def newline(self, msg="", prefix="\t"): """ Output a line separator, prefixed with L{prefix}, C{"// "}, and the L{msg}, if the latter is not empty. @param msg: Optional message to output first. @type msg: C{str} @param prefix: Additional white space in front of the comment. @type prefix: C{str} """ raise NotImplementedError("Implement newline() in {}".format(type(self))) def comment(self, msg): """ Output a textual comment at a line by itself. @param msg: Comment message. @type msg: C{str} @note: Only use if no bytes have been written to the current line. """ raise NotImplementedError("Implement comment() in {}".format(type(self))) def start_sprite(self, expected_size, is_real_sprite=False): """ Note to the output stream that a sprite is about to be written. @param expected_size: Expected size of the sprite data. @type expected_size: C{int} @param is_real_sprite: Self-explanatory. @type is_real_sprite: C{bool} """ del is_real_sprite # unused in base impl. assert not self.in_sprite self.in_sprite = True self.expected_count = expected_size self.byte_count = 0 def end_sprite(self): """ Note to the output stream that a sprite has been written. The number of bytes denoted as expected size with the L{start_sprite} call, should have been written. """ assert self.in_sprite self.in_sprite = False self.newline() assert self.expected_count == self.byte_count, "Expected {:d} bytes to be written to sprite, got {:d}".format( self.expected_count, self.byte_count ) class TextOutputBase(OutputBase): """ Base class for textual output. """ def __init__(self, filename): OutputBase.__init__(self, filename) def open(self): self.file = io.StringIO() class BinaryOutputBase(SpriteOutputBase): """ Class for binary output. """ def __init__(self, filename): SpriteOutputBase.__init__(self, filename) def open(self): self.file = array.array("B") def newline(self, msg="", prefix="\t"): pass def print_data(self, data): """ Print a chunck of data in one go @param data: Data to output @type data: C{array} """ self.byte_count += len(data) self.file.extend(data) def wb(self, byte): self.file.append(byte) def print_byte(self, value): value = self.prepare_byte(value) self.wb(value) def print_bytex(self, value, pretty_print=None): self.print_byte(value) def print_word(self, value): value = self.prepare_word(value) self.wb(value & 0xFF) self.wb(value >> 8) def print_wordx(self, value): self.print_word(value) def print_dword(self, value): value = self.prepare_dword(value) self.wb(value & 0xFF) self.wb((value >> 8) & 0xFF) self.wb((value >> 16) & 0xFF) self.wb(value >> 24) def print_dwordx(self, value): self.print_dword(value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/output_dep.py0000644000175100001660000000232114754345605015260 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # -*- coding: utf-8 -*- from nml import output_base class OutputDEP(output_base.TextOutputBase): """ Class for output to a dependency file in makefile format. """ def __init__(self, filename, grf_filename): output_base.TextOutputBase.__init__(self, filename) self.grf_filename = grf_filename def open_file(self): return open(self.filename, "w", encoding="utf-8") def write(self, text): self.file.write(self.grf_filename + ": " + text + "\n") def skip_sprite_checks(self): return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/output_grf.py0000644000175100001660000001664314754345605015302 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import hashlib import os from nml import generic, grfstrings, output_base class OutputGRF(output_base.BinaryOutputBase): def __init__(self, filename): output_base.BinaryOutputBase.__init__(self, filename) self.encoder = None self.sprite_output = output_base.BinaryOutputBase(filename + ".sprite.tmp") self.md5 = hashlib.md5() # sprite_num is deliberately off-by-one because it is used as an # id between data and sprite section. For the sprite section an id # of 0 is invalid (means end of sprites), and for a non-NewGRF GRF # the first sprite is a real sprite. self.sprite_num = 1 def open_file(self): # Remove / unlink the file, most useful for linux systems # See also issue #4165 # If the file happens to be in use or non-existant, ignore try: os.unlink(self.filename) except OSError: # Ignore pass return open(self.filename, "wb") def get_md5(self): return self.md5.hexdigest() def assemble_file(self, real_file): # add end-of-chunks self.in_sprite = True self.print_dword(0) self.in_sprite = False self.sprite_output.in_sprite = True self.sprite_output.print_dword(0) self.sprite_output.in_sprite = False # add header header = bytearray([0x00, 0x00, ord("G"), ord("R"), ord("F"), 0x82, 0x0D, 0x0A, 0x1A, 0x0A]) size = len(self.file) + 1 header.append(size & 0xFF) header.append((size >> 8) & 0xFF) header.append((size >> 16) & 0xFF) header.append(size >> 24) header.append(0) # no compression header_str = bytes(header) real_file.write(header_str) self.md5.update(header_str) # add data section, and then the sprite section real_file.write(self.file) self.md5.update(self.file) real_file.write(self.sprite_output.file) def open(self): output_base.BinaryOutputBase.open(self) self.sprite_output.open() def close(self): output_base.BinaryOutputBase.close(self) self.sprite_output.discard() def _print_utf8(self, char, stream): for c in chr(char).encode("utf8"): stream.print_byte(c) def print_string(self, value, final_zero=True, force_ascii=False, stream=None): if stream is None: stream = self if not grfstrings.is_ascii_string(value): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") stream.print_byte(0xC3) stream.print_byte(0x9E) i = 0 while i < len(value): if value[i] == "\\": if value[i + 1] in ("\\", '"'): stream.print_byte(ord(value[i + 1])) i += 2 elif value[i + 1] == "U": self._print_utf8(int(value[i + 2 : i + 6], 16), stream) i += 6 else: stream.print_byte(int(value[i + 1 : i + 3], 16)) i += 3 else: self._print_utf8(ord(value[i]), stream) i += 1 if final_zero: stream.print_byte(0) def comment(self, msg): pass def start_sprite(self, size, is_real_sprite=False): if is_real_sprite: # Real sprite, this means no data is written to the data section # This call is still needed to open 'output mode' assert size == 0 output_base.BinaryOutputBase.start_sprite(self, 9) self.print_dword(4) self.print_byte(0xFD) self.print_dword(self.sprite_num) else: output_base.BinaryOutputBase.start_sprite(self, size + 5) self.print_dword(size) self.print_byte(0xFF) def print_sprite(self, sprite_list): """ @param sprite_list: List of non-empty real sprites for various bit depths / zoom levels @type sprite_list: C{list} of L{RealSprite} """ self.start_sprite(0, True) for sprite in sprite_list: self.print_single_sprite(sprite) self.end_sprite() def print_single_sprite(self, sprite_info): assert sprite_info.file is not None or sprite_info.mask_file is not None # Position for warning messages pos_warning = None if sprite_info.mask_file is not None: pos_warning = sprite_info.mask_file.pos elif sprite_info.file is not None: pos_warning = sprite_info.file.pos size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, warnings = self.encoder.get( sprite_info ) for w in warnings: generic.print_warning(generic.Warning.GENERIC, w, pos_warning) self.sprite_output.start_sprite(len(compressed_data) + 18) self.wsprite_header(size_x, size_y, len(compressed_data), xoffset, yoffset, info_byte, sprite_info.zoom_level) self.sprite_output.print_data(compressed_data) self.sprite_output.end_sprite() def print_empty_realsprite(self): self.start_sprite(1) self.print_byte(0) self.end_sprite() def wsprite_header(self, size_x, size_y, size, xoffset, yoffset, info, zoom_level): self.sprite_output.print_dword(self.sprite_num) self.sprite_output.print_dword(size + 10) self.sprite_output.print_byte(info) self.sprite_output.print_byte(zoom_level) self.sprite_output.print_word(size_y) self.sprite_output.print_word(size_x) self.sprite_output.print_word(xoffset) self.sprite_output.print_word(yoffset) def print_named_filedata(self, filename): name = os.path.split(filename)[1] size = os.path.getsize(filename) self.start_sprite(0, True) self.sprite_output.start_sprite(8 + 3 + len(name) + 1 + size) self.sprite_output.print_dword(self.sprite_num) self.sprite_output.print_dword(3 + len(name) + 1 + size) self.sprite_output.print_byte(0xFF) self.sprite_output.print_byte(0xFF) self.sprite_output.print_byte(len(name)) self.print_string( name, force_ascii=True, final_zero=True, stream=self.sprite_output ) # ASCII filenames seems sufficient. with open(generic.find_file(filename), "rb") as file: while True: data = file.read(1024) if len(data) == 0: break for d in data: self.sprite_output.print_byte(d) self.sprite_output.end_sprite() self.end_sprite() def end_sprite(self): output_base.BinaryOutputBase.end_sprite(self) self.sprite_num += 1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/output_nfo.py0000644000175100001660000001420714754345605015300 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # -*- coding: utf-8 -*- import io from nml import generic, grfstrings, output_base from nml.actions import real_sprite zoom_levels = { 0: "normal", 1: "zi4", 2: "zi2", 3: "zo2", 4: "zo4", 5: "zo8", } bit_depths = { 8: "8bpp", 32: "32bpp", } class OutputNFO(output_base.SpriteOutputBase): def __init__(self, filename, start_sprite_num): output_base.SpriteOutputBase.__init__(self, filename) self.sprite_num = start_sprite_num def open(self): self.file = io.StringIO() def open_file(self): handle = open(self.filename, "w", encoding="utf-8") handle.write( "// Automatically generated by GRFCODEC. Do not modify!\n" "// (Info version 32)\n" "// Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^" " 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>>\n" "// Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C\n" "// Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D%\n" "// Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags\n\n" ) return handle def assemble_file(self, real_file): # All print functions add a space in case there's something written after so remove trailing whitespaces real_file.write(self.file.getvalue().replace(" \n", "\n")) def print_byte(self, value): value = self.prepare_byte(value) self.file.write("\\b" + str(value) + " ") def print_bytex(self, value, pretty_print=None): value = self.prepare_byte(value) if pretty_print is not None: self.file.write(pretty_print + " ") return self.file.write("{:02X} ".format(value)) def print_word(self, value): value = self.prepare_word(value) self.file.write("\\w{:d} ".format(value)) def print_wordx(self, value): value = self.prepare_word(value) self.file.write("\\wx{:04X} ".format(value)) def print_dword(self, value): value = self.prepare_dword(value) self.file.write("\\d{:d} ".format(value)) def print_dwordx(self, value): value = self.prepare_dword(value) self.file.write("\\dx{:08X} ".format(value)) def print_string(self, value, final_zero=True, force_ascii=False): assert self.in_sprite self.file.write('"') if not grfstrings.is_ascii_string(value): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") self.file.write("Þ") # b'\xC3\x9E'.decode('utf-8') self.file.write(value.replace('"', '\\"')) self.byte_count += grfstrings.get_string_size(value, final_zero, force_ascii) self.file.write('" ') if final_zero: self.print_bytex(0) # get_string_size already includes the final 0 byte # but print_bytex also increases byte_count, so decrease # it here by one to correct it. self.byte_count -= 1 def print_decimal(self, value): assert self.in_sprite self.file.write(str(value) + " ") def newline(self, msg="", prefix="\t"): if msg != "": msg = prefix + "// " + msg self.file.write(msg + "\n") def comment(self, msg): self.file.write("// " + msg + "\n") def start_sprite(self, size, is_real_sprite=False): output_base.SpriteOutputBase.start_sprite(self, size, is_real_sprite) self.print_decimal(self.sprite_num) self.sprite_num += 1 if not is_real_sprite: self.file.write("* ") self.print_decimal(size) def print_sprite(self, sprite_list): """ @param sprite_list: List of non-empty real sprites for various bit depths / zoom levels @type sprite_list: C{list} of L{RealSprite} """ self.start_sprite(0, True) for i, sprite_info in enumerate(sprite_list): self.file.write(sprite_info.file.value + " ") self.file.write(bit_depths[sprite_info.bit_depth] + " ") self.print_decimal(sprite_info.xpos.value) self.print_decimal(sprite_info.ypos.value) self.print_decimal(sprite_info.xsize.value) self.print_decimal(sprite_info.ysize.value) self.print_decimal(sprite_info.xrel.value) self.print_decimal(sprite_info.yrel.value) self.file.write(zoom_levels[sprite_info.zoom_level] + " ") if (sprite_info.flags.value & real_sprite.FLAG_NOCROP) != 0: self.file.write("nocrop ") if sprite_info.mask_file is not None: self.newline() self.file.write("|\t") self.file.write(sprite_info.mask_file.value) self.file.write(" mask ") mask_x, mask_y = ( sprite_info.mask_pos if sprite_info.mask_pos is not None else (sprite_info.xpos, sprite_info.ypos) ) self.print_decimal(mask_x.value) self.print_decimal(mask_y.value) if i + 1 < len(sprite_list): self.newline() self.file.write("|\t") self.end_sprite() def print_empty_realsprite(self): self.start_sprite(1) self.print_bytex(0) self.end_sprite() def print_named_filedata(self, filename): self.start_sprite(0, True) self.file.write("** " + filename) self.end_sprite() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/output_nml.py0000644000175100001660000000207714754345605015306 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import output_base class OutputNML(output_base.TextOutputBase): """ Class for outputting NML. """ def __init__(self, filename): output_base.TextOutputBase.__init__(self, filename) def open_file(self): return open(self.filename, "w", encoding="utf-8") def write(self, text): self.file.write(text) def newline(self): self.file.write("\n") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/palette.py0000644000175100001660000005127214754345605014537 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic # fmt: off raw_palette_data = [ # DOS palette [ 0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 255, 255, 255 ], # end of DOS palette # Windows palette [ 0, 0, 255, 238, 0, 238, 239, 0, 239, 240, 0, 240, 241, 0, 241, 242, 0, 242, 243, 0, 243, 244, 0, 244, 245, 0, 245, 246, 0, 246, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 100, 100, 100, 116, 116, 116, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 132, 132, 132, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 64, 64, 64, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 80, 80, 80, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 48, 48, 48, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 16, 16, 16, 32, 32, 32, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 148, 148, 148, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 255, 255, 255 ], # end of Windows palette # DOS Toyland palette [ 0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 28, 108, 124, 32, 112, 128, 36, 116, 132, 40, 120, 136, 44, 124, 140, 92, 164, 184, 116, 180, 196, 216, 244, 252, 112, 176, 192, 88, 160, 180, 255, 255, 255 ], # end of DOS Toyland palette # Windows Toyland palette [ 0, 255, 255, 238, 0, 238, 239, 0, 239, 240, 0, 240, 241, 0, 241, 242, 0, 242, 243, 0, 243, 244, 0, 244, 245, 0, 245, 246, 0, 246, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 100, 100, 100, 116, 116, 116, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 132, 132, 132, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 64, 64, 64, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 80, 80, 80, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 48, 48, 48, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 16, 16, 16, 32, 32, 32, 28, 108, 124, 32, 112, 128, 36, 116, 132, 40, 120, 136, 44, 124, 140, 92, 164, 184, 116, 180, 196, 216, 244, 252, 112, 176, 192, 88, 160, 180, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 148, 148, 148, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 255, 255, 255 ], # end of Windows Toyland palette ] # fmt: on # Convert palettes to strings for fast comparison. palette_data = [bytes(pal) for pal in raw_palette_data] palette_name = ["DEFAULT", "LEGACY", "DEFAULT_TOYLAND", "LEGACY_TOYLAND"] def validate_palette(image, filename): palette = image.palette.palette if len(palette) != 768: raise generic.ImageError("Invalid palette; does not contain 256 entries.", filename) for i, pal in enumerate(palette_data): if pal != palette: continue return palette_name[i] raise generic.ImageError("Palette is not recognized as a valid palette.", filename) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/parser.py0000644000175100001660000007147114754345605014400 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import ply.yacc as yacc from nml import expression, generic, nmlop, tokens, unit from nml.actions import actionD, real_sprite from nml.ast import ( alt_sprites, assignment, base_graphics, basecost, cargotable, conditional, constant, deactivate, disable_item, error, font, general, grf, item, loop, override, produce, replace, skipall, snowline, sort_vehicles, spriteblock, switch, tilelayout, townnames, tracktypetable, ) class NMLParser: """ @ivar lexer: Scanner providing tokens. @type lexer: L{NMLLexer} @ivar tokens: Tokens of the scanner (used by PLY). @type tokens: C{List} of C{str} @ivar parser: PLY parser. @type parser: L{ply.yacc} """ def __init__(self, rebuild=False, debug=False): if debug: try: import os os.remove(os.path.normpath(os.path.join(os.path.dirname(__file__), "generated", "parsetab.py"))) except FileNotFoundError: # Tried to remove a non existing file pass self.lexer = tokens.NMLLexer() self.lexer.build(rebuild or debug) self.tokens = self.lexer.tokens self.parser = yacc.yacc( module=self, debug=debug, optimize=not (rebuild or debug), write_tables=not debug, tabmodule="nml.generated.parsetab", ) def parse(self, text, input_filename): self.lexer.setup(text, input_filename) return self.parser.parse(None, lexer=self.lexer.lexer) # operator precedence (lower in the list = higher priority) precedence = ( ("left", "COMMA"), ("right", "TERNARY_OPEN", "COLON"), ("left", "LOGICAL_OR"), ("left", "LOGICAL_AND"), ("left", "OR"), ("left", "XOR"), ("left", "AND"), ("left", "COMP_EQ", "COMP_NEQ", "COMP_LE", "COMP_GE", "COMP_LT", "COMP_GT"), ("left", "SHIFT_LEFT", "SHIFT_RIGHT", "SHIFTU_RIGHT"), ("left", "PLUS", "MINUS"), ("left", "TIMES", "DIVIDE", "MODULO"), ("left", "LOGICAL_NOT", "BINARY_NOT"), ) def p_error(self, t): if t is None: raise generic.ScriptError("Syntax error, unexpected end-of-file") else: raise generic.ScriptError('Syntax error, unexpected token "{}"'.format(t.value), t.lineno) # # Main script blocks # def p_main_script(self, t): "main_script : script" t[0] = general.MainScript(t[1]) def p_script(self, t): """script : | script main_block""" if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_main_block(self, t): """main_block : switch | random_switch | produce | spriteset | spritegroup | spritelayout | template_declaration | tilelayout | town_names | cargotable | railtype | roadtype | tramtype | grf_block | param_assignment | skip_all | conditional | loop | item | property_block | graphics_block | liveryoverride_block | error_block | disable_item | deactivate | replace | replace_new | base_graphics | font_glyph | alt_sprites | snowline | engine_override | sort_vehicles | basecost | constant""" t[0] = t[1] # # Expressions # def p_expression(self, t): """expression : NUMBER | FLOAT | param | variable | ID | STRING_LITERAL | string""" t[0] = t[1] def p_parenthesed_expression(self, t): "expression : LPAREN expression RPAREN" t[0] = t[2] def p_parameter(self, t): "param : PARAMETER LBRACKET expression RBRACKET" t[0] = expression.Parameter(t[3], t.lineno(1), True) def p_parameter_other_grf(self, t): "param : PARAMETER LBRACKET expression COMMA expression RBRACKET" t[0] = expression.OtherGRFParameter(t[3], t[5], t.lineno(1)) code_to_op = { "+": nmlop.ADD, "-": nmlop.SUB, "*": nmlop.MUL, "/": nmlop.DIV, "%": nmlop.MOD, "&": nmlop.AND, "|": nmlop.OR, "^": nmlop.XOR, "&&": nmlop.AND, "||": nmlop.OR, "==": nmlop.CMP_EQ, "!=": nmlop.CMP_NEQ, "<=": nmlop.CMP_LE, ">=": nmlop.CMP_GE, "<": nmlop.CMP_LT, ">": nmlop.CMP_GT, "<<": nmlop.SHIFT_LEFT, ">>": nmlop.SHIFT_RIGHT, ">>>": nmlop.SHIFTU_RIGHT, } def p_binop(self, t): """expression : expression PLUS expression | expression MINUS expression | expression TIMES expression | expression DIVIDE expression | expression MODULO expression | expression AND expression | expression OR expression | expression XOR expression | expression SHIFT_LEFT expression | expression SHIFT_RIGHT expression | expression SHIFTU_RIGHT expression | expression COMP_EQ expression | expression COMP_NEQ expression | expression COMP_LE expression | expression COMP_GE expression | expression COMP_LT expression | expression COMP_GT expression""" t[0] = expression.BinOp(self.code_to_op[t[2]], t[1], t[3], t[1].pos) def p_binop_logical(self, t): """expression : expression LOGICAL_AND expression | expression LOGICAL_OR expression""" t[0] = expression.BinOp(self.code_to_op[t[2]], expression.Boolean(t[1]), expression.Boolean(t[3]), t[1].pos) def p_logical_not(self, t): "expression : LOGICAL_NOT expression" t[0] = expression.Not(expression.Boolean(t[2]), t.lineno(1)) def p_binary_not(self, t): "expression : BINARY_NOT expression" t[0] = expression.BinNot(t[2], t.lineno(1)) def p_ternary_op(self, t): "expression : expression TERNARY_OPEN expression COLON expression" t[0] = expression.TernaryOp(t[1], t[3], t[5], t[1].pos) def p_unary_minus(self, t): "expression : MINUS expression" t[0] = nmlop.SUB(0, t[2], t.lineno(1)) def p_variable(self, t): "variable : VARIABLE LBRACKET expression_list RBRACKET" t[0] = expression.Variable(*t[3]) t[0].pos = t.lineno(1) def p_function(self, t): "expression : ID LPAREN expression_list RPAREN" t[0] = expression.FunctionCall(t[1], t[3], t[1].pos) def p_array(self, t): "expression : LBRACKET expression_list RBRACKET" t[0] = expression.Array(t[2], t.lineno(1)) # # Commonly used non-terminals that are not expressions # def p_assignment_list(self, t): """assignment_list : assignment | param_desc | assignment_list assignment | assignment_list param_desc""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_assignment(self, t): "assignment : ID COLON expression SEMICOLON" t[0] = assignment.Assignment(t[1], t[3], t[1].pos) def p_param_desc(self, t): """param_desc : PARAMETER expression LBRACE setting_list RBRACE | PARAMETER LBRACE setting_list RBRACE""" if len(t) == 5: t[0] = grf.ParameterDescription(t[3]) else: t[0] = grf.ParameterDescription(t[4], t[2]) def p_setting_list(self, t): """setting_list : setting | setting_list setting""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_setting(self, t): "setting : ID LBRACE setting_value_list RBRACE" t[0] = grf.ParameterSetting(t[1], t[3]) def p_setting_value_list(self, t): """setting_value_list : setting_value | setting_value_list setting_value""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_setting_value(self, t): "setting_value : assignment" t[0] = t[1] def p_names_setting_value(self, t): "setting_value : ID COLON LBRACE name_string_list RBRACE SEMICOLON" t[0] = assignment.Assignment(t[1], t[4], t[1].pos) def p_name_string_list(self, t): """name_string_list : name_string_item | name_string_list name_string_item""" if len(t) == 2: t[0] = expression.Array([t[1]], t[1].pos) else: t[0] = expression.Array(t[1].values + [t[2]], t[1].pos) def p_name_string_item(self, t): "name_string_item : expression COLON string SEMICOLON" t[0] = assignment.Assignment(t[1], t[3], t[1].pos) def p_string(self, t): "string : STRING LPAREN expression_list RPAREN" t[0] = expression.String(t[3], t.lineno(1)) def p_non_empty_expression_list(self, t): """non_empty_expression_list : expression | non_empty_expression_list COMMA expression""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_expression_list(self, t): """expression_list : | non_empty_expression_list | non_empty_expression_list COMMA""" t[0] = [] if len(t) == 1 else t[1] def p_non_empty_id_list(self, t): """non_empty_id_list : ID | non_empty_id_list COMMA ID""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_id_list(self, t): """id_list : | non_empty_id_list | non_empty_id_list COMMA""" t[0] = [] if len(t) == 1 else t[1] def p_generic_assignment(self, t): "generic_assignment : expression COLON expression SEMICOLON" t[0] = assignment.Assignment(t[1], t[3], t.lineno(1)) def p_generic_assignment_list(self, t): """generic_assignment_list : | generic_assignment_list generic_assignment""" t[0] = [] if len(t) == 1 else t[1] + [t[2]] def p_snowline_assignment(self, t): """snowline_assignment : expression COLON expression SEMICOLON | expression COLON expression UNIT SEMICOLON""" unit_value = None if len(t) == 5 else unit.get_unit(t[4]) t[0] = assignment.UnitAssignment(t[1], t[3], unit_value, t.lineno(1)) def p_snowline_assignment_list(self, t): """snowline_assignment_list : | snowline_assignment_list snowline_assignment""" t[0] = [] if len(t) == 1 else t[1] + [t[2]] # # Item blocks # def p_item(self, t): "item : ITEM LPAREN expression_list RPAREN LBRACE script RBRACE" t[0] = item.Item(t[3], t[6], t.lineno(1)) def p_property_block(self, t): "property_block : PROPERTY LBRACE property_list RBRACE" t[0] = item.PropertyBlock(t[3], t.lineno(1)) def p_property_list(self, t): """property_list : property_assignment | property_list property_assignment""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_property_assignment(self, t): """property_assignment : ID COLON expression SEMICOLON | ID COLON expression UNIT SEMICOLON | NUMBER COLON expression SEMICOLON | NUMBER COLON expression UNIT SEMICOLON""" name = t[1] unit_value = None if len(t) == 5 else unit.get_unit(t[4]) t[0] = item.Property(name, t[3], unit_value, t.lineno(1)) def p_graphics_block(self, t): "graphics_block : GRAPHICS LBRACE graphics_list RBRACE" t[0] = item.GraphicsBlock(t[3][0], t[3][1], t.lineno(1)) def p_liveryoverride_block(self, t): "liveryoverride_block : LIVERYOVERRIDE LPAREN expression RPAREN LBRACE graphics_list RBRACE" t[0] = item.LiveryOverride(t[3], item.GraphicsBlock(t[6][0], t[6][1], t.lineno(1)), t.lineno(1)) def p_graphics_list(self, t): """graphics_list : graphics_assignment_list | graphics_assignment_list switch_value | switch_value""" # Save graphics block as a tuple, we need to add position info later if len(t) == 2: if isinstance(t[1], list): t[0] = (t[1], None) else: t[0] = ([], t[1]) else: t[0] = (t[1], t[2]) def p_graphics_assignment(self, t): "graphics_assignment : expression COLON switch_value" t[0] = item.GraphicsDefinition(t[1], t[3]) def p_graphics_assignment_list(self, t): """graphics_assignment_list : graphics_assignment | graphics_assignment_list graphics_assignment""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] # # Program flow control (if/else/while) # def p_conditional(self, t): """conditional : if_else_parts | if_else_parts else_block""" parts = t[1] if len(t) > 2: parts.append(t[2]) t[0] = conditional.ConditionalList(parts) def p_else_block(self, t): "else_block : ELSE LBRACE script RBRACE" t[0] = conditional.Conditional(None, t[3], t.lineno(1)) def p_if_else_parts(self, t): """if_else_parts : IF LPAREN expression RPAREN LBRACE script RBRACE | if_else_parts ELSE IF LPAREN expression RPAREN LBRACE script RBRACE""" if len(t) == 8: t[0] = [conditional.Conditional(t[3], t[6], t.lineno(1))] else: t[0] = t[1] + [conditional.Conditional(t[5], t[8], t.lineno(2))] def p_loop(self, t): "loop : WHILE LPAREN expression RPAREN LBRACE script RBRACE" t[0] = loop.Loop(t[3], t[6], t.lineno(1)) # # (Random) Switch block # def p_switch(self, t): "switch : SWITCH LPAREN expression_list RPAREN LBRACE switch_body RBRACE" t[0] = switch.Switch(t[3], t[6], t.lineno(1)) def p_switch_body(self, t): """switch_body : switch_ranges switch_value | switch_ranges""" t[0] = switch.SwitchBody(t[1], t[2] if len(t) == 3 else None) def p_switch_ranges(self, t): """switch_ranges : | switch_ranges expression COLON switch_value | switch_ranges expression UNIT COLON switch_value | switch_ranges expression RANGE expression COLON switch_value | switch_ranges expression RANGE expression UNIT COLON switch_value""" if len(t) == 1: t[0] = [] elif len(t) == 5: t[0] = t[1] + [switch.SwitchRange(t[2], t[2], t[4])] elif len(t) == 6: t[0] = t[1] + [switch.SwitchRange(t[2], t[2], t[5], t[3])] elif len(t) == 7: t[0] = t[1] + [switch.SwitchRange(t[2], t[4], t[6])] else: t[0] = t[1] + [switch.SwitchRange(t[2], t[4], t[7], t[5])] def p_switch_value(self, t): """switch_value : RETURN expression SEMICOLON | RETURN SEMICOLON | expression SEMICOLON""" if len(t) == 4: t[0] = switch.SwitchValue(t[2], True, t[2].pos) elif t[1] == "return": t[0] = switch.SwitchValue(None, True, t.lineno(1)) else: t[0] = switch.SwitchValue(t[1], False, t[1].pos) def p_random_switch(self, t): "random_switch : RANDOMSWITCH LPAREN expression_list RPAREN LBRACE random_body RBRACE" t[0] = switch.RandomSwitch(t[3], t[6], t.lineno(1)) def p_random_body(self, t): """random_body : | random_body expression COLON switch_value""" if len(t) == 1: t[0] = [] else: t[0] = t[1] + [switch.RandomChoice(t[2], t[4])] def p_produce_cargo_list(self, t): """produce_cargo_list : LBRACKET RBRACKET | LBRACKET setting_value_list RBRACKET""" if len(t) == 3: t[0] = [] else: t[0] = t[2] def p_produce(self, t): """produce : PRODUCE LPAREN ID COMMA expression_list RPAREN SEMICOLON | PRODUCE LPAREN ID COMMA produce_cargo_list COMMA produce_cargo_list COMMA expression RPAREN | PRODUCE LPAREN ID COMMA produce_cargo_list COMMA produce_cargo_list RPAREN""" if len(t) == 8: t[0] = produce.ProduceOld([t[3]] + t[5], t.lineno(1)) elif len(t) == 11: t[0] = produce.Produce(t[3], t[5], t[7], t[9], t.lineno(1)) else: t[0] = produce.Produce(t[3], t[5], t[7], expression.ConstantNumeric(0), t.lineno(1)) # # Real sprites and related stuff # def p_real_sprite(self, t): """real_sprite : LBRACKET expression_list RBRACKET | ID COLON LBRACKET expression_list RBRACKET""" if len(t) == 4: t[0] = real_sprite.RealSprite(param_list=t[2], poslist=[t.lineno(1)]) else: t[0] = real_sprite.RealSprite(param_list=t[4], label=t[1], poslist=[t.lineno(1)]) def p_recolour_assignment_list(self, t): """recolour_assignment_list : | recolour_assignment_list recolour_assignment""" t[0] = [] if len(t) == 1 else t[1] + [t[2]] def p_recolour_assignment_1(self, t): "recolour_assignment : expression COLON expression SEMICOLON" t[0] = assignment.Assignment(assignment.Range(t[1], None), assignment.Range(t[3], None), t[1].pos) def p_recolour_assignment_2(self, t): "recolour_assignment : expression RANGE expression COLON expression RANGE expression SEMICOLON" t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], t[7]), t[1].pos) def p_recolour_assignment_3(self, t): "recolour_assignment : expression RANGE expression COLON expression SEMICOLON" t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], None), t[1].pos) def p_recolour_sprite(self, t): """real_sprite : RECOLOUR_SPRITE LBRACE recolour_assignment_list RBRACE | ID COLON RECOLOUR_SPRITE LBRACE recolour_assignment_list RBRACE""" if len(t) == 5: t[0] = real_sprite.RecolourSprite(mapping=t[3], poslist=[t.lineno(1)]) else: t[0] = real_sprite.RecolourSprite(mapping=t[5], label=t[1], poslist=[t.lineno(1)]) def p_template_declaration(self, t): "template_declaration : TEMPLATE ID LPAREN id_list RPAREN LBRACE spriteset_contents RBRACE" t[0] = spriteblock.TemplateDeclaration(t[2], t[4], t[7], t.lineno(1)) def p_template_usage(self, t): """template_usage : ID LPAREN expression_list RPAREN | ID COLON ID LPAREN expression_list RPAREN""" if len(t) == 5: t[0] = real_sprite.TemplateUsage(t[1], t[3], None, t.lineno(1)) else: t[0] = real_sprite.TemplateUsage(t[3], t[5], t[1], t.lineno(1)) def p_spriteset_contents(self, t): """spriteset_contents : real_sprite | template_usage | spriteset_contents real_sprite | spriteset_contents template_usage""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_replace(self, t): """replace : REPLACESPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | REPLACESPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE""" if len(t) == 9: t[0] = replace.ReplaceSprite(t[4], t[7], t[2], t.lineno(1)) else: t[0] = replace.ReplaceSprite(t[3], t[6], None, t.lineno(1)) def p_replace_new(self, t): """replace_new : REPLACENEWSPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | REPLACENEWSPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE""" if len(t) == 9: t[0] = replace.ReplaceNewSprite(t[4], t[7], t[2], t.lineno(1)) else: t[0] = replace.ReplaceNewSprite(t[3], t[6], None, t.lineno(1)) def p_base_graphics(self, t): """base_graphics : BASE_GRAPHICS LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | BASE_GRAPHICS ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE""" if len(t) == 9: t[0] = base_graphics.BaseGraphics(t[4], t[7], t[2], t.lineno(1)) else: t[0] = base_graphics.BaseGraphics(t[3], t[6], None, t.lineno(1)) def p_font_glyph(self, t): """font_glyph : FONTGLYPH LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | FONTGLYPH ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE""" if len(t) == 9: t[0] = font.FontGlyphBlock(t[4], t[7], t[2], t.lineno(1)) else: t[0] = font.FontGlyphBlock(t[3], t[6], None, t.lineno(1)) def p_alt_sprites(self, t): "alt_sprites : ALT_SPRITES LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE" t[0] = alt_sprites.AltSpritesBlock(t[3], t[6], t.lineno(1)) # # Sprite sets/groups and such # def p_spriteset(self, t): "spriteset : SPRITESET LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE" t[0] = spriteblock.SpriteSet(t[3], t[6], t.lineno(1)) def p_spritegroup_normal(self, t): "spritegroup : SPRITEGROUP ID LBRACE spriteview_list RBRACE" t[0] = spriteblock.SpriteGroup(t[2], t[4], t.lineno(1)) def p_spritelayout(self, t): """spritelayout : SPRITELAYOUT ID LBRACE layout_sprite_list RBRACE | SPRITELAYOUT ID LPAREN id_list RPAREN LBRACE layout_sprite_list RBRACE""" if len(t) == 6: t[0] = spriteblock.SpriteLayout(t[2], [], t[4], t.lineno(1)) else: t[0] = spriteblock.SpriteLayout(t[2], t[4], t[7], t.lineno(1)) def p_spriteview_list(self, t): """spriteview_list : | spriteview_list spriteview""" if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_spriteview(self, t): """spriteview : ID COLON LBRACKET expression_list RBRACKET SEMICOLON | ID COLON expression SEMICOLON""" if len(t) == 7: t[0] = spriteblock.SpriteView(t[1], t[4], t.lineno(1)) else: t[0] = spriteblock.SpriteView(t[1], [t[3]], t.lineno(1)) def p_layout_sprite_list(self, t): """layout_sprite_list : | layout_sprite_list layout_sprite""" if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_layout_sprite(self, t): "layout_sprite : ID LBRACE layout_param_list RBRACE" t[0] = spriteblock.LayoutSprite(t[1], t[3], t.lineno(1)) def p_layout_param_list(self, t): """layout_param_list : assignment | layout_param_list assignment""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] # # Town names # def p_town_names(self, t): """town_names : TOWN_NAMES LPAREN expression RPAREN LBRACE town_names_param_list RBRACE | TOWN_NAMES LBRACE town_names_param_list RBRACE""" if len(t) == 8: t[0] = townnames.TownNames(t[3], t[6], t.lineno(1)) else: t[0] = townnames.TownNames(None, t[3], t.lineno(1)) def p_town_names_param_list(self, t): """town_names_param_list : town_names_param | town_names_param_list town_names_param""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_town_names_param(self, t): """town_names_param : ID COLON string SEMICOLON | LBRACE town_names_part_list RBRACE | LBRACE town_names_part_list COMMA RBRACE""" if t[1] != "{": t[0] = townnames.TownNamesParam(t[1], t[3], t.lineno(1)) else: t[0] = townnames.TownNamesPart(t[2], t.lineno(1)) def p_town_names_part_list(self, t): """town_names_part_list : town_names_part | town_names_part_list COMMA town_names_part""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_town_names_part(self, t): """town_names_part : TOWN_NAMES LPAREN expression COMMA expression RPAREN | ID LPAREN STRING_LITERAL COMMA expression RPAREN""" if t[1] == "town_names": t[0] = townnames.TownNamesEntryDefinition(t[3], t[5], t.lineno(1)) else: t[0] = townnames.TownNamesEntryText(t[1], t[3], t[5], t.lineno(1)) # # Snow line # def p_snowline(self, t): "snowline : SNOWLINE LPAREN ID RPAREN LBRACE snowline_assignment_list RBRACE" t[0] = snowline.Snowline(t[3], t[6], t.lineno(1)) # # Various misc. main script blocks that don't belong anywhere else # def p_param_assignment(self, t): "param_assignment : expression EQ expression SEMICOLON" t[0] = actionD.ParameterAssignment(t[1], t[3]) def p_error_block(self, t): "error_block : ERROR LPAREN expression_list RPAREN SEMICOLON" t[0] = error.Error(t[3], t.lineno(1)) def p_disable_item(self, t): "disable_item : DISABLE_ITEM LPAREN expression_list RPAREN SEMICOLON" t[0] = disable_item.DisableItem(t[3], t.lineno(1)) def p_cargotable(self, t): """cargotable : CARGOTABLE LBRACE cargotable_list RBRACE | CARGOTABLE LBRACE cargotable_list COMMA RBRACE""" t[0] = cargotable.CargoTable(t[3], t.lineno(1)) def p_cargotable_list(self, t): """cargotable_list : ID | STRING_LITERAL | cargotable_list COMMA ID | cargotable_list COMMA STRING_LITERAL""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_railtypetable(self, t): """railtype : RAILTYPETABLE LBRACE tracktypetable_list RBRACE | RAILTYPETABLE LBRACE tracktypetable_list COMMA RBRACE""" t[0] = tracktypetable.RailtypeTable(t[3], t.lineno(1)) def p_roadtypetable(self, t): """roadtype : ROADTYPETABLE LBRACE tracktypetable_list RBRACE | ROADTYPETABLE LBRACE tracktypetable_list COMMA RBRACE""" t[0] = tracktypetable.RoadtypeTable(t[3], t.lineno(1)) def p_tramtypetable(self, t): """tramtype : TRAMTYPETABLE LBRACE tracktypetable_list RBRACE | TRAMTYPETABLE LBRACE tracktypetable_list COMMA RBRACE""" t[0] = tracktypetable.TramtypeTable(t[3], t.lineno(1)) def p_tracktypetable_list(self, t): """tracktypetable_list : tracktypetable_item | tracktypetable_list COMMA tracktypetable_item""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_tracktypetable_item(self, t): """tracktypetable_item : ID | STRING_LITERAL | ID COLON LBRACKET expression_list RBRACKET""" if len(t) == 2: t[0] = t[1] else: t[0] = assignment.Assignment(t[1], t[4], t[1].pos) def p_basecost(self, t): "basecost : BASECOST LBRACE generic_assignment_list RBRACE" t[0] = basecost.BaseCost(t[3], t.lineno(1)) def p_deactivate(self, t): "deactivate : DEACTIVATE LPAREN expression_list RPAREN SEMICOLON" t[0] = deactivate.DeactivateBlock(t[3], t.lineno(1)) def p_grf_block(self, t): "grf_block : GRF LBRACE assignment_list RBRACE" t[0] = grf.GRF(t[3], t.lineno(1)) def p_skip_all(self, t): "skip_all : SKIP_ALL SEMICOLON" t[0] = skipall.SkipAll(t.lineno(1)) def p_engine_override(self, t): "engine_override : ENGINE_OVERRIDE LPAREN expression_list RPAREN SEMICOLON" t[0] = override.EngineOverride(t[3], t.lineno(1)) def p_sort_vehicles(self, t): "sort_vehicles : SORT_VEHICLES LPAREN expression_list RPAREN SEMICOLON" t[0] = sort_vehicles.SortVehicles(t[3], t.lineno(1)) def p_tilelayout(self, t): "tilelayout : TILELAYOUT ID LBRACE tilelayout_list RBRACE" t[0] = tilelayout.TileLayout(t[2], t[4], t.lineno(1)) def p_tilelayout_list(self, t): """tilelayout_list : tilelayout_item | tilelayout_list tilelayout_item""" if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_tilelayout_item_tile(self, t): "tilelayout_item : expression COMMA expression COLON expression SEMICOLON" t[0] = tilelayout.LayoutTile(t[1], t[3], t[5]) def p_tilelayout_item_prop(self, t): "tilelayout_item : assignment" t[0] = t[1] def p_constant(self, t): "constant : CONST expression EQ expression SEMICOLON" t[0] = constant.Constant(t[2], t[4]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/spritecache.py0000644000175100001660000003125414754345605015371 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import array import json import os from nml import generic keep_orphaned = True class SpriteCache: """ Cache for compressed sprites. @ivar sources: Tuple of paths to files the cache belongs to or depends on. @type sources: C{tuple} of (C{str} or C{None}) @ivar cache_time: Date of cache files. The cache is invalid, if the source image files are newer. @type cache_time: C{int} @ivar cached_sprites: Cache contents @type cached_sprites: C{dict} mapping cache keys to cache items. Cache file format description: Format of cache index is JSON (JavaScript Object Notation), which is easily readable by both humans (for debugging) and machines. Format is as follows: A list of dictionaries, each corresponding to a sprite, with the following keys - rgb_file: filename of the 32bpp sprite (string) - rgb_rect: (uncropped) rectangle of the 32bpp sprite (list with 4 elements (x,y,w,h)) - mask_file, mask_rect: same as above, but for 8bpp sprite - mask_pal: palette of mask file, 'DEFAULT' or 'LEGACY'. Not present if 'mask_file' is not present. - crop: List of 4 positive integers, indicating how much to crop if cropping is enabled Order is (left, right, top, bottom), it is not present if cropping is disabled - info: Info byte of the sprite - pixel_stats: Dictionary with statistics about pixels: 'total': Total amount of pixels. 'alpha': Amount of semi-transparent pixels in 32bpp. 'white': Amount of pure-white pixels in 8bpp. 'anim': Amount of animated pixels in 8bpp. - offset: Offset into the cache file for this sprite - size: Length of this sprite in the cache file Either rgb_file/rect, mask_file/rect, or both must be specified, depending on the sprite The cache should contain the sprite data, but not the header (sizes/offsets and such) For easy lookup, this information is stored in a dictionary Tuples are used because these are hashable The key is a (rgb_file, rgb_rect, mask_file, mask_rect, do_crop, palette)-tuple The rectangles are 4-tuples, non-existent items (e.g. no rgb) are None do_crop is a boolean indicating if this sprite has been cropped palette is a string identifier The value that this key maps to is a 6-tuple, containing: - the sprite data (as a byte array) - The 'info' byte of the sprite - The cropping information (see above) (None if 'do_crop' in the key is false) - The pixel_stats dictionary with statistics. - Whether the sprite exists in the old (loaded)cache - whether the sprite is used by the current GRF The cache file itself is simply the binary sprite data with no meta-information or padding. Offsets and sizes for the various sprites are in the cacheindex file. """ def __init__(self, sources=()): self.sources = sources self.cache_time = 0 self.cached_sprites = {} def get_item(self, cache_key, palette): """ Get item from cache. @param cache_key: Sprite metadata key, as returned by RealSprite.get_cache_key @type cache_key: C{tuple} @param palette: Palette identifier, one of palette.palette_name @type palette: C{str} @return: Cache item @rtype: C{tuple} or C{None} """ if cache_key[2] is not None: key = cache_key + (palette,) else: key = cache_key + (None,) return self.cached_sprites.get(key, None) def add_item(self, cache_key, palette, item): """ Add item to cache. @param cache_key: Sprite metadata key, as returned by RealSprite.get_cache_key @type cache_key: C{tuple} @param palette: Palette identifier, one of palette.palette_name @type palette: C{str} @param item: Cache item @type item: C{tuple} """ if cache_key[2] is not None: key = cache_key + (palette,) else: key = cache_key + (None,) self.cached_sprites[key] = item def count_orphaned(self): """ Count number of items in cache, which have not been used. @return: Number of unused items. @type: C{int} """ return sum(not item[5] for item in self.cached_sprites.values()) def read_cache(self): """ Read the *.grf.cache[index] files. """ try: with generic.open_cache_file(self.sources, ".cache", "rb") as cache_file: cache_data = array.array("B") cache_size = os.fstat(cache_file.fileno()).st_size cache_data.fromfile(cache_file, cache_size) assert cache_size == len(cache_data) self.cache_time = os.path.getmtime(cache_file.name) with generic.open_cache_file(self.sources, ".cacheindex", "r") as index_file: index_file_name = index_file.name sprite_index = json.load(index_file) except OSError: # Cache files don't exist (or otherwise aren't readable) return except json.JSONDecodeError: generic.print_warning( generic.Warning.GENERIC, "{} contains invalid data, ignoring.".format(index_file_name) + " Please remove the file and file a bug report if this warning keeps appearing", ) self.cached_sprites = {} return source_mtime = {} try: # Just assert and print a generic message on errors, as the cache data should be correct # Not asserting could lead to errors later on # Also, it doesn't make sense to inform the user about things he shouldn't know about and can't fix assert isinstance(sprite_index, list) for sprite in sprite_index: assert isinstance(sprite, dict) # load RGB (32bpp) data rgb_key = (None, None) if "rgb_file" in sprite and "rgb_rect" in sprite: assert isinstance(sprite["rgb_file"], str) assert isinstance(sprite["rgb_rect"], list) and len(sprite["rgb_rect"]) == 4 assert all(isinstance(num, int) for num in sprite["rgb_rect"]) rgb_key = (sprite["rgb_file"], tuple(sprite["rgb_rect"])) # load Mask (8bpp) data mask_key = (None, None) if "mask_file" in sprite and "mask_rect" in sprite: assert isinstance(sprite["mask_file"], str) assert isinstance(sprite["mask_rect"], list) and len(sprite["mask_rect"]) == 4 assert all(isinstance(num, int) for num in sprite["mask_rect"]) mask_key = (sprite["mask_file"], tuple(sprite["mask_rect"])) palette_key = None if "mask_pal" in sprite: palette_key = sprite["mask_pal"] # Compose key assert any(i is not None for i in rgb_key + mask_key) key = rgb_key + mask_key + ("crop" in sprite, palette_key) assert key not in self.cached_sprites # Read size/offset from cache assert "offset" in sprite and "size" in sprite offset, size = sprite["offset"], sprite["size"] assert isinstance(offset, int) and isinstance(size, int) assert offset >= 0 and size > 0 assert offset + size <= cache_size data = cache_data[offset : offset + size] # Read info / cropping data from cache assert "info" in sprite and isinstance(sprite["info"], int) info = sprite["info"] if "crop" in sprite: assert isinstance(sprite["crop"], list) and len(sprite["crop"]) == 4 assert all(isinstance(num, int) for num in sprite["crop"]) crop = tuple(sprite["crop"]) else: crop = None if "pixel_stats" in sprite: assert isinstance(sprite["pixel_stats"], dict) pixel_stats = sprite["pixel_stats"] else: pixel_stats = {} # Compose value value = (data, info, crop, pixel_stats, True, False) # Check if cache item is still valid is_valid = True if rgb_key[0] is not None: mtime = source_mtime.get(rgb_key[0]) if mtime is None: mtime = os.path.getmtime(generic.find_file(rgb_key[0])) source_mtime[rgb_key[0]] = mtime if mtime > self.cache_time: is_valid = False if mask_key[0] is not None: mtime = source_mtime.get(mask_key[0]) if mtime is None: mtime = os.path.getmtime(generic.find_file(mask_key[0])) source_mtime[mask_key[0]] = mtime if mtime > self.cache_time: is_valid = False # Drop items from older spritecache format without palette entry if (mask_key[0] is None) != (palette_key is None): is_valid = False if is_valid: self.cached_sprites[key] = value except Exception: generic.print_warning( generic.Warning.GENERIC, "{} contains invalid data, ignoring.".format(index_file_name) + " Please remove the file and file a bug report if this warning keeps appearing", ) self.cached_sprites = {} # Clear cache def write_cache(self): """ Write the cache data to the .cache[index] files. """ if generic.cache_root_dir is None: # Writing cache files will fail, so bail early. return index_data = [] sprite_data = array.array("B") offset = 0 old_cache_valid = True for key, value in self.cached_sprites.items(): # Unpack key/value rgb_file, rgb_rect, mask_file, mask_rect, do_crop, mask_pal = key data, info, crop_rect, pixel_stats, in_old_cache, in_use = value assert do_crop == (crop_rect is not None) assert (mask_file is None) == (mask_pal is None) # If this cache information is exactly the same as the old cache, then we don't bother writing later on if not in_use and not keep_orphaned: old_cache_valid = False continue if not in_old_cache: old_cache_valid = False # Create dictionary with information sprite = {} if rgb_file is not None: sprite["rgb_file"] = rgb_file sprite["rgb_rect"] = tuple(rgb_rect) if mask_file is not None: sprite["mask_file"] = mask_file sprite["mask_rect"] = tuple(mask_rect) sprite["mask_pal"] = mask_pal size = len(data) sprite["offset"] = offset sprite["size"] = size sprite["info"] = info if do_crop: sprite["crop"] = tuple(crop_rect) sprite["pixel_stats"] = pixel_stats index_data.append(sprite) sprite_data.extend(data) offset += size if old_cache_valid: return index_output = json.JSONEncoder(sort_keys=True).encode(index_data) try: with generic.open_cache_file(self.sources, ".cache", "wb") as cache_file, generic.open_cache_file( self.sources, ".cacheindex", "w" ) as index_file: index_file.write(index_output) sprite_data.tofile(cache_file) except OSError: return ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/spriteencoder.py0000644000175100001660000005312314754345605015744 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import array from nml import generic, lz77, palette, spritecache from nml.actions import real_sprite try: from PIL import Image except ImportError: # Image is required only when using graphics pass # Some constants for the 'info' byte INFO_RGB = 1 INFO_ALPHA = 2 INFO_PAL = 4 INFO_TILE = 8 INFO_NOCROP = 0x40 def get_bpp(info): bpp = 0 if (info & INFO_RGB) != 0: bpp += 3 if (info & INFO_ALPHA) != 0: bpp += 1 if (info & INFO_PAL) != 0: bpp += 1 return bpp def has_transparency(info): return (info & (INFO_ALPHA | INFO_PAL)) != 0 def transparency_offset(info): """ Determine which byte within a pixel should be 0 for a pixel to be transparent """ assert has_transparency(info) # There is an alpha or palette component, or both, else no transparency at all # Pixel is transparent if either: # - alpha channel present and 0 # - palette present and 0, and alpha not present # In either case, the pixel is transparent, # if byte 3 (with RGB present) or byte 0 (without RGB) is 0 if (info & INFO_RGB) != 0: return 3 else: return 0 class SpriteEncoder: """ Algorithms for cropping and compressing sprites. That is encoding source images into GRF sprites. @ivar compress_grf: Compress sprites. @type compress_grf: C{bool} @ivar crop_sprites: Crop sprites if possible. @type crop_sprites: C{bool} @ivar palette: Palette for encoding, see L{palette.palette_name}. @type palette: C{str} @ivar cached_image_files: Currently opened source image files. @type cached_image_files: C{dict} mapping C{str} to C{Image} """ def __init__(self, compress_grf, crop_sprites, palette): self.compress_grf = compress_grf self.crop_sprites = crop_sprites self.palette = palette self.sprite_cache = spritecache.SpriteCache() self.cached_image_files = {} def open(self, sprite_files): """ Start the encoder, read caches, and stuff. @param sprite_files: List of sprites per source image file. @type sprite_files: C{dict} that maps (C{tuple} of C{str}) to (C{RealSprite}) """ num_sprites = sum(len(sprite_list) for sprite_list in sprite_files.values()) generic.print_progress("Encoding ...") num_cached = 0 num_dup = 0 num_enc = 0 num_orphaned = 0 count_sprites = 0 for sources, sprite_list in sprite_files.items(): # Iterate over sprites grouped by source image file. # - Open source files only once. (speed) # - Do not keep files around for long. (memory) source_name = "_".join(src for src in sources if src is not None) local_cache = spritecache.SpriteCache(sources) local_cache.read_cache() for sprite_info in sprite_list: count_sprites += 1 generic.print_progress( "Encoding {}/{}: {}".format(count_sprites, num_sprites, source_name), incremental=True ) cache_key = sprite_info.get_cache_key(self.crop_sprites) cache_item = local_cache.get_item(cache_key, self.palette) in_use = False in_old_cache = False if cache_item is not None: # Write a sprite from the cached data compressed_data, info_byte, crop_rect, pixel_stats, in_old_cache, in_use = cache_item if in_use: num_dup += 1 else: num_cached += 1 else: ( size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, pixel_stats, ) = self.encode_sprite(sprite_info) num_enc += 1 # Store sprite in cache, unless already up-to-date if not in_use: cache_item = (compressed_data, info_byte, crop_rect, pixel_stats, in_old_cache, True) local_cache.add_item(cache_key, self.palette, cache_item) # Delete all files from dictionary to free memory self.cached_image_files.clear() num_orphaned += local_cache.count_orphaned() # Only write cache if compression is enabled. Uncompressed data is not worth to be cached. if self.compress_grf: local_cache.write_cache() # Transfer data to global cache for later usage self.sprite_cache.cached_sprites.update(local_cache.cached_sprites) generic.print_progress("Encoding ...", incremental=True) generic.clear_progress() generic.print_info( "{} sprites, {} cached, {} orphaned, {} duplicates, {} newly encoded ({})".format( num_sprites, num_cached, num_orphaned, num_dup, num_enc, "native" if lz77.is_native else "python" ) ) def close(self): """ Close the encoder, validate data, write caches, and stuff. """ pass def get(self, sprite_info): """ Get encoded sprite date, either from cache, or new. @param sprite_info: Sprite meta data @type sprite_info: C{RealSprite} """ # Try finding the file in the cache # open() should have put all sprites into it. cache_key = sprite_info.get_cache_key(self.crop_sprites) cache_item = self.sprite_cache.get_item(cache_key, self.palette) assert cache_item is not None compressed_data, info_byte, crop_rect, pixel_stats, in_old_cache, in_use = cache_item size_x = sprite_info.xsize.value size_y = sprite_info.ysize.value xoffset = sprite_info.xrel.value yoffset = sprite_info.yrel.value if cache_key[-1]: size_x, size_y, xoffset, yoffset = self.recompute_offsets(size_x, size_y, xoffset, yoffset, crop_rect) warnings = [] total = pixel_stats.get("total", 0) if total > 0: if cache_key[0] is not None: image_32_pos = generic.PixelPosition(cache_key[0], cache_key[1][0], cache_key[1][1]) alpha = pixel_stats.get("alpha", 0) if alpha > 0 and (sprite_info.flags.value & real_sprite.FLAG_NOALPHA) != 0: warnings.append( "{}: {:d} of {:d} pixels ({:d}%) are semi-transparent, but NOALPHA is in flags".format( str(image_32_pos), alpha, total, alpha * 100 // total ) ) if cache_key[2] is not None: image_8_pos = generic.PixelPosition(cache_key[2], cache_key[3][0], cache_key[3][1]) white = pixel_stats.get("white", 0) anim = pixel_stats.get("anim", 0) if white > 0 and (sprite_info.flags.value & real_sprite.FLAG_WHITE) == 0: warnings.append( "{}: {:d} of {:d} pixels ({:d}%) are pure white, but WHITE isn't in flags".format( str(image_8_pos), white, total, white * 100 // total ) ) if anim > 0 and (sprite_info.flags.value & real_sprite.FLAG_ANIM) == 0: warnings.append( "{}: {:d} of {:d} pixels ({:d}%) are animated, but ANIM isn't in flags".format( str(image_8_pos), anim, total, anim * 100 // total ) ) return (size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, warnings) def open_image_file(self, filename): """ Obtain a handle to an image file @param filename: Name of the file @type filename: C{str} @return: Image file @rtype: L{Image} """ if filename in self.cached_image_files: im = self.cached_image_files[filename] else: im = Image.open(generic.find_file(filename)) self.cached_image_files[filename] = im return im def encode_sprite(self, sprite_info): """ Crop and compress a real sprite. @param sprite_info: Sprite meta data @type sprite_info: C{RealSprite} @return: size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, pixel_stats @rtype: C{tuple} """ filename_8bpp = None filename_32bpp = None if sprite_info.bit_depth == 8: filename_8bpp = sprite_info.file else: filename_32bpp = sprite_info.file filename_8bpp = sprite_info.mask_file # Get initial info_byte and dimensions from sprite_info. # These values will be changed depending on cropping and compression. info_byte = INFO_NOCROP if (sprite_info.flags.value & real_sprite.FLAG_NOCROP) != 0 else 0 size_x = sprite_info.xsize.value size_y = sprite_info.ysize.value xoffset = sprite_info.xrel.value yoffset = sprite_info.yrel.value # Select region of image bounded by x/ypos and x/ysize x = sprite_info.xpos.value y = sprite_info.ypos.value if sprite_info.bit_depth == 8 or sprite_info.mask_pos is None: mask_x, mask_y = x, y else: mask_x = sprite_info.mask_pos[0].value mask_y = sprite_info.mask_pos[1].value pixel_stats = {"total": size_x * size_y, "alpha": 0, "white": 0, "anim": 0} # Read and validate image data if filename_32bpp is not None: im = self.open_image_file(filename_32bpp.value) if im.mode not in ("RGB", "RGBA"): pos = generic.build_position(sprite_info.poslist) raise generic.ImageError("32bpp image is not a full colour RGB(A) image.", filename_32bpp.value, pos) info_byte |= INFO_RGB if im.mode == "RGBA": info_byte |= INFO_ALPHA (im_width, im_height) = im.size if x < 0 or y < 0 or x + size_x > im_width or y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), pos) try: sprite = im.crop((x, y, x + size_x, y + size_y)) except OSError: pos = generic.build_position(sprite_info.poslist) raise generic.ImageError("Failed to crop 32bpp {} image".format(im.format), filename_32bpp.value, pos) rgb_sprite_data = sprite.tobytes() if (info_byte & INFO_ALPHA) != 0: # Check for half-transparent pixels (not valid for ground sprites) pixel_stats["alpha"] = sum(0x00 < p < 0xFF for p in rgb_sprite_data[3::4]) if filename_8bpp is not None: mask_im = self.open_image_file(filename_8bpp.value) if mask_im.mode != "P": pos = generic.build_position(sprite_info.poslist) raise generic.ImageError("8bpp image does not have a palette", filename_8bpp.value, pos) im_mask_pal = palette.validate_palette(mask_im, filename_8bpp.value) info_byte |= INFO_PAL (im_width, im_height) = mask_im.size if mask_x < 0 or mask_y < 0 or mask_x + size_x > im_width or mask_y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), pos) try: mask_sprite = mask_im.crop((mask_x, mask_y, mask_x + size_x, mask_y + size_y)) except OSError: pos = generic.build_position(sprite_info.poslist) raise generic.ImageError( "Failed to crop 8bpp {} image".format(mask_im.format), filename_8bpp.value, pos ) mask_sprite_data = self.palconvert(mask_sprite.tobytes(), im_mask_pal) # Check for white pixels; those that cause "artefacts" when shading pixel_stats["white"] = sum(p == 255 for p in mask_sprite_data) # Check for palette animation colours if self.palette == "DEFAULT": pixel_stats["anim"] = sum(0xE3 <= p <= 0xFE for p in mask_sprite_data) else: pixel_stats["anim"] = sum(0xD9 <= p <= 0xF4 for p in mask_sprite_data) # Compose pixel information in an array of bytes sprite_data = array.array("B") if (info_byte & INFO_RGB) != 0 and (info_byte & INFO_PAL) != 0: mask_data = array.array("B", mask_sprite_data) # Convert to numeric rgb_data = array.array("B", rgb_sprite_data) if (info_byte & INFO_ALPHA) != 0: for i in range(len(mask_sprite_data)): sprite_data.extend(rgb_data[4 * i : 4 * (i + 1)]) sprite_data.append(mask_data[i]) else: for i in range(len(mask_sprite_data)): sprite_data.extend(rgb_data[3 * i : 3 * (i + 1)]) sprite_data.append(mask_data[i]) elif (info_byte & INFO_RGB) != 0: sprite_data.frombytes(rgb_sprite_data) else: sprite_data.frombytes(mask_sprite_data) bpp = get_bpp(info_byte) assert len(sprite_data) == size_x * size_y * bpp if self.crop_sprites and ((info_byte & INFO_NOCROP) == 0): sprite_data, crop_rect = self.crop_sprite(sprite_data, size_x, size_y, info_byte, bpp) size_x, size_y, xoffset, yoffset = self.recompute_offsets(size_x, size_y, xoffset, yoffset, crop_rect) else: crop_rect = None assert len(sprite_data) == size_x * size_y * bpp compressed_data = self.sprite_compress(sprite_data) # Try tile compression, and see if it results in a smaller file size tile_data = self.sprite_encode_tile(size_x, size_y, sprite_data, info_byte, bpp) if tile_data is not None: tile_compressed_data = self.sprite_compress(tile_data) # Tile compression adds another 4 bytes for the uncompressed chunked data in the header if len(tile_compressed_data) + 4 < len(compressed_data): info_byte |= INFO_TILE data_len = len(tile_data) compressed_data = array.array("B") compressed_data.append(data_len & 0xFF) compressed_data.append((data_len >> 8) & 0xFF) compressed_data.append((data_len >> 16) & 0xFF) compressed_data.append((data_len >> 24) & 0xFF) compressed_data.extend(tile_compressed_data) return (size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, pixel_stats) def fakecompress(self, data): i = 0 output = array.array("B") length = len(data) while i < length: n = min(length - i, 127) output.append(n) output.extend(data[i : i + n]) i += n return output def sprite_compress(self, data): if self.compress_grf: stream = lz77.encode(data) else: stream = self.fakecompress(data) return stream def sprite_encode_tile(self, size_x, size_y, data, info, bpp, long_format=False): long_chunk = size_x > 256 # There are basically four different encoding configurations here, # but just two variables. If the width of the sprite is more than # 256, then the chunk could be 'out of bounds' and thus the long # chunk format is used. If the sprite is more than 65536 bytes, # then the offsets might not fit and the long format method is # used. The latter is enabled via recursion if it's needed. if not has_transparency(info): return None trans_offset = transparency_offset(info) max_chunk_len = 0x7FFF if long_chunk else 0x7F line_offset_size = 4 if long_format else 2 # Whether to use 2 or 4 bytes in the list of line offsets output = array.array("B", [0] * (line_offset_size * size_y)) for y in range(size_y): # Write offset in the correct place, in little-endian format offset = len(output) output[y * line_offset_size] = offset & 0xFF output[y * line_offset_size + 1] = (offset >> 8) & 0xFF if long_format: output[y * line_offset_size + 2] = (offset >> 16) & 0xFF output[y * line_offset_size + 3] = (offset >> 24) & 0xFF line_start = y * size_x * bpp line_parts = [] x1 = 0 while True: # Skip transparent pixels while x1 < size_x and data[line_start + x1 * bpp + trans_offset] == 0: x1 += 1 if x1 == size_x: # End-of-line reached break # Grab as many non-transparent pixels as possible, but not without x2-x1 going out of bounds # Only stop the chunk when encountering 3 consecutive transparent pixels x2 = x1 + 1 while x2 - x1 < max_chunk_len and ( (x2 < size_x and data[line_start + x2 * bpp + trans_offset] != 0) or (x2 + 1 < size_x and data[line_start + (x2 + 1) * bpp + trans_offset] != 0) or (x2 + 2 < size_x and data[line_start + (x2 + 2) * bpp + trans_offset] != 0) ): x2 += 1 line_parts.append((x1, x2)) x1 = x2 if len(line_parts) == 0: # Completely transparent line if long_chunk: output.extend((0, 0x80, 0, 0)) else: output.extend((0x80, 0)) continue for idx, part in enumerate(line_parts): x1, x2 = part last_mask = 0x80 if idx == len(line_parts) - 1 else 0 chunk_len = x2 - x1 if long_chunk: output.extend((chunk_len & 0xFF, (chunk_len >> 8) | last_mask, x1 & 0xFF, x1 >> 8)) else: output.extend((chunk_len | last_mask, x1)) output.extend(data[line_start + x1 * bpp : line_start + x2 * bpp]) if len(output) > 65535 and not long_format: # Recurse into the long format if that's possible. return self.sprite_encode_tile(size_x, size_y, data, info, bpp, True) return output def recompute_offsets(self, size_x, size_y, xoffset, yoffset, crop_rect): # Recompute sizes and offsets after cropping a sprite left, right, top, bottom = crop_rect size_x -= left + right size_y -= top + bottom xoffset += left yoffset += top return size_x, size_y, xoffset, yoffset def crop_sprite(self, data, size_x, size_y, info, bpp): left, right, top, bottom = 0, 0, 0, 0 if not has_transparency(info): return (data, (left, right, top, bottom)) trans_offset = transparency_offset(info) line_size = size_x * bpp # size (no. of bytes) of a scan line data_size = len(data) # Crop the top of the sprite while size_y > 1 and not any(data[line_size * top + trans_offset : line_size * (top + 1) : bpp]): top += 1 size_y -= 1 # Crop the bottom of the sprite while size_y > 1 and not any( data[data_size - line_size * (bottom + 1) + trans_offset : data_size - line_size * bottom : bpp] ): # Don't use negative indexing, it breaks for the last line (where you'd need index 0) bottom += 1 size_y -= 1 # Modify data by removing top/bottom data = data[line_size * top : data_size - line_size * bottom] # Crop the left of the sprite while size_x > 1 and not any(data[left * bpp + trans_offset :: line_size]): left += 1 size_x -= 1 # Crop the right of the sprite while size_x > 1 and not any(data[line_size - (right + 1) * bpp + trans_offset :: line_size]): right += 1 size_x -= 1 # Removing left/right data is not easily done by slicing # Best to create a new array if left + right > 0: new_data = array.array("B") for y in range(0, size_y): a = data[y * line_size + left * bpp : (y + 1) * line_size - right * bpp] new_data.extend(a) data = new_data return (data, (left, right, top, bottom)) def palconvert(self, sprite_str, orig_pal): if orig_pal == "LEGACY" and self.palette == "DEFAULT": return sprite_str.translate(real_sprite.translate_w2d) else: return sprite_str ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/tokens.py0000644000175100001660000002272614754345605014406 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import re import sys import ply.lex as lex from nml import expression, generic # fmt: off reserved = { "grf": "GRF", "var": "VARIABLE", "param": "PARAMETER", "cargotable": "CARGOTABLE", "railtypetable": "RAILTYPETABLE", "roadtypetable": "ROADTYPETABLE", "tramtypetable": "TRAMTYPETABLE", "if": "IF", "else": "ELSE", "while": "WHILE", # reserved "item": "ITEM", # action 0/3 "property": "PROPERTY", "graphics": "GRAPHICS", "snowline": "SNOWLINE", "basecost": "BASECOST", "template": "TEMPLATE", # sprite template for action1 "spriteset": "SPRITESET", # action 1 "spritegroup": "SPRITEGROUP", # action 2 "switch": "SWITCH", # deterministic varaction2 "random_switch": "RANDOMSWITCH", # random action2 "produce": "PRODUCE", # production action2 "error": "ERROR", # action B "disable_item": "DISABLE_ITEM", "replace": "REPLACESPRITE", # action A "replacenew": "REPLACENEWSPRITE", # action 5 "font_glyph": "FONTGLYPH", # action 12 "deactivate": "DEACTIVATE", # action E "town_names": "TOWN_NAMES", # action F "string": "STRING", "return": "RETURN", "livery_override": "LIVERYOVERRIDE", "exit": "SKIP_ALL", "tilelayout": "TILELAYOUT", "spritelayout": "SPRITELAYOUT", "alternative_sprites": "ALT_SPRITES", "base_graphics": "BASE_GRAPHICS", "recolour_sprite": "RECOLOUR_SPRITE", "engine_override": "ENGINE_OVERRIDE", "sort": "SORT_VEHICLES", "const": "CONST" } # fmt: on line_directive1_pat = re.compile(r'\#line\s+(\d+)\s*(\r?\n|"(.*)"\r?\n)') line_directive2_pat = re.compile(r'\#\s+(\d+)\s+"(.*)"\s*((?:\d+\s*)*)\r?\n') class NMLLexer: """ @ivar lexer: PLY scanner object. @type lexer: L{ply.lex} @ivar includes: Stack of included files. @type includes: C{List} of L{generic.LinePosition} @ivar text: Input text to scan. @type text: C{str} """ # Tokens tokens = list(reserved.values()) + [ "ID", "PLUS", "MINUS", "TIMES", "DIVIDE", "MODULO", "AND", "OR", "XOR", "LOGICAL_AND", "LOGICAL_OR", "LOGICAL_NOT", "BINARY_NOT", "EQ", "LPAREN", "RPAREN", "SHIFT_LEFT", "SHIFT_RIGHT", "SHIFTU_RIGHT", "COMP_EQ", "COMP_NEQ", "COMP_LE", "COMP_GE", "COMP_LT", "COMP_GT", "COMMA", "RANGE", "LBRACKET", "RBRACKET", "LBRACE", "RBRACE", "TERNARY_OPEN", "COLON", "SEMICOLON", "STRING_LITERAL", "NUMBER", "FLOAT", "UNIT", ] t_PLUS = r"\+" t_MINUS = r"-" t_TIMES = r"\*" t_MODULO = r"%" t_DIVIDE = r"/" t_AND = r"&" t_OR = r"\|" t_XOR = r"\^" t_LOGICAL_AND = r"&&" t_LOGICAL_OR = r"\|\|" t_LOGICAL_NOT = r"!" t_BINARY_NOT = r"~" t_EQ = r"=" t_LPAREN = r"\(" t_RPAREN = r"\)" t_SHIFT_LEFT = r"<<" t_SHIFT_RIGHT = r">>" t_SHIFTU_RIGHT = r">>>" t_COMP_EQ = r"==" t_COMP_NEQ = r"!=" t_COMP_LE = r"<=" t_COMP_GE = r">=" t_COMP_LT = r"<" t_COMP_GT = r">" t_COMMA = r"," t_RANGE = r"\.\." t_LBRACKET = r"\[" t_RBRACKET = r"\]" t_LBRACE = r"{" t_RBRACE = r"}" t_TERNARY_OPEN = r"\?" t_COLON = r":" t_SEMICOLON = r";" def t_FLOAT(self, t): r"\d+\.\d+" t.value = expression.ConstantFloat(float(t.value), t.lineno) return t def t_NUMBER(self, t): r"(0x[0-9a-fA-F]+)|(\d+)" base = 10 if len(t.value) >= 2 and t.value[0:2] == "0x": t.value = t.value[2:] base = 16 t.value = expression.ConstantNumeric(int(t.value, base), t.lineno) return t def t_UNIT(self, t): r"(nfo)|(mph)|(km/h)|(m/s)|(hpI)|(hpM)|(hp)|(kW)|(tons)|(ton)|(kg)|(snow%)" return t def t_ID(self, t): r"[a-zA-Z_][a-zA-Z0-9_]*" if t.value in reserved: # Check for reserved words t.type = reserved[t.value] else: t.type = "ID" t.value = expression.Identifier(t.value, t.lineno) return t def t_STRING_LITERAL(self, t): r'"([^"\\]|\\.)*"' t.value = expression.StringLiteral(t.value[1:-1], t.lineno) return t # Ignored characters def t_ignore_comment(self, t): r"(/\*(\n|.)*?\*/)|(//.*)" self.increment_lines(t.value.count("\n")) def t_ignore_whitespace(self, t): r"[ \t\r]+" pass def t_line_directive1(self, t): r'\#line\s+\d+\s*(\r?\n|".*"\r?\n)' # See: https://gcc.gnu.org/onlinedocs/cpp/Line-Control.html m = line_directive1_pat.match(t.value) assert m is not None fname = self.lexer.lineno.filename if m.group(3) is None else m.group(3) # This type of line directive contains no information about includes, so we have to make some assumptions if self.includes and self.includes[-1].filename == fname: # Filename equal to the one on top of the stack -> end of an include self.includes.pop() elif fname != self.lexer.lineno.filename: # Not an end of include and not the current file -> start of an include self.includes.append(self.lexer.lineno) self.set_position(fname, int(m.group(1), 10)) self.increment_lines(t.value.count("\n") - 1) def t_line_directive2(self, t): r'\#\s+\d+\s+".*"\s*(\d+\s*)*\r?\n' # Format: # lineno filename flags # See: https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html m = line_directive2_pat.match(t.value) assert m is not None line, fname, flags = m.groups() line = int(line, 10) flags = [int(f, 10) for f in flags.split(" ") if f != ""] if flags is not None else [] if 1 in flags: # File is being included, add current file/line to stack self.includes.append(self.lexer.lineno) elif 2 in flags: # End of include, new file should be equal to the one on top of the stack if self.includes and self.includes[-1].filename == fname: self.includes.pop() else: # But of course user input can never be trusted generic.print_warning( generic.Warning.GENERIC, "Information about included files is inconsistent, position information for errors may be wrong.", ) self.set_position(fname, line) self.increment_lines(t.value.count("\n") - 1) def t_newline(self, t): r"\n+" self.increment_lines(len(t.value)) def t_error(self, t): print( ( "Illegal character '{}' (character code 0x{:02X}) at {}, column {:d}".format( t.value[0], ord(t.value[0]), t.lexer.lineno, self.find_column(t) ) ) ) sys.exit(1) def build(self, rebuild=False): """ Initial construction of the scanner. """ if rebuild: try: import os os.remove(os.path.normpath(os.path.join(os.path.dirname(__file__), "generated", "lextab.py"))) except FileNotFoundError: # Tried to remove a non existing file pass self.lexer = lex.lex(module=self, optimize=1, lextab="nml.generated.lextab") def setup(self, text, fname): """ Setup scanner for scanning an input file. @param text: Input text to scan. @type text: C{str} @param fname: Filename associated with the input text (main input file). @type fname: C{str} """ self.includes = [] self.text = text self.set_position(fname, 1) self.lexer.input(text) def set_position(self, fname, line): """ @note: The lexer.lineno contains a Position object. """ self.lexer.lineno = generic.LinePosition(fname, line, self.includes[:]) def increment_lines(self, count): self.set_position(self.lexer.lineno.filename, self.lexer.lineno.line_start + count) def find_column(self, t): last_cr = self.text.rfind("\n", 0, t.lexpos) if last_cr < 0: last_cr = 0 return t.lexpos - last_cr ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/unit.py0000644000175100001660000000752714754345605014064 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # Available units, mapping of unit name to L{Unit} objects. units = {} def get_unit(name): """ Get a unit by name. @param name: Name of the unit. @type name: C{str} @return: The requested unit. @rtype: L{Unit} """ return units[name] class Unit: def __init__(self, name, type, convert, ottd_mul, ottd_shift): """ Unit definition. Conversion factor works like this: 1 reference_unit = convert other_unit So nfo_value = property_value / convert * property_specific_conversion_factor To avoid using fractions, rational numbers (as 2-tuples) are used instead. ottd_mul and ottd_shift are the values taken from OpenTTD's src/strings.cpp and are used to calculate the displayed value by OpenTTD. If possible, adjust_values increases or decreases the NFO value so that the desired display value is actually achieved. @ivar name: Name of the unit. @type name: C{str} @ivar type: Kind of unit. @type type: C{str} @ivar convert: Conversion factor of the unit. @type convert: Either C{int} or a rational tuple (C{int}, C{int}) @ivar ottd_mul: OpenTTD multiplication factor for displaying a value of this unit. @type ottd_mul: C{int} @ivar ottd_shift: OpenTTD shift factor for displaying a value of this unit. @type ottd_shift: C{int} """ self.name = name self.type = type self.convert = convert self.ottd_mul = ottd_mul self.ottd_shift = ottd_shift def __str__(self): return self.name def add_unit(name, type, convert, ottd_mul, ottd_shift): """ Construct new unit, and add it to L{units}. @param name: Name of the unit. @type name: C{str} @param type: Kind of unit. @type type: C{str} @param convert: Conversion factor of the unit. @type convert: Either C{int} or a rational tuple (C{int}, C{int}) @param ottd_mul: OpenTTD multiplication factor for displaying a value of this unit. @type ottd_mul: C{int} @param ottd_shift: OpenTTD shift factor for displaying a value of this unit. @type ottd_shift: C{int} """ unit = Unit(name, type, convert, ottd_mul, ottd_shift) units[name] = unit # name type convert mul shift add_unit("nfo", "nfo", 1, 1, 0) # don't convert, take value literal # Speed (reference: m/s) # name type convert mul shift add_unit("mph", "speed", (3125, 1397), 1, 0) add_unit("km/h", "speed", (18, 5), 103, 6) add_unit("m/s", "speed", 1, 1831, 12) # Power (reference: hpI (imperial hp)) # name type convert mul shift add_unit("hp", "power", 1, 1, 0) # Default to imperial hp add_unit("kW", "power", (2211, 2965), 6109, 13) add_unit("hpM", "power", (731, 721), 4153, 12) add_unit("hpI", "power", 1, 1, 0) # Weight (reference: ton) # name type convert mul shift add_unit("ton", "weight", 1, 1, 0) add_unit("tons", "weight", 1, 1, 0) add_unit("kg", "weight", 1000, 1000, 0) # Snowline height # name type convert mul shift add_unit("snow%", "snowline", (255, 100), 1, 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nml/version_info.py0000644000175100001660000000451614754345605015600 0ustar00runnerdocker__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import os import sys def get_lib_versions(): versions = {} # PIL try: import PIL versions["PIL"] = PIL.__version__ except ImportError: versions["PIL"] = "Not found!" # PLY try: from ply import lex versions["PLY"] = lex.__version__ except ImportError: versions["PLY"] = "Not found!" return versions def get_nml_version(): # First check if this is a git repository, and use that version if available. # (unless this is a released tarball, see below) try: from nml import version_update version = version_update.get_git_version() if version: return version except ImportError: # version_update is excluded from release tarballs, # so that the predetermined version is always used. pass # No repository was found. Return the version which was saved upon build. try: from nml import __version__ version = __version__.version except ImportError: version = "unknown" return version def get_cli_version(): # Version string for usage in command line result = get_nml_version() + "\n\n" nmlc_path = os.path.abspath(sys.argv[0]) result += "nmlc: {}\n".format(nmlc_path) from nml import lz77 lz77_ver = "C (native)" if lz77.is_native else "Python" result += "LZ77 implementation: {}\n\n".format(lz77_ver) result += "Library versions encountered:\n" for lib, lib_ver in get_lib_versions().items(): result += " {}: {}\n".format(lib, lib_ver) result += "\nPython: {}\n".format(sys.executable) result += "version {}".format(sys.version) return result ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0904627 nml-0.7.6/nml.egg-info/0000755000175100001660000000000014754345610014206 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705223.0 nml-0.7.6/nml.egg-info/PKG-INFO0000644000175100001660000000230514754345607015311 0ustar00runnerdockerMetadata-Version: 2.2 Name: nml Version: 0.7.6 Summary: An OpenTTD NewGRF compiler for the nml language Home-page: https://github.com/OpenTTD/nml Author: NML Development Team Author-email: nml-team@openttdcoop.org License: GPL-2.0+ Classifier: Development Status :: 2 - Pre-Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Topic :: Software Development :: Compilers Requires-Python: >=3.5 License-File: LICENSE Requires-Dist: Pillow>=3.4 Requires-Dist: ply Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: license Dynamic: requires-dist Dynamic: requires-python Dynamic: summary A tool to compile NewGRFs for OpenTTD from nml filesNML is a meta-language that aims to be a lot simpler to learn and use than nfo used traditionally to write NewGRFs. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705224.0 nml-0.7.6/nml.egg-info/SOURCES.txt0000644000175100001660000002437614754345610016106 0ustar00runnerdockerLICENSE MANIFEST.in Makefile README.md nmlc pyproject.toml setup.py docs/changelog.txt docs/index.html docs/nml.spec docs/nmlc.1 examples/industry/example_industry.nml examples/industry/lang/english.lng examples/object/cc_grid.png examples/object/example_object.nml examples/object/lang/english.lng examples/railtype/example_railtype.nml examples/railtype/gfx/depot_electric.png examples/railtype/gfx/depot_normal.png examples/railtype/gfx/fences.png examples/railtype/gfx/gui_erail.png examples/railtype/gfx/gui_rail.png examples/railtype/gfx/lc_left.png examples/railtype/gfx/lc_right.png examples/railtype/gfx/rails_overlays.png examples/railtype/gfx/tunnel_track.png examples/railtype/lang/english.lng examples/road_vehicle/example_road_vehicle.nml examples/road_vehicle/gfx/flatbed_truck_1_copper.png examples/road_vehicle/gfx/flatbed_truck_1_goods.png examples/road_vehicle/gfx/flatbed_truck_1_paper.png examples/road_vehicle/gfx/flatbed_truck_1_steel.png examples/road_vehicle/gfx/flatbed_truck_1_wood.png examples/road_vehicle/lang/english.lng examples/roadtype_and_tramtype/example_roadtype_and_tramtype.nml examples/roadtype_and_tramtype/gfx/depot_electric.png examples/roadtype_and_tramtype/gfx/depot_normal.png examples/roadtype_and_tramtype/gfx/direction_markings.png examples/roadtype_and_tramtype/gfx/fences.png examples/roadtype_and_tramtype/gfx/gui_erail.png examples/roadtype_and_tramtype/gfx/gui_rail.png examples/roadtype_and_tramtype/gfx/lc_left.png examples/roadtype_and_tramtype/gfx/lc_right.png examples/roadtype_and_tramtype/gfx/rails_overlays.png examples/roadtype_and_tramtype/gfx/roads_blue.png examples/roadtype_and_tramtype/gfx/roads_red.png examples/roadtype_and_tramtype/gfx/roads_underlay.png examples/roadtype_and_tramtype/gfx/roads_yellow.png examples/roadtype_and_tramtype/gfx/tram_green.png examples/roadtype_and_tramtype/gfx/tunnel_track.png examples/roadtype_and_tramtype/lang/english.lng examples/station/cows_cargo.png examples/station/example_station.nml examples/station/lang/english.lng examples/train/cargo_wagons.png examples/train/example_train.nml examples/train/icm.png examples/train/lang/dutch.lng examples/train/lang/english.lng nml/__init__.py nml/__version__.py nml/_lz77.c nml/free_number_list.py nml/generic.py nml/global_constants.py nml/grfstrings.py nml/lz77.py nml/main.py nml/nmlop.py nml/output_base.py nml/output_dep.py nml/output_grf.py nml/output_nfo.py nml/output_nml.py nml/palette.py nml/parser.py nml/spritecache.py nml/spriteencoder.py nml/tokens.py nml/unit.py nml/version_info.py nml.egg-info/PKG-INFO nml.egg-info/SOURCES.txt nml.egg-info/dependency_links.txt nml.egg-info/entry_points.txt nml.egg-info/requires.txt nml.egg-info/top_level.txt nml/actions/__init__.py nml/actions/action0.py nml/actions/action0properties.py nml/actions/action1.py nml/actions/action10.py nml/actions/action11.py nml/actions/action12.py nml/actions/action14.py nml/actions/action2.py nml/actions/action2layout.py nml/actions/action2production.py nml/actions/action2random.py nml/actions/action2real.py nml/actions/action2var.py nml/actions/action2var_variables.py nml/actions/action3.py nml/actions/action3_callbacks.py nml/actions/action4.py nml/actions/action5.py nml/actions/action6.py nml/actions/action7.py nml/actions/action8.py nml/actions/actionA.py nml/actions/actionB.py nml/actions/actionD.py nml/actions/actionE.py nml/actions/actionF.py nml/actions/base_action.py nml/actions/real_sprite.py nml/actions/sprite_count.py nml/ast/__init__.py nml/ast/alt_sprites.py nml/ast/assignment.py nml/ast/base_graphics.py nml/ast/base_statement.py nml/ast/basecost.py nml/ast/cargotable.py nml/ast/conditional.py nml/ast/constant.py nml/ast/deactivate.py nml/ast/disable_item.py nml/ast/error.py nml/ast/font.py nml/ast/general.py nml/ast/grf.py nml/ast/item.py nml/ast/loop.py nml/ast/override.py nml/ast/produce.py nml/ast/replace.py nml/ast/skipall.py nml/ast/snowline.py nml/ast/sort_vehicles.py nml/ast/sprite_container.py nml/ast/spriteblock.py nml/ast/switch.py nml/ast/tilelayout.py nml/ast/townnames.py nml/ast/tracktypetable.py nml/editors/__init__.py nml/editors/extract_tables.py nml/editors/kate.py nml/editors/notepadpp.py nml/editors/visualstudiocode.py nml/expression/__init__.py nml/expression/abs_op.py nml/expression/array.py nml/expression/base_expression.py nml/expression/bin_not.py nml/expression/binop.py nml/expression/bitmask.py nml/expression/boolean.py nml/expression/cargo.py nml/expression/functioncall.py nml/expression/functionptr.py nml/expression/identifier.py nml/expression/parameter.py nml/expression/patch_variable.py nml/expression/special_parameter.py nml/expression/spritegroup_ref.py nml/expression/storage_op.py nml/expression/string.py nml/expression/string_literal.py nml/expression/ternaryop.py nml/expression/variable.py nml/generated/__init__.py regression/001_action8.nml regression/002_sounds.nml regression/003_assignment.nml regression/004_deactivate.nml regression/005_error.nml regression/006_vehicle.nml regression/007_townnames.nml regression/008_railtypes.nml regression/009_replace.nml regression/010_liveryoverride.nml regression/011_snowline.nml regression/012_basecost.nml regression/013_train_callback.nml regression/014_read_special_param.nml regression/015_basic_object.nml regression/016_basic_airporttiles.nml regression/017_articulated_tram.nml regression/018_airport_tile.nml regression/019_switch.nml regression/020_recolour.nml regression/021_grf_parameter.nml regression/022_disable_item.nml regression/023_engine_override.nml regression/024_conditional.nml regression/025_loop.nml regression/026_asl.nml regression/027_airport_layout.nml regression/028_font.nml regression/029_base_graphics.nml regression/030_house.nml regression/031_aircraft.nml regression/032_simple_house.nml regression/033_procedure.nml regression/034_roadtypes.nml regression/035_switch_scope.nml regression/036_procedure_scope.nml regression/037_optimised_trigger.nml regression/038_optimised_scope.nml regression/039_storage.nml regression/040_station.nml regression/041_articulated_tram_32bpp.nml regression/Makefile regression/arctic_railwagons.pcx regression/beef.wav regression/brewery.png regression/brewery_snow.png regression/font_addl.png regression/fonts.png regression/groundtiles.png regression/nlhs.png regression/oneway.png regression/opengfx_generic_trams1.pcx regression/opengfx_generic_trams1.png regression/opengfx_trains_start.pcx regression/station.png regression/temperate_railwagons.png regression/tram_foster_express.32.png regression/tram_foster_express.png regression/expected/001_action8.grf regression/expected/001_action8.nfo regression/expected/002_sounds.grf regression/expected/002_sounds.nfo regression/expected/003_assignment.grf regression/expected/003_assignment.nfo regression/expected/004_deactivate.grf regression/expected/004_deactivate.nfo regression/expected/005_error.grf regression/expected/005_error.nfo regression/expected/006_vehicle.grf regression/expected/006_vehicle.nfo regression/expected/007_townnames.grf regression/expected/007_townnames.nfo regression/expected/008_railtypes.grf regression/expected/008_railtypes.nfo regression/expected/009_replace.grf regression/expected/009_replace.nfo regression/expected/010_liveryoverride.grf regression/expected/010_liveryoverride.nfo regression/expected/011_snowline.grf regression/expected/011_snowline.nfo regression/expected/012_basecost.grf regression/expected/012_basecost.nfo regression/expected/013_train_callback.grf regression/expected/013_train_callback.nfo regression/expected/014_read_special_param.grf regression/expected/014_read_special_param.nfo regression/expected/015_basic_object.grf regression/expected/015_basic_object.nfo regression/expected/016_basic_airporttiles.grf regression/expected/016_basic_airporttiles.nfo regression/expected/017_articulated_tram.grf regression/expected/017_articulated_tram.nfo regression/expected/018_airport_tile.grf regression/expected/018_airport_tile.nfo regression/expected/019_switch.grf regression/expected/019_switch.nfo regression/expected/020_recolour.grf regression/expected/020_recolour.nfo regression/expected/021_grf_parameter.grf regression/expected/021_grf_parameter.nfo regression/expected/022_disable_item.grf regression/expected/022_disable_item.nfo regression/expected/023_engine_override.grf regression/expected/023_engine_override.nfo regression/expected/024_conditional.grf regression/expected/024_conditional.nfo regression/expected/025_loop.grf regression/expected/025_loop.nfo regression/expected/026_asl.grf regression/expected/026_asl.nfo regression/expected/027_airport_layout.grf regression/expected/027_airport_layout.nfo regression/expected/028_font.grf regression/expected/028_font.nfo regression/expected/029_base_graphics.grf regression/expected/029_base_graphics.nfo regression/expected/030_house.grf regression/expected/030_house.nfo regression/expected/031_aircraft.grf regression/expected/031_aircraft.nfo regression/expected/032_simple_house.grf regression/expected/032_simple_house.nfo regression/expected/033_procedure.grf regression/expected/033_procedure.nfo regression/expected/034_roadtypes.grf regression/expected/034_roadtypes.nfo regression/expected/035_switch_scope.grf regression/expected/035_switch_scope.nfo regression/expected/036_procedure_scope.grf regression/expected/036_procedure_scope.nfo regression/expected/037_optimised_trigger.grf regression/expected/037_optimised_trigger.nfo regression/expected/038_optimised_scope.grf regression/expected/038_optimised_scope.nfo regression/expected/039_storage.grf regression/expected/039_storage.nfo regression/expected/040_station.grf regression/expected/040_station.nfo regression/expected/041_articulated_tram_32bpp.grf regression/expected/041_articulated_tram_32bpp.nfo regression/expected/example_industry.grf regression/expected/example_industry.nfo regression/expected/example_object.grf regression/expected/example_object.nfo regression/expected/example_railtype.grf regression/expected/example_railtype.nfo regression/expected/example_road_vehicle.grf regression/expected/example_road_vehicle.nfo regression/expected/example_roadtype_and_tramtype.grf regression/expected/example_roadtype_and_tramtype.nfo regression/expected/example_station.grf regression/expected/example_station.nfo regression/expected/example_train.grf regression/expected/example_train.nfo regression/lang/dutch.lng regression/lang/english.lng regression/lang/us.lng././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705223.0 nml-0.7.6/nml.egg-info/dependency_links.txt0000644000175100001660000000000114754345607020262 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705223.0 nml-0.7.6/nml.egg-info/entry_points.txt0000644000175100001660000000004614754345607017512 0ustar00runnerdocker[console_scripts] nmlc = nml.main:run ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705223.0 nml-0.7.6/nml.egg-info/requires.txt0000644000175100001660000000002014754345607016604 0ustar00runnerdockerPillow>=3.4 ply ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705223.0 nml-0.7.6/nml.egg-info/top_level.txt0000644000175100001660000000001514754345607016742 0ustar00runnerdockernml nml_lz77 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/nmlc0000755000175100001660000000013114754345605012604 0ustar00runnerdocker#! /usr/bin/env python3 from nml import main if __name__ == "__main__": main.run() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/pyproject.toml0000644000175100001660000000006014754345605014642 0ustar00runnerdocker[build-system] requires = ["setuptools", "ply"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0764625 nml-0.7.6/regression/0000755000175100001660000000000014754345610014106 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/001_action8.nml0000644000175100001660000000023114754345605016543 0ustar00runnerdockergrf { grfid: "NML\1"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/002_sounds.nml0000644000175100001660000000036314754345605016520 0ustar00runnerdockerparam[0] = sound("beef.wav", 80); param[1] = import_sound("\12\34\56\78", 3, 100); param[2] = sound("beef.wav", 80); param[3] = sound("beef.wav", 60); param[4] = import_sound("\12\34\56\78", 3, 40); param[5] = import_sound("\12\34\56\78", 3); ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/003_assignment.nml0000644000175100001660000000051014754345605017350 0ustar00runnerdockerparam[0] = 3; param[1] = 2 + 2; param[2] = param[0] + param[1]; param[3] = param[0] * 3; param[4] = bitmask(0, 1, 3); // 00001011 = 11 = 0x0B param[5] = param[4] & 3; param[6] = param[1] + param[2] + param[3]; param[7] = param[param[3]]; param[param[0]] = 5; param[param[0]] = param[param[3]]; //param[5] = min(3, param[3], 4);././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/004_deactivate.nml0000644000175100001660000000024414754345605017316 0ustar00runnerdocker/* Test deactivate(grfids...), which deactivates other grfs The nfo / grf equivalent of this is ActionE */ deactivate("ABCD"); deactivate("\01\02\03\04", "EFGH"); ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/005_error.nml0000644000175100001660000000105114754345605016334 0ustar00runnerdocker// Regression test for error(..) statements (ActionB) error(NOTICE, USED_WITH, string(STR_REGRESSION_CARE)); error(FATAL, string(STR_REGRESSION_ERROR), string(STR_ANSWER), 14, param[1] + 12 * param[2]); if (version_openttd(1,11,0) > openttd_version) { error(FATAL, REQUIRES_OPENTTD, string(STR_REGRESSION_ERROR)); } if (version_openttd(12,0) > openttd_version) { error(FATAL, REQUIRES_OPENTTD, string(STR_REGRESSION_ERROR)); } if (version_openttd(16,0) > openttd_version) { error(FATAL, REQUIRES_OPENTTD, string(STR_REGRESSION_ERROR)); } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/006_vehicle.nml0000644000175100001660000000572014754345605016632 0ustar00runnerdocker/* A simple vehicle from OpenGFX+ Code is modified in some places for testing reasons. */ //Add action8, so we can test the vehicle in-game grf { grfid: "NML\6"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } cargotable { PASS, MAIL, GOOD, IORE, "GOLD", "FOOD" } // Foster Express tram spriteset(foster_express_set, "opengfx_generic_trams1.pcx") { [ 48,56, 8,18, -3,-10] [ 64,56, 20,19, -14, -5] [ 96,56, 28,15, -14, -8] [144,56, 20,19, -6, -7] [176,56, 8,18, -3,-10] [192,56, 20,19, -14, -9] [224,56, 28,15, -14, -8] [272,56, 20,19, -6, -7] } alternative_sprites(foster_express_set, ZOOM_LEVEL_IN_2X, BIT_DEPTH_8BPP, "opengfx_generic_trams1.pcx") { [ 48,56, 8,18, -3,-10] [ 64,56, 20,19, -14, -5] [ 96,56, 28,15, -14, -8] [144,56, 20,19, -6, -7] [176,56, 8,18, -3,-10] [192,56, 20,19, -14, -9] [224,56, 28,15, -14, -8] [272,56, 20,19, -6, -7] } alternative_sprites(foster_express_set, ZOOM_LEVEL_NORMAL, BIT_DEPTH_32BPP, "opengfx_generic_trams1.png", "opengfx_generic_trams1.pcx") { [ 48,56, 8,18, -3,-10] [ 64,56, 20,19, -14, -5] [ 96,56, 28,15, -14, -8] [144,56, 20,19, -6, -7] [176,56, 8,18, -3,-10] [192,56, 20,19, -14, -9] [224,56, 28,15, -14, -8] [272,56, 20,19, -6, -7] } param[0] = 9; switch(FEAT_ROADVEHS, SELF, switch_length, d, position_in_consist + d) { 1: return 5; return 6; } // Trams: item(FEAT_ROADVEHS, foster_express_tram, 89) { property { name: string(STR_NAME_FOSTER_EXPRESS_TRAM); climates_available: bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC); model_life: 40; // years vehicle_life: 30; // years introduction_date: date(1965,1,1); reliability_decay: 1; running_cost_base: RUNNING_COST_ROADVEH; // Default road vehicle running cost base running_cost_factor: 135; cost_factor: 143; speed: 90 km/h; power: 220 hp; weight: 22 ton; sprite_id: SPRITE_ID_NEW_ROADVEH; // We have our own sprites loading_speed: 16; // loading speed tractive_effort_coefficient: 0.3; air_drag_coefficient: 0.5; cargo_capacity: param[0] * 5 + 5; // passengers refittable_cargo_classes: bitmask(CC_PASSENGERS, CC_MAIL); non_refittable_cargo_classes: NO_CARGO_CLASS; // Disallow other cargos misc_flags: bitmask(ROADVEH_FLAG_TRAM); // This is a tram } graphics { length: switch_length(0); default: foster_express_set; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/007_townnames.nml0000644000175100001660000000226214754345605017225 0ustar00runnerdockergrf { grfid: "NML\7"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } // named town-name town_names(A) { { text("small", 10), text("medium", 10), text("big", 2), text("", 1) } { text("village", 13), text("town", 10), text("city", 1) } } // specified number town_names(1) { { text("tiny village", 1) } } // complex construct town_names(prefixes) { { text("1", 1), text("2", 1), text("3", 1), text("4", 1), text("5", 1), } } town_names(bodies) { { text("A", 1), text("B", 1), text("C", 1), text("D", 1), text("E", 1), } } town_names(simple) { { town_names(bodies, 1), } } town_names(complex) { { town_names(prefixes, 1), } { text("-", 1), } { town_names(bodies, 1), } } // main town_names { styles: string(STR_STATIONS); { text("MainCapital", 10), town_names(A, 5), town_names(1, 1), town_names(simple, 1), town_names(complex, 8), } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/008_railtypes.nml0000644000175100001660000000023614754345605017226 0ustar00runnerdocker item(FEAT_RAILTYPES, normal_rail, 0) { property { label: "RAIL"; speed_limit: 40km/h; compatible_railtype_list: ["ELRL"]; } }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/009_replace.nml0000644000175100001660000000135514754345605016631 0ustar00runnerdockergrf { grfid: "NML\9"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } template no_param() { [48, 56, 8, 18, -3, -10] } template many_param(a, b, c, d, e, f, g, h) { [a, b, c, d, e+f, f*h] } replace (3092, "opengfx_generic_trams1.pcx") { // replace the first two bus sprites no_param() many_param(48, 56, 8, 18, -5, 2, -5, 2) } /* Sprites / code from opengfx (slightly modified) */ replacenew(ONE_WAY_ROAD, "oneway.png") { [ 18, 8, 24, 16, -12, -8] [ 50, 8, 24, 16, -12, -8] [ 82, 8, 28, 16, -14, -8] [ 114, 8, 24, 16, -10, -8] [ 146, 8, 24, 16, -10, -8] [ 178, 8, 28, 16, -12, -8] } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/010_liveryoverride.nml0000644000175100001660000000471714754345605020265 0ustar00runnerdockergrf { grfid: "NML\10"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } /* * Turbo train engine (arctic) * Livery override for passenger wagon * Graphics by DanMacK, adopted from OpenGFX+ for testing purposes */ spriteset(turbotrain_engine_set, "opengfx_trains_start.pcx") { [142,112, 8,22, -3,-10] [158,112, 21,15, -14, -7] [190,112, 31,12, -16, -8] [238,112, 21,16, -6, -7] [270,112, 8,24, -3,-10] [286,112, 21,16, -15, -6] [318,112, 32,12, -16, -8] [366,112, 21,15, -6, -7] } spritegroup turbotrain_engine_group { loading: turbotrain_engine_set; loaded: turbotrain_engine_set; } spriteset(normal_passenger_set, "arctic_railwagons.pcx") { [ 0, 0, 8,24, -3,-12] [ 16, 0, 22,17, -14, -9] [ 48, 0, 32,12, -16, -8] [ 96, 0, 22,17, -6, -9] [ 0, 0, 8,24, -3,-12] [ 16, 0, 22,17, -14, -9] [ 48, 0, 32,12, -16, -8] [ 96, 0, 22,17, -6, -9] } spritegroup normal_passenger_group { loading: normal_passenger_set; loaded: normal_passenger_set; } spriteset(turbotrain_passenger_set, "opengfx_trains_start.pcx") { [142,139, 8,21, -3,-10] [158,139, 20,15, -13, -7] [190,139, 28,10, -12, -6] [238,139, 20,16, -6, -7] [270,139, 8,21, -3,-10] [286,139, 20,15, -15, -6] [318,139, 28,10, -16, -6] [366,139, 20,16, -6, -7] } spritegroup turbotrain_passenger_group { loading: turbotrain_passenger_set; loaded: turbotrain_passenger_set; } // Turbotrain engine: item(FEAT_TRAINS, turbotrain, 20) { property { sprite_id: SPRITE_ID_NEW_TRAIN; // We have our own sprites misc_flags: bitmask(TRAIN_FLAG_MU); // We use special sprites for passenger and mail wagons } graphics { turbotrain_engine_group; } livery_override(passenger_wagon) { turbotrain_passenger_group; } } item(FEAT_TRAINS, passenger_wagon, 27) { property { sprite_id: SPRITE_ID_NEW_TRAIN; // We have our own sprites misc_flags: bitmask(TRAIN_FLAG_MU); // We use special sprites for passenger and mail wagons refittable_cargo_classes: bitmask(CC_PASSENGERS); // Allow passengers (and tourists) non_refittable_cargo_classes: NO_CARGO_CLASS; // Disallow other cargos } graphics { normal_passenger_group; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/011_snowline.nml0000644000175100001660000000043214754345605017040 0ustar00runnerdockergrf { grfid: "NML\11"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } snowline (linear) { day_of_year(2, 1): 0; day_of_year(11, 1): 18; 177: 255; day_of_year(10, 1): 50 snow%; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/012_basecost.nml0000644000175100001660000000047714754345605017017 0ustar00runnerdockergrf { grfid: "NML\12"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } basecost { 1: param[2]; PR_RUNNING: -2; PR_BUILD_VEHICLE: param[1] + 1; PR_BUILD_WAYPOINT_RAIL: 14 - 1; param[param[11]]: param[param[11]+1]; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/013_train_callback.nml0000644000175100001660000001221614754345605020140 0ustar00runnerdockergrf { grfid: "NML\13"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } /* * ******************************************** * Define cargo and railtype translation tables * ******************************************** */ cargotable { // cargos needed for special refit orders WDPR, SCRP, CMNT, WOOD, // bulk, bulk+piece+flat, tank, piece LVST, STEL, VEHI, BRCK, // piece+flat, piece, piece, piece WOOL, BUBL, TOYS, FZDR, // flat, flat, flat, tank FRUT, FRVG, FOOD, // bulk, bulk, piece // cargos only referenced OIL_, GOOD, WATR, MILK, COAL, IORE, AORE, CLAY, GRVL, SAND, GRAI, RSGR, MAIZ, CORE, FERT, CTCD, SULP, WHEA, RFPR, COLA, PETR, PAPR, TOFF, SUGR, PASS, MAIL, BATT, SWET, RUBR, FMSP, ENSP, MNSP, FICR, PLAS, PLST } railtypetable { RAIL, ELRL, MONO, MGLV, TRPD } template tmpl_railwagon(x,y) { [ 0+x, y, 8,24, -3,-12] [ 16+x, y, 22,17, -14, -9] [ 48+x, y, 32,12, -16, -8] [ 96+x, y, 22,17, -6, -9] } spriteset(bulk_wagon_empty_set, "temperate_railwagons.png") { tmpl_railwagon(0,25) } spriteset(bulk_wagon_coal_default_set, "temperate_railwagons.png") { tmpl_railwagon(0,250) } spritegroup bulk_wagon_coal_default_group { loaded: [bulk_wagon_empty_set, bulk_wagon_coal_default_set]; loading: [bulk_wagon_empty_set, bulk_wagon_coal_default_set]; } spriteset(bulk_wagon_coal_arctic_empty_set, "arctic_railwagons.pcx") { tmpl_railwagon(0,25) } spriteset(bulk_wagon_coal_arctic_full_set, "arctic_railwagons.pcx") { tmpl_railwagon(0,250) } spritegroup bulk_wagon_coal_arctic_group { loaded: [bulk_wagon_coal_arctic_empty_set, bulk_wagon_coal_arctic_full_set]; loading: [bulk_wagon_coal_arctic_empty_set, bulk_wagon_coal_arctic_full_set]; } spriteset(bulk_wagon_coal2_empty_set, "temperate_railwagons.png") { tmpl_railwagon(0,225) } spriteset(bulk_wagon_coal2_full_set, "temperate_railwagons.png") { tmpl_railwagon(0,350) } spritegroup bulk_wagon_coal2_group { loaded: [bulk_wagon_coal2_empty_set, bulk_wagon_coal2_full_set]; loading: [bulk_wagon_coal2_empty_set, bulk_wagon_coal2_full_set]; } spriteset(bulk_wagon_empty_grain_set, "temperate_railwagons.png") { tmpl_railwagon(0,150) } spriteset(bulk_wagon_grain_set, "temperate_railwagons.png") { tmpl_railwagon(0,275) } spritegroup bulk_wagon_grain_group { loaded: [bulk_wagon_empty_grain_set, bulk_wagon_grain_set]; loading: [bulk_wagon_empty_grain_set, bulk_wagon_grain_set]; } random_switch (FEAT_TRAINS, SELF, bulk_wagon_coal_default_switch, bitmask(TRIGGER_VEHICLE_SERVICE)) { 2: bulk_wagon_coal_default_group; 1: bulk_wagon_coal2_group; } switch (FEAT_TRAINS, SELF, bulk_wagon_coal_climate_switch, climate) { CLIMATE_ARCTIC: bulk_wagon_coal_arctic_group; bulk_wagon_coal_default_switch; } switch(FEAT_TRAINS, SELF, bulk_wagon_graphics_switch, cargo_type_in_veh) { COAL: bulk_wagon_coal_climate_switch; bulk_wagon_grain_group; // default to grain } switch (FEAT_TRAINS, SELF, bulk_wagon_cb_capacity_switch, cargo_type_in_veh) { FICR: return 25; FRUT: return 20; FRVG: return 20; GRAI: return 25; MAIZ: return 25; RSGR: return 20; WHEA: return 25; // no default: instead fail CB and use capacity set in properties (30) } switch (FEAT_TRAINS, SELF, bulk_wagon_cb_weight_switch, cargo_type_in_veh) { COAL: return 18; FRUT: return 18; FRVG: return 18; RSGR: return 18; // no default: instead fail CB and use weight set in properties (25t) } switch (FEAT_TRAINS, SELF, bulk_wagon_cb_name_switch, STORE_TEMP(string(STR_NAME_BULK_WAGON), 0x100)) { return string(STR_JUST_STRING); } item(FEAT_TRAINS, bulk_wagon) { property { // Some bogus property assignments to test handling of units speed: 10 m/s; speed: param[1] m/s; speed: param[2] km/h; speed: param[3] mph; power: param[4] kW; } property { // We try to simulate the stats of the temperate grain wagon name: string(STR_NAME_BULK_WAGON); climates_available: ALL_CLIMATES; refittable_cargo_classes: bitmask(CC_BULK); non_refittable_cargo_classes: bitmask(CC_PASSENGERS, CC_MAIL, CC_ARMOURED, CC_LIQUID, CC_REFRIGERATED, CC_HAZARDOUS); cargo_allow_refit: [WDPR, SCRP, FRUT, FRVG]; default_cargo_type: COAL; sprite_id: SPRITE_ID_NEW_TRAIN; introduction_date: date(1880,1,1); model_life: VEHICLE_NEVER_EXPIRES; retire_early: 0; vehicle_life: 30; reliability_decay: 0; loading_speed: 10; cost_factor: 182; running_cost_factor: 5; speed: 0; refit_cost: 40; track_type: RAIL; power: 0; running_cost_base: RUNNING_COST_STEAM; cargo_capacity: 30; weight: param[1] ton; bitmask_vehicle_info: 0; } graphics { weight: bulk_wagon_cb_weight_switch; purchase_weight: return 25; cargo_capacity: bulk_wagon_cb_capacity_switch; purchase_cargo_capacity: return 30; name: bulk_wagon_cb_name_switch; default: bulk_wagon_graphics_switch; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/014_read_special_param.nml0000644000175100001660000000214514754345605021003 0ustar00runnerdockergrf { grfid: "NML\14"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; param { int_param { type: int; min_value: 0; max_value: 2; def_value: 0; name: string(STR_PARAM_NAME); desc: string(STR_PARAM_DESC); } } param { bool_param_1 { type: bool; def_value: 0; } bool_param_2 { type: bool; def_value: 1; bit: 2; } } } /* Read a user-changeable integer setting */ param[0x10] = int_param; /* Read a user-changeable bool setting */ param[0x11] = bool_param_1; /* Read a user-changeable bool setting */ param[0x12] = bool_param_2; /* Read a setting from another newgrf */ param[0x13] = param["NML\01", 0]; /* Read and write a misc grf bit */ desert_paved_roads = 1; param[0x14] = train_width_32_px; /* Read and write a special grf parameter */ traininfo_y_offset = -2; param[0x15] = year_loaded; /* Read a patch variable */ param[0x16] = map_size; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/015_basic_object.nml0000644000175100001660000000207714754345605017624 0ustar00runnerdockergrf { grfid: "NML\15"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } spritelayout obj_basic_tile { ground { sprite : 1420; } building { sprite : 2632; recolour_mode : RECOLOUR_REMAP; palette : 0x307; xextent : 16; yextent : 16; zextent : 30; } } item (FEAT_OBJECTS, obj_basic) { property { class : "MISC"; classname : string(STR_OBJ_MISC_CLASS); name : string(STR_OBJ_BASIC); climates_available : ALL_CLIMATES & ~bitmask(CLIMATE_TOYLAND); size : [ 1, 1 ]; introduction_date : date(1900, 1, 1); end_of_life_date : date(1930, 1, 1); build_cost_multiplier : 1; remove_cost_multiplier : 1; object_flags : bitmask(OBJ_FLAG_ALLOW_BRIDGE); height : 4; } graphics { obj_basic_tile; } }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/016_basic_airporttiles.nml0000644000175100001660000000104714754345605021074 0ustar00runnerdockerspriteset(turbotrain_engine_set, "opengfx_trains_start.pcx") { [142,112, 8,22, -3,-10] } spritelayout small_airport_tiles_graphics { ground {sprite: GROUNDSPRITE_NORMAL; } childsprite { sprite: turbotrain_engine_set; always_draw: 1; } } item(FEAT_AIRPORTTILES, small_airport_tiles) { property { substitute: 0; animation_info: [ANIMATION_LOOPING, 4]; // loop, 4 frames animation_speed: 1; animation_triggers: 1; } graphics { small_airport_tiles_graphics; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/017_articulated_tram.nml0000644000175100001660000000456014754345605020542 0ustar00runnerdocker/* A simple articulated tram, graphics from OpenGFX+rv Code is modified in some places for testing reasons. */ grf { grfid: "NML\17"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } template tmpl_tram(x, y) { [ x, y, 8, 18, -3, -10] [ 16 + x, y, 20, 18, -14, -5] [ 48 + x, y, 28, 15, -14, -8] [ 96 + x, y, 20, 18, -6, -7] [128 + x, y, 8, 18, -3, -10] [144 + x, y, 20, 18, -14, -9] [176 + x, y, 28, 15, -14, -8] [224 + x, y, 20, 18, -6, -7] } spriteset(foster_express_set, "tram_foster_express.png") { tmpl_tram(48,1) } switch(FEAT_ROADVEHS, SELF, foster_express_articulated_parts, extra_callback_info1) { 1..3: return foster_express_tram; return 0xFF; } item(FEAT_ROADVEHS, foster_express_tram, 88) { property { name: string(STR_NAME_FOSTER_TURBO_TRAM); climates_available: ALL_CLIMATES; model_life: 40; // years vehicle_life: 30; // years introduction_date: date(1965,1,1); reliability_decay: 1; running_cost_base: RUNNING_COST_ROADVEH; // Default road vehicle running cost base running_cost_factor: 135; cost_factor: 143; speed: 317 mph; power: 220 hp; weight: 22 ton; sprite_id: SPRITE_ID_NEW_ROADVEH; // We have our own sprites loading_speed: 16; // loading speed tractive_effort_coefficient: 0.3; air_drag_coefficient: 0.5; cargo_capacity: 45; // passengers refittable_cargo_classes: bitmask(CC_PASSENGERS); // Allow passengers (and tourists) non_refittable_cargo_classes: NO_CARGO_CLASS; // Disallow other cargos cargo_allow_refit: []; default_cargo_type: DEFAULT_CARGO_FIRST_REFITTABLE; misc_flags: bitmask(ROADVEH_FLAG_TRAM); // This is a tram } graphics { articulated_part: foster_express_articulated_parts; foster_express_set; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/018_airport_tile.nml0000644000175100001660000000104014754345605017702 0ustar00runnerdockergrf { grfid: "NML\18"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } runway_sprite = 0xA68; spritelayout dirt_runway_sw_snow { building { sprite: runway_sprite; yoffset: 0x0F; yextent: 1; zextent: 6; recolour_mode: RECOLOUR_TRANSPARENT; palette: PALETTE_TRANSPARENT; } } item(FEAT_AIRPORTTILES, small_airport_tiles) { graphics { dirt_runway_sw_snow; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/019_switch.nml0000644000175100001660000000175714754345605016526 0ustar00runnerdockergrf { grfid: "NML\19"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } switch(FEAT_INDUSTRIES, SELF, return_switch, STORE_PERM(current_year - 1950, 0x01)) { return 0; } /* store a value of 4 into the permanent register 0x00, but evaluate current_month * use the array notation */ switch(FEAT_INDUSTRIES, SELF, coal_mine_subtype_switch, a, [STORE_PERM(a, 0x00), current_month]) { 0..10: return string(STR_COALMINE_MONTH_0_10); 13: return_switch; // unreachable return string(STR_COALMINE_MONTH_11); } item(FEAT_INDUSTRIES, coal_mine) { property { substitute: INDUSTRYTYPE_COAL_MINE; override: INDUSTRYTYPE_COAL_MINE; } graphics { extra_text_industry: return string(STR_COALMINE_EXTRA_TEXT); cargo_subtype_display: coal_mine_subtype_switch(4); control_special: return extra_callback_info1 & 1; // random with 50% chance // no default } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/020_recolour.nml0000644000175100001660000000232414754345605017036 0ustar00runnerdockergrf { grfid: "NML\20"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } template company_recolour(offset) { recolour_sprite { 0xC6..0xCD: offset..offset+7; } } param[3] = reserve_sprites(3); replace(param[3]) { recolour_sprite { 0xC6: 0x28; 0xC7: 0xF5; 0xC8..0xCD: 0x0A..0x0F; } company_recolour(0x3E) company_recolour(0x9A) } // To write any recolour sprite to the output nmlc needs to know which palette // to use. One option would be to specify the palette via the commandline, but // for now just include some graphics so nmlc can determine the palette from that. // Foster Express tram spriteset(foster_express_set, "opengfx_generic_trams1.pcx") { [ 48,56, 8,18, -3,-10] [ 64,56, 20,19, -14, -5] [ 96,56, 28,15, -14, -8] [144,56, 20,19, -6, -7] [176,56, 8,18, -3,-10] [192,56, 20,19, -14, -9] [224,56, 28,15, -14, -8] [272,56, 20,19, -6, -7] } spritegroup foster_express_group { loading: foster_express_set; loaded: foster_express_set; } item(FEAT_ROADVEHS, foster_express_tram) { graphics { foster_express_group; } }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/021_grf_parameter.nml0000644000175100001660000000242414754345605020024 0ustar00runnerdocker// define the newgrf grf { grfid: "NML\21"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 1; min_compatible_version: 0; param (0) { param_NoGrid { type: bool; name: string(STR_PARAM_NOGRID); desc: string(STR_PARAM_NOGRID_DESC); def_value: 1; bit: 0; } transmitter2rock { type: bool; name: string(STR_PARAM_TRANSMITTER); desc: string(STR_PARAM_TRANSMITTER_DESC); def_value: 0; bit: 1; } } param (1) { param_landscape { type: int; name: string(STR_PARAM_LANDSCAPE); desc: string(STR_PARAM_LANDSCAPE_DESC); def_value: 0; min_value: 0; max_value: 1; names: { 0: string(STR_PARAM_LANDSCAPE_NORMAL); 1: string(STR_PARAM_LANDSCAPE_ALPINE); 2: string(STR_PARAM_LANDSCAPE_TEMPERATE); 3: string(STR_PARAM_LANDSCAPE_ARCTIC); 4: string(STR_PARAM_LANDSCAPE_TROPICAL); 5: string(STR_PARAM_LANDSCAPE_TOYLAND); }; } } } param[10] = param_landscape; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/022_disable_item.nml0000644000175100001660000000051614754345605017630 0ustar00runnerdockergrf { grfid: "NML\22"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } // disable all trains disable_item(FEAT_TRAINS); // disable industry with ID 12 disable_item(FEAT_INDUSTRIES, 12); // disable cargos 10 to 12 disable_item(FEAT_CARGOS, 10, 12); ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/023_engine_override.nml0000644000175100001660000000054214754345605020353 0ustar00runnerdockergrf { grfid: "NML\23"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } // let this grf override some grf with GRFID "ABCD" engine_override("test", "ABCD"); // also override some other grf, this time not setting our own grfid explicitly engine_override("\12\34\56\78"); ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/024_conditional.nml0000644000175100001660000000014014754345605017505 0ustar00runnerdockerif (param[0]) { param[1] = 1; } if (0) { param[2] = 1; } if (1) { param[3] = 1; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/025_loop.nml0000644000175100001660000000005014754345605016154 0ustar00runnerdockeri = 0; while (i < 5) { i = i + 1; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/026_asl.nml0000644000175100001660000000147614754345605016000 0ustar00runnerdockerspritelayout layout1 { building { sprite: 0xA68 + animation_frame; } } item(FEAT_AIRPORTTILES, tile1) { graphics { layout1; } } template tmpl() { [ 64,56, 20,19, -14, -5, "opengfx_generic_trams1.pcx"] b: [] } spriteset(set2) { tmpl() [] } spriteset(recset) { recolour_sprite { 0x80 : 0x81; } lbl: recolour_sprite { 0x80 : 0x82; } [] } spritelayout layout2 { building { sprite: set2(animation_frame); } building { sprite: set2(b); hide_sprite: nearby_tile_is_water(0, 0); recolour_mode: RECOLOUR_REMAP; palette: recset(lbl); } } item(FEAT_AIRPORTTILES, tile2) { graphics { layout2; } } item(FEAT_OBJECTS, tile2) { graphics { default: layout2; colour: CB_FAILED; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/027_airport_layout.nml0000644000175100001660000000174214754345605020273 0ustar00runnerdockerspriteset(small_airport_tile_set) { [] } spritelayout small_airport_tile_layout { ground { sprite: small_airport_tile_set; } childsprite { sprite: small_airport_tile_set; always_draw: 1; xoffset: 32; yoffset: 16; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } building { sprite: small_airport_tile_set; } } item(FEAT_AIRPORTTILES, small_airport_tiles) { property { substitute: 0; animation_info: [1, 4]; // loop, 4 frames animation_speed: 1; animation_triggers: 1; } graphics { small_airport_tile_layout; } } tilelayout small_airport_layout_north { rotation: DIRECTION_NORTH; 0, 0: small_airport_tiles; 1, 0: small_airport_tiles; 2, 0: small_airport_tiles; 3, 0: 70; // original airport tile } item(FEAT_AIRPORTS, small_airport) { property { override: 0; layouts: [small_airport_layout_north]; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/028_font.nml0000644000175100001660000000162414754345605016164 0ustar00runnerdocker// Source: OpenGFX // "Characters" template tmpl_normal(x, y, width) { [x, y, width, 13, 0, -2, NOCROP] } template tmpl_small(x, y, width) { [x, y, width, 8, 0, 0, NOCROP] } template tmpl_large(x, y, width) { [x, y, width, 21, 0, -2, NOCROP] } template tmpl_mono(x, y) { [x, y, 7, 13, 0, 0, NOCROP] } // All fonts except monospaced // U+007B: Left Curly Bracket // U+007C: Vertical Line // U+007D: Right Curly Bracket // U+007E: Tilde font_glyph(NORMAL, 0x007B, "font_addl.png") { tmpl_normal(10, 10, 5) tmpl_normal(30, 10, 3) tmpl_normal(50, 10, 5) tmpl_normal(70, 10, 7) } font_glyph(SMALL, 0x007B, "font_addl.png") { tmpl_small(10, 30, 3) tmpl_small(30, 30, 1) tmpl_small(50, 30, 3) tmpl_small(70, 30, 4) } font_glyph(LARGE, 0x007B, "font_addl.png") { tmpl_large(10, 40, 7) tmpl_large(30, 40, 2) tmpl_large(50, 40, 7) tmpl_large(70, 40, 11) } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/029_base_graphics.nml0000644000175100001660000000266014754345605020012 0ustar00runnerdocker// Source: OpenGFX base_graphics(0, "") { // PALETTE_TILE_RED_PULSATING (red tile border pulsating) recolour_sprite { 0x00..0xD6: 0x00; 0xE3..0xFF: 0x00; 0x0A..0x0C: 0xF0; 0x0D..0x0F: 0xEF; 0x10: 0x0F; 0x06: 0xF0; 0x07: 0xF0; 0x08: 0xF0; 0x04: 0xF0; 0x05: 0xF0; 0x03: 0xF0; 0x01: 0xF0; 0x02: 0xF0; 0x09: 0xF0; } // PALETTE_SEL_TILE_RED (red tile border) recolour_sprite { 0x00..0xD6: 0x00; 0xE3..0xFF: 0x00; 0x0A..0x0D: 0xA3; 0x0E..0x0F: 0xA4; 0x10: 0x0F; 0x06: 0xB4; 0x07: 0xB4; 0x08: 0xB5; 0x04: 0xB3; 0x05: 0xB3; 0x03: 0xB3; 0x01: 0xB2; 0x02: 0xB3; 0x09: 0xB5; } } base_graphics( 2, "fonts.png") { [ 10, 10, 2, 1, 0, -2, NOCROP] } base_graphics( 3, "fonts.png") { [ 30, 10, 3, 13, 0, -2] } base_graphics( 4, "fonts.png") { [ 50, 10, 5, 13, 0, -2] } base_graphics( 5, "fonts.png") { [ 70, 10, 10, 13, 0, -2] } base_graphics( 6, "fonts.png") { [ 90, 10, 9, 13, 0, -2] } base_graphics( 7, "fonts.png") { [ 110, 10, 12, 13, 0, -2] } base_graphics( 8, "fonts.png") { [ 130, 10, 9, 13, 0, -2] } base_graphics( 9, "fonts.png") { [ 150, 10, 3, 13, 0, -2] } base_graphics( 10, "fonts.png") { [ 170, 10, 5, 13, 0, -2] } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/030_house.nml0000644000175100001660000001512114754345605016327 0ustar00runnerdocker/* * This NewGRF adds the FIRS Brewery as a 2x2 town building * Original graphics by FooBar and andythenorth * * It accepts grain (as well as some/pax) mail. * Whenever grain is accepted, a smoke plume rises above the building. * The Brewery has a very high probability to appear, but special checks * make sure that only 1 appears per town. */ /* Make our NewGRF visible to OpenTTD */ grf { grfid: "NML\30"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } /* Add a cargo translation table */ cargotable { PASS, MAIL, GRAI, WHEA } /* Sprite templates for both ground and building sprites */ template tmpl_ground_tile(x, y, filename) { [x, y, 64, 31, -31, 0, filename] } template tmpl_building_sprite(x, y, h, dy, filename) { [x, y, 64, h, -31, dy, NOCROP, filename] } /* Spriteset containing all ground sprites */ spriteset(brewery_spriteset_ground) { tmpl_ground_tile( 10, 10, "groundtiles.png") //bare tmpl_ground_tile(150, 10, "groundtiles.png") //stones tmpl_ground_tile(220, 10, "groundtiles.png") //snowed } spriteset(brewery_spriteset_building) { tmpl_building_sprite( 10, 60, 91, -60, "brewery.png") // tile with chimney tmpl_building_sprite( 80, 60, 91, -60, "brewery.png") // left part of large building tmpl_building_sprite(150, 60, 91, -60, "brewery.png") // right part of large building tmpl_building_sprite( 10, 60, 91, -60, "brewery_snow.png") // idem, with snow tmpl_building_sprite( 80, 60, 91, -60, "brewery_snow.png") tmpl_building_sprite(150, 60, 91, -60, "brewery_snow.png") } const default_palette = PALETTE_USE_DEFAULT; /* Generic sprite layout that is used for all tiles of the building. Parameters: * - building_sprite: offset in the brewery_spriteset_building spriteset to use. -1 to skip building sprite. * - with_smoke: Show smoke above the tile (also depends on animation state) */ spritelayout brewery_sprite_layout(building_sprite, with_smoke) { /* First: Draw the normal ground sprite as a base layer */ ground { sprite: GROUNDSPRITE_NORMAL; } /* Now draw our own ground sprite * Below the snowline, it is either a bare land sprite (construction state 0) or stones (construction state 1..3) * Above the snowline, it is always a snowed sprite */ childsprite { sprite: brewery_spriteset_ground((terrain_type == TILETYPE_SNOW) ? 2 : min(construction_state, 1)); always_draw: 1; // Draw this sprite even in transparent mode } /* Now draw the building sprite, assuming the building is fully constructed */ building { sprite: brewery_spriteset_building((terrain_type == TILETYPE_SNOW) * 3 + building_sprite); /* Enable recolouring for the flag on top, it will get a random colour */ recolour_mode: RECOLOUR_REMAP; palette: default_palette; zextent: 48; hide_sprite: building_sprite == -1 || construction_state != 3; } building { /* 3079 .. 3083 are steam smoke sprites * Draw one of them, assuming the animation frame != 0 */ sprite: 3079 + (animation_frame - 1) / 4; xoffset: 8; yoffset: 0; zoffset: 55 + (animation_frame - 1); xextent: 11; zextent: 7; hide_sprite: !with_smoke || animation_frame == 0; } } switch(FEAT_HOUSES, SELF, brewery_layout_1, house_tile) { HOUSE_TILE_NORTH: brewery_sprite_layout(2, 0); HOUSE_TILE_WEST: brewery_sprite_layout(1, 0); HOUSE_TILE_EAST: brewery_sprite_layout(-1, 0); // empty tile brewery_sprite_layout(0, 1); //south, building with chimney } switch(FEAT_HOUSES, SELF, brewery_layout_2, house_tile) { HOUSE_TILE_NORTH: brewery_sprite_layout(-1, 0); // empty tile HOUSE_TILE_WEST: brewery_sprite_layout(0, 1); // building with chimney HOUSE_TILE_EAST: brewery_sprite_layout(2, 0); brewery_sprite_layout(1, 0); //south } /* Randomly choose a graphics layout */ switch(FEAT_HOUSES, SELF, brewery_choose_layout, random_bits & 1) { 0 : brewery_layout_1; brewery_layout_2; } /* Stop the animation in frame 0, else continue normally * We choose frame 0 as 'stop' frame (no smoke visible) so there is no smoke * during / after construction */ switch(FEAT_HOUSES, SELF, brewery_next_frame, animation_frame) { 0 : return CB_RESULT_STOP_ANIMATION; return CB_RESULT_NEXT_FRAME; } /* When grain / wheat is accepted, start the animation if: * - The animation is currently stopped (frame 0) * - The house tile actually contains smoke. This saves CPU time * Else, do nothing */ switch(FEAT_HOUSES, SELF, brewery_cargo_accepted, house_tile == ((random_bits & 1) ? HOUSE_TILE_WEST : HOUSE_TILE_SOUTH)) { // only animate the tile that has the chimney with smoke 1 : return (animation_frame == 0) ? 1 : CB_RESULT_DO_NOTHING; return CB_RESULT_DO_NOTHING; } /* Only allow construction of a brewery if there isn't already one in the same town */ switch(FEAT_HOUSES, SELF, brewery_check_location, same_house_count_town) { 0 : 1; 0; } /* Only enable the brewery if there is a grain or wheat cargo type */ if (cargotype_available("GRAI") || cargotype_available("WHEA")) { item(FEAT_HOUSES, item_brewery, -1, HOUSE_SIZE_2X2) { property { substitute: 40; // use 2x2 shopping mall as fallback name: string(STR_BREWERY_NAME); /* do not replace (override) any original house type */ building_flags: bitmask(HOUSE_FLAG_ANIMATE); population: 100; mail_multiplier: 25; accepted_cargos: [[GRAI, 8], [WHEA, 8], [PASS, 2], [MAIL, 1]]; local_authority_impact: 200; removal_cost_multiplier: 250; probability: 15; // high probability for testing purposes years_available: [1940, 2050]; minimum_lifetime: 20; availability_mask: [bitmask(TOWNZONE_EDGE, TOWNZONE_OUTSKIRT, TOWNZONE_OUTER_SUBURB), bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, ABOVE_SNOWLINE)]; random_colours: [COLOUR_RED, COLOUR_BLUE, COLOUR_GREEN, COLOUR_YELLOW]; refresh_multiplier: 0; animation_info: [ANIMATION_LOOPING, 21]; // 'stop' frame + 20 normal frames. Auto-loop back to frame 0 animation_speed: 2; /* no building class set, it's pointless */ watched_cargo_types: [GRAI, WHEA]; } graphics { default: brewery_choose_layout; anim_next_frame: brewery_next_frame; watched_cargo_accepted: brewery_cargo_accepted; construction_check: brewery_check_location; } } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/031_aircraft.nml0000644000175100001660000000123014754345605016774 0ustar00runnerdockergrf { grfid : "NML\30"; name : string(STR_REGRESSION_NAME); desc : string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } item (FEAT_AIRCRAFT, Boeing_2707, 0x14) { property { name: string(STR_NAME_PLANE); introduction_date: date(1978,01,01); // Introduction two years after Concorde model_life: 30; vehicle_life: 30; climates_available: bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); speed: 805 km/h; range: 1024; // same as concorde with typical max payload passenger_capacity: 277; // would have cruised 6480 km at M2.7 with this load } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/032_simple_house.nml0000644000175100001660000000154214754345605017704 0ustar00runnerdockergrf { grfid: "NML\32"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } spriteset (spriteset_townhouse, "nlhs.png") { [98, 8, 44, 36, -22, 0, NOCROP] } spritelayout spritelayout_townhouse { ground { sprite: GROUNDSPRITE_NORMAL; } building { sprite: spriteset_townhouse; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; xextent: 8; yextent: 16; zextent: 27; xoffset: 4; yoffset: 2; zoffset: 0; hide_sprite: 0; } } item (FEAT_HOUSES, item_townhouse, -1, HOUSE_SIZE_1X1) { property { substitute: 2; override: 2; probability: 10; name: string(STR_032_HOUSE); } graphics { default: spritelayout_townhouse; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/033_procedure.nml0000644000175100001660000000200714754345605017176 0ustar00runnerdockergrf { grfid: "NML\33"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } switch(FEAT_INDUSTRIES, SELF, dumb_add, a, b, a + b) { return; } /* some computation that requires registers for intermediate values */ switch(FEAT_INDUSTRIES, SELF, callee, a, (water_distance+layout_num)*(founder_type+founder_colour1)+a) { return a + dumb_add(5, a); } switch(FEAT_INDUSTRIES, SELF, caller2, dumb_add(1,dumb_add(founder_colour2, build_date))*(water_distance+founder_colour1)*(random_bits+callee(1))*(founder_type+layout_num)*(build_type+counter)) { return; } switch(FEAT_INDUSTRIES, SELF, caller1, (founder_colour2+build_date)*dumb_add(random_bits, callee(0))*(build_type+counter)) { return; } item(FEAT_INDUSTRIES, coal_mine) { property { substitute: INDUSTRYTYPE_COAL_MINE; override: INDUSTRYTYPE_COAL_MINE; } graphics { build_prod_change: return caller1; control_special: return caller2; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/034_roadtypes.nml0000644000175100001660000000017014754345605017220 0ustar00runnerdocker item(FEAT_ROADTYPES, regression_road, 0) { property { label: "ROAD"; speed_limit: 130km/h; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/035_switch_scope.nml0000644000175100001660000000162614754345605017710 0ustar00runnerdockergrf { grfid: "NML\35"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } random_switch(FEAT_INDUSTRYTILES, SELF, random1, bitmask(TRIGGER_INDUSTRYTILE_TILELOOP)) { 2: relative_x < 2; 1: relative_y > 5; } switch(FEAT_INDUSTRYTILES, SELF, var1, town_zone) { 0: relative_x < 2; default: relative_y > 5; } random_switch(FEAT_INDUSTRYTILES, PARENT, random2, bitmask(TRIGGER_INDUSTRYTILE_TILELOOP)) { 2: founder_colour1 < 2; 1: founder_colour2 > 5; } switch(FEAT_INDUSTRYTILES, PARENT, var2, production_level) { 0: founder_colour1 < 2; default: founder_colour2 > 5; } item(FEAT_INDUSTRYTILES, coal_mine) { property { substitute: 0; override: 0; } graphics { foundations: random1; autoslope: random2; anim_speed: var1; anim_control: var2; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/036_procedure_scope.nml0000644000175100001660000000255014754345605020375 0ustar00runnerdockergrf { grfid: "NML\36"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } switch(FEAT_INDUSTRIES, PARENT, proc_call_optimisation, population) { 0: return 0; return STORE_TEMP(0,0); } switch(FEAT_INDUSTRIES, SELF, ternary_optimisation, proc_call_optimisation() ? 1 : proc_call_optimisation()) { return 5; } switch(FEAT_INDUSTRIES, SELF, dumb_add, a, b, a + b) { return; } /* some computation that requires registers for intermediate values */ switch(FEAT_INDUSTRIES, PARENT, callee, a, (population+(has_church?500:1))*(num_houses-4*has_stadium)+a) { 0..5: return a + dumb_add(5, a); default: return a + dumb_add(6, a); } switch(FEAT_INDUSTRIES, SELF, caller2, dumb_add(1,dumb_add(founder_colour2, build_date))*(water_distance+founder_colour1)*(random_bits+callee(1))*(founder_type+layout_num)*(build_type+counter)) { return; } switch(FEAT_INDUSTRIES, SELF, caller1, (founder_colour2+build_date)*dumb_add(random_bits, callee(0))*(build_type+counter)) { return; } item(FEAT_INDUSTRIES, coal_mine) { property { substitute: INDUSTRYTYPE_COAL_MINE; override: INDUSTRYTYPE_COAL_MINE; } graphics { build_prod_change: return caller1; control_special: return caller2; construction_probability: return ternary_optimisation; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/037_optimised_trigger.nml0000644000175100001660000000073414754345605020737 0ustar00runnerdockergrf { grfid: "NML\37"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } random_switch(FEAT_TRAINS, SELF, unconditional_switch) { 8: return 5; } random_switch(FEAT_TRAINS, SELF, trigger_only, bitmask(TRIGGER_VEHICLE_NEW_LOAD)) { 8: return 7; } item(FEAT_TRAINS, colored_train, 100) { graphics { colour_mapping: unconditional_switch; default: trigger_only; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/038_optimised_scope.nml0000644000175100001660000000271614754345605020410 0ustar00runnerdockergrf { grfid: "NML\38"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } switch (FEAT_TRAINS, SELF, color_conditional, vehicle_is_flipped) { 1: return base_sprite_2cc + 16 * company_colour1 + company_colour2; return base_sprite_2cc + 16 * company_colour2 + company_colour1; } switch (FEAT_TRAINS, SELF, color_unconditional, 0) { 1: return base_sprite_2cc + 16 * company_colour1 + company_colour2; return base_sprite_2cc + 16 * company_colour2 + company_colour1; } switch (FEAT_TRAINS, SELF, color_indirect1, 1) { 1: return color_conditional; return color_unconditional; } switch (FEAT_TRAINS, SELF, color_indirect0, 0) { 1: return color_conditional; return color_unconditional; } switch (FEAT_TRAINS, SELF, color_indirect, 0) { 1: return color_indirect1; return color_indirect0; } switch (FEAT_TRAINS, PARENT, color_override, vehicle_type_id) { 3000: return color_conditional; return color_indirect; } switch (FEAT_TRAINS, PARENT, text, 0) { return string(STR_REGRESSION_DESC); } switch (FEAT_TRAINS, SELF, scope_capacity, cap, cargo_capacity + cap) { return; } switch (FEAT_TRAINS, PARENT, mixed_scope_capacity, 0) { return scope_capacity(cargo_capacity); } item(FEAT_TRAINS, colored_train, 100) { graphics { colour_mapping: color_override; cargo_capacity: mixed_scope_capacity; additional_text: text; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/039_storage.nml0000644000175100001660000000117114754345605016661 0ustar00runnerdockergrf { grfid: "NML\39"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } switch(FEAT_INDUSTRIES, PARENT, switch_storage2, [ STORE_PERM(LOAD_PERM(0x00) + LOAD_PERM(0x01, "GRID"), 0x00) ]) { return 0; } switch(FEAT_INDUSTRIES, SELF, switch_storage1, [ STORE_PERM(LOAD_PERM(0x00) + 1, 0x00) ]) { return switch_storage2; } item(FEAT_INDUSTRIES, coal_mine) { property { substitute: INDUSTRYTYPE_COAL_MINE; override: INDUSTRYTYPE_COAL_MINE; } graphics { monthly_prod_change: switch_storage1; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/040_station.nml0000644000175100001660000001003114754345605016661 0ustar00runnerdockergrf { grfid: "NML\40"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } cargotable { COAL, LVST } spriteset(station_spriteset, "station.png") { lbl:[ 1, 1, 5, 5, -2, -2] [ 7, 1, 5, 5, -2, -2] } spriteset(station_spriteset2, "station.png") { [ 2, 2, 3, 3, -1, -1] lbl:[ 8, 2, 3, 3, -1, -1] } spritelayout station_sprite_layout_0 { ground { sprite: GROUNDSPRITE_RAIL_X; } building { sprite: 0x42E; xoffset: 0; yoffset: 0; zoffset: 0; xextent: 16; yextent: 5; zextent: 2; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } childsprite { sprite: DEFAULT(0); xoffset: 17; yoffset: 11; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } building { sprite: 0x430; xoffset: 0; yoffset: 11; zoffset: 0; xextent: 16; yextent: 5; zextent: 2; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } childsprite { sprite: station_spriteset(lbl); xoffset: 17; yoffset: 10; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } } spritelayout station_sprite_layout_1(a) { ground { sprite: GROUNDSPRITE_RAIL_Y; } building { sprite: 0x42F; xoffset: 0; yoffset: 0; zoffset: 0; xextent: 5; yextent: 16; zextent: 2; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } childsprite { sprite: station_spriteset(a); xoffset: 20; yoffset: 11; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } building { sprite: 0x42D; xoffset: 11; yoffset: 0; zoffset: 0; xextent: 5; yextent: 16; zextent: 2; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } childsprite { sprite: station_spriteset2(lbl); xoffset: 20; yoffset: 10; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } } item (FEAT_STATIONS, basic_station, 255) { property { class : "TEST"; classname: string(STR_STATION_TEST_CLASS); name : string(STR_STATION_BASIC); general_flags: bitmask(STAT_FLAG_EXTENDED_FOUNDATIONS); cargo_random_triggers: [LVST]; disabled_platforms: bitmask(5, 6, 7, 8); tile_flags: [ 0, bitmask(STAT_TILE_PYLON), bitmask(STAT_TILE_NOWIRE), bitmask(STAT_TILE_PYLON, STAT_TILE_NOWIRE), bitmask(STAT_TILE_BLOCKED), bitmask(STAT_TILE_PYLON, STAT_TILE_BLOCKED), bitmask(STAT_TILE_NOWIRE, STAT_TILE_BLOCKED), bitmask(STAT_TILE_PYLON, STAT_TILE_NOWIRE, STAT_TILE_BLOCKED), bitmask(STAT_TILE_NOWIRE), bitmask(STAT_TILE_PYLON, STAT_TILE_BLOCKED), ]; station_layouts: [ [ [0], ], [ [2], ], [ [4, 5], [6, 7] ], ]; } graphics { foundations: 0; prepare_layout: [STORE_TEMP(0,nearby_tile_station_id(-1,2)), STORE_TEMP(1,1)]; purchase_prepare_layout: STORE_TEMP(3,3); sprite_layouts: [ station_sprite_layout_0, station_sprite_layout_1(1) ]; anim_speed: company_colour1 + company_colour2; LVST: station_spriteset; COAL: station_spriteset2; station_spriteset; } } item (FEAT_STATIONS, basic_station_copied_layout, 256) { property { class : "TEST"; classname: string(STR_STATION_TEST_CLASS); name : string(STR_STATION_BASIC2); station_layouts: basic_station; } graphics { sprite_layouts: basic_station; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/041_articulated_tram_32bpp.nml0000644000175100001660000000462714754345605021551 0ustar00runnerdocker/* A simple articulated tram, graphics from OpenGFX+rv Code is modified in some places for testing reasons. */ grf { grfid: "NML\41"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } template tmpl_tram(x, y) { [ x, y, 8, 18, -3, -10] [ 16 + x, y, 20, 18, -14, -5] [ 48 + x, y, 28, 15, -14, -8] [ 96 + x, y, 20, 18, -6, -7] [128 + x, y, 8, 18, -3, -10] [144 + x, y, 20, 18, -14, -9] [176 + x, y, 28, 15, -14, -8] [224 + x, y, 20, 18, -6, -7] } spriteset(foster_express_set, ZOOM_LEVEL_NORMAL, BIT_DEPTH_32BPP, "tram_foster_express.32.png") { tmpl_tram(48,1) } switch(FEAT_ROADVEHS, SELF, foster_express_articulated_parts, extra_callback_info1) { 1..3: return foster_express_tram; return 0xFF; } item(FEAT_ROADVEHS, foster_express_tram, 88) { property { name: string(STR_NAME_FOSTER_TURBO_TRAM); climates_available: ALL_CLIMATES; model_life: 40; // years vehicle_life: 30; // years introduction_date: date(1965,1,1); reliability_decay: 1; running_cost_base: RUNNING_COST_ROADVEH; // Default road vehicle running cost base running_cost_factor: 135; cost_factor: 143; speed: 317 mph; power: 220 hp; weight: 22 ton; sprite_id: SPRITE_ID_NEW_ROADVEH; // We have our own sprites loading_speed: 16; // loading speed tractive_effort_coefficient: 0.3; air_drag_coefficient: 0.5; cargo_capacity: 45; // passengers refittable_cargo_classes: bitmask(CC_PASSENGERS); // Allow passengers (and tourists) non_refittable_cargo_classes: NO_CARGO_CLASS; // Disallow other cargos cargo_allow_refit: []; default_cargo_type: DEFAULT_CARGO_FIRST_REFITTABLE; misc_flags: bitmask(ROADVEH_FLAG_TRAM); // This is a tram } graphics { articulated_part: foster_express_articulated_parts; foster_express_set; } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/Makefile0000644000175100001660000000373014754345605015555 0ustar00runnerdocker.PHONY: clean all .DEFAULT: _V ?= @ _E ?= @echo _SE ?= echo TEST_FILES = $(basename $(shell ls *.nml)) EXAMPLES = $(shell ls ../examples) NMLC ?= $(abspath ../nmlc) # Note: Manually overriding NML_FLAGS may break the regression test NML_FLAGS ?= -s -c --verbosity=1 .PHONY: $(TEST_FILES) $(EXAMPLES) clean all: $(TEST_FILES) $(EXAMPLES) $(TEST_FILES): $(_V) echo "Running test $@" $(_V) mkdir -p output nml_output output2 # First pass : check compilation of source nml and generation of optimised nml $(_V) $(NMLC) $(NML_FLAGS) --nfo output/$@.nfo --grf output/$@.grf $@.nml --nml nml_output/$@.nml $(_V) diff -u --strip-trailing-cr expected/$@.nfo output/$@.nfo $(_V) diff expected/$@.grf output/$@.grf # Second pass : check compilation of optimised nml $(_V) $(NMLC) $(NML_FLAGS) -n --nfo output2/$@.nfo --grf output2/$@.grf nml_output/$@.nml $(_V) diff -u --strip-trailing-cr expected/$@.nfo output2/$@.nfo $(_V) diff expected/$@.grf output2/$@.grf $(EXAMPLES): $(_V) echo "Testing example $@" $(_V) mkdir -p output nml_output output2 # First pass as above # We must change directory, because nmlc provides no way to set a base directory for realsprite files. $(_V) cd ../examples/$@/ && \ $(NMLC) $(NML_FLAGS) -n \ --nfo ../../regression/output/example_$@.nfo \ --nml ../../regression/nml_output/example_$@.nml \ --grf ../../regression/output/example_$@.grf \ example_$@.nml && cd ../../regression $(_V) diff -u --strip-trailing-cr expected/example_$@.nfo output/example_$@.nfo $(_V) diff expected/example_$@.grf output/example_$@.grf # Second pass $(_V) cd ../examples/$@/ && \ $(NMLC) $(NML_FLAGS) -n \ --nfo ../../regression/output2/example_$@.nfo \ --grf ../../regression/output2/example_$@.grf \ ../../regression/nml_output/example_$@.nml && cd ../../regression $(_V) diff -u --strip-trailing-cr expected/example_$@.nfo output2/example_$@.nfo $(_V) diff expected/example_$@.grf output2/example_$@.grf clean: $(_V) rm -rf output nml_output output2 .nmlcache ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/arctic_railwagons.pcx0000644000175100001660000006002614754345605020325 0ustar00runnerdocker HH׈Xj׈ˆX׈׈׈׈ˆˆĈXˆXX׈Xj׈׈X׈XjXj X׈׈ˆ׈XXXˆXjXjX׈׈X j׈׈XjˆjXj XjX׈XjXjXjXjXjXjXj X׈ˆX j׈ˆj׈Xj XˆX ׈j ׈ˆXj jˆXjX׈Xj jXX ňXˆXj( ׈׈Xj j׈׈XjX׈׈Xj jXX ˈ ׈XjX jˆXjX׈ˆj jXX ˈ ׈XjX j׈Xj Xˆ׈j jXX Xˈ( ׈Xjj׈Xj XjXX Xj!͈jX( X׈Xj XXXX j!׈XˆX׈XˆXjX( X׈Xj X׃ǁ Ɉ!jX!jXjXj!jX!j ׁ׈Xj Xׄǃ Ɉ׃׈Xj X׈XɈXXjXj XˆXˆXj XXj!jXjj!jXX׃ׄ׈XˆXȈmnonmnomnmnlk5nonlmlklklk5omnlkmnnmnomlk5inonmlklklk5k5iomnlkmnnlkomlkl5ino5nnlklk5ioi5mnkmnnlklknmnlkl5no5m5nmno5imkmnnlklkmmnlkl5mn5l5m5i5kiklil5il5il5il5iml5ili5ikimk5knomkmmnlkmn5l5l5mi5i5ik5ilk5ikl5il5im5i5i5i5imk5k5k5nom5immnlklkmn5l5l5mi5i5ik5iklkikl5il5׈kik5imk5k5inomk5k5ini5mnlmn5l5l5lj!ˈjXkik5kimk5inommk5k5ini5kmn5l5l5lj!XˆXɈXˆXjXkikilimnomlmk5oi5klk5l5lj!j!XXj!j!kiklkilnk5k5oi5klklkilil׈mk5oXˆ5kklˆXmk5oXˆˆXnk5k5oXj!jXɈXnmnoj!jXi5klmni5klmi5kƈɈɈ؈X؈ƈˈX؈ƈX׈ƈZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`bZ`bZ`bZ`bZ`bZ`bZ`bZ`bZ`bY`ZYZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZYcb[Z`QYZbcZY`ZYbc[Z``ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZaZcb[ZQ`bYZbY`ZYbc[Z[`Y`ZLjYZYZ`aZcb[Z`QYZbcZY`ZYbc[Z[`YZ`bYbYb`Z`ZYY`aZcb[ZQ`bYZbY`ZYbc[Z[X`ZY`bYbYbˆ`Y`YZYY`Z`X`aZcb[Z`QYZbcZZYbc[Z[XX`YZ`bYZbYb؈`Z`ZYY`Y`X`aZcbQ`bYZb[Z[YbX؈`ZYbYZbZYb؈`Y`YbYY`Z`XXY`a`QYZbcZYbYZˆ`YjZYbZ`Z`bZY`Y`ˆX`YQ`bYZbYZYbˆ؈`j׈YLj`Y`Y؈`ˆ``QYZbcZYbYb؈ jj jj ׈׈`YQ`bYZbYbYZ׈``QYZbcZbZ`Y`YYb`ʈjɈjƈmnonmnomnmnlˆnonl׈XLjXˆXˆX5omnkmnn׈Xoml׈Xnon׈ˆ׈׈׈׈XXXomnˆˆkmnnˆoml׈XXno5nnˆ׈׈׈׈Xoi5mnXˆkmnnXnmnl׈Xˆno5m5nmno5imXˆkmnnÈmmnl׈XÈmn5l5m5i5kiklil5il5il5il5iml5ili5ikimXˆknomXˆmmnl׈X׈mn5l5l5mi5i5ik5ilk5ikl5il5im5i5i5i5imˆX5nomÈmmnl׈Xmn5l5l5mi5i5ik5iklkikl5il5׈kik5imÈXinomXˆni5mn׈mn5l5l5lj!ˈjXkik5kimÈXnommÈni5kmn5l5l5lj!XˆXXˆXjXkikilimnomlm׈X׈oi5klk5l5lj!j!XXj!j!kiklkilnÈoi5klklkilil׈mXˆoXˆ5kklˆXmÈoXˆˆXn׈X׈oXj!jXɈXnmnoj!jXi5klmni5klmi5kƈ                  ( X X  ( X!! (XˆX(X  ( ( XjXjj!j!X ( ( ( ˆ( X ˆ ˆ( XX ˆ ˆXXˆ (( ( (( ( ƈZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`bZ`bZ`bZ`bZ`bZ`bZ`bZ`bZ`bY`ZYZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZYcb[Z`QYZbcZY`ZYbc[Z`ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZY`ZaZcb[ZQ`bYZbY`ZYbc[Z[`ZYZYZ`aZcb[Z`QYZbcZY`ZYbc[Z[`ZbYbYb`Z`bYZbYZYY`aZcb[ZQ`bYZbY`ZYbc[Z[`YZbYbYb`Y`YZYZbZYZYY`Z`Y`aZcb[Z`QYZbcZZYbc[Z[`Y`ZbZbYZbYb`Z`bYbYZYY`Y`Y`aZcbQ`bYZb[Z[Yb`YZ`YZbYZbZYb`Y`YbYZbYY`Z`Y`Y`a`QYZbcZYbYZ`Z`Y`ZYjZYbZ`Z`bYZbZYbZY`Y`Y`YQ`bYZbYZYb`YZYj׈Y`Y`YY׈`Y`Y``QYZbcZYbYb`Z`Y jj jj `Y`YZ`YQ`bYZbYbYZ`Y`ZY`Y`QYZbcZbZ`YZ`YYb`ʈjɈjƈ 4 H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTE4QQFIb̚=b{ iޝEA"mLsUiJq5_ 5(-(!eQ 1*mb3CiX)•BʻthXSZ9bwW "jcLUK,+шpN#QWeK9!QkJqQ"h:>Ī>hJ1RThiE]|lrOZc NH"Eqckc$kA犊h(u\$6IIH""PggUnXw&6ˬIb8!:!9wʾwogci4I)%D#bDQi~cZw~6V{ߜ,PZve\ aj^w21>jot^.JQǮ+:b%`ɟ5Q4֬n+վ u깔RJJOk2iɴxVg&@WRRnXMo8dRѬ*)ը'sLIߨ\tjr|aM&WI\AHh T JJ/U媲اvo2L& WXZLx&Jz0t:;Xk\{WvYfm%`*R6]3:w&366{kp RԄhiRhv`^FɛU}1I{& #Q&P%7Ө'L0_FU׭C51 ^=(H)"j#D[c֓@IncXk5cbJmHVEtA4Rј(Bl_IljLU@J?t],&)=*^/!"S\g9))$ƘʥkИYEa!6v]o (Ei宝LL`94"a DGiZ^eT:)kAV4镻*ҩi宫43}K1E4V뺈Xޔs5ơERyv:ơZ^W7*wXi!F=.Mqw]R.˛9)틬h)Z.oXnX5UneEIUT%98Zw7Er9F&8^"czRWԽw-{ʳbQU46ɾY(I,uqBHbQD6[ғUncXks\ rBBCGIVr db5uUY1?7Խ]kKjтVYbꫪr!$tՓX?JG¹"*QƮp[ߪkn>vl?UezwXj* ]U+!䴬ʒlqm%kY1BD٭<|b7ۆL Nc~a;B5׺\Ӳ*D[\[X|*F#GI 2jlwy. ګW4:_wUp\*\R#RFcX Xx˵2ju1XA솥_{ȫ|y>)\["J!$㸥,!`8#2C2ὐ5L-U=Z[(dxbk.WWw)e "QTFؔ,pt]'"7˺nT#Ρ隬Tէy%~kT3u8svQf v#Qn]4F5h9t:\R5\]<4W*U' cg~ ].ks㬙IՌGݰRuL 8zwF6JI84^B]ϙVɸ5c8<<<uƒfmS 8~;oj|d 8d3Qb/b3Umw5b ZQ99%9m׳ܺi TmxdɹSrGcHZl /xW\4R?%6G0 `&궞/ UѴkc*v,Ⱥ?ZE@ᔃ:x ~9[\@틬A{xUSUK)T;"yWaf}bU\?u?«cB7FG.$0/y~K^mJ6Jɫf"+%%xu㱒|eLZ.0ˣput|?k1^u|}ha٬>k;/bU^./5}~Qc)ȧ/_/_R-ײƻš)S*_}= b=³{X l̫枪xP˫+%rq9]? jC]䪫:z5<%zg9LI%q bj9>hJz #mTR9x~:MA?X sѬFݟR 3Ppr@ɺͦo>NWɪgp%~?_a.@y5o~mF3h` 5|y<Ϭ 9n+>.v5jz0 flU^ _^#Aףx`^~۲^׻Fє{)=t3^]wx h<~qym^dW,N^y hp8ٛ^~Axce/>}~zcF#Ap0;y\_޼0 n=/YSʶ8POeX Wo?9F /jJb>\mzժ ޼ ~Cuみ_ Cw,jU>̿ H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTE4ȟ *B$s "mK[b"DHt$i~FҗKTnNun-aLg:-X4(ljE,tԲgWz ܏,;kXKxn-"Yk0 d{yzmNscիw7a9Lyn,oٳeX5B̲T-E:g(bY %"8:jH3)%{%*rG) Sҳ, +&@A@anT{˰"*1 r"R% c8)i 5vX*eaUTQItquzhE`"`5JJ#UF-4 C[>ӱc"g~cH fFmahmYaܢ*CDh-"!YWպgY[rΕaU \ݣ{sTHU(X0+ժ\J[êm j)KT1L. /̨묲jUّ-7ЭIc ˖`Q7e2Z2 C[~2ֶU?ӸwEtY2 :[ }Y-ֽgtT,, rgڼiq 4@L۸BGe0Bk)4m{q-?pk]Z2}9X Nn+^i7''vp5"Q4^eh-Uݓ(u0 Ӽ[Ƣ}| :AE"NZ{]_$ED 2jXL,D:[d˳0 Ot[Xpк z*7u2]Zkj7o2 2R\.XA-:DN=ȖYfI={+-Yfk&Gvt#ۉ(ɖgax%,uC-fV]˗UzⷶAP'ʐ։kpneff᛹6-ODҳlKL vkuHA0N79#2: )pB7xԢT݁R[oK4a"ke qip^Q:u5SF8ٛQXWm6g/tlxAGd`)ϲp/7hn3rTltȰUE>S7̷(e ~/;Pc&=(jɀx/&M?MMrm4#ӼPp8rm!uf3*ϥf*@9[s<=/+ ScX B`f`8glFY1'CnaG_꼘08u֩*dcAQB^ϙ|ݫԧy#8<<<ƒZ6bg5ŨC}^LXa U!u 3>2KqzUMBË!hM6)2t"a{<(pdm0UGz6M=/ؤR*o,6Yrnթ2 VoZG pvQ煔 7afbATM>;hn& Um#u?URl)YA ^c樎9ozՁ uֺ9Q5MPp{{tttt>Yj4=/\J)q܏'f);ujx8qu sfgG!tʕK`F*8:z.-H,W[ZLESJJ֛0|f8M2jD +1bF&d. ѻxP 磺wm T.eΦY|ɀM2pxt?jT) 5&Fx0oRtѶBJ%w~BԽ䷟ΆBhJx&%]URRI)煐-m)T GBֽʨw;PXB4%4F ~A,-= /UWqW9FFzPy|Y/zU~NM C{,jdT<º2j<7VQq]ejdԷX leUv̅x*SmX ~ĭX?*ccy,<cy,<cy,<cy,<cy,+Gq=(IENDB`././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0904627 nml-0.7.6/regression/expected/0000755000175100001660000000000014754345610015707 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/001_action8.grf0000644000175100001660000000022414754345605020336 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/001_action8.nfo0000644000175100001660000000121214754345605020340 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d2 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\1" "NML regression test" 00 "A test newgrf testing NML" 00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/002_sounds.grf0000644000175100001660000000035414754345605020311 0ustar00runnerdockerGRF  I J I K L J4Vx 4Vx If KL3beef.wav beef.wav././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/002_sounds.nfo0000644000175100001660000000163314754345605020316 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[0] = 73 0 * 9 0D 00 \D= FF 00 \dx00000049 // param[1] = 74 1 * 9 0D 01 \D= FF 00 \dx0000004A // param[2] = 73 2 * 9 0D 02 \D= FF 00 \dx00000049 // param[3] = 75 3 * 9 0D 03 \D= FF 00 \dx0000004B // param[4] = 76 4 * 9 0D 04 \D= FF 00 \dx0000004C // param[5] = 74 5 * 9 0D 05 \D= FF 00 \dx0000004A 6 * 3 11 \w4 7 ** beef.wav 8 * 8 FE 00 \dx78563412 \wx0003 9 ** beef.wav 10 * 8 FE 00 \dx78563412 \wx0003 11 * 9 00 0C \b1 01 FF \wx0049 08 66 12 * 10 00 0C \b1 02 FF \wx004B 08 4C 33 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/003_assignment.grf0000644000175100001660000000033214754345605021143 0ustar00runnerdockerGRF               ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/003_assignment.nfo0000644000175100001660000000233114754345605021150 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[0] = 3 0 * 9 0D 00 \D= FF 00 \dx00000003 // param[1] = 4 1 * 9 0D 01 \D= FF 00 \dx00000004 // param[2] = (param[0] + param[1]) 2 * 5 0D 02 \D+ 00 01 // param[3] = (param[0] * 3) 3 * 9 0D 03 \D* 00 FF \dx00000003 // param[4] = 11 4 * 9 0D 04 \D= FF 00 \dx0000000B // param[5] = (param[4] & 3) 5 * 9 0D 05 \D& 04 FF \dx00000003 // param[127] = (param[1] + param[2]) 6 * 5 0D 7F \D+ 01 02 // param[6] = (param[127] + param[3]) 7 * 5 0D 06 \D+ 7F 03 // param[127] = param[3] 8 * 5 0D 7F \D= 03 00 9 * 7 06 7F 01 FF \wx0003 FF // param[7] = param[0] 10 * 5 0D 07 \D= 00 00 11 * 7 06 00 01 FF \wx0001 FF // param[0] = 5 12 * 9 0D 00 \D= FF 00 \dx00000005 // param[127] = param[3] 13 * 5 0D 7F \D= 03 00 14 * 12 06 00 01 FF \wx0001 7F 01 FF \wx0003 FF // param[0] = param[0] 15 * 5 0D 00 \D= 00 00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/004_deactivate.grf0000644000175100001660000000006114754345605021104 0ustar00runnerdockerGRF  ABCD EFGH././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/004_deactivate.nfo0000644000175100001660000000075214754345605021117 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 0E \b1 \dx44434241 1 * 10 0E \b2 \dx04030201 \dx48474645 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/005_error.grf0000644000175100001660000000125714754345605020135 0ustar00runnerdockerGRF   zorg care  }  ~}B De wissels zijn bevroren, onze excuses voor het ongemak.42~* Something bad (tm) has happened.42~ ~ ~  = De wissels zijn bevroren, onze excuses voor het ongemak.% Something bad (tm) has happened. ~ ~  = De wissels zijn bevroren, onze excuses voor het ongemak.% Something bad (tm) has happened. ~ ~  = De wissels zijn bevroren, onze excuses voor het ongemak.% Something bad (tm) has happened.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/005_error.nfo0000644000175100001660000000366414754345605020145 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 9 0B 00 1F 02 "zorg" 00 1 * 9 0B 00 7F 02 "care" 00 // param[127] = 14 2 * 9 0D 7F \D= FF 00 \dx0000000E // param[125] = (param[2] * 12) 3 * 9 0D 7D \D* 02 FF \dx0000000C // param[126] = (param[1] + param[125]) 4 * 5 0D 7E \D+ 01 7D 5 * 66 0B 03 1F FF "De wissels zijn bevroren, onze excuses voor het ongemak." 00 "42" 00 7F 7E 6 * 42 0B 03 7F FF "Something bad (tm) has happened." 00 "42" 00 7F 7E // param[126] = param[161] 7 * 5 0D 7E \D= A1 00 // param[127] = (param[126] - 453509120) 8 * 9 0D 7F \D- 7E FF \dx1B080000 // param[127] = (param[127] << -31) 9 * 9 0D 7F \Du<< 7F FF \dxFFFFFFE1 10 * 9 09 7F 04 \7= \dx00000000 02 11 * 61 0B 03 1F 06 "De wissels zijn bevroren, onze excuses voor het ongemak." 00 12 * 37 0B 03 7F 06 "Something bad (tm) has happened." 00 // param[126] = param[161] 13 * 5 0D 7E \D= A1 00 // param[127] = (param[126] - 469762048) 14 * 9 0D 7F \D- 7E FF \dx1C000000 // param[127] = (param[127] << -31) 15 * 9 0D 7F \Du<< 7F FF \dxFFFFFFE1 16 * 9 09 7F 04 \7= \dx00000000 02 17 * 61 0B 03 1F 06 "De wissels zijn bevroren, onze excuses voor het ongemak." 00 18 * 37 0B 03 7F 06 "Something bad (tm) has happened." 00 // param[126] = param[161] 19 * 5 0D 7E \D= A1 00 // param[127] = (param[126] - 536870912) 20 * 9 0D 7F \D- 7E FF \dx20000000 // param[127] = (param[127] << -31) 21 * 9 0D 7F \Du<< 7F FF \dxFFFFFFE1 22 * 9 09 7F 04 \7= \dx00000000 02 23 * 61 0B 03 1F 06 "De wissels zijn bevroren, onze excuses voor het ongemak." 00 24 * 37 0B 03 7F 06 "Something bad (tm) has happened." 00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/006_vehicle.grf0000644000175100001660000001506514754345605020426 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSWBBLTR34NMLNML regression testA test newgrf testing NML  PASSMAILGOODIOREGOLDFOOD } @ ~ ~,?Y(  HL XMYFoster Express TramYFoster Sneltram    &  ~## 66 Yw  0YjbȃʃȄʄH{ǵ )-~>Roɷ#aǨ(2(7(UK(P-ؑPx؈(͈(@Yx(hٕ#qȉ#jF 4,ρ(;-EXbgXڏ|ͩw  0YjbȃʃȄʄH{ǵ )ˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H8k #2-~>oɷRˈP_n}aZ_Z_dnÈ҈Zع_d܉'6ZU@j ;٤ ;4h ZZB{i!ZZv&#qȈq{diڙ‰6Zհٕ,ρnljˆӉ0X"k‰˰ZdⲻِdhZB-~ɰ" #2AP\RpؘجU7*Єoɷ̈\XWkK#in 9CpaukuN莉K9HWfu&ȉڜ%ۀ ,ρ4;(-~ %/*%*499CH؆C4,ρRΈR";W\aLj 4%^RCff¸a#qшщkz f ׈kۉ։҆fpXkpڟ %ۀ50k̉K\ڌDFsaARNJRS˚PCfֻ4 >H r*hj ؈3ː@̸̠˸, ɨ,UA,́XXT ǀjXBf+nǁ/XʀXf4(BXʙ#jyj  10ʁIɀ :M44a j# a-~4 >#((RAKAP#oɷZ((x(؛P}x(PsȈ(EsPmٟ,ρ %ۀm#7 4;ѕA##('4Oۉwyj  10ʁIɀ :M44a j ؈$&ːL˨ +5$ ʄXGk[ɀZɀ+H| ˂XȀɸ$ XjɁɅ5:CHH`)/&-4;LvIl88vj؁ -~Rˈ a> %**/%oɷ̰ 9c99\ ;4\ӰR̸RB{aJӉ*WWۈ\2f%ۀ\7-4R x^' j ؈$&ːL˨ +5$ ʄXGk[ɀZɀ+H| ˂XȀɸ$ XjɁɅ5:CHH`) j؈6 l !XX$7 !%4 /(-Tzq́˃ʀ 蹈Tпin XXXXИjj`%*6B1Hd j ؂ -~ #2APrd>ʙ Rˈ(>kk_d oɷ /RX_/ԉ莈zňԈ㉏Ȏ%ۀωމ[je. ,ρИ ;4( (7FUdU_oɷnдdn܉-~ɨi'׈܉w4,ρin }EJ j;inaLjxEN#qdnn܈҉d'܈܉Tڔ׉nJ5W\_XiO&N0nՊq&ڔUdNLnX*߲UԌ{YH#2$x⏽дZ׍&숴6 ̠+,AjAXȃW;,UPBBX,X ʃjX,ʄjXX<ʵXȹXʵ$././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/006_vehicle.nfo0000644000175100001660000001024114754345605020421 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d28 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "3" 00 00 2 * 52 08 08 "NML\6" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 32 00 08 \b1 06 FF \wx0000 09 "PASS" "MAIL" "GOOD" "IORE" "GOLD" "FOOD" // param[0] = 9 4 * 9 0D 00 \D= FF 00 \dx00000009 // Name: switch_length // d : register 80 5 * 31 02 01 FF 89 7D 80 20 \dxFFFFFFFF // d \2+ 40 00 \dx000000FF \b1 \wx8005 \dx00000001 \dx00000001 // 1 .. 1: return 5; \wx8006 // default: return 6; // param[126] = (param[0] * 5) 6 * 9 0D 7E \D* 00 FF \dx00000005 // param[127] = (param[126] + 5) 7 * 9 0D 7F \D+ 7E FF \dx00000005 8 * 7 06 7F 01 FF \wx002C FF 9 * 63 00 01 \b21 01 FF \wx0059 06 03 04 28 03 1E 1F \dx000AF386 02 01 0A \dx00004C48 09 87 11 8F 08 B4 13 16 14 58 0E FF 07 10 18 4D 19 80 0F 00 1D \wx0003 16 \dx00000000 1E \wx0000 16 \dx00000000 1C 01 10 * 27 04 01 7F 01 FF \wx0059 "Foster Express Tram" 00 11 * 23 04 01 1F 01 FF \wx0059 "Foster Sneltram" 00 12 * 6 01 01 \b1 FF \wx0008 13 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 normal | opengfx_generic_trams1.png 32bpp 48 56 8 18 -3 -10 normal | opengfx_generic_trams1.pcx mask 48 56 | opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 zi2 14 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal | opengfx_generic_trams1.png 32bpp 64 56 20 19 -14 -5 normal | opengfx_generic_trams1.pcx mask 64 56 | opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 zi2 15 opengfx_generic_trams1.pcx 8bpp 96 56 28 15 -14 -8 normal | opengfx_generic_trams1.png 32bpp 96 56 28 15 -14 -8 normal | opengfx_generic_trams1.pcx mask 96 56 | opengfx_generic_trams1.pcx 8bpp 96 56 28 15 -14 -8 zi2 16 opengfx_generic_trams1.pcx 8bpp 144 56 20 19 -6 -7 normal | opengfx_generic_trams1.png 32bpp 144 56 20 19 -6 -7 normal | opengfx_generic_trams1.pcx mask 144 56 | opengfx_generic_trams1.pcx 8bpp 144 56 20 19 -6 -7 zi2 17 opengfx_generic_trams1.pcx 8bpp 176 56 8 18 -3 -10 normal | opengfx_generic_trams1.png 32bpp 176 56 8 18 -3 -10 normal | opengfx_generic_trams1.pcx mask 176 56 | opengfx_generic_trams1.pcx 8bpp 176 56 8 18 -3 -10 zi2 18 opengfx_generic_trams1.pcx 8bpp 192 56 20 19 -14 -9 normal | opengfx_generic_trams1.png 32bpp 192 56 20 19 -14 -9 normal | opengfx_generic_trams1.pcx mask 192 56 | opengfx_generic_trams1.pcx 8bpp 192 56 20 19 -14 -9 zi2 19 opengfx_generic_trams1.pcx 8bpp 224 56 28 15 -14 -8 normal | opengfx_generic_trams1.png 32bpp 224 56 28 15 -14 -8 normal | opengfx_generic_trams1.pcx mask 224 56 | opengfx_generic_trams1.pcx 8bpp 224 56 28 15 -14 -8 zi2 20 opengfx_generic_trams1.pcx 8bpp 272 56 20 19 -6 -7 normal | opengfx_generic_trams1.png 32bpp 272 56 20 19 -6 -7 normal | opengfx_generic_trams1.pcx mask 272 56 | opengfx_generic_trams1.pcx 8bpp 272 56 20 19 -6 -7 zi2 // Name: foster_express_set - feature 01 21 * 9 02 01 FE \b1 \b1 \w0 \w0 // Name: @CB_FAILED_REAL01 22 * 9 02 01 FD \b1 \b1 \w0 \w0 // Name: @CB_FAILED01 23 * 23 02 01 FD 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FD // Non-graphics callback, return graphics result // Name: @return_action_0 24 * 20 02 01 FC 89 1A 20 \dx00000008 \2- 1C 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value // Name: @action3_1 25 * 38 02 01 FC 89 1A 20 \dx00000000 \2sto 1A 20 \dx00000080 \2r 7E FF 00 \dxFFFFFFFF // switch_length(0) \b1 \wx00FD \dx0000FFFF \dx0000FFFF // @CB_FAILED01; \wx00FC // return (8 - var[0x1C, 0, -1]) // Name: @action3_0 26 * 23 02 01 FC 89 10 00 \dx000000FF \b1 \wx00FC \dx00000023 \dx00000023 // @action3_1; \wx00FE // foster_express_set; // Name: @action3_2 27 * 23 02 01 FE 89 0C 00 \dx0000FFFF \b1 \wx00FC \dx00000036 \dx00000036 // @action3_0; \wx00FE // foster_express_set; 28 * 9 03 01 01 FF \wx0059 \b0 \wx00FE // @action3_2; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/007_townnames.grf0000644000175100001660000000063014754345605021013 0ustar00runnerdockerGRF   6CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML4 small mediumbig village towncitytiny village12345ABCDE-TUS StationsTest stationnnetjesÞTeßt Stätiöµſ  MainCapital././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/007_townnames.nfo0000644000175100001660000000241414754345605021021 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d9 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\7" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 52 0F 00 // A 02 04 00 05 0A "small" 00 0A "medium" 00 02 "big" 00 01 "" 00 03 05 05 0D "village" 00 0A "town" 00 01 "city" 00 4 * 20 0F 01 // 1 01 01 00 01 01 "tiny village" 00 5 * 21 0F 02 // prefixes 01 05 00 03 01 "1" 00 01 "2" 00 01 "3" 00 01 "4" 00 01 "5" 00 6 * 21 0F 03 // bodies 01 05 03 03 01 "A" 00 01 "B" 00 01 "C" 00 01 "D" 00 01 "E" 00 7 * 8 0F 04 // simple 01 01 06 01 81 03 8 * 19 0F 05 // complex 03 01 06 01 81 02 01 06 01 01 "-" 00 01 06 01 81 03 9 * 84 0F 86 00 "US Stations" 00 1F "Test stationnnetjes" 00 7F "ÞTeßt Stätiöµſ" 00 00 01 05 0A 05 0A "MainCapital" 00 85 00 81 01 81 04 88 05 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/008_railtypes.grf0000644000175100001660000000006114754345605021013 0ustar00runnerdockerGRF  RAIL(ELRL././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/008_railtypes.nfo0000644000175100001660000000075414754345605021030 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 21 00 10 \b3 01 FF \wx0000 08 "RAIL" 14 \wx0028 0E \b1 "ELRL" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/009_replace.grf0000644000175100001660000000165614754345605020426 0ustar00runnerdockerGRF   6CINFOBVRSNBMINVBNPARBPALSWBBLTR84NML NML regression testA test newgrf testing NML       w  0YjbȃʃȄʄH{ǵ )w  0YjbȃʃȄʄH{ǵ )6  -QYYYY 3   - YYYY ^ #<Z7xT<ܰ67Tc 4  GGGGЊ 2   GGGG ` 4;Nh9TЖʈ䠂26Lf././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/009_replace.nfo0000644000175100001660000000207514754345605020426 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d12 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\9" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 5 0A \b1 \b2 \w3092 4 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 normal 5 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 4 normal 6 * 8 05 89 FF \w6 FF \w0 7 oneway.png 8bpp 18 8 24 16 -12 -8 normal 8 oneway.png 8bpp 50 8 24 16 -12 -8 normal 9 oneway.png 8bpp 82 8 28 16 -14 -8 normal 10 oneway.png 8bpp 114 8 24 16 -10 -8 normal 11 oneway.png 8bpp 146 8 24 16 -10 -8 normal 12 oneway.png 8bpp 178 8 28 16 -12 -8 normal ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/010_liveryoverride.grf0000644000175100001660000001252514754345605022052 0ustar00runnerdockerGRF  #6CINFOBVRSNBMINVBNPARBPALSWBBLTR84NMLNML regression testA test newgrf testing NML         '  '()  9/!@Y`P0JhPq Șɠ&̸9 ʈ N)b? Xa   <  d  (a  (& !Ў( (  9_1:  ( ǘ': X ,.;<A̾) j<8D j:?BK (X        >  (! X( (! ((((X !!!!!jj X׈ ʈˈ-H1{ (((]^D X!!(X !(( (Xj(WjXj!!  j(!Xjj- j X *.Z XXjXر ؈XZ؈ס!ȈטFF   &7 (8OQ/Z   `Y#(  jX! j j j؈Xjj j؈  ƈɈ&>',I7)(K6I  (s  !׈( jX (؈jjj  ( ؈ & Xة׉/A 5  ( ! (T@#CF ʈX!-K̈j ˸L!|(  j    Xa  ! ! (!!!(( ( ׈ jj Xj  ɘ . E,!!* !!\rY). X j[ Xjj !.XXjD(k !((ظ)ש DX& ׈׈XjXj& j E8ju ׃Xˈ ׈XXX(XjXj (((D jXxR(נ XXjj!XXx( j!׃dׄxX9ɰXX-xTU  ׈ XXXXjjjj:jj \&)   omG b     GM! !j Xjj ׈X 0׈j0( _HXjX `ɠXɰHjjjXȸ`jXFxׁ׃0XCnr׈׈XjXj& j E8ju ׃Xˈ ׈XXX(XjXj (((D jXxR(נ XXjj!XXx( j!׃dׄxX9ɰXX-xTU  ׈ XXXXjjjj:jj \&)   omG b     GM! !j Xjj ׈X 0׈j0( _HXjX `ɠXɰHjjjXȸ`jXFxׁ׃0XCnrf  0@P^ni r  ƈɈʰ;*6&  (n6  !p   jH  6 H ! ׈ס     $<:4@G      !     (! (!! (XX  Xj ʈːʈɈa(((X!!Xo X !(!Bo*j jB-jjBXB((B('װX!Ƙ!96f  0@P^ni r  ƈɈʰ;*6&  (n6  !p   jH  6 H ! ׈ס     $<:4@G      !     (! (!! (XX  Xj ʈːʈɈa(((X!!Xo X !(!Bo*j jB-jjBXB((B('װX!Ƙ!96././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/010_liveryoverride.nfo0000644000175100001660000000515514754345605022057 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d35 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\10" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 6 01 00 \b3 FF \wx0008 4 opengfx_trains_start.pcx 8bpp 142 112 8 22 -3 -10 normal 5 opengfx_trains_start.pcx 8bpp 158 112 21 15 -14 -7 normal 6 opengfx_trains_start.pcx 8bpp 190 112 31 12 -16 -8 normal 7 opengfx_trains_start.pcx 8bpp 238 112 21 16 -6 -7 normal 8 opengfx_trains_start.pcx 8bpp 270 112 8 24 -3 -10 normal 9 opengfx_trains_start.pcx 8bpp 286 112 21 16 -15 -6 normal 10 opengfx_trains_start.pcx 8bpp 318 112 32 12 -16 -8 normal 11 opengfx_trains_start.pcx 8bpp 366 112 21 15 -6 -7 normal 12 arctic_railwagons.pcx 8bpp 0 0 8 24 -3 -12 normal 13 arctic_railwagons.pcx 8bpp 16 0 22 17 -14 -9 normal 14 arctic_railwagons.pcx 8bpp 48 0 32 12 -16 -8 normal 15 arctic_railwagons.pcx 8bpp 96 0 22 17 -6 -9 normal 16 arctic_railwagons.pcx 8bpp 0 0 8 24 -3 -12 normal 17 arctic_railwagons.pcx 8bpp 16 0 22 17 -14 -9 normal 18 arctic_railwagons.pcx 8bpp 48 0 32 12 -16 -8 normal 19 arctic_railwagons.pcx 8bpp 96 0 22 17 -6 -9 normal 20 opengfx_trains_start.pcx 8bpp 142 139 8 21 -3 -10 normal 21 opengfx_trains_start.pcx 8bpp 158 139 20 15 -13 -7 normal 22 opengfx_trains_start.pcx 8bpp 190 139 28 10 -12 -6 normal 23 opengfx_trains_start.pcx 8bpp 238 139 20 16 -6 -7 normal 24 opengfx_trains_start.pcx 8bpp 270 139 8 21 -3 -10 normal 25 opengfx_trains_start.pcx 8bpp 286 139 20 15 -15 -6 normal 26 opengfx_trains_start.pcx 8bpp 318 139 28 10 -16 -6 normal 27 opengfx_trains_start.pcx 8bpp 366 139 20 16 -6 -7 normal // Name: turbotrain_engine_group - feature 00 28 * 9 02 00 FF \b1 \b1 \w0 \w0 // Name: normal_passenger_group - feature 00 29 * 9 02 00 FE \b1 \b1 \w1 \w1 // Name: turbotrain_passenger_group - feature 00 30 * 9 02 00 FD \b1 \b1 \w2 \w2 31 * 11 00 00 \b2 01 FF \wx0014 12 FD 27 04 32 * 9 03 00 01 FF \wx0014 \b0 \wx00FF // turbotrain_engine_group; 33 * 9 03 00 81 FF \wx001B \b0 \wx00FD // turbotrain_passenger_group; 34 * 27 00 00 \b6 01 FF \wx001B 12 FD 27 04 28 \wx0001 1D \dx00000000 29 \wx0000 1D \dx00000000 35 * 9 03 00 01 FF \wx001B \b0 \wx00FE // normal_passenger_group; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/011_snowline.grf0000644000175100001660000000104114754345605020626 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML  !#%'(*,./////13578:<>?ACEFHJLMOQSTVXZ[]_abdffhjkmoqrtvxy{}|yurnkgd`]YURNKGD@=952.+'$  ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/011_snowline.nfo0000644000175100001660000000345714754345605020647 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d3 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\11" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 392 00 08 \b1 01 FF \wx0000 10 06 06 06 05 05 05 05 05 04 04 04 04 04 04 03 03 03 03 03 02 02 02 02 02 01 01 01 01 01 00 00 00 00 02 04 05 07 09 0B 0C 0E 10 12 13 15 17 19 1A 1C 1E 20 21 23 25 27 28 2A 2C 2E 2F 2F 2F 2F 2F 31 33 35 37 38 3A 3C 3E 3F 41 43 45 46 48 4A 4C 4D 4F 51 53 54 56 58 5A 5B 5D 5F 61 62 64 66 66 68 6A 6B 6D 6F 71 72 74 76 78 79 7B 7D 7F 80 82 84 86 87 89 8B 8D 8E 90 92 94 95 97 99 9B 9B 9B 9D 9E A0 A2 A4 A5 A7 A9 AB AC AE B0 B2 B3 B5 B7 B9 BA BC BE C0 C1 C3 C5 C7 C8 CA CC CE D0 D1 D1 D3 D5 D7 D8 DA DC DE DF E1 E3 E5 E6 E8 EA EC ED EF F1 F3 F4 F6 F8 FA FB FD FF FE FC FB FA FA FA F8 F7 F6 F5 F3 F2 F1 EF EE ED EB EA E9 E7 E6 E5 E4 E2 E1 E0 DE DD DC DA D9 D8 D6 D5 D4 D2 D1 D1 D0 CF CD CC CB C9 C8 C7 C5 C4 C3 C1 C0 BF BE BC BB BA B8 B7 B6 B4 B3 B2 B0 AF AE AD AB AA A9 A9 A7 A6 A5 A3 A2 A1 9F 9E 9D 9B 9A 99 98 96 95 94 92 91 90 8E 8D 8C 8A 89 88 87 85 84 83 81 81 81 80 7C 79 75 72 6E 6B 67 64 60 5D 59 55 52 4E 4B 47 44 40 3D 39 35 32 2E 2B 27 24 20 1D 19 16 16 12 12 12 11 11 11 11 11 10 10 10 10 10 0F 0F 0F 0F 0F 0E 0E 0E 0E 0E 0E 0D 0D 0D 0D 0D 0C 0C 0C 0C 0C 0C 0C 0B 0B 0B 0B 0B 0A 0A 0A 0A 0A 09 09 09 09 09 08 08 08 08 08 07 07 07 07 07 06 06 06 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/012_basecost.grf0000644000175100001660000000064014754345605020600 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML  * B ~ ~~ ~ ~ ~  4 | | } z z { |{ }| ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/012_basecost.nfo0000644000175100001660000000305014754345605020602 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d20 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\12" "NML regression test" 00 "A test newgrf testing NML" 00 // param[127] = (param[2] + 8) 3 * 9 0D 7F \D+ 02 FF \dx00000008 4 * 7 06 7F 01 FF \wx0008 FF 5 * 9 00 08 \b1 01 FF \wx0001 08 00 6 * 14 00 08 \b1 06 FF \wx002A 08 06 06 06 06 06 06 7 * 13 00 08 \b1 05 FF \wx0042 08 06 06 06 06 06 // param[126] = (param[1] + 9) 8 * 9 0D 7E \D+ 01 FF \dx00000009 9 * 27 06 7E 01 FF \wx0008 7E 01 FF \wx0009 7E 01 FF \wx000A 7E 01 FF \wx000B 7E 01 FF \wx000C FF 10 * 13 00 08 \b1 05 FF \wx000F 08 00 00 00 00 00 11 * 9 00 08 \b1 01 FF \wx0034 08 15 // param[124] = param[11] 12 * 5 0D 7C \D= 0B 00 13 * 7 06 7C 01 FF \wx0003 FF // param[125] = param[0] 14 * 5 0D 7D \D= 00 00 // param[122] = (param[11] + 1) 15 * 9 0D 7A \D+ 0B FF \dx00000001 16 * 7 06 7A 01 FF \wx0003 FF // param[123] = param[0] 17 * 5 0D 7B \D= 00 00 // param[124] = (param[123] + 8) 18 * 9 0D 7C \D+ 7B FF \dx00000008 19 * 12 06 7D 02 FF \wx0005 7C 01 FF \wx0008 FF 20 * 9 00 08 \b1 01 FF \wx0000 08 00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/013_train_callback.grf0000644000175100001660000001727314754345605021741 0ustar00runnerdockerGRF  N6CINFOBVRSNBMINVBNPARBPALSWBBLTR84NMLNML regression testA test newgrf testing NMLЀNML Test bulk wagon2 WDPRSCRPCMNTWOODLVSTSTELVEHIBRCKWOOLBUBLTOYSFZDRFRUTFRVGFOODOIL_GOODWATRMILKCOALIOREAORECLAYGRVLSANDGRAIRSGRMAIZCOREFERTCTCDSULPWHEARFPRCOLAPETRPAPRTOFFSUGRPASSMAILBATTSWETRUBRFMSPENSPMNSPFICRPLASPLSTRAILELRLMONOMGLVTRPD      !"#$%&'(     G  SG//   5G    } ~}  ~u |0 }| ~ }1 { |{ } | z {zQ | { ~}|t $  ~ M~ORt(), *@z &   ( 0L$%tNML Test bulk wagon t1!!! 66! 66aa t s    0vzjj װ0P(\)> j!hj!2PgPjiP-Pjd*      `h~Ѐ  t!I@ ȟj XXXX Xj!j!    >0s jX`ؾ```j`-jX,j( p׈X׈XXH( v jj}׈Xװ0X(\,<Pe j!j!2PPjP-Pjd*  ׈X XXXX ׈$(,X" "%&X`h  KI@  jX  j!j!   ׈>XXX jX`ؾ```j`-jX,j(nnnnmnoonlllllkklknkmmkkkmm5555ii5k n  5on5oofg oi5kkllmnii5 mi mnnollk5mmk55ii0lno5n<l@5m5)mn5l(+kkli6,(lj!i5kmmii55@lX.d nnmmm nnnoonllmlllkkkkk55 k5555i@68ii h~Ѐi5kkiWill5imihlilk m5iii$$׈ˈXXXXא Xj!j!XX׸mmmn mnlkkkli5mn055imm-ik5Hno505k5kikHRnojXHmm0ilimnlll0l׈kXס,jXɈ(|nn׈Xoon׈Xmmn@@o0X nmmnoooi5kkllmnii5 mi mnnol׈ mml׈Xװ0Xno5n<(5m5mn5l(B"*IHJIH      Ɉ#  @#)  j!j!    ʈ˘FGIIGHHJ̸II00JI0IjX辐ȼǰHʡ0jjH-,j(! ( (wzX \ jj" 0P>3˸S?FN6j!2WB"JN*DCB`h~Ѐ  Ȣ$%" !^[_bЉ XXXX Xj!j!  ( @ACCABBDCV0CBDDɸC jX0)j,jX0j(././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/013_train_callback.nfo0000644000175100001660000001757014754345605021745 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d78 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\13" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 8 04 00 FF 01 \wxD000 "\80" 00 4 * 26 04 00 FF 01 \wxDC00 "NML Test bulk wagon" 00 5 * 208 00 08 \b1 32 FF \wx0000 09 "WDPR" "SCRP" "CMNT" "WOOD" "LVST" "STEL" "VEHI" "BRCK" "WOOL" "BUBL" "TOYS" "FZDR" "FRUT" "FRVG" "FOOD" "OIL_" "GOOD" "WATR" "MILK" "COAL" "IORE" "AORE" "CLAY" "GRVL" "SAND" "GRAI" "RSGR" "MAIZ" "CORE" "FERT" "CTCD" "SULP" "WHEA" "RFPR" "COLA" "PETR" "PAPR" "TOFF" "SUGR" "PASS" "MAIL" "BATT" "SWET" "RUBR" "FMSP" "ENSP" "MNSP" "FICR" "PLAS" "PLST" 6 * 28 00 08 \b1 05 FF \wx0000 12 "RAIL" "ELRL" "MONO" "MGLV" "TRPD" 7 * 6 01 00 \b8 FF \wx0004 8 temperate_railwagons.png 8bpp 0 25 8 24 -3 -12 normal 9 temperate_railwagons.png 8bpp 16 25 22 17 -14 -9 normal 10 temperate_railwagons.png 8bpp 48 25 32 12 -16 -8 normal 11 temperate_railwagons.png 8bpp 96 25 22 17 -6 -9 normal 12 temperate_railwagons.png 8bpp 0 250 8 24 -3 -12 normal 13 temperate_railwagons.png 8bpp 16 250 22 17 -14 -9 normal 14 temperate_railwagons.png 8bpp 48 250 32 12 -16 -8 normal 15 temperate_railwagons.png 8bpp 96 250 22 17 -6 -9 normal 16 arctic_railwagons.pcx 8bpp 0 25 8 24 -3 -12 normal 17 arctic_railwagons.pcx 8bpp 16 25 22 17 -14 -9 normal 18 arctic_railwagons.pcx 8bpp 48 25 32 12 -16 -8 normal 19 arctic_railwagons.pcx 8bpp 96 25 22 17 -6 -9 normal 20 arctic_railwagons.pcx 8bpp 0 250 8 24 -3 -12 normal 21 arctic_railwagons.pcx 8bpp 16 250 22 17 -14 -9 normal 22 arctic_railwagons.pcx 8bpp 48 250 32 12 -16 -8 normal 23 arctic_railwagons.pcx 8bpp 96 250 22 17 -6 -9 normal 24 temperate_railwagons.png 8bpp 0 225 8 24 -3 -12 normal 25 temperate_railwagons.png 8bpp 16 225 22 17 -14 -9 normal 26 temperate_railwagons.png 8bpp 48 225 32 12 -16 -8 normal 27 temperate_railwagons.png 8bpp 96 225 22 17 -6 -9 normal 28 temperate_railwagons.png 8bpp 0 350 8 24 -3 -12 normal 29 temperate_railwagons.png 8bpp 16 350 22 17 -14 -9 normal 30 temperate_railwagons.png 8bpp 48 350 32 12 -16 -8 normal 31 temperate_railwagons.png 8bpp 96 350 22 17 -6 -9 normal 32 temperate_railwagons.png 8bpp 0 150 8 24 -3 -12 normal 33 temperate_railwagons.png 8bpp 16 150 22 17 -14 -9 normal 34 temperate_railwagons.png 8bpp 48 150 32 12 -16 -8 normal 35 temperate_railwagons.png 8bpp 96 150 22 17 -6 -9 normal 36 temperate_railwagons.png 8bpp 0 275 8 24 -3 -12 normal 37 temperate_railwagons.png 8bpp 16 275 22 17 -14 -9 normal 38 temperate_railwagons.png 8bpp 48 275 32 12 -16 -8 normal 39 temperate_railwagons.png 8bpp 96 275 22 17 -6 -9 normal // Name: bulk_wagon_coal_default_group - feature 00 40 * 13 02 00 FF \b2 \b2 \w0 \w1 \w0 \w1 // Name: bulk_wagon_coal_arctic_group - feature 00 41 * 13 02 00 FE \b2 \b2 \w2 \w3 \w2 \w3 // Name: bulk_wagon_coal2_group - feature 00 42 * 13 02 00 FD \b2 \b2 \w4 \w5 \w4 \w5 // Name: bulk_wagon_grain_group - feature 00 43 * 13 02 00 FC \b2 \b2 \w6 \w7 \w6 \w7 // Name: bulk_wagon_coal_default_switch 44 * 15 02 00 FD 80 02 \b0 04 \wx00FF \wx00FF \wx00FF // (2/3) -> (3/4): bulk_wagon_coal_default_group; \wx00FD // (1/3) -> (1/4): bulk_wagon_coal2_group; // param[127] = (param[131] & 255) 45 * 9 0D 7F \D& 83 FF \dx000000FF 46 * 7 06 7F 04 FF \wx0006 FF // Name: bulk_wagon_coal_climate_switch 47 * 23 02 00 FD 89 1A 00 \dx00000000 // param[127] \b1 \wx00FE \dx00000001 \dx00000001 // 1 .. 1: bulk_wagon_coal_arctic_group; \wx00FD // default: bulk_wagon_coal_default_switch; // Name: bulk_wagon_graphics_switch 48 * 23 02 00 FC 89 47 00 \dx000000FF \b1 \wx00FD \dx00000013 \dx00000013 // 19 .. 19: bulk_wagon_coal_climate_switch; \wx00FC // default: bulk_wagon_grain_group; // Name: @CB_FAILED_REAL00 49 * 9 02 00 FD \b1 \b1 \w0 \w0 // Name: @CB_FAILED00 50 * 23 02 00 FD 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FD // Non-graphics callback, return graphics result // Name: bulk_wagon_cb_capacity_switch 51 * 83 02 00 FE 89 47 00 \dx000000FF \b7 \wx8019 \dx0000002F \dx0000002F // 47 .. 47: return 25; \wx8014 \dx0000000C \dx0000000C // 12 .. 12: return 20; \wx8014 \dx0000000D \dx0000000D // 13 .. 13: return 20; \wx8019 \dx00000019 \dx00000019 // 25 .. 25: return 25; \wx8019 \dx0000001B \dx0000001B // 27 .. 27: return 25; \wx8014 \dx0000001A \dx0000001A // 26 .. 26: return 20; \wx8019 \dx00000020 \dx00000020 // 32 .. 32: return 25; \wx00FD // No default specified -> fail callback // Name: bulk_wagon_cb_weight_switch 52 * 53 02 00 FF 89 47 00 \dx000000FF \b4 \wx8012 \dx00000013 \dx00000013 // 19 .. 19: return 18; \wx8012 \dx0000000C \dx0000000C // 12 .. 12: return 18; \wx8012 \dx0000000D \dx0000000D // 13 .. 13: return 18; \wx8012 \dx0000001A \dx0000001A // 26 .. 26: return 18; \wx00FD // No default specified -> fail callback // Name: bulk_wagon_cb_name_switch 53 * 30 02 00 FD 89 1A 20 \dx0000DC00 \2sto 1A 00 \dx00000100 \b1 \wx00FD \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx8000 // default: return string(STR_JUST_STRING); // param[125] = (param[1] * 5000) 54 * 9 0D 7D \D* 01 FF \dx00001388 // param[126] = (param[125] + 698) 55 * 9 0D 7E \D+ 7D FF \dx000002BA // param[127] = (param[126] / 1397) 56 * 9 0D 7F \D/ 7E FF \dx00000575 // param[124] = (param[2] * 12500) 57 * 9 0D 7C \D* 02 FF \dx000030D4 // param[125] = (param[124] + 6286) 58 * 9 0D 7D \D+ 7C FF \dx0000188E // param[126] = (param[125] / 12573) 59 * 9 0D 7E \D/ 7D FF \dx0000311D // param[123] = (param[3] * 8) 60 * 9 0D 7B \D* 03 FF \dx00000008 // param[124] = (param[123] + 2) 61 * 9 0D 7C \D+ 7B FF \dx00000002 // param[125] = (param[124] / 5) 62 * 9 0D 7D \D/ 7C FF \dx00000005 // param[122] = (param[4] * 2965) 63 * 9 0D 7A \D* 04 FF \dx00000B95 // param[123] = (param[122] + 1105) 64 * 9 0D 7B \D+ 7A FF \dx00000451 // param[124] = (param[123] / 2211) 65 * 9 0D 7C \D/ 7B FF \dx000008A3 66 * 22 06 7F 02 FF \wx000B 7E 02 FF \wx000E 7D 02 FF \wx0011 7C 02 FF \wx0014 FF 67 * 22 00 00 \b5 01 FF \wx0074 09 \wx0024 09 \wx0000 09 \wx0000 09 \wx0000 0B \wx0000 // param[127] = (param[1] & 255) 68 * 9 0D 7F \D& 01 FF \dx000000FF // param[126] = (param[1] << -8) 69 * 9 0D 7E \D<< 01 FF \dxFFFFFFF8 70 * 12 06 7F 01 FF \wx004D 7E 01 FF \wx004F FF 71 * 82 00 00 \b26 01 FF \wx0074 06 0F 28 \wx0010 1D \dx00000000 29 \wx01CB 1D \dx00000000 2C \b4 00 01 0C 0D 1D \dx00000000 15 13 12 FD 2A \dx000A7A40 04 FF 26 00 03 1E 02 00 07 0A 17 B6 0D 05 09 \wx0000 1C 28 05 00 0B \wx0000 0E \dx00004C30 14 1E 16 00 24 00 25 00 72 * 27 04 00 7F 01 FF \wx0074 "NML Test bulk wagon" 00 73 * 11 00 00 \b2 01 FF \wx0074 1E 08 31 01 // Name: @action3_0 74 * 33 02 00 FF 89 10 00 \dx000000FF \b2 \wx00FE \dx00000014 \dx00000014 // bulk_wagon_cb_capacity_switch; \wx00FF \dx00000016 \dx00000016 // bulk_wagon_cb_weight_switch; \wx00FC // bulk_wagon_graphics_switch; // Name: @action3_1 75 * 33 02 00 FB 89 10 00 \dx000000FF \b2 \wx801E \dx00000014 \dx00000014 // return 30; \wx8019 \dx00000016 \dx00000016 // return 25; \wx00FC // bulk_wagon_graphics_switch; // Name: @action3_2 76 * 33 02 00 FF 89 0C 00 \dx0000FFFF \b2 \wx00FE \dx00000015 \dx00000015 // bulk_wagon_cb_capacity_switch; \wx00FF \dx00000036 \dx00000036 // @action3_0; \wx00FC // bulk_wagon_graphics_switch; // Name: @action3_3 77 * 33 02 00 FC 89 0C 00 \dx0000FFFF \b2 \wx00FB \dx00000036 \dx00000036 // @action3_1; \wx00FD \dx00000161 \dx00000161 // bulk_wagon_cb_name_switch; \wx00FC // bulk_wagon_graphics_switch; 78 * 12 03 00 01 FF \wx0074 \b1 FF \wx00FC // @action3_3; \wx00FF // @action3_2; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/014_read_special_param.grf0000644000175100001660000000106014754345605022567 0ustar00runnerdockerGRF  CINFOBVRSNBMINVBNPARCPARACTNAMEName of the int settingTDESCDescription of a settingBMASKBLIMIBDFLTCBTYPEBMASKBDFLTCBTYPEBMASKBDFLTBPALSABBLTR84NMLNML regression testA test newgrf testing NML        NML       } ~} ~ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/014_read_special_param.nfo0000644000175100001660000000363214754345605022602 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d20 1 * 231 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 03 "C" "PARA" "C" \d0 "T" "NAME" 7F "Name of the int setting" 00 "T" "DESC" 7F "Description of a setting" 00 "B" "MASK" \w1 00 "B" "LIMI" \w8 \d0 \d2 "B" "DFLT" \w4 \dx00000000 00 "C" \d1 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b1 \b0 \b1 "B" "DFLT" \w4 \dx00000000 00 "C" \d2 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b1 \b2 \b1 "B" "DFLT" \w4 \dx00000001 00 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\14" "NML regression test" 00 "A test newgrf testing NML" 00 // param[16] = param[0] 3 * 5 0D 10 \D= 00 00 // param[17] = 0 4 * 9 0D 11 \D= FF 00 \dx00000000 5 * 6 09 01 01 \70 00 01 // param[17] = 1 6 * 9 0D 11 \D= FF 00 \dx00000001 // param[18] = 0 7 * 9 0D 12 \D= FF 00 \dx00000000 8 * 6 09 01 01 \70 02 01 // param[18] = 1 9 * 9 0D 12 \D= FF 00 \dx00000001 // param[19] = param[0] 10 * 9 0D 13 \D= 00 FE \dx014C4D4E // param[158] = (param[158] | 2) 11 * 9 0D 9E \D| 9E FF \dx00000002 // param[20] = 0 12 * 9 0D 14 \D= FF 00 \dx00000000 13 * 6 09 9E 01 \70 03 01 // param[20] = 1 14 * 9 0D 14 \D= FF 00 \dx00000001 // param[142] = -2 15 * 9 0D 8E \D= FF 00 \dxFFFFFFFE // param[21] = param[164] 16 * 5 0D 15 \D= A4 00 // param[125] = param[19] 17 * 9 0D 7D \D= 13 FE \dx0000FFFF // param[126] = (param[125] & 255) 18 * 9 0D 7E \D& 7D FF \dx000000FF // param[127] = (param[126] + 12) 19 * 9 0D 7F \D+ 7E FF \dx0000000C // param[22] = (1 << param[127]) 20 * 9 0D 16 \D<< FF 7F \dx00000001 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/015_basic_object.grf0000644000175100001660000000041314754345605021405 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML!MiscellaneousBasic objectH) MISC   ɖ  ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/015_basic_object.nfo0000644000175100001660000000176414754345605021423 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d6 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\15" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 33 04 0F FF 02 \wxD000 "Miscellaneous" 00 "Basic object" 00 // Name: obj_basic_tile - feature 0F 4 * 18 02 0F FF \b1 \dx0000058C \dx03078A48 \b0 \b0 \b0 \b16 \b16 \b30 5 * 41 00 0F \b11 01 FF \wx0000 08 "MISC" 09 \wxD000 0A \wxD001 0B 07 0C 11 0E \dx000A96C9 0F \dx000AC196 0D 01 14 01 10 \wx0800 16 04 6 * 7 03 0F 01 00 \b0 \wx00FF // obj_basic_tile; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/016_basic_airporttiles.grf0000644000175100001660000000040514754345605022662 0ustar00runnerdockerGRF  N 9/!@Y`P0JhPq ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/016_basic_airporttiles.nfo0000644000175100001660000000135214754345605022670 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 01 11 \b1 FF \wx0001 1 opengfx_trains_start.pcx 8bpp 142 112 8 22 -3 -10 normal // Name: small_airport_tiles_graphics - feature 11 2 * 15 02 11 FF \b1 \dx00000F8D \dxC0000000 \b0 \b0 80 3 * 16 00 11 \b4 01 FF \wx0000 08 00 0F \wx0103 10 01 11 01 4 * 7 03 11 01 00 \b0 \wx00FF // small_airport_tiles_graphics; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/017_articulated_tram.grf0000644000175100001660000000376014754345605022334 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSWBBLTR84NMLNML regression testA test newgrf testing NMLXJX(  HL XM-$XFoster Turbo Tram       X  Xw  0YjbȃʃȄʄH{ǵ ) ˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H8 j ؈ 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d18 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\17" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: foster_express_articulated_parts 3 * 23 02 01 FF 89 10 00 \dxFFFFFFFF \b1 \wx8058 \dx00000001 \dx00000003 // 1 .. 3: return 88; \wx80FF // default: return 255; 4 * 74 00 01 \b25 01 FF \wx0058 06 0F 04 28 03 1E 1F \dx000AF386 02 01 0A \dx00004C48 09 87 11 8F 08 FF 15 FE 13 16 14 58 0E FF 07 10 18 4D 19 80 0F 2D 1D \wx0001 16 \dx00000000 1E \wx0000 16 \dx00000000 24 \b0 16 \dx00000000 10 FF 1C 01 5 * 25 04 01 7F 01 FF \wx0058 "Foster Turbo Tram" 00 6 * 6 01 01 \b1 FF \wx0008 7 tram_foster_express.png 8bpp 48 1 8 18 -3 -10 normal 8 tram_foster_express.png 8bpp 64 1 20 18 -14 -5 normal 9 tram_foster_express.png 8bpp 96 1 28 15 -14 -8 normal 10 tram_foster_express.png 8bpp 144 1 20 18 -6 -7 normal 11 tram_foster_express.png 8bpp 176 1 8 18 -3 -10 normal 12 tram_foster_express.png 8bpp 192 1 20 18 -14 -9 normal 13 tram_foster_express.png 8bpp 224 1 28 15 -14 -8 normal 14 tram_foster_express.png 8bpp 272 1 20 18 -6 -7 normal // Name: foster_express_set - feature 01 15 * 9 02 01 FE \b1 \b1 \w0 \w0 16 * 9 00 01 \b1 01 FF \wx0058 17 10 // Name: @action3_0 17 * 23 02 01 FE 89 0C 00 \dx0000FFFF \b1 \wx00FF \dx00000016 \dx00000016 // foster_express_articulated_parts; \wx00FE // foster_express_set; 18 * 9 03 01 01 FF \wx0058 \b0 \wx00FE // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/018_airport_tile.grf0000644000175100001660000000033714754345605021503 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML h ~@"~././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/018_airport_tile.nfo0000644000175100001660000000172514754345605021511 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d7 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\18" "NML regression test" 00 "A test newgrf testing NML" 00 // param[127] = 2664 3 * 9 0D 7F \D= FF 00 \dx00000A68 // param[126] = (param[127] + 52576256) 4 * 9 0D 7E \D+ 7F FF \dx03224000 5 * 7 06 7E 04 FF \wx0008 FF // Name: dirt_runway_sw_snow - feature 11 6 * 18 02 11 FF \b1 \dx00000000 \dx00000000 \b0 \b15 \b0 \b16 \b1 \b6 7 * 7 03 11 01 00 \b0 \wx00FF // dirt_runway_sw_snow; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/019_switch.grf0000644000175100001660000000075514754345605020314 0ustar00runnerdockerGRF   6CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML3ИcoaldiamondsExtra info for coal mine: {  & $`b0 }     !@"    + 77::;; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/019_switch.nfo0000644000175100001660000000436614754345605020322 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d13 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\19" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 51 04 00 FF 03 \wxD000 "\98coal" 00 "\98diamonds" 00 "\98Extra info for coal mine: \7B" 00 // Name: @CB_FAILED_PROD 4 * 15 02 0A FF 00 \wx0000 \wx0000 \wx0000 \wx0000 \wx0000 00 // Name: @CB_FAILED0A 5 * 23 02 0A FF 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FF // Non-graphics callback, return graphics result // Name: return_switch 6 * 38 02 0A FE 89 24 60 \dxFFFFFFFF \dxFFFFF862 \dx00000001 \2psto 1A 00 \dx00000001 \b1 \wx00FF \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx8000 // default: return 0; // Name: coal_mine_subtype_switch // a : register 80 7 * 48 02 0A FE 89 7D 80 20 \dxFFFFFFFF // a \2psto 1A 20 \dx00000000 \2r 02 00 \dx000000FF \b2 \wx8000 \dx00000000 \dx0000000A // 0 .. 10: return string(STR_COALMINE_MONTH_0_10); \wx00FE \dx0000000D \dx0000000D // 13 .. 13: return_switch; \wx8001 // default: return string(STR_COALMINE_MONTH_11); 8 * 11 00 0A \b2 01 FF \wx0000 08 00 09 00 9 * 11 00 0A \b2 01 FF \wx0000 21 40 22 03 // Name: @return_action_0 10 * 30 02 0A FE 89 1A 20 \dx00000004 \2sto 1A 00 \dx00000080 \b1 \wx00FE \dx00000000 \dx00000000 // coal_mine_subtype_switch \wx00FE // coal_mine_subtype_switch // Name: @return_action_1 11 * 13 02 0A FD 89 10 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @action3_0 12 * 43 02 0A FF 89 0C 00 \dx0000FFFF \b3 \wx00FE \dx00000037 \dx00000037 // @return_action_0; \wx8002 \dx0000003A \dx0000003A // return string(STR_COALMINE_EXTRA_TEXT); \wx00FD \dx0000003B \dx0000003B // return var[0x10, 0, 1] \wx00FF // @CB_FAILED0A; 13 * 7 03 0A 01 00 \b0 \wx00FF // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/020_recolour.grf0000644000175100001660000000517514754345605020636 0ustar00runnerdockerGRF  '6CINFOBVRSNBMINVBNPARBPALSWBBLTR84NML NML regression testA test newgrf testing NML    !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~Ų   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~>?@ABCDE  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~Ś     X w  0YjbȃʃȄʄH{ǵ ) ˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H8 j ؈ 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d19 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\20" "NML regression test" 00 "A test newgrf testing NML" 00 // param[3] = param[\DR] 3 * 9 0D 03 \D= \DR FE \dx000308FF 4 * 7 06 03 02 FF \wx0003 FF 5 * 5 0A \b1 \b3 \w0 6 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 B2 D9 0A 0B 0C 0D 0E 0F CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 7 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 3E 3F 40 41 42 43 44 45 CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 8 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 9A 9B 9C 9D 9E 9F A0 A1 CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 9 * 6 01 01 \b1 FF \wx0008 10 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 normal 11 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal 12 opengfx_generic_trams1.pcx 8bpp 96 56 28 15 -14 -8 normal 13 opengfx_generic_trams1.pcx 8bpp 144 56 20 19 -6 -7 normal 14 opengfx_generic_trams1.pcx 8bpp 176 56 8 18 -3 -10 normal 15 opengfx_generic_trams1.pcx 8bpp 192 56 20 19 -14 -9 normal 16 opengfx_generic_trams1.pcx 8bpp 224 56 28 15 -14 -8 normal 17 opengfx_generic_trams1.pcx 8bpp 272 56 20 19 -6 -7 normal // Name: foster_express_group - feature 01 18 * 9 02 01 FF \b1 \b1 \w0 \w0 19 * 9 03 01 01 FF \wx0058 \b0 \wx00FF // foster_express_group; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/021_grf_parameter.grf0000644000175100001660000000163114754345605021614 0ustar00runnerdockerGRF  1CINFOBVRSNBMINVBNPARCPARACTNAMEDisable gridlinesTDESCThis setting allows to replace all ground sprites such that the landscape is painted (mostly) without grid lines. Note that roads and train tracks don't yet follow this ruleBTYPEBMASKBDFLTCTNAMEReplace the transmitter tower by a rockTDESCEnable to replace the transmitter tower by a rock (useful for early scenarios without telecomunication towers)BTYPEBMASKBDFLTCTNAMELandscape typeTDESCSelect the landscape (ground tile) typeBMASKBLIMICVALUTnormal (according to climate)Talpine (temperate grass in arctic)Ttemperate (not implemented)Tarctic (not implemented)Ttropical (not implemented)Ttoyland (not implemented)BDFLTBPALSABBLTR84NML!NML regression testA test newgrf testing NML ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/021_grf_parameter.nfo0000644000175100001660000000332714754345605021624 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d3 1 * 817 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 03 "C" "PARA" "C" \d0 "T" "NAME" 7F "Disable gridlines" 00 "T" "DESC" 7F "This setting allows to replace all ground sprites such that the landscape is painted (mostly) without grid lines. Note that roads and train tracks don't yet follow this rule" 00 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b0 \b0 \b1 "B" "DFLT" \w4 \dx00000001 00 "C" \d1 "T" "NAME" 7F "Replace the transmitter tower by a rock" 00 "T" "DESC" 7F "Enable to replace the transmitter tower by a rock (useful for early scenarios without telecomunication towers)" 00 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b0 \b1 \b1 "B" "DFLT" \w4 \dx00000000 00 "C" \d2 "T" "NAME" 7F "Landscape type" 00 "T" "DESC" 7F "Select the landscape (ground tile) type" 00 "B" "MASK" \w1 01 "B" "LIMI" \w8 \d0 \d1 "C" "VALU" "T" \d0 7F "normal (according to climate)" 00 "T" \d1 7F "alpine (temperate grass in arctic)" 00 "T" \d2 7F "temperate (not implemented)" 00 "T" \d3 7F "arctic (not implemented)" 00 "T" \d4 7F "tropical (not implemented)" 00 "T" \d5 7F "toyland (not implemented)" 00 00 "B" "DFLT" \w4 \dx00000000 00 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\21" "NML regression test" 00 "A test newgrf testing NML" 00 // param[10] = param[1] 3 * 5 0D 0A \D= 01 00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/022_disable_item.grf0000644000175100001660000000050014754345605021412 0ustar00runnerdockerGRF  .6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML"NML regression testA test newgrf testing NML|t    ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/022_disable_item.nfo0000644000175100001660000000220314754345605021420 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d5 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\22" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 124 00 00 \b1 74 FF \wx0000 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4 * 9 00 0A \b1 01 FF \wx000C 08 FF 5 * 24 00 0B \b2 03 FF \wx000A 08 FF FF FF 17 \dx00000000 \dx00000000 \dx00000000 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/023_engine_override.grf0000644000175100001660000000027614754345605022150 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML#NML regression testA test newgrf testing NMLtestABCDNML#4Vx././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/023_engine_override.nfo0000644000175100001660000000140114754345605022143 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d4 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\23" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 16 00 08 \b1 01 FF \wx0000 11 \dx74736574 \dx44434241 4 * 16 00 08 \b1 01 FF \wx0000 11 \dx234C4D4E \dx78563412 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/024_conditional.grf0000644000175100001660000000011314754345605021276 0ustar00runnerdockerGRF  9    ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/024_conditional.nfo0000644000175100001660000000114214754345605021305 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[127] = param[0] 0 * 5 0D 7F \D= 00 00 1 * 9 09 7F 04 \7= \dx00000000 01 // param[1] = 1 2 * 9 0D 01 \D= FF 00 \dx00000001 // param[3] = 1 3 * 9 0D 03 \D= FF 00 \dx00000001 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/025_loop.grf0000644000175100001660000000015714754345605017755 0ustar00runnerdockerGRF  ]  ~ ~~ ~  ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/025_loop.nfo0000644000175100001660000000137114754345605017760 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[127] = 0 0 * 9 0D 7F \D= FF 00 \dx00000000 1 * 2 10 10 // param[126] = (param[127] - 5) 2 * 9 0D 7E \D- 7F FF \dx00000005 // param[126] = (param[126] << -31) 3 * 9 0D 7E \Du<< 7E FF \dxFFFFFFE1 4 * 9 09 7E 04 \7= \dx00000000 02 // param[127] = (param[127] + 1) 5 * 9 0D 7F \D+ 7F FF \dx00000001 6 * 6 09 9A 01 \71 00 10 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/026_asl.grf0000644000175100001660000000370714754345605017570 0ustar00runnerdockerGRF  A&D`h   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~&BBC    b)    !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~&BBD    `)     [[ˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H8ˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H8././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/026_asl.nfo0000644000175100001660000001267514754345605017600 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // Name: layout1 - feature 11 0 * 23 02 11 FF \b65 \dx00000000 \wx0000 \dx00000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 // Name: layout1@registers - feature 11 1 * 38 02 11 FF 89 44 60 \dx000000FF \dx00000A68 \dx00000001 \2sto 1A 00 \dx00000080 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // 2 * 7 03 11 01 00 \b0 \wx00FF // layout1; 3 * 6 01 0F \b2 FF \wx0003 4 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal 5 * 1 00 6 * 1 00 7 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 81 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 8 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 82 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 9 * 1 00 // Name: layout2 - feature 0F 10 * 38 02 0F FF \b66 \dx00000000 \wx0000 \dx80000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 \dx80018000 \wx000F \b0 \b0 \b0 \b16 \b16 \b16 82 81 81 // Name: layout2@registers - feature 0F 11 * 66 02 0F FF 89 43 20 \dx000000FF \2sto 1A 20 \dx00000080 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 \2r 62 00 29 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 00 \dx00000082 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // 12 * 6 01 11 \b2 FF \wx0003 13 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal 14 * 1 00 15 * 1 00 16 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 81 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 17 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 82 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 18 * 1 00 // Name: layout2 - feature 11 19 * 38 02 11 FE \b66 \dx00000000 \wx0000 \dx80000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 \dx80018000 \wx000F \b0 \b0 \b0 \b16 \b16 \b16 82 81 81 // Name: layout2@registers - feature 11 20 * 66 02 11 FE 89 44 20 \dx000000FF \2sto 1A 20 \dx00000080 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 \2r 60 00 29 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 00 \dx00000082 \b1 \wx00FE \dx00000000 \dx00000000 \wx00FE // 21 * 7 03 11 01 01 \b0 \wx00FE // layout2; 22 * 10 00 0F \b1 01 FF \wx0001 15 \wx0008 // Name: @CB_FAILED_LAYOUT0F 23 * 17 02 0F FE \b0 \dx00000000 \dx00000000 \b0 \b0 \b0 \b0 \b0 // Name: @CB_FAILED0F 24 * 23 02 0F FE 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FE // Non-graphics callback, return graphics result // Name: @action3_0 25 * 23 02 0F FF 89 0C 00 \dx0000FFFF \b1 \wx00FE \dx0000015B \dx0000015B // @CB_FAILED0F; \wx00FF // layout2; 26 * 7 03 0F 01 01 \b0 \wx00FF // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/027_airport_layout.grf0000644000175100001660000000022014754345605022052 0ustar00runnerdockerGRF  ~ $  F././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/027_airport_layout.nfo0000644000175100001660000000152314754345605022065 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 01 11 \b1 FF \wx0001 1 * 1 00 // Name: small_airport_tile_layout - feature 11 2 * 25 02 11 FF \b2 \dx80000000 \dxC0008000 \b32 \b16 80 \dx80000000 \b0 \b0 \b0 \b16 \b16 \b16 3 * 16 00 11 \b4 01 FF \wx0000 08 00 0F \wx0103 10 01 11 01 4 * 7 03 11 01 00 \b0 \wx00FF // small_airport_tile_layout; 5 * 36 00 0D \b2 01 FF \wx0000 08 00 0A \b1 \d21 00 00 00 FE \wx0000 01 00 FE \wx0000 02 00 FE \wx0000 03 00 46 00 80 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/028_font.grf0000644000175100001660000000131614754345605017753 0ustar00runnerdockerGRF  {{  {  *D  #D  )D  /D  '>MD  D D  D 2D  #? D 2D ,%-+?LpЍ;D  '6ET '././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/028_font.nfo0000644000175100001660000000213714754345605017761 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 12 \b1 00 \b4 \w123 1 font_addl.png 8bpp 10 10 5 13 0 -2 normal nocrop 2 font_addl.png 8bpp 30 10 3 13 0 -2 normal nocrop 3 font_addl.png 8bpp 50 10 5 13 0 -2 normal nocrop 4 font_addl.png 8bpp 70 10 7 13 0 -2 normal nocrop 5 * 6 12 \b1 01 \b4 \w123 6 font_addl.png 8bpp 10 30 3 8 0 0 normal nocrop 7 font_addl.png 8bpp 30 30 1 8 0 0 normal nocrop 8 font_addl.png 8bpp 50 30 3 8 0 0 normal nocrop 9 font_addl.png 8bpp 70 30 4 8 0 0 normal nocrop 10 * 6 12 \b1 02 \b4 \w123 11 font_addl.png 8bpp 10 40 7 21 0 -2 normal nocrop 12 font_addl.png 8bpp 30 40 2 21 0 -2 normal nocrop 13 font_addl.png 8bpp 50 40 7 21 0 -2 normal nocrop 14 font_addl.png 8bpp 70 40 11 21 0 -2 normal nocrop ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/029_base_graphics.grf0000644000175100001660000000207614754345605021604 0ustar00runnerdockerGRF  b    D   6  :O,/B  %/$'?-$/6E    (* *"*? D&?DU :   ## B+   )   '-././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/029_base_graphics.nfo0000644000175100001660000000447614754345605021616 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 257 00 00 00 00 00 00 00 00 00 00 00 F0 F0 F0 EF EF EF 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 F0 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 1 * 257 00 00 00 00 00 00 00 00 00 00 00 A3 A3 A3 A3 A4 A4 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B4 B4 00 00 00 00 00 00 B5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B2 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B5 00 00 00 00 00 00 00 00 00 00 2 fonts.png 8bpp 10 10 2 1 0 -2 normal nocrop 3 fonts.png 8bpp 30 10 3 13 0 -2 normal 4 fonts.png 8bpp 50 10 5 13 0 -2 normal 5 fonts.png 8bpp 70 10 10 13 0 -2 normal 6 fonts.png 8bpp 90 10 9 13 0 -2 normal 7 fonts.png 8bpp 110 10 12 13 0 -2 normal 8 fonts.png 8bpp 130 10 9 13 0 -2 normal 9 fonts.png 8bpp 150 10 3 13 0 -2 normal 10 fonts.png 8bpp 170 10 5 13 0 -2 normal ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/030_house.grf0000644000175100001660000003400614754345605020123 0ustar00runnerdockerGRF  96CINFOBVRSNBMINVBNPARBPALSWBBLTR84NML0NML regression testA test newgrf testing NMLBrewery PASSMAILGRAIWHEA    1C0# jC       @   }  }  }  C      }  @       }    }   F`   F`6 F    }     }  ,   ,   ,   ,   ,},   ,   ,   ,   ,}_F)F     ;_(    }0 D ~  GRAI ~ }  WHEA } ~} ()*+ 0  d # !"8  9   HH  /   HH  /   HH  /   HH "@ {{ll"1@Oll{l{{kkds|liA{5{|x{k|l{Bkiivi{k|5l 5Ɖ8{{j{klik5Di 55l{t|4k{v9Mɲ1lk5FMԉn(|5(jIkl{iƑsklkzj{{k||liilጉιt&u2i|kiKKJ{5_i~vEY>AI[|5+7a}O |Pl5li |||&kl(2j=;y < ؽ!e`58Ak{Kjl؈j|Wj\Ү|oغ٦v l{(5$.xi>AO)Z45lk~jlljĊu;|ij|؛R:>vjkj׋F6xсpx@D?H썲 o5QڿJ ;-|ڿI;NB㿍yS usˍ|ۼ3BVi|ri5@ #$"1@O}###"Br|#"sø> |>J|: sG *96 $$ #8#s}#8Jz$$"}}#}$$}J6$$#w}| Ή y؄> }}}FN6ؾ $tly ɲ}8ֈʪ}88|V ʊ"u ػx }| }R$:""".2}#s"ڈ""+l#) s} :KVT#:-t$s.| }$ɹ8sVZt82""sN 8#E"*9|}> JӦs | s$5ۺsw } ò1.{# :>#s΋"}"#"6; F66B 케}ی≍tpt:Ѯ" ̺lɌ֋|xB=J:.BuFSȽJ}ƎO&5DFBq @ ҟ҈"1@O?Bp>~>Bҟƈ *9r>L l{C;Tɲ5ԉh bٳxؘtsؔZX͊.&~v ь#Р|]Ԫn=ٌOЀ^.'6)-NY Dʢۦѡ⬸<бфЉ#Z.؝=ђE8"yuھlòӆڿ>29O}AFmD`ɶv?SҪDD6ю.=|:DΎl{ʔN4njۄl1@Գds L[@x (08@HPX`hpx2QsGv/]:Cr2b3JXbhjlnprtv {IKt H؈XNN KKJIKvyy HJyv,t&i I <<0>7>F{I@08IHXKXx؈x% "c\\\\\ #\\][5c[&-\\]^N+ؒ\=T9Ti#ZAtNucIc]\ɶcec`r]%daceLN'c`C`+'eaacet jp=`)ea-7tce! -\ȟZ+j aeeab0]W ! !deed/`1!!(-cȌNXKȸ {LXj jIL## .r#"2{is{J` L #8s i^ tr (#""sHJ{i'. {L(#8 (s.KL*$s( r!!r{L#  (r"(#б!H{r  s#$####IHJ($$ $#$$$## D[@ '6ETc͈r͈@Έ݈@ 0?N]n}@ȉ׈1@O^|_͊[]^A [\]^A>M~\@ж~]^ж]~@]зB3EL@N\\[\ن>^^?B|]^\\0]͍\`[>`a]@N8``B@>aؾᛍ4baB]|ccea^K^]`Bb]]@da1caZB|[\^^afc@p;A\aZaZc`bcebPB]eeaɎl\`c}Z`ee[JS_eaeHHB[[0\c [_яIIB']]@S[c_Ϗ@@{Lut&l{J>?uK{L-7кfeZb]ȺeavZJ{{HHcuc`кf [ZIiJIiII~K{KLA}Z[[@caوAefAI}@c,c`Baaaedcro]dedcMI,c@`paBc eceebL{Li@CgB\pAcd>e`eL|cab`XCBcc>biI}c8Na@C:"%]{؂ebe`5Ѐ@8|H@r}Icn@BIHGiƹt?=NnJJIr_HGJa`7غJKt{N>tr@q|J@{LKI|MMMՀ|I|K΀@C@HIJkJiJb`f,| vLtKiHKtt|>GHHI|;qHIrIJia|vLu'FFpGHp{A@rKtu|~LIЀHkɌًIrK,{{##Ji{I;{H5!rv{|K@LK}m,6LI>( J!mrٵҀRk{imJKGpHIK\LIss>BB@JLKIFGp~t>؄sIIIr@k{tGFGp"|BJMIXʀJ{LiUI<>{irI{iH{KJLj{I|JJ{iJMx|JL  Ɍc!! {LJL(}#}6EJ!HMr{txIJL| Br{IB"I {L # JLL(.:I}@y( 8芸NJ֊!0?N]l L[@9 #-9GWi} %=Vo0I:ay !9Qi !)/357. _ ^_ ^_ ^^^_ ] ]] ^da afca eeacea__  e]^__ fd A, ]^]]^^^__S]] __ ^ ^'q_ @]]]^Z*]\\\]deC[\\cc^6ac[[ee` _\c[[[dfbe0\\ccbe` !2Beaf0uKcccB KNB-0cL؅C. eact KvD/ct{NDD J{{M LNC|Kv0B,JKKLN`LBJKtLKv0B,uB,LtKKNvB,uLtKtN-uLucLK؆}.umK}N-m|i7>F{I@08IHXKXx؈x% "ՠ 5,ԡNNՠJؒ֠ Ԡ;o2#8C蛨"\ciSɶKNc`daԩLN'c`C؍ԡ`+'eaaԫt jp ~-)ea-7t! -/Z+j aԭeab0]!ئW ! !ed/`1!!(-cȌNXKȸ {LXj jIL## .r#"2{is{J` L #8s i^ tr (#""sHJ{i'. {L(#8 (s.KL*$s( r!!r{L#  (r"(#б!H{r  s#$####IHJ($$ $#$$$## D[@ '6ETcr@Έ݈@ 0?N]n}@ȉ׈1@O^|Ԋ͊ԡA ՠԈ?N?ՠԠԡr~‹شؽM ȾҌ@O֠?L҉ь69?=ҡƨ}ԠC|耉ѵՠ`}`aҊM``|`aa?8bCB`acce؀}aBb@{>da1zcaZB|Q{|afc@ aZaZc`bceb@ReeaɎٿ Z`ee?eaeHDcؿMϏ IHH@cNΈ@CccececNI=ZZ`ffeAy4oefdccc҉h1 ed!ttt!!|>acPI>{Lut|l{JBttuK{L; IiJIiII~K{KLA(r@caغݼ{=<{tK|L @xh+M=>eeL|b`XCBccDZA>biI}8N@C`bؿ;:c[ebe`5Ѐՠ8|H@r}Iՠn@BIHGiJt?=nJJIr_HGHa`7ՠJKt{N>tr,q|J@{LKItMMMՀGHHI|HHqHIrIJi>|vLu'FFpGHp{A@rKtu|~LIЀHkًIrK,>{{##Ji{I;{H5!rv{|K@LK}m,6LI>( J!mrٵҀk{ilmJKGpHIK\LIss>BB@JLKIFGp~t>؄sIIIr@k{tGFGp"|BJMIXҀJ{LiUI<>{irI{iH{KJLj{I|JJ{iJMx|JL  Ɍc!! {LJL(}#}6EJ!HMr{txIJL| Br{IB"I {L # JLL(.:I}@y( 8芸NJ֊!0?N]lL[@9 #-9GWi} %=Vo0I:ay !9Qi !)/357. ԡ ԡ ԡ ԡԠ da afca eeacea  eԡ fd ,Pԡ^& * ԡyY<deo \[\ ccԠa[[eeԖ c[[[dfbe0cbe` !^2`eaf0uK]B KN0B-0KLzC. eaԡt KvD/Mt{ND eJ{{M LNCա|Kv0B,JKKLN`LBtLKv0B,un,LtKKNvBX,uLtKtNo-uLuԡLK؆}.uuաmK}N-m|i 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d57 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\30" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 14 04 07 FF 01 \wxDC00 "Brewery" 00 4 * 24 00 08 \b1 04 FF \wx0000 09 "PASS" "MAIL" "GRAI" "WHEA" 5 * 6 01 07 \b1 FF \wx0003 6 groundtiles.png 8bpp 10 10 64 31 -31 0 normal 7 groundtiles.png 8bpp 150 10 64 31 -31 0 normal 8 groundtiles.png 8bpp 220 10 64 31 -31 0 normal 9 * 12 01 07 00 FF \wx0001 FF \wx0001 FF \wx0006 10 brewery.png 8bpp 10 60 64 91 -31 -60 normal nocrop 11 brewery.png 8bpp 80 60 64 91 -31 -60 normal nocrop 12 brewery.png 8bpp 150 60 64 91 -31 -60 normal nocrop 13 brewery_snow.png 8bpp 10 60 64 91 -31 -60 normal nocrop 14 brewery_snow.png 8bpp 80 60 64 91 -31 -60 normal nocrop 15 brewery_snow.png 8bpp 150 60 64 91 -31 -60 normal nocrop // Name: brewery_sprite_layout - feature 07 // building_sprite : register 8B // with_smoke : register 8C 16 * 49 02 07 FF \b67 \dx00000F8D \wx0000 \dxC0000000 \wx0002 \b0 \b0 80 83 \dx80008001 \wx0003 \b0 \b0 \b0 \b16 \b16 \b48 86 84 \dx00000000 \wx0023 \b8 \b0 \b0 \b11 \b16 \b7 8A 87 88 // Name: brewery_sprite_layout@registers - feature 07 17 * 362 02 07 FF 89 43 20 \dx000000FF \2cmp 1A 20 \dx00000004 \2& 1A 20 \dx00000001 \2sto 1A 20 \dx00000080 // guard \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 // !guard \2r 40 20 \dx00000003 \2< 1A 20 \dx00000001 \2* 7D 81 20 \dxFFFFFFFF \2sto 1A 20 \dx00000082 \2r 7D 80 20 \dxFFFFFFFF \2* 1A 20 \dx00000002 \2+ 7D 82 20 \dxFFFFFFFF \2sto 1A 20 \dx00000083 \2r 43 20 \dx000000FF \2cmp 1A 20 \dx00000004 \2& 1A 20 \dx00000001 \2* 1A 20 \dx00000003 \2+ 7D 8B 20 \dxFFFFFFFF // building_sprite \2sto 1A 20 \dx00000084 \2r 40 20 \dx00000003 \2cmp 1A 20 \dx00000003 \2& 1A 20 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000085 \2r 7D 8B 20 \dxFFFFFFFF // building_sprite \2cmp 1A 20 \dxFFFFFFFF \2& 1A 20 \dx00000001 \2| 7D 85 20 \dxFFFFFFFF \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000086 \2r 46 60 \dx000000FF \dxFFFFFFFF \dx00000004 \2+ 1A 20 \dx00000C07 \2sto 1A 20 \dx00000087 \2r 46 60 \dx000000FF \dx00000036 \dx00000001 \2sto 1A 20 \dx00000088 \2r 46 20 \dx000000FF \2cmp 1A 20 \dx00000000 \2& 1A 20 \dx00000001 \2sto 1A 20 \dx00000089 \2r 7D 8C 20 \dxFFFFFFFF // with_smoke \2u< 1A 20 \dx00000001 \2^ 1A 20 \dx00000001 \2| 7D 89 20 \dxFFFFFFFF \2^ 1A 20 \dx00000001 \2sto 1A 00 \dx0000008A \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // // Name: @return_action_0 18 * 44 02 07 FE 89 1A 20 \dx00000002 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_1 19 * 44 02 07 FD 89 1A 20 \dx00000001 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_2 20 * 44 02 07 FC 89 1A 20 \dxFFFFFFFF \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_3 21 * 44 02 07 FB 89 1A 20 \dx00000000 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000001 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: brewery_layout_1 22 * 44 02 07 FB 89 7D FF 10 \dx000000FF \b3 \wx00FE \dx00000000 \dx00000000 // 0 .. 0: @return_action_0; \wx00FD \dx00000002 \dx00000002 // 2 .. 2: @return_action_1; \wx00FC \dx00000001 \dx00000001 // 1 .. 1: @return_action_2; \wx00FB // default: @return_action_3; // Name: @return_action_0 23 * 44 02 07 FC 89 1A 20 \dxFFFFFFFF \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_1 24 * 44 02 07 FD 89 1A 20 \dx00000000 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000001 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_2 25 * 44 02 07 FE 89 1A 20 \dx00000002 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_3 26 * 44 02 07 FF 89 1A 20 \dx00000001 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: brewery_layout_2 27 * 44 02 07 FF 89 7D FF 10 \dx000000FF \b3 \wx00FC \dx00000000 \dx00000000 // 0 .. 0: @return_action_0; \wx00FD \dx00000002 \dx00000002 // 2 .. 2: @return_action_1; \wx00FE \dx00000001 \dx00000001 // 1 .. 1: @return_action_2; \wx00FF // default: @return_action_3; // Name: brewery_choose_layout 28 * 23 02 07 FF 89 5F 08 \dx00000001 \b1 \wx00FB \dx00000000 \dx00000000 // 0 .. 0: brewery_layout_1; \wx00FF // default: brewery_layout_2; // Name: brewery_next_frame 29 * 23 02 07 FB 89 46 00 \dx000000FF \b1 \wx80FF \dx00000000 \dx00000000 // 0 .. 0: return 255; \wx80FE // default: return 254; // Name: @return_action_0 30 * 41 02 07 FE 89 46 20 \dx000000FF \2cmp 1A 20 \dx00000000 \2& 1A 20 \dx00000001 \2* 1A 20 \dxFFFFFF04 // expr1 - expr2 \2+ 1A 00 \dx000000FD \b0 \wx8000 // Return computed value // Name: brewery_cargo_accepted 31 * 59 02 07 FE 89 5F 28 \dx00000001 \2u< 1A 20 \dx00000001 \2* 1A 20 \dxFFFFFFFF // expr1 - expr2 \2+ 1A 20 \dx00000003 \2cmp 7D FF 30 \dx000000FF \2& 1A 00 \dx00000001 \b1 \wx00FE \dx00000001 \dx00000001 // 1 .. 1: return ((var[0x46, 0, 255] == 0) ? 1 : 253) \wx80FD // default: return 253; // Name: brewery_check_location 32 * 23 02 07 FD 89 44 00 \dx000000FF \b1 \wx8001 \dx00000000 \dx00000000 // 0 .. 0: return 1; \wx8000 // default: return 0; // param[126] = 0 33 * 9 0D 7E \D= FF 00 \dx00000000 34 * 9 09 00 04 \7c \dx49415247 01 // param[126] = 1 35 * 9 0D 7E \D= FF 00 \dx00000001 // param[125] = 0 36 * 9 0D 7D \D= FF 00 \dx00000000 37 * 9 09 00 04 \7c \dx41454857 01 // param[125] = 1 38 * 9 0D 7D \D= FF 00 \dx00000001 // param[127] = (param[126] | param[125]) 39 * 5 0D 7F \D| 7E 7D 40 * 9 07 7F 04 \7= \dx00000000 02 41 * 183 00 07 \b20 04 FF \wx0000 08 28 29 2A 2B 12 \wxDC00 \wxDC00 \wxDC00 \wxDC00 09 30 20 20 20 19 00 00 00 00 0B 64 00 00 00 0C 19 00 00 00 23 \b4 \wx0802 \wx0803 \wx0200 \wx0101 \b4 \wx0802 \wx0803 \wx0200 \wx0101 \b4 \wx0802 \wx0803 \wx0200 \wx0101 \b4 \wx0802 \wx0803 \wx0200 \wx0101 10 \wx00C8 \wx00C8 \wx00C8 \wx00C8 11 FA FA FA FA 18 F0 00 00 00 0A \wx8214 \wx0000 \wx0000 \wx0000 21 \wx0794 \wx0000 \wx0000 \wx0000 22 \wx0802 \wx0000 \wx0000 \wx0000 1F 14 00 00 00 13 \wx3807 \wx0000 \wx0000 \wx0000 17 \dx03060804 \dx03060804 \dx03060804 \dx03060804 16 00 00 00 00 1A 94 94 94 94 1B 02 02 02 02 20 \b2 02 03 \b2 02 03 \b2 02 03 \b2 02 03 42 * 9 00 07 \b1 01 FF \wx0000 14 03 // Name: @action3_0 43 * 57 02 07 FD 89 1A 20 \dx00000000 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b3 \wx00FD \dx00000017 \dx00000017 // brewery_check_location; \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 44 * 9 07 7F 04 \7= \dx00000000 02 45 * 7 03 07 01 00 \b0 \wx00FD // @action3_0; 46 * 9 00 07 \b1 01 FF \wx0001 14 02 // Name: @action3_1 47 * 47 02 07 FD 89 1A 20 \dx00010100 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b2 \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 48 * 9 07 7F 04 \7= \dx00000000 02 49 * 7 03 07 01 01 \b0 \wx00FD // @action3_1; 50 * 9 00 07 \b1 01 FF \wx0002 14 02 // Name: @action3_2 51 * 47 02 07 FD 89 1A 20 \dx00020001 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b2 \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 52 * 9 07 7F 04 \7= \dx00000000 02 53 * 7 03 07 01 02 \b0 \wx00FD // @action3_2; 54 * 9 00 07 \b1 01 FF \wx0003 14 02 // Name: @action3_3 55 * 47 02 07 FF 89 1A 20 \dx00030101 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b2 \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 56 * 9 07 7F 04 \7= \dx00000000 01 57 * 7 03 07 01 03 \b0 \wx00FF // @action3_3; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/031_aircraft.grf0000644000175100001660000000031714754345605020572 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML0NML regression testA test newgrf testing NML  ?Test plane 0x14././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/031_aircraft.nfo0000644000175100001660000000143414754345605020577 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d4 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\30" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 26 00 03 \b7 01 FF \wx0014 1A \dx000B0612 04 1E 03 1E 06 07 0C 3F 1F \wx0400 0F \wx0115 4 * 23 04 03 7F 01 FF \wx0014 "Test plane 0x14" 00 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/032_simple_house.grf0000644000175100001660000000136014754345605021473 0ustar00runnerdockerGRF  9 6CINFOBVRSNBMINVBNPARBPALSDBBLTR84NML2NML regression testA test newgrf testing NMLExample houseA %   D$,,---  ,,,.0=+.HG^l+]HIJ+,YIJؘ- -D\H .rH.剠,\剞,H.渰H,\HH.,\̰.\,J+ _\蹉B,J2[,, +. ,B+,B,XH X,昃J`1,+J鹸,0,,X!,YY戄CHYY戰q,ܪ,ˉ~谈,,4.`G,qXō@S././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/032_simple_house.nfo0000644000175100001660000000255314754345605021504 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d10 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "D" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\32" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 20 04 07 FF 01 \wxDC00 "Example house" 00 4 * 6 01 07 \b1 FF \wx0001 5 nlhs.png 8bpp 98 8 44 36 -22 0 normal nocrop // Name: spritelayout_townhouse - feature 07 6 * 23 02 07 FF \b65 \dx00000F8D \wx0000 \dx80008000 \wx0001 \b4 \b2 \b0 \b8 \b16 \b27 80 // Name: spritelayout_townhouse@registers - feature 07 7 * 30 02 07 FF 89 1A 20 \dx00000001 \2sto 1A 00 \dx00000080 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // 8 * 16 00 07 \b4 01 FF \wx0000 08 02 15 02 18 A0 12 \wxDC00 // Name: @action3_0 9 * 37 02 07 FF 89 1A 20 \dx00000000 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b1 \wx00FF \dx00000000 \dx00000000 // spritelayout_townhouse; \wx00FF // spritelayout_townhouse; 10 * 7 03 07 01 00 \b0 \wx00FF // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/033_procedure.grf0000644000175100001660000000150114754345605020765 0ustar00runnerdockerGRF  / 6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML3NML regression testA test newgrf testing NML } }  B   }  ~  } }C E0E8 C D } }   E0D    ~ _( C E8   E< F  ~  }  }  ~ } } } }   _(   ~  }  }  ~  E<F } }  "B! ;;__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/033_procedure.nfo0000644000175100001660000000674614754345605021011 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d13 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\33" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: dumb_add // a : register 88 // b : register 89 3 * 22 02 0A FF 89 7D 88 20 \dxFFFFFFFF // a \2+ 7D 89 00 \dxFFFFFFFF // b \b0 \wx8000 // Return computed value // Name: @CB_FAILED_PROD 4 * 15 02 0A FE 00 \wx0000 \wx0000 \wx0000 \wx0000 \wx0000 00 // Name: @CB_FAILED0A 5 * 23 02 0A FE 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FE // Non-graphics callback, return graphics result // Name: @return_action_0 6 * 66 02 0A FD 89 1A 20 \dx00000005 \2sto 1A 20 \dx00000088 \2r 7D 86 20 \dxFFFFFFFF // a \2sto 1A 20 \dx00000089 \2r 7E FF 20 \dxFFFFFFFF // dumb_add(5, a) \2sto 1A 20 \dx00000087 \2r 7D 86 20 \dxFFFFFFFF // a \2+ 7D 87 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value // Name: callee // a : register 86 7 * 67 02 0A FD 89 45 30 \dx00000003 \2+ 45 38 \dx0000000F \2sto 1A 20 \dx00000087 \2r 43 20 \dxFFFFFFFF \2+ 44 20 \dx000000FF \2* 7D 87 20 \dxFFFFFFFF \2+ 7D 86 00 \dxFFFFFFFF // a \b1 \wx00FE \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx00FD // default: return (a + dumb_add(5, a)) // Name: caller2 8 * 232 02 0A FC 89 B3 20 \dx00000003 \2+ AA 20 \dx0000FFFF \2sto 1A 20 \dx00000080 \2r 45 30 \dx00000003 \2+ 44 20 \dx000000FF \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000086 \2r 7E FD 20 \dxFFFFFFFF // callee(1) \2+ 5F 28 \dx0000FFFF \2sto 1A 20 \dx00000082 \2r 43 20 \dxFFFFFFFF \2+ 45 38 \dx0000000F \2sto 1A 20 \dx00000083 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000084 \2r 45 3C \dx0000000F \2sto 1A 20 \dx00000088 \2r 46 20 \dxFFFFFFFF \2sto 1A 20 \dx00000089 \2r 7E FF 20 \dxFFFFFFFF // dumb_add(var[0x45, 28, 15], var[0x46, 0, -1]) \2sto 1A 20 \dx00000085 \2r 7D 84 20 \dxFFFFFFFF \2sto 1A 20 \dx00000088 \2r 7D 85 20 \dxFFFFFFFF \2sto 1A 20 \dx00000089 \2r 7E FF 20 \dxFFFFFFFF // dumb_add(1, dumb_add(var[0x45, 28, 15], var[0x46, 0, -1])) \2* 7D 83 20 \dxFFFFFFFF \2* 7D 82 20 \dxFFFFFFFF \2* 7D 81 20 \dxFFFFFFFF \2* 7D 80 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value // Name: caller1 9 * 145 02 0A FF 89 B3 20 \dx00000003 \2+ AA 20 \dx0000FFFF \2sto 1A 20 \dx00000080 \2r 5F 28 \dx0000FFFF \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000000 \2sto 1A 20 \dx00000086 \2r 7E FD 20 \dxFFFFFFFF // callee(0) \2sto 1A 20 \dx00000082 \2r 7D 81 20 \dxFFFFFFFF \2sto 1A 20 \dx00000088 \2r 7D 82 20 \dxFFFFFFFF \2sto 1A 20 \dx00000089 \2r 7E FF 20 \dxFFFFFFFF // dumb_add(var[0x5F, 8, 65535], callee(0)) \2sto 1A 20 \dx00000083 \2r 45 3C \dx0000000F \2+ 46 20 \dxFFFFFFFF \2* 7D 83 20 \dxFFFFFFFF \2* 7D 80 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value 10 * 11 00 0A \b2 01 FF \wx0000 08 00 09 00 11 * 9 00 0A \b1 01 FF \wx0000 22 42 // Name: @action3_0 12 * 33 02 0A FE 89 0C 00 \dx0000FFFF \b2 \wx00FC \dx0000003B \dx0000003B // caller2; \wx00FF \dx0000015F \dx0000015F // caller1; \wx00FE // @CB_FAILED0A; 13 * 7 03 0A 01 00 \b0 \wx00FE // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/034_roadtypes.grf0000644000175100001660000000005314754345605021011 0ustar00runnerdockerGRF  ROAD././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/034_roadtypes.nfo0000644000175100001660000000073614754345605021025 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 15 00 12 \b2 01 FF \wx0000 08 "ROAD" 14 \wx0104 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/035_switch_scope.grf0000644000175100001660000000130214754345605021470 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML5NML regression testA test newgrf testing NML" C    " C(   " C    " C(   B" E8   " E<   " E8   " E<     b  5 %%''00<< ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/035_switch_scope.nfo0000644000175100001660000000651614754345605021510 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d20 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\35" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: @return_action_0 3 * 34 02 09 FF 89 43 20 \dx000000FF \2cmp 1A 20 \dx00000002 \2< 1A 20 \dx00000001 \2^ 1A 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @return_action_1 4 * 34 02 09 FE 89 43 28 \dx000000FF \2cmp 1A 20 \dx00000005 \2- 1A 20 \dx00000001 \2> 1A 00 \dx00000000 \b0 \wx8000 // Return computed value // Name: random1 5 * 15 02 09 FE 80 01 \b0 04 \wx00FF \wx00FF \wx00FF // (2/3) -> (3/4): return (var[0x43, 0, 255] < 2) \wx00FE // (1/3) -> (1/4): return (var[0x43, 8, 255] > 5) // Name: @return_action_0 6 * 34 02 09 FF 89 43 20 \dx000000FF \2cmp 1A 20 \dx00000002 \2< 1A 20 \dx00000001 \2^ 1A 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @return_action_1 7 * 34 02 09 FD 89 43 28 \dx000000FF \2cmp 1A 20 \dx00000005 \2- 1A 20 \dx00000001 \2> 1A 00 \dx00000000 \b0 \wx8000 // Return computed value // Name: var1 8 * 23 02 09 FD 89 42 00 \dx00000007 \b1 \wx00FF \dx00000000 \dx00000000 // 0 .. 0: return (var[0x43, 0, 255] < 2) \wx00FD // default: return (var[0x43, 8, 255] > 5) // Name: @return_action_2 9 * 34 02 09 FF 8A 45 38 \dx0000000F \2cmp 1A 20 \dx00000002 \2< 1A 20 \dx00000001 \2^ 1A 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @return_action_3 10 * 34 02 09 FC 8A 45 3C \dx0000000F \2cmp 1A 20 \dx00000005 \2- 1A 20 \dx00000001 \2> 1A 00 \dx00000000 \b0 \wx8000 // Return computed value // Name: random2 11 * 15 02 09 FC 83 01 \b0 04 \wx00FF \wx00FF \wx00FF // (2/3) -> (3/4): return (var[0x45, 24, 15] < 2) \wx00FC // (1/3) -> (1/4): return (var[0x45, 28, 15] > 5) // Name: @return_action_0 12 * 34 02 09 FF 8A 45 38 \dx0000000F \2cmp 1A 20 \dx00000002 \2< 1A 20 \dx00000001 \2^ 1A 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @return_action_1 13 * 34 02 09 FB 8A 45 3C \dx0000000F \2cmp 1A 20 \dx00000005 \2- 1A 20 \dx00000001 \2> 1A 00 \dx00000000 \b0 \wx8000 // Return computed value // Name: var2 14 * 23 02 09 FB 8A 93 00 \dx000000FF \b1 \wx00FF \dx00000000 \dx00000000 // 0 .. 0: return (var[0x45, 24, 15] < 2) \wx00FB // default: return (var[0x45, 28, 15] > 5) 15 * 11 00 09 \b2 01 FF \wx0000 08 00 09 00 16 * 9 00 09 \b1 01 FF \wx0000 0E 62 // Name: @CB_FAILED_LAYOUT09 17 * 17 02 09 FF \b0 \dx00000000 \dx00000000 \b0 \b0 \b0 \b0 \b0 // Name: @CB_FAILED09 18 * 23 02 09 FF 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FF // Non-graphics callback, return graphics result // Name: @action3_0 19 * 53 02 09 FF 89 0C 00 \dx0000FFFF \b4 \wx00FB \dx00000025 \dx00000025 // var2; \wx00FD \dx00000027 \dx00000027 // var1; \wx00FE \dx00000030 \dx00000030 // random1; \wx00FC \dx0000003C \dx0000003C // random2; \wx00FF // @CB_FAILED09; 20 * 7 03 09 01 00 \b0 \wx00FF // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/036_procedure_scope.grf0000644000175100001660000000205614754345605022167 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML6NML regression testA test newgrf testing NML    ~   ~ } }B   }  ~  } }B   }  ~  } }n "    }  !     } }   E0D    ~ _( C E8   E< F  ~  }  }  ~ } } } }   _(   ~  }  }  ~  E<F } }  !"B+ "";;__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/036_procedure_scope.nfo0000644000175100001660000001123514754345605022172 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d18 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\36" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: @return_action_0 3 * 20 02 0A FF 8A 1A 20 \dx00000000 \2sto 1A 00 \dx00000000 \b0 \wx8000 // Return computed value // Name: proc_call_optimisation 4 * 23 02 0A FF 8A 82 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // 0 .. 0: return 0; \wx00FF // default: return STORE_TEMP(0, 0) // Name: @ternary_action_0 5 * 24 02 0A FF 89 7E FF 00 \dxFFFFFFFF // proc_call_optimisation \b1 \wx00FF \dx00000000 \dx00000000 // proc_call_optimisation; \wx8001 // return 1; // Name: @CB_FAILED_PROD 6 * 15 02 0A FE 00 \wx0000 \wx0000 \wx0000 \wx0000 \wx0000 00 // Name: @CB_FAILED0A 7 * 23 02 0A FE 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FE // Non-graphics callback, return graphics result // Name: ternary_optimisation 8 * 24 02 0A FF 89 7E FF 00 \dxFFFFFFFF // @ternary_action_0 \b1 \wx00FE \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx8005 // default: return 5; // Name: dumb_add // a : register 88 // b : register 89 9 * 22 02 0A FD 89 7D 88 20 \dxFFFFFFFF // a \2+ 7D 89 00 \dxFFFFFFFF // b \b0 \wx8000 // Return computed value // Name: @return_action_0 10 * 66 02 0A FC 8A 1A 20 \dx00000005 \2sto 1A 20 \dx00000088 \2r 7D 86 20 \dxFFFFFFFF // a \2sto 1A 20 \dx00000089 \2r 7E FD 20 \dxFFFFFFFF // dumb_add(5, a) \2sto 1A 20 \dx00000087 \2r 7D 86 20 \dxFFFFFFFF // a \2+ 7D 87 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value // Name: @return_action_1 11 * 66 02 0A FB 8A 1A 20 \dx00000006 \2sto 1A 20 \dx00000088 \2r 7D 86 20 \dxFFFFFFFF // a \2sto 1A 20 \dx00000089 \2r 7E FD 20 \dxFFFFFFFF // dumb_add(6, a) \2sto 1A 20 \dx00000087 \2r 7D 86 20 \dxFFFFFFFF // a \2+ 7D 87 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value // Name: callee // a : register 86 12 * 110 02 0A FB 8A 92 22 \dx00000001 \2* 1A 20 \dx00000004 \2sto 1A 20 \dx00000087 \2r B6 20 \dx0000FFFF \2- 7D 87 20 \dxFFFFFFFF \2sto 1A 20 \dx00000088 \2r 92 21 \dx00000001 \2u< 1A 20 \dx00000001 \2* 1A 20 \dx000001F3 // expr1 - expr2 \2+ 1A 20 \dx00000001 \2+ 82 20 \dx0000FFFF \2* 7D 88 20 \dxFFFFFFFF \2+ 7D 86 00 \dxFFFFFFFF // a \b1 \wx00FC \dx00000000 \dx00000005 // 0 .. 5: return (a + dumb_add(5, a)) \wx00FB // default: return (a + dumb_add(6, a)) // Name: caller2 13 * 232 02 0A FC 89 B3 20 \dx00000003 \2+ AA 20 \dx0000FFFF \2sto 1A 20 \dx00000080 \2r 45 30 \dx00000003 \2+ 44 20 \dx000000FF \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000086 \2r 7E FB 20 \dxFFFFFFFF // callee(1) \2+ 5F 28 \dx0000FFFF \2sto 1A 20 \dx00000082 \2r 43 20 \dxFFFFFFFF \2+ 45 38 \dx0000000F \2sto 1A 20 \dx00000083 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000084 \2r 45 3C \dx0000000F \2sto 1A 20 \dx00000088 \2r 46 20 \dxFFFFFFFF \2sto 1A 20 \dx00000089 \2r 7E FD 20 \dxFFFFFFFF // dumb_add(var[0x45, 28, 15], var[0x46, 0, -1]) \2sto 1A 20 \dx00000085 \2r 7D 84 20 \dxFFFFFFFF \2sto 1A 20 \dx00000088 \2r 7D 85 20 \dxFFFFFFFF \2sto 1A 20 \dx00000089 \2r 7E FD 20 \dxFFFFFFFF // dumb_add(1, dumb_add(var[0x45, 28, 15], var[0x46, 0, -1])) \2* 7D 83 20 \dxFFFFFFFF \2* 7D 82 20 \dxFFFFFFFF \2* 7D 81 20 \dxFFFFFFFF \2* 7D 80 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value // Name: caller1 14 * 145 02 0A FD 89 B3 20 \dx00000003 \2+ AA 20 \dx0000FFFF \2sto 1A 20 \dx00000080 \2r 5F 28 \dx0000FFFF \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000000 \2sto 1A 20 \dx00000086 \2r 7E FB 20 \dxFFFFFFFF // callee(0) \2sto 1A 20 \dx00000082 \2r 7D 81 20 \dxFFFFFFFF \2sto 1A 20 \dx00000088 \2r 7D 82 20 \dxFFFFFFFF \2sto 1A 20 \dx00000089 \2r 7E FD 20 \dxFFFFFFFF // dumb_add(var[0x5F, 8, 65535], callee(0)) \2sto 1A 20 \dx00000083 \2r 45 3C \dx0000000F \2+ 46 20 \dxFFFFFFFF \2* 7D 83 20 \dxFFFFFFFF \2* 7D 80 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value 15 * 11 00 0A \b2 01 FF \wx0000 08 00 09 00 16 * 11 00 0A \b2 01 FF \wx0000 21 01 22 42 // Name: @action3_0 17 * 43 02 0A FE 89 0C 00 \dx0000FFFF \b3 \wx00FF \dx00000022 \dx00000022 // ternary_optimisation; \wx00FC \dx0000003B \dx0000003B // caller2; \wx00FD \dx0000015F \dx0000015F // caller1; \wx00FE // @CB_FAILED0A; 18 * 7 03 0A 01 00 \b0 \wx00FE // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/037_optimised_trigger.grf0000644000175100001660000000035014754345605022522 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML7NML regression testA test newgrf testing NML d@ -- d././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/037_optimised_trigger.nfo0000644000175100001660000000177514754345605022542 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d6 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\37" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: trigger_only 3 * 23 02 00 FF 80 01 \b0 08 \wx8007 \wx8007 \wx8007 \wx8007 \wx8007 \wx8007 \wx8007 \wx8007 // (8/8) -> (8/8): return 7; 4 * 9 00 00 \b1 01 FF \wx0064 1E 40 // Name: @action3_0 5 * 23 02 00 FF 89 0C 00 \dx0000FFFF \b1 \wx8005 \dx0000002D \dx0000002D // return 5; \wx00FF // trigger_only; 6 * 9 03 00 01 FF \wx0064 \b0 \wx00FF // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/038_optimised_scope.grf0000644000175100001660000000133314754345605022173 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML8NML regression testA test newgrf testing NML A test newgrf testing NML "C8   C ~~"C<   C "C<   C }   ~ dH  + --66+ ##--66 d././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/038_optimised_scope.nfo0000644000175100001660000000672514754345605022211 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d25 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\38" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 32 04 00 FF 01 \wxD000 "A test newgrf testing NML" 00 // param[127] = param[17] 4 * 9 0D 7F \D= 11 FE \dx0000FFFF 5 * 7 06 7F 04 FF \wx0014 FF // Name: @return_action_0 6 * 34 02 00 FF 89 43 38 \dx0000000F \2* 1A 20 \dx00000010 \2+ 1A 20 \dx00000000 // param[127] \2+ 43 1C \dx0000000F \b0 \wx8000 // Return computed value // param[126] = param[17] 7 * 9 0D 7E \D= 11 FE \dx0000FFFF 8 * 7 06 7E 04 FF \wx0014 FF // Name: @return_action_1 9 * 34 02 00 FE 89 43 3C \dx0000000F \2* 1A 20 \dx00000010 \2+ 1A 20 \dx00000000 // param[126] \2+ 43 18 \dx0000000F \b0 \wx8000 // Return computed value // Name: color_conditional 10 * 23 02 00 FE 89 C8 01 \dx00000001 \b1 \wx00FF \dx00000001 \dx00000001 // 1 .. 1: return (((var[0x43, 24, 15] * 16) + base_sprite_2cc) + var[0x43, 28, 15]) \wx00FE // default: return (((var[0x43, 28, 15] * 16) + base_sprite_2cc) + var[0x43, 24, 15]) // param[127] = param[17] 11 * 9 0D 7F \D= 11 FE \dx0000FFFF 12 * 7 06 7F 04 FF \wx0014 FF // Name: color_unconditional 13 * 34 02 00 FF 89 43 3C \dx0000000F \2* 1A 20 \dx00000010 \2+ 1A 20 \dx00000000 // param[127] \2+ 43 18 \dx0000000F \b0 \wx8000 // Return computed value // Name: color_override 14 * 23 02 00 FF 8A C6 00 \dx0000FFFF \b1 \wx00FE \dx00000BB8 \dx00000BB8 // 3000 .. 3000: color_conditional; \wx00FF // default: color_unconditional; // Name: scope_capacity // cap : register 80 15 * 21 02 00 FE 89 7D 80 20 \dxFFFFFFFF // cap \2+ BA 00 \dx0000FFFF \b0 \wx8000 // Return computed value // Name: mixed_scope_capacity 16 * 28 02 00 FE 8A BA 20 \dx0000FFFF \2sto 1A 20 \dx00000080 \2r 7E FE 00 \dxFFFFFFFF // scope_capacity(var[0xBA, 0, 65535]) \b0 \wx8000 // Return computed value 17 * 9 00 00 \b1 01 FF \wx0064 1E 48 18 * 6 01 00 \b1 FF \wx0000 // Name: @CB_FAILED_REAL00 19 * 9 02 00 FD \b1 \b1 \w0 \w0 // Name: @CB_FAILED00 20 * 23 02 00 FD 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FD // Non-graphics callback, return graphics result // Name: @action3_0 21 * 23 02 00 FC 89 10 00 \dx000000FF \b1 \wx00FE \dx00000014 \dx00000014 // mixed_scope_capacity; \wx00FD // @CB_FAILED00; // Name: @action3_1 22 * 23 02 00 FB 89 10 00 \dx000000FF \b1 \wx00FE \dx00000014 \dx00000014 // mixed_scope_capacity; \wx00FD // @CB_FAILED00; // Name: @action3_2 23 * 43 02 00 FC 89 0C 00 \dx0000FFFF \b3 \wx00FE \dx00000015 \dx00000015 // mixed_scope_capacity; \wx00FF \dx0000002D \dx0000002D // color_override; \wx00FC \dx00000036 \dx00000036 // @action3_0; \wx00FD // @CB_FAILED00; // Name: @action3_3 24 * 43 02 00 FD 89 0C 00 \dx0000FFFF \b3 \wx8000 \dx00000023 \dx00000023 // return string(STR_REGRESSION_DESC); \wx00FF \dx0000002D \dx0000002D // color_override; \wx00FB \dx00000036 \dx00000036 // @action3_1; \wx00FD // @CB_FAILED00; 25 * 12 03 00 01 FF \wx0064 \b1 FF \wx00FD // @action3_3; \wx00FC // @action3_2; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/039_storage.grf0000644000175100001660000000063214754345605020453 0ustar00runnerdockerGRF   6CINFOBVRSNBMINVBNPARBPALSABBLTR84NML9NML regression testA test newgrf testing NML  `    GRID |    | } & |    !  55 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/039_storage.nfo0000644000175100001660000000343514754345605020463 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d10 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\39" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: @CB_FAILED_PROD 3 * 15 02 0A FF 00 \wx0000 \wx0000 \wx0000 \wx0000 \wx0000 00 // Name: @CB_FAILED0A 4 * 23 02 0A FF 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FF // Non-graphics callback, return graphics result // Name: switch_storage2 5 * 96 02 0A FE 8A 1A 20 \dxFFFFFFFF \2sto 1A 20 \dx00000100 \2r 1A 20 \dx44495247 \2sto 1A 20 \dx00000100 \2r 7C 01 20 \dxFFFFFFFF \2sto 1A 20 \dx00000080 \2r 1A 20 \dxFFFFFFFF \2sto 1A 20 \dx00000100 \2r 7C 00 20 \dxFFFFFFFF \2+ 7D 80 20 \dxFFFFFFFF \2psto 1A 00 \dx00000000 \b1 \wx00FF \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx8000 // default: return 0; // Name: switch_storage1 6 * 38 02 0A FE 89 7C 00 20 \dxFFFFFFFF \2+ 1A 20 \dx00000001 \2psto 1A 00 \dx00000000 \b1 \wx00FF \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx00FE // default: switch_storage2; 7 * 11 00 0A \b2 01 FF \wx0000 08 00 09 00 8 * 9 00 0A \b1 01 FF \wx0000 21 20 // Name: @action3_0 9 * 23 02 0A FF 89 0C 00 \dx0000FFFF \b1 \wx00FE \dx00000035 \dx00000035 // switch_storage1; \wx00FF // @CB_FAILED0A; 10 * 7 03 0A 01 00 \b0 \wx00FF // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/040_station.grf0000644000175100001660000000240314754345605020456 0ustar00runnerdockerGRF  #6CINFOBVRSNBMINVBNPARBPALSWBBLTR84NML@NML regression testA test newgrf testing NMLTestBasic station 2 COALLVST/TEST   TestBasic station    rD.- 0 -B D/-B - -B ?    }   # k/  1~ ~ 1~ ~ 1~ ~ 7  ~   C8C BBC8C! BBC8C! BBTEST   )~      ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/040_station.nfo0000644000175100001660000001234314754345605020466 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d35 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\40" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 27 04 04 FF 02 \wxDC00 "Test" 00 "Basic station 2" 00 4 * 16 00 08 \b1 02 FF \wx0000 09 "COAL" "LVST" 5 * 47 00 04 \b6 01 FF \wx00FF 08 "TEST" 13 18 12 \dx00000002 0C F0 1E FF \w10 00 01 02 03 04 05 06 07 02 05 0E \b1 \b1 02 \b2 \b2 04 04 06 06 \b0 \b0 6 * 11 04 04 FF 01 \wxC4FF "Test" 00 7 * 20 04 04 FF 01 \wxC5FF "Basic station" 00 8 * 6 01 04 \b2 FF \wx0002 9 station.png 8bpp 1 1 5 5 -2 -2 normal 10 station.png 8bpp 7 1 5 5 -2 -2 normal 11 station.png 8bpp 2 2 3 3 -1 -1 normal 12 station.png 8bpp 8 2 3 3 -1 -1 normal // Name: station_spriteset - feature 04 13 * 7 02 04 FF \b0 \b1 \w0 // Name: station_spriteset2 - feature 04 14 * 7 02 04 FE \b0 \b1 \w1 15 * 114 00 04 \b1 01 FF \wx00FF 1A \b2 \b68 \dx000003F4 \wx0000 \dx0000842E \wx0000 \b0 \b0 \b0 \b16 \b5 \b2 \dx8000842D \wx0002 \b17 \b11 80 81 \dx00008430 \wx0000 \b0 \b11 \b0 \b16 \b5 \b2 \dx8000842D \wx0042 \b17 \b10 80 81 01 \b68 \dx000003F3 \wx0000 \dx0000842F \wx0000 \b0 \b0 \b0 \b5 \b16 \b2 \dx8000842D \wx0042 \b20 \b11 80 82 01 \dx0000842D \wx0000 \b11 \b0 \b0 \b5 \b16 \b2 \dx8000842D \wx0042 \b20 \b10 80 83 03 // Name: Station Layout@registers - Id FF // a : register 80 16 * 63 02 04 FD 89 1A 20 \dx00000001 \2sto 1A 20 \dx00000080 \2r 1A 20 \dx00000000 \2sto 1A 20 \dx00000081 \2r 7D 80 20 \dxFFFFFFFF // a \2sto 1A 20 \dx00000082 \2r 1A 20 \dx00000001 \2sto 1A 00 \dx00000083 \b0 \wx8000 // Return computed value // Name: Station Layout@prepare - Id FF 17 * 35 02 04 FC 89 1A 20 \dx00000000 \2sto 6B 2F 20 \dx0000FFFF \2r 1A 20 \dx00000001 \2sto 1A 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @action3_0 18 * 49 02 04 FB 89 7E FC 20 \dxFFFFFFFF // Station Layout@prepare - Id FF \2r 7E FD 20 \dxFFFFFFFF // Station Layout@registers - Id FF \2r 10 00 \dx000000FF \b2 \wx8000 \dx00000002 \dx00000002 // return 0; \wx00FE \dx00000003 \dx00000003 // station_spriteset2; \wx00FF // station_spriteset; // Name: @action3_1 19 * 49 02 04 FA 89 7E FC 20 \dxFFFFFFFF // Station Layout@prepare - Id FF \2r 7E FD 20 \dxFFFFFFFF // Station Layout@registers - Id FF \2r 10 00 \dx000000FF \b2 \wx00FF \dx00000001 \dx00000001 // station_spriteset; \wx8000 \dx00000002 \dx00000002 // return 0; \wx00FE // station_spriteset2; // Name: @action3_2 20 * 49 02 04 FC 89 7E FC 20 \dxFFFFFFFF // Station Layout@prepare - Id FF \2r 7E FD 20 \dxFFFFFFFF // Station Layout@registers - Id FF \2r 10 00 \dx000000FF \b2 \wx8000 \dx00000002 \dx00000002 // return 0; \wx00FE \dx00000003 \dx00000003 // station_spriteset2; \wx00FF // station_spriteset; // Name: @action3_3 21 * 55 02 04 F9 89 1A 20 \dx00000003 \2sto 1A 20 \dx00000003 \2r 7E FD 20 \dxFFFFFFFF // Station Layout@registers - Id FF \2r 10 00 \dx000000FF \b2 \wx8000 \dx00000002 \dx00000002 // return 0; \wx00FE \dx00000003 \dx00000003 // station_spriteset2; \wx00FF // station_spriteset; 22 * 9 00 04 \b1 01 FF \wx00FF 0B 08 // Name: @return_action_0 23 * 20 02 04 F8 89 43 38 \dx0000000F \2+ 43 1C \dx0000000F \b0 \wx8000 // Return computed value // Name: @action3_4 24 * 23 02 04 F8 89 0C 00 \dx0000FFFF \b1 \wx00F8 \dx00000142 \dx00000142 // return (var[0x43, 24, 15] + var[0x43, 28, 15]) \wx00FB // @action3_0; // Name: @return_action_1 25 * 20 02 04 F7 89 43 38 \dx0000000F \2+ 43 1C \dx0000000F \b0 \wx8000 // Return computed value // Name: @action3_5 26 * 33 02 04 F7 89 0C 00 \dx0000FFFF \b2 \wx00FA \dx00000000 \dx00000000 // @action3_1; \wx00F7 \dx00000142 \dx00000142 // return (var[0x43, 24, 15] + var[0x43, 28, 15]) \wx00FB // @action3_0; // Name: @return_action_2 27 * 20 02 04 FA 89 43 38 \dx0000000F \2+ 43 1C \dx0000000F \b0 \wx8000 // Return computed value // Name: @action3_6 28 * 33 02 04 FB 89 0C 00 \dx0000FFFF \b2 \wx00FC \dx00000000 \dx00000000 // @action3_2; \wx00FA \dx00000142 \dx00000142 // return (var[0x43, 24, 15] + var[0x43, 28, 15]) \wx00FB // @action3_0; 29 * 18 03 04 01 FF \wx00FF \b3 00 \wx00F7 // @action3_5; 01 \wx00FB // @action3_6; FF \wx00F9 // @action3_3; \wx00F8 // @action3_4; 30 * 22 00 04 \b4 01 FF \wx0100 08 "TEST" 1D \wxDC00 1C \wxDC01 0F FF \wx00FF 31 * 11 00 04 \b1 01 FF \wx0100 0A FF \wx00FF // Name: @CB_FAILED_REAL04 32 * 7 02 04 F8 \b0 \b1 \w0 // Name: @CB_FAILED04 33 * 23 02 04 F8 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00F8 // Non-graphics callback, return graphics result // Name: @action3_7 34 * 41 02 04 F8 89 7E FD 20 \dxFFFFFFFF // Station Layout@registers - Id FF \2r 10 00 \dx000000FF \b2 \wx00FF \dx00000001 \dx00000001 // station_spriteset; \wx00FE \dx00000003 \dx00000003 // station_spriteset2; \wx00F8 // @CB_FAILED04; 35 * 9 03 04 01 FF \wx0100 \b0 \wx00F8 // @action3_7; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/041_articulated_tram_32bpp.grf0000644000175100001660000000566414754345605023344 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSABBLTR34NMLANML regression testA test newgrf testing NMLXJX(  HL XM-$XFoster Turbo Tram       X  X D(\8xH $h ( , D< @$t@`@` ` @ D4|HࠡdPPP8PPt04T dd$$ ,< E 'D(\H8x@O^m $hHLHLPXHLPఈ숐HD`PPP ܈HPddLtPؘ HTHĈHH4| PPT ؈HLD04TXhl @@d@d⼉h HP(@H Hø@DdH HJY8h،P ی  PPP  '6ETctxΈx (7FU(\Ddh  ,<X8xhx D ,(pH؈P@@@L\<TXPM\ $hD(\PTXHHLH ܈HHXd0HԈddPPt< Pؘ HTЉ`HHHllHH$(DĉhؘHPTXt „@@d@4|P04ThMDLHXLPPXȘDX/ PPt04TTXdl PPPddtX $hXѠᨉ`4|PXXX\P숰T@X`ȉhL@@@T ` @@dٜXDDPأpX࢈`D4`-``@D|`oĄH././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/041_articulated_tram_32bpp.nfo0000644000175100001660000000365314754345605023344 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d18 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "3" 00 00 2 * 52 08 08 "NML\41" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: foster_express_articulated_parts 3 * 23 02 01 FF 89 10 00 \dxFFFFFFFF \b1 \wx8058 \dx00000001 \dx00000003 // 1 .. 3: return 88; \wx80FF // default: return 255; 4 * 74 00 01 \b25 01 FF \wx0058 06 0F 04 28 03 1E 1F \dx000AF386 02 01 0A \dx00004C48 09 87 11 8F 08 FF 15 FE 13 16 14 58 0E FF 07 10 18 4D 19 80 0F 2D 1D \wx0001 16 \dx00000000 1E \wx0000 16 \dx00000000 24 \b0 16 \dx00000000 10 FF 1C 01 5 * 25 04 01 7F 01 FF \wx0058 "Foster Turbo Tram" 00 6 * 6 01 01 \b1 FF \wx0008 7 tram_foster_express.32.png 32bpp 48 1 8 18 -3 -10 normal 8 tram_foster_express.32.png 32bpp 64 1 20 18 -14 -5 normal 9 tram_foster_express.32.png 32bpp 96 1 28 15 -14 -8 normal 10 tram_foster_express.32.png 32bpp 144 1 20 18 -6 -7 normal 11 tram_foster_express.32.png 32bpp 176 1 8 18 -3 -10 normal 12 tram_foster_express.32.png 32bpp 192 1 20 18 -14 -9 normal 13 tram_foster_express.32.png 32bpp 224 1 28 15 -14 -8 normal 14 tram_foster_express.32.png 32bpp 272 1 20 18 -6 -7 normal // Name: foster_express_set - feature 01 15 * 9 02 01 FE \b1 \b1 \w0 \w0 16 * 9 00 01 \b1 01 FF \wx0058 17 10 // Name: @action3_0 17 * 23 02 01 FE 89 0C 00 \dx0000FFFF \b1 \wx00FF \dx00000016 \dx00000016 // foster_express_articulated_parts; \wx00FE // foster_express_set; 18 * 9 03 01 01 FF \wx0058 \b0 \wx00FE // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_industry.grf0000644000175100001660000000160714754345605022013 0ustar00runnerdockerGRF  u6CINFOBVRSNBMINVBNPARBPALSABBLTR8NMLNML Example NewGRF: IndustryNML Example NewGRF: Industry This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.% Goods produced this month: |4 PASSCOALMAILOIL_LVSTGOODGRAIWOODIORESTELVALU  R }  }  }       E o  o  o  o   j   % &'( !" ! ::  ' ' ( ( ) ) * *  %&' (././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_industry.nfo0000644000175100001660000000621114754345605022013 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d22 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000001 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 165 08 08 "NML\04" "NML Example NewGRF: Industry" 00 "\8ENML Example NewGRF: Industry\0D\98This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML." 00 3 * 37 04 00 FF 01 \wxD000 "\0DGoods produced this month: \90\7C" 00 4 * 52 00 08 \b1 0B FF \wx0000 09 "PASS" "COAL" "MAIL" "OIL_" "LVST" "GOOD" "GRAI" "WOOD" "IORE" "STEL" "VALU" // Name: consume_all_prod 5 * 17 02 0A FF 02 \b3 01 01 08 02 06 03 \b2 09 80 05 81 82 // Name: consume_all_prod@registers 6 * 82 02 0A FF 89 7D 01 20 \dxFFFFFFFF \2/ 1A 20 \dx00000002 \2+ 7D 02 20 \dxFFFFFFFF \2sto 1A 20 \dx00000080 \2r 7D 03 20 \dxFFFFFFFF \2* 1A 20 \dx00000002 \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000000 \2sto 1A 00 \dx00000082 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // // Name: do_nothing_prod 7 * 7 02 0A FE 02 \b0 \b0 80 // Name: do_nothing_prod@registers 8 * 30 02 0A FE 89 1A 20 \dx00000000 \2sto 1A 00 \dx00000080 \b1 \wx00FE \dx00000000 \dx00000000 \wx00FE // // Name: factory_production_switch 9 * 69 02 0A FF 89 6F 01 20 \dxFFFFFFFF \2sto 1A 20 \dx00000001 \2r 6F 08 20 \dxFFFFFFFF \2sto 1A 20 \dx00000002 \2r 6F 06 20 \dxFFFFFFFF \2sto 1A 20 \dx00000003 \2r 6F 01 00 \dxFFFFFFFF \b1 \wx00FE \dx00000000 \dx00000000 // 0 .. 0: do_nothing_prod; \wx00FF // default: consume_all_prod; // Name: @CB_FAILED_PROD 10 * 15 02 0A FE 00 \wx0000 \wx0000 \wx0000 \wx0000 \wx0000 00 // Name: @CB_FAILED0A 11 * 23 02 0A FE 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FE // Non-graphics callback, return graphics result // Name: extra_text_switch 12 * 31 02 0A FD 89 6A 05 20 \dxFFFFFFFF \2sto 1A 00 \dx00000100 \b1 \wx00FE \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx8000 // default: return string(STR_INDUSTRY_EXTRA_TEXT); 13 * 27 00 0A \b6 01 FF \wx0000 08 06 09 06 25 \b2 09 05 26 \b3 01 08 06 27 \b2 00 00 28 \b0 \b0 14 * 11 00 0A \b2 01 FF \wx0000 21 02 22 01 // Name: @action3_0 15 * 23 02 0A FF 89 18 00 \dx000000FF \b1 \wx00FF \dx00000000 \dx00000000 // factory_production_switch; \wx00FE // @CB_FAILED0A; // Name: @action3_1 16 * 33 02 0A FE 89 0C 00 \dx0000FFFF \b2 \wx00FF \dx00000000 \dx00000000 // @action3_0; \wx00FD \dx0000003A \dx0000003A // extra_text_switch; \wx00FE // @CB_FAILED0A; 17 * 7 03 0A 01 00 \b0 \wx00FE // @action3_1; 18 * 13 00 09 \b3 01 FF \wx0000 08 27 09 27 12 02 19 * 13 00 09 \b3 01 FF \wx0001 08 28 09 28 12 02 20 * 13 00 09 \b3 01 FF \wx0002 08 29 09 29 12 02 21 * 13 00 09 \b3 01 FF \wx0003 08 2A 09 2A 12 02 22 * 26 00 0A \b6 01 FF \wx0001 08 09 09 09 25 \b3 04 06 07 26 \b0 27 \b3 08 0C 04 28 \b0 \b0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_object.grf0000644000175100001660000001566714754345605021413 0ustar00runnerdockerGRF  '6CINFOBVRSNBMINVBNPARBPALSDBBLTR8$NMLNML Example NewGRF: ObjectNML Example NewGRF: Object This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML. Original graphics by planetmaker, coding by planetmaker. This NewGRF defines a tile which can act as company-land replacement."InfrastructureCompany landCompany land ~ ~X   1.2.0 (r22723)  B>} }  } }  A(      A(      A(      A(      A(   A(     } A(} } } }    A       } }  }  }  A       } }  }  } 3    + INFR    nI74 + WW\\  @jF>DN\n"6J^r&:LZdȈ  **"$((&<<(PP*d d,x x.02468((PxxȰȈ@hhމ& $ @mG>ERf#5GYk} 1CR^gȋ Ȓ 1 ."I$6d&[P(d*I,Ȉ.$Ț06Ț2HȾ4Z 6l 8~$ HlMɗ"N{챨  Ȅ)@ Ȉ"1@>@Bq|B@ƈƉ1@m|6JtΊ.]{.RlՊҊ ֊M\Z&Z苬܋+UL{at9 zE,;JhwA@ Ȉ"1@ERf#5GYk} 1CR^gȋ Ȓ !;%8*S6.n2k7  6ȣ$5ȵ63HvZ1/$ȆǨH-lȆ+Ȇ)Ȇ'Ȇ&*Ȇ{W#Ȇ"Ȇ! )*@ #2AADs>DɸyG4C>|ىԉ$ .1`^loVi؊Չ.PN lKdg׊ҋf1m|}tx+:Igv@ #2AP|ڊԋ/1OQ/ً$3B.o~y{C )8G&etk'@ Ȉ!0?Ncr?A?Ĉĉ.=q:FxɊ0? L{4Ls΋>\z/Qnԋԋ-[h*Vhӌ⌘ٌKZX\$ύލ&Sb؍ꎏkmڎTnя3򏋏O4ُLȏ@ŏ‹^Zɏk'@ Ȉ!0?>?Bo~|@|ĉ0?>hwt7F4ʊ 96WFutl̊ϊ &Vʋd'Ql΋݊Ί Ռ%(UTcQ"͌܌G'ٌ'Tc捐] nۍڍ&Sb 2nڎVҏC22OyLҏNŏŏȏˏϏ'@ Ȉ!0?Ncr?A?Ĉĉ.=q:FxɊ0? L{4Ls΋>\z/Qnԋԋ-[h*Vhӌ⌘ٌKZXhkٍCoqKz掶sގ9,;Jhw@$@ Ȉ!0?>?Bo~|@|ĉ0?>hwt7F4ʊ 96WFutl̊ϊ &Vʋd'Ql΋݊΋h Ռ&UTpQl܌ڋ'ٌTcp3[ۍҍDՎ Q},;JgvBj'@ Ȉ"1@>?Bp}@ˆ~B1@ȉl{x9Ĉ> Ί<:ZJx4uQrRx΋;YUsnыcXg^mЌߋ܋ ֌'WSۍeS"ύތX ֎%VTtdNkˎl7Uso2}ŏIBŏk'@ Ȉ"1@>@Bq|B@ƈƉ1@m|6JtΊ.]{.RlՊҊ ֊M\Z&Zd͋܋˜!IXgv&}όވ׍GVTVP݉Ɏ7Usqs̎؏65$rQmyɏŏŏȏڷ'@ Ȉ"1@>?Bp}@ˆ~B1@ȉl{x9Ĉ> Ί<:ZJx4uQrRx΋;YUsnыcٓڋWU/mόދ΋ ʌ'VЍb%M卬ɍlʎ6l4Br/{Bx+:I~ds~[ /@g^bjt 0@P`p 0@P`pȆȊ Ȏ&."#  %00&@@'PP)``*pp+Ȁ Ȁ-Ȑ Ȑ.Ƞ Ƞ/Ȱ Ȱ1235679 (: @` @` @`|¤ @#&6Pr<^Ș+ <%M^*o.w2BBʈʉ9Jt"@ Ȉ"1@@Dr}BLj~DȈ͉=9;Ήy6QVĈ}Vڊ:ZX4t^܊;4YUsnы)XTv/Ќˌ͋ Ȍ&Wb""ό̍퍥Ǝ)8CEt/4@4~x(7Fds~~././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_object.nfo0000644000175100001660000001246714754345605021412 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d39 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000001 "B" "NPAR" \w1 00 "B" "PALS" \w1 "D" "B" "BLTR" \w1 "8" 00 00 2 * 292 08 08 "NML\01" "NML Example NewGRF: Object" 00 "\8ENML Example NewGRF: Object\0D\98This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.\0DOriginal graphics by \89planetmaker, \98coding by \89planetmaker.\0D\98This NewGRF defines a tile which can act as company-land replacement." 00 3 * 34 04 0F FF 02 \wxD000 "Infrastructure" 00 "Company land" 00 4 * 19 04 00 FF 01 \wxD002 "Company land" 00 // param[126] = param[161] 5 * 5 0D 7E \D= A1 00 // param[127] = (param[126] - 302012611) 6 * 9 0D 7F \D- 7E FF \dx120058C3 // param[127] = (param[127] << -31) 7 * 9 0D 7F \Du<< 7F FF \dxFFFFFFE1 8 * 9 09 7F 04 \7= \dx00000000 01 9 * 19 0B 03 7F 06 "1.2.0 (r22723)" 00 10 * 6 01 0F \b1 FF \wx0013 11 cc_grid.png 8bpp 1 1 64 31 -31 0 normal 12 cc_grid.png 8bpp 81 1 64 31 -31 0 normal 13 cc_grid.png 8bpp 161 1 64 23 -31 0 normal 14 cc_grid.png 8bpp 241 1 64 23 -31 0 normal 15 cc_grid.png 8bpp 321 1 64 31 -31 0 normal 16 cc_grid.png 8bpp 399 1 64 31 -31 0 normal 17 cc_grid.png 8bpp 479 1 64 23 -31 0 normal 18 cc_grid.png 8bpp 559 1 64 23 -31 0 normal 19 cc_grid.png 8bpp 639 1 64 39 -31 -8 normal 20 cc_grid.png 8bpp 719 1 64 39 -31 -8 normal 21 cc_grid.png 8bpp 799 1 64 31 -31 -8 normal 22 cc_grid.png 8bpp 879 1 64 31 -31 -8 normal 23 cc_grid.png 8bpp 959 1 64 39 -31 -8 normal 24 cc_grid.png 8bpp 1039 1 64 39 -31 -8 normal 25 cc_grid.png 8bpp 1119 1 64 31 -31 -8 normal 26 cc_grid.png 8bpp 1197 1 64 47 -31 -16 normal 27 cc_grid.png 8bpp 1277 1 64 15 -31 0 normal 28 cc_grid.png 8bpp 1357 1 64 31 -31 -8 normal 29 cc_grid.png 8bpp 1437 1 64 31 -31 -8 normal // Name: company_land_layout - feature 0F 30 * 31 02 0F FF \b66 \dx00000000 \wx0002 80 \dxC0008000 \wx0002 \b0 \b0 80 00 \dx00000000 \wx0002 \b0 \b0 80 81 // Name: company_land_layout@registers - feature 0F 31 * 62 02 0F FF 89 7D 00 20 \dxFFFFFFFF \2+ 7D 01 20 \dxFFFFFFFF \2sto 1A 20 \dx00000080 \2r 7D 00 20 \dxFFFFFFFF \2+ 7D 01 20 \dxFFFFFFFF \2sto 1A 00 \dx00000081 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // // Name: @CB_FAILED_LAYOUT0F 32 * 17 02 0F FE \b0 \dx00000000 \dx00000000 \b0 \b0 \b0 \b0 \b0 // Name: @CB_FAILED0F 33 * 23 02 0F FE 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FE // Non-graphics callback, return graphics result // Name: company_land_terrain_switch 34 * 470 02 0F FD 89 41 28 \dx0000001F \2cmp 1A 20 \dx0000001E \2& 1A 20 \dx00000001 \2* 1A 20 \dx00000012 \2sto 1A 20 \dx00000080 \2r 41 28 \dx0000001F \2cmp 1A 20 \dx0000001D \2& 1A 20 \dx00000001 \2* 1A 20 \dx0000000F \2sto 1A 20 \dx00000081 \2r 41 28 \dx0000001F \2cmp 1A 20 \dx0000001B \2& 1A 20 \dx00000001 \2* 1A 20 \dx00000011 \2sto 1A 20 \dx00000082 \2r 41 28 \dx0000001F \2cmp 1A 20 \dx00000017 \2& 1A 20 \dx00000001 \2* 1A 20 \dx00000010 \2sto 1A 20 \dx00000083 \2r 41 28 \dx0000001F \2cmp 1A 20 \dx00000000 \2< 1A 20 \dx00000001 \2sto 1A 20 \dx00000084 \2r 41 28 \dx0000001F \2cmp 1A 20 \dx0000000E \2^ 1A 20 \dx00000002 \2< 1A 20 \dx00000001 \2& 7D 84 20 \dxFFFFFFFF \2* 41 28 \dx0000001F \2+ 7D 83 20 \dxFFFFFFFF \2+ 7D 82 20 \dxFFFFFFFF \2+ 7D 81 20 \dxFFFFFFFF \2+ 7D 80 20 \dxFFFFFFFF \2sto 1A 20 \dx00000000 \2r 1A 20 \dx00000F8D \2sto 1A 20 \dx00000001 \2r 41 20 \dx00000007 \2cmp 1A 20 \dx00000001 \2& 1A 20 \dx00000001 \2sto 1A 20 \dx00000085 // guard \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000086 // !guard \2r 7D 01 20 \dxFFFFFFFF \2* 7D 86 20 \dxFFFFFFFF \2sto 1A 20 \dx00000087 \2r 7D 85 20 \dxFFFFFFFF \2* 1A 20 \dx000011C6 \2+ 7D 87 20 \dxFFFFFFFF \2sto 1A 20 \dx00000001 \2r 41 20 \dx00000007 \2cmp 1A 20 \dx00000004 \2& 1A 20 \dx00000001 \2sto 1A 20 \dx00000088 // guard \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000089 // !guard \2r 7D 01 20 \dxFFFFFFFF \2* 7D 89 20 \dxFFFFFFFF \2sto 1A 20 \dx0000008A \2r 7D 88 20 \dxFFFFFFFF \2* 1A 20 \dx000011C6 \2+ 7D 8A 20 \dxFFFFFFFF \2sto 1A 00 \dx00000001 \b1 \wx00FE \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx00FF // default: company_land_layout; // Name: company_land_purchase_switch 35 * 51 02 0F FF 89 1A 20 \dx00000000 \2sto 1A 20 \dx00000000 \2r 1A 20 \dx00000F8D \2sto 1A 20 \dx00000001 \2r 1A 00 \dx00000001 \b1 \wx00FE \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx00FF // default: company_land_layout; 36 * 43 00 0F \b12 01 FF \wx0000 08 "INFR" 09 \wxD000 0A \wxD001 0B 0F 0C 11 0D 01 14 01 0E \dx0000016E 0F \dx0037BB49 10 \wx0834 16 00 17 01 37 * 10 00 0F \b1 01 FF \wx0000 15 \wx0011 // Name: @action3_0 38 * 43 02 0F FF 89 0C 00 \dx0000FFFF \b3 \wx00FF \dx00000000 \dx00000000 // company_land_purchase_switch; \wx8400 \dx00000157 \dx00000157 // return 1024; \wx8002 \dx0000015C \dx0000015C // return string(STR_NAME_COMPANY_LAND); \wx00FD // company_land_terrain_switch; 39 * 10 03 0F 01 00 \b1 FF \wx00FF // @action3_0; \wx00FD // company_land_terrain_switch; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_railtype.grf0000644000175100001660000011514014754345605021761 0ustar00runnerdockerGRF  p 6CINFOBVRSNBMINVBNPARBPALSWBBLTR8%NMLNML Example NewGRF: RailtypeNML Example NewGRF: Railtype This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML. Original graphics by Irwe, coding by planetmaker. This NewGRF defines a graphical replacement for normal and electric rails  ~ DJT ~ ~   NuTracks   !"#$%&'()*+,-./0123456789:;<=>BBRAILRAILELRL_040_080RLOWRMEDRHIGE040E080ELOWEMEDEHIGHSTRDBNNDBNEDBHNDBHERAILELRL_040_080RLOWRMEDRHIGE040E080ELOWEMEDEHIGHSTRDBNNDBNEDBHNDBHE IJKLMNOPQRSTUVWX [\]^ abcdef  ijklmnopqr  uvwxyz{|    xELRLRAILELRL_040_080RLOWRMEDRHIGE040E080ELOWEMEDEHIGHSTRDBNNDBNEDBHNDBHE ELRLE040E080ELOWEMEDEHIGHSTRDBNEDBHE      , 85 /> 855.\k q 9855j!j|ؚ6*X7׈*iG7n7i\*75k5G_TT*G94*GGG `**ii*T 9~҉∨ ҉&69 9ɤZ8 8*zT GҊrLڡGkTGi+:6k"GGi@\kT$ۡL r9"&,58 5558 0?\ j!j5589 q]l7X.6 Zji7n7Gi.5׈QG5k5\9G.\79+ GG.g<.ii.9 G ѱԈ\\B.p9 9V568 *( \(rފ(ykG(-(5҄2A8i(Gk6\kT9iڄ۲r ℋV91 &&&&$% #|& &&׈(׈<$P%`&t&&С %$L%L$##%#*&AAB*&TTP~ &&&׈-<&EBEE$qȀ%ȗ.Э %.%$\W$#\#A* #BATTLT~t5 &&&$ %$ #{& &׈'&2F&K%+$b.{A.Ѝ2ABAQ\d\2\d. #$% $4#U5 &&&$ %$ #1 &&&&$% #x &%$*&*A@&HAB&רZATנ 855.\k q 9855j!j|ؚ6*X7׈*iG7n7i\*75k5G_TT*G94*GGG `**ii*T 9~҉∨ ҉&69 9ɤZ8 8*zT GҊrLڡGkTGi+:6k"GGi@\kT$ۡL r9"&,58 5558 0?\ j!j5589 q]l7X.6 Zji7n7Gi.5׈QG5k5\9G.\79+ GG.g<.ii.9 G ѱԈ\\B.p9 9V568 *( \(rފ(ykG(-(5҄2A8i(Gk6\kT9iڄ۲r ℋV91 &&&&$% #n&% A;ABA&&@&`$ `\`$`l  %$ $$ $###`&% AB9&6 &&$HP  %\%$$#h#$5 &&&$ %$ #d&%&&1A4BA$=M$T%T$T# %$p$$##5 &&&$ %$ #1 &&&&$% # o&% ABAC&X&`&`$ `dx$Ȁ%$% $$ ## #ث!, 85 /> 855.\k q 9855j!j|ؚ6*X7׈*iG7n7i\*75k5G_TT*G94*GGG `**ii*T 9~҉∨ ҉&69 9ɤZ8 8*zT GҊrLڡGkTGi+:6k"GGi@\kT$ۡL r9""&,58 5558 0?\ j!j5589 q]l7X.6 Zji7n7Gi.5׈QG5k5\9G.\79+ GG.g<.ii.9 G ѱԈ\\B.p9 9V568 *( \(rފ(ykG(-(5҄2A8i(Gk6\kT9iڄ۲r ℋV9# &&׈'6&=&??$et%z؉***T %m%$&%#$##& A&ABA0LLri&$5 &&&$ %$ #%1 &&&&$% #&{ &#&0&%EH2$9%A|?AB$Adm.d.2.؝% #$5##qU'1 &&&&$% #(& &)&׈0?׈HV$`o%t.. %\%\$##%#2ؓ&A AB2:Il2){  &%&(&׈&>HA&<?ABAb$L[L$r%&'% T<$%T##$**5 &&&$ %$ #+, 85 /> 855.\k q 9855j!j|ؚ6*X7׈*iG7n7i\*75k5G_TT*G94*GGG `**ii*T 9~҉∨ ҉&69 9ɤZ8 8*zT GҊrLڡGkTGi+:6k"GGi@\kT$ۡL r9",&,58 5558 0?\ j!j5589 q]l7X.6 Zji7n7Gi.5׈QG5k5\9G.\79+ GG.g<.ii.9 G ѱԈ\\B.p9 9V568 *( \(rފ(ykG(-(5҄2A8i(Gk6\kT9iڄ۲r ℋV9-b&% &A&AB&M$T%T$T%i %$$#$##.5 &&&$ %$ #/1 &&&&$% #0c&%$ &AB<A$ATO%$% #$p##/ 11 &&&&$% #2l&%ABA<&ITT$ Yi$p% %$%$%$##*3` &% A BA&&> &&$HI U$% $$##$$##45 &&&$ %$ #5( 55 /&j!jJY6&X׈tr&iGiT6L5k5GWȘL&V5kȣGG(&;GGLVkL0:Vә|0䈘0|y||jkȊ 76(55 )j!j*7PX*6TL{Gi*~IG5k5T6G*ˈT(G*56TTj,TG5Pn+IFzΉPTPP̈vĉLkLL@7t(66G6G6",!! *jjjjXXrpx؀Ј~kt!|~pGGGG 8t(66G6G6 ! ! XXjjjjpxؖ*j{&*x&~GGGМ9r 66 GGjii  06666 66ii! iiiij6jHj$ 00 0!```!l <`:s  66 GGjGG" 66 66660 GijGGGG !0jLj0 0G 0`!0`0`<ؐj!о`;A) 55.=X(!jXKZPX!XXGku6(G|UX!(5GG ˠw( PkGG0G؝Y&w(w5YNwk5wk5Yweؽw숟fkY w<:5X 45G[feTY+٫Т{ɫkYe%ڢэ#B,ex܉G⅋܉,+]7FЌ]l=< (X5 +Gj !! ,7)G,^GGk5k55G,rGG-,Gs5G5kX,,WXXGk܈ذk\X55G؉X{|Ή4= ) 5j! /% (!jKJ%XGsJ%GG5k5kG[%5k%[JJGG؃op%Чo%DodXG5o%kؙ >_*5 .j5*8R j (jUc}k5G PGGGj+ GG5555+ j֘GwGGWj!*+ ؂5i7!(( ؂!LiiG jiZۈW: iaL`e j+߈᱉2L6 Xd 3^݊,e⊉L b㍋剱b !7 2gbbێ3)Il, 85 /> 855.\k q 9855j!j|8 8 6*X7׈9TXiG7n7i\T56T5k5G_q8rT5T*G949 5kȳGGG ` 9k*ii*rq 8kG7GT9 9'kn7T @ n6_GGTzPR9T8 8Tn7n rR GT8r*T& r/>6k"GGi껋YhT$ӠL~9{Jh,58555.=Z j!j5589 .j7X.6 8 Ti7n7Gi.59 QG5k5\65\9G.\5\r0 GG.56\ rC.ii.k9 q 舰\G7g5k8 q8 \7nkA56n 99 \p6\.i\rBn7n\8 \5(@.(\p҄28i(Gk6\k9\۲rkꄋ̛K .  r 8q r /66G6 8 96 r8P_!! ؏jjjjXX7n676GGn}Ȇ!@  9 GGGG89 E 988_8c r q7rq 8}| L .8 r  8r q 9q 8q9 r q 9q8 r 66G69 868 6GG6PW! !,0XXjjjj,`G6n767r7n76,j,0XGGG  8 qSq9|9 ًM 8   q 9966&r GGjii9 q 8 n76nE 7 UD6666 6628ii! iiiijii9 rj3 j 8 q9 qjUr #D8q!iiD DDrj7j! qr{Vw_8 r8脰q89)4tJ!U67 !2I2}gZyN r  8 669"GGjGG qD7n76 8C n 8e66 666" 98 GijGG@!GG r  j6n77j8 q9qj7Dj8rDD:D 8q9 8!jvD  9qD9D q "! ؈! q!Cr8!lSـ9 O9 .  /,955jP_X8 X(!jXȅ  5X!XXG57kq 6-G757n77` !-555GG7!r( n7kG7d GG@qسGk7kdG r8P~9r(І57dG q!!!Ȇ-rN,ѽq95k7dG q-ɽf ؆85dY9OP9г;d 赈,ȳ Ȇk 9q8Z [7775G9D8ɿk 8OْE7 dԋ.#nk6G#r;9kd{  P6Xٿѿ! qQrB6ɒ!9 ѿd7d g;J5r  9g^س7kn\q쌓E~9P7~f E$(0EqSP . ,X5 1?^Gj !! 2 q 8f77nG7G21 XG7k5k55G2!؄SG7n329 GGG5775G5kd8:!8q 282n7cd0! 99rdGkdGk22q qdk,8d55G؉ȗr Ж,2q 9,qGᲊ>M2jxeQ.  q /> r 9 5j!P_ 9 8  * (!j *X7G7n7|8 諨*GG5k5knGe*57nk*eT5n7*qGGGG q՘~k** qj!~7~T r !K~ }JP*XG57~8 n7kT q8 rȪ%v9TNDШҊytR.5 !j55 80=[ j (j5 r_m؋ k75G 5^ 77n7GGGj/ 9  GG5555/ j q /  8GGGk_j!8qrY_7knk5i؎ 9L 9!((  = GGn7k!_8l8TiiG qTjiz骉_knz_8LU9eٟiii rT KW kk_ǹqȽik7 j/L`q_TNJƪXr{p TƱKS5n؎.r qx7k–zTT55m98 i `qŌ7Tx,씍IXtS, *ט- 8 =Z56 d9 r55\j!j5/r9ȈU655j!jT75X.6[q9 q*X57kT57n75k. 5 r 9 56*\Q5k\-8 *76k_G#n7\~TG qGG.\TXGG r#0r .X5kT+ 8 \иkT8 9 8\&BTqz؊pGGB4 q86z 8$9*!مp PP\r9ة8 LTkȉk8."Pqp6(6Lnzp\>7nTmBGokLrOm^ڄ"(qvӟTY1  !0qq8 qj5Ud 88 8 8 qg 88rr9  jj].8 rq2^9 898 9q88qr89r  9q4IZU8r98j+mq og/-?0虈j^9815 qqrr؇8ej $3lrqq2E.Uqf/ <r8j Gqjɗjqj=L aD2jسȂ{q)0 ]kNrL%-"89 2ڦ 8kٔ#~89bk'QjZkr8:Rj v9 Ҵ\낈^HU ᕈ XqZxۮ@jⰌԜ/jqUc1 jqqj qjjqjq-<8 8/8 j P_^ q(q 888  99 8' rq838898f/8' 98r8 r89r89U8j8q688\8rhnqg k9jdsjvkj8 )d8rr0qZ :38\r@8q9- ~qj !U 9 8q /L7 ٭Rr-0ؙOȁT83q qq&Z% j28;3,r\9 5}#lj^qص9k#qC qҴ/3urKg2؛b O0 .=29ؙjٔtȌ֌VS1  /-jq 8qqSb] q 8 8 88 jj  9rr88 0aq888rdjq88q98 89f.q98 ؍88 r98r̸]U qW7j898je.95z N9P ^jJ |؟rr?1 5 qqDX/Vp r jоH \80q7% j8r&v0pjcMD jq9(ȥA9 5Jbro8k$7FkW 98ً|kq`q9oNمjVr\Й~ڗh¨+{,o:V>& jDፌƼqjW`1 qjqjjq jqqj .= j 838 8Ra8 99  888 q:q f^8988/8qr ;88 r98r 8r89 >8衈3ȍ*,8q8jo89kHW-8S998hxِ8kVerrɄ2r`^j[q jk 9wO=,7F M}59q ؎J SL{Aj1G 8 rjk&9DqdЧ軺jkxvRrqqU50Eqrr /꺣qj|Uo?4h9ٹ9cUf/3җjٿ87r9j鸈/6؋]g,¶\qKX2 jqqj qjjqjq.=8 808 j 4a` q)q 888  99 8( rq848898k08( 98r8 r89r89W8j8q79ؐ8^8rjpqi :jfujxkj8 *f8rr1q\ &;98crFkj 8q9.؀qj59 "W 98q^qjI"Yr 8j y `>9FqpPCf0 jjE}<42rh9 5jzSnk| ڱbw3 ڨ9 q xkU٦'r\qkG d,k/<8I#qDSn؝_r 9Ҵw܆kۃ|>F싌hܩܳS9 3B[  ׈׈!!EjG?jkks66XG7G.8 65G7n79 97kkGG!j* 8*iG777Gs9 ?7Gk5JqGr *n7_ *tG*i rȲkG*i8 8@*BZT ?p99\h,58555.=Z j!j5589 .j7X.6 8 Ti7n7Gi.59 QG5k5\65\9G.\5\r0 GG.56\ rC.ii.k9 q 舰\G7g5k8 q8 \7nkA56n 99 \p6\.i\rBn7n\8 \5(@.(\p҄28i(Gk6\k9\۲rkꄋ̛]l, 85 /> 855.\k q 9855j!j|8 8 6*X7׈9TXiG7n7i\T56T5k5G_q8rT5T*G949 5kȳGGG ` 9k*ii*rq 8kG7GT9 9'kn7T @ n6_GGTzPR9T8 8Tn7n rR GT8r*T& r/>6k"GGi껋YhT$ӠL~9{^  6׈&j5=!!!jTXGj!n7GXXy7i6 8ГGGGkk069 9!!G08 0jm5kG7H 9Gm`5rXU0 i0 8i0Gk6-_0Ar  蹉Ud9ian h{JKLLLKKKhh{K LhJKj"KKhh KL Lj?J{<>LL?{HBQҝ{J=JB̹ӸtL{>DHJJ؀ѻ;5JJX@J{6J@JJۖzJL줠>@{JHJzжXAـJB᷐|B{HA?|H{JJJ{􈺏ȄHB?LP18{~耏㯈鈀β^ՒȀ㱏銸Ժ@/8ҢRiv͋Z_ҋ{cc {J{HHH{HA@Oq|tXjX>茈<}?> |xX|J苉4C}>P.XjЉsษ6E҉t(6Ј8jŊ8|3ШtEj!Kňêl>rx{j!镉6. ؋-wj!;}תI[[مم[tltЪ! dedXq8ٷЊQSq*&tɂ؏ۑhhh =wwɂI<{<<< hȜ)5N=&<{JKH{{HKLHH5CBHirڑ{T>KL@BJ؃<82:}LLLJ?{HL؀JKV{Hh{EкE0tL;LJJ@BXhhِ /tLLJL@?sȺ8L@ >8LL@ALK@@eJKJ{~ .LKJ>цKKK|V<dL|ۣ(Ⰸ.HHjjHJ⮹X؀&HXLʘ@.ɿX%lǑ壋&‹d0e,@ ( ؈Xj؈0?N>A@Oq|tXjX>茈<}?> |xX|J苉4C}>P.XjЉsษ6E҉t(6Ј8jŊ8|3ШtEj!Kňêl>rx{j!镉6. ؋-wj!;}תI[[مم[tltЪ! dedXq8ٷЊQSq<<&tɂ؏ۑhhh =wwɂI<{<<< hȜ)5N=&<{JKH{{HLKLHH5CBH hnrڑ{T|KKL@BJ؆<82:}LLL{JHJ?JLLLK@<@JEкE0tLLKȹA /tLLKLɺكٌsȺ8LK3ي>8LC<8JJALK=KKJ{<{è8?>Bʃ .LK誷xǘK|BG< &J (ⰈOJ!ڌ @.6ZJ6Lʡ@.ɚ%lljΊ.􊅊Z.f,@ ؈ jX (!0?1Bj!Bm|~tBDB Djňƈ~XBCX!2~胨BIJwLL?{HJBwMtK>{JЀHJJ؀zL{>BHJJ ;5=~EǩQGيz>ؿGKL{z=L|zсـH8ٹK{HB:;ڼ>B P?>{~rʴƱD5ж4ơt֙H0j6ґH[؀6tHBJ|ѡԪ@@΢@l̈B_Zil, 85 /> 855.\k q 9855j!j|8 8 6*X7׈9TXiG7n7i\T56T5k5G_q8rT5T*G949 5kȳGGG ` 9k*ii*rq 8kG7GT9 9'kn7T @ n6_GGTzPR9T8 8Tn7n rR GT8r*T& r/>6k"GGi껋YhT$ӠL~9{jh,58555.=Z j!j5589 .j7X.6 8 Ti7n7Gi.59 QG5k5\65\9G.\5\r0 GG.56\ rC.ii.k9 q 舰\G7g5k8 q8 \7nkA56n 99 \p6\.i\rBn7n\8 \5(@.(\p҄28i(Gk6\k9\۲rkꄋ̛k9 .  /,955jP_X8 X(!jXȅ  5X!XXG57kq 6-G757n77` !-555GG7!r( n7kG7d GG@qسGk7kdG r8P~9r(І57dG q!!!Ȇ-rN,ѽq95k7dG q-ɽf ؆85dY9OP9г;d 赈,ȳ Ȇk 9q8Z [7775G9D8ɿk 8OْE7 dԋ.#nk6G#r;9kd{  P6Xٿѿ! qQrB6ɒ!9 ѿd7d g;J5r  9g^س7kn\q쌓E~9P7~f E$(0EqSl . ,X5 1?^Gj !! 2 q 8f77nG7G21 XG7k5k55G2!؄SG7n329 GGG5775G5kd8:!8q 282n7cd0! 99rdGkdGk22q qdk,8d55G؉ȗr Ж,2q 9,qGᲊ>M2jxem.  q /> r 9 5j!P_ 9 8  * (!j *X7G7n7|8 諨*GG5k5knGe*57nk*eT5n7*qGGGG q՘~k** qj!~7~T r !K~ }JP*XG57~8 n7kT q8 rȪ%v9TNDШҊytn.5 !j55 80=[ j (j5 r_m؋ k75G 5^ 77n7GGGj/ 9  GG5555/ j q /  8GGGk_j!8qrY_7knk5i؎ 9L 9!((  = GGn7k!_8l8TiiG qTjiz骉_knz_8LU9eٟiii rT KW kk_ǹqȽik7 j/L`q_TNJƪXr{p TƱKS5n؎.r qx7k–zTT55m98 i `qŌ7Tx,씍IXto .  r 8q r /66G6 8 96 r8P_!! ؏jjjjXX7n676GGn}Ȇ!@  9 GGGG89 E 988_8c r q7rq 8}| p .8 r  8r q 9q 8q9 r q 9q8 r 66G69 868 6GG6PW! !,0XXjjjj,`G6n767r7n76,j,0XGGG  8 qSq9|9 ًq 8   q 9966&r GGjii9 q 8 n76nE 7 UD6666 6628ii! iiiijii9 rj3 j 8 q9 qjUr #D8q!iiD DDrj7j! qr{Vw_8 r8脰q89)4tJ!U67 !2I2}gZyr r  8 669"GGjGG qD7n76 8C n 8e66 666" 98 GijGG@!GG r  j6n77j8 q9qj7Dj8rDD:D 8q9 8!jvD  9qD9D q "! ؈! q!Cr8!lSـ9 u{   / ?<ˈ@Zʈ@ ʈx@xxxxx0lhhh 0@BQvt    ̈"1@ˈDQ Dft脈"fԈ򈈉=[yvĉ≘- w!  xK@  $0  $0Ȁʨ $ʨ $yV   / @8 (@8ʈ|8p8ppɈ/zY    $ $ $GHUd(HsHH܈؈؉ ({   / @O@m?\@ʈ  ڈ@7RSpp܉ppp6pTprp((̊(Ë.!L[|    !0 ̈C@ˈCrʈd  Ȉ Ȉ@ȉȉ5ȉSȉqȉȉȉډCaXʊXXX.X@O W $ 66 6666 66ii! iiiijii$j$j$0000```lؐ !l 55j!jXi׈$X55A$%A$djXHHSAl5$lk$Ġ$؈$$Y 666 66!! jXXjjj,64D;<8!@C!<j5׈D`[o׈D}ࠈD""5ވDj!DX<}Y[^ F ň!/ĈB@!`È@!@ˆƐ@B!@@!@B @F؉kF?ʼnDZA}j5555j Xj!j55j!jX(5"iAj:5<15ec[Zj je~}x XX j j(hhX ̸! )j(5XjXj5ijØe':I[eY^ F ň!/ĈB@!`È@!@ˆƐ@B!@@!@B @F؉kF?Fj ؠ"A"jD?XjX"(DXؠ"XXjDX"(Xj#f"fH "XjD"~ H<"!"!H"d X X؈S.4 UTRYR6Q `656699j7,@ ؈ jX (!0?1Bj!Bm|~tBDB Djňƈ~XBCX!2~胨BIJw@7888#8zXACHB6"C>B#"A8?5|"#988Ӫr6"##H”?:}B鈺18JQ7~9R:#^^'R7⹏:%%AԺ8%:8ЫZiv͈Bߏҋ7a 7#7"""7"65 76557#"#75  "556""" ""OB6T$6b,@ ( ؈Xj؈0?N>A@Oq|tXjX>茈<}?> |xX|J苉4C}>P.XjЉsษ6E҉t(6Ј8jŊ8|3ШtEj!Kňêl>rx{j!镉6. ؋-wj!;}תI[[مم[tltЪ! dedXq8ٷЊQSq*&tɂ؏ۑ555 =wwɂI67666 5Ȝ)5N=&6789"77"65-kYL>9:""5CB"irڑ{T|99:;""B#؃682:}%%:;7#"#?7"6#Ӭz6 7889:%:#"6DǸ>:::89V7"h7EкE0t:;:88@BXhhِ /t::8:@?sȺ:@ >%::@A:9@@e887~ %:;98>ц99|V6ٸ>&:p|8%#!::9H"jj"ڮ:67X%V.릉"Xn$ʘ@.ɿA%lǑe&"&.,@ ( ؈Xj؈0?N>A@Oq|tXjX>茈<}?> |xX|J苉4C}>P.XjЉsษ6E҉t(6Ј8jŊ8|3ШtEj!Kňêl>rx{j!镉6. ؋-wj!;}תI[[مم[tltЪ! dedXq8ٷЊQSq*&tɂ؏ۑ555 =wwɂI67666 5Ȝ)5N=&6789"77"65-kYL>9:""5CB"irڑ{T|99:;""B#؆682:}%%:;7#"#?#6#Ӭz6 7889:9%:#<6DǸ>:::9@<@JEкE0t::9ȹA /t::9:ɺكٌsȺ:93ي>%::A:968#JA:999896{è}87~Bʃ %:;98>Bxǘ躨>ك6ٸ>B &p|.8%O#!::9ڌ⮹.6؀%%Z#.릉˨$ʡ@.ɚ%lljΊ.􊅊"..,@ ؈ jX (!0?1Bj!Bm|~tBDB Djňƈ~XBCX!2~胨BIJwH8#8z=|z::8"Bطٺ:<96"78"AQRL7A 76""#9P?;7>6"##ș:%79>BH?٬:yن>Є651ٱ4[7~4|7#:ɱJ*6##60%66#x1t6.R6rڔ@@>΢@ˊY7芸 ( 66 6(66 6 ii! iii i$ j +( 8 8( 8 8 iij 8 j8p8 p8>jp(ph8!ب! i(Ш!8j!t   $:55j65 j(XZ$(!+( u !X( 5AjXi(5(5($HP$556d$S(߰$S(Ї$$!(A4  ((((! !! ( 4(k p66 66!!  jXXjjjjXj6=Dj<;:2 !<<-p<j)  . ,j55>X(j 5B (+!(,n j5 (X! (5(iXj (C PX,dB55,(M,i(M,X,ȑ,7 ((# ($, (!( +j D 5 (+ (( H !j55j!X (iXj!jXi(,(  <+6jj j PM6 XX j !dj(HhwhX  (! Z! 55*! XXjXjiXjdjjem55jwXjj!#+X1X;% j ؠjبX(,XذjX XX9BXjjXJ(V (XX X<8989965 ( <8 588<8875 9؂7#,<87X5555 ׂ#",68iiX!X 5887"7؂""97kk7"7778XXn7,6",8;,7 6"#7"#97iBJ6A87ki_\6"h $< TT[\\V[+$UU]]@6 T]]]! UUZTZ[S[U!!jXXkSSTTTU XXXXX@ YR[T[$XYYRTTTTjX  TSYSSSS& S" RYRF pYRRR؈X׈ TSRYY؈Fh UTSYTFFh<< ZS 7kX!Xk7'ThXn76STSU%, UZZT%;gSi׉n| $ (((((%9$L ) 666 661jXXjE<6*G!<jXjjjhdjjEF ňĈ><ZÈ|<<ˆ<>@<<> @F؉UxFn?蹱 ( ٤=u 66 6(66 6 ii! iii iD&Z j +( x xx x ׈x iij  S.4 UTRYRo 謪CRR؈X׈ @dRYY؈Fh UTSYTFFh"<< -ZS 7kX!Xk7]ThXn7"CpSTSU=DZZT=_?Siqګ ț^ F ň!/ĈB@!`È@!@ˆƐ@B!@@!@B @F؉kF-? (((((=ű"A}04  'Ȅؒ =66 6 )66^ I~jXXji`#R6` k!`!jXjjj6jЋ jBʸ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_railtype.nfo0000644000175100001660000002563214754345605021773 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d171 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000001 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 293 08 08 "NML\02" "NML Example NewGRF: Railtype" 00 "\8ENML Example NewGRF: Railtype\0D\98This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.\0DOriginal graphics by \89Irwe, \98coding by \89planetmaker.\0D\98This NewGRF defines a graphical replacement for normal and electric rails" 00 // param[127] = 0 3 * 9 0D 7F \D= FF 00 \dx00000000 // param[126] = 0 4 * 9 0D 7E \D= FF 00 \dx00000000 5 * 9 09 88 04 \7gG \dx01544A44 01 // param[126] = 1 6 * 9 0D 7E \D= FF 00 \dx00000001 7 * 9 09 7E 04 \7= \dx00000000 01 // param[127] = 1 8 * 9 0D 7F \D= FF 00 \dx00000001 9 * 9 09 7F 04 \7! \dx00000000 01 10 * 13 0B 03 7F 05 "NuTracks" 00 11 * 6 01 10 \b5 FF \wx000A 12 gfx/lc_right.png 8bpp 5 5 44 23 -21 4 normal 13 gfx/lc_right.png 8bpp 55 5 44 23 -21 4 normal 14 gfx/lc_right.png 8bpp 0 100 5 12 -3 -8 normal 15 gfx/lc_right.png 8bpp 50 100 19 19 -4 -6 normal 16 gfx/lc_right.png 8bpp 100 100 23 17 -24 -9 normal 17 gfx/lc_right.png 8bpp 150 100 5 12 -5 -8 normal 18 gfx/lc_right.png 8bpp 200 100 25 14 3 -9 normal 19 gfx/lc_right.png 8bpp 250 100 5 12 -1 -8 normal 20 gfx/lc_right.png 8bpp 300 100 5 12 -3 -10 normal 21 gfx/lc_right.png 8bpp 350 100 19 14 -15 -11 normal 22 gfx/lc_right.png 8bpp 5 5 44 23 -21 4 normal 23 gfx/lc_right.png 8bpp 55 5 44 23 -21 4 normal 24 gfx/lc_right.png 8bpp 0 50 5 12 -3 -8 normal 25 gfx/lc_right.png 8bpp 50 50 8 21 -5 -14 normal 26 gfx/lc_right.png 8bpp 100 50 6 23 -7 -20 normal 27 gfx/lc_right.png 8bpp 150 50 5 12 -5 -8 normal 28 gfx/lc_right.png 8bpp 200 50 7 21 3 -15 normal 29 gfx/lc_right.png 8bpp 250 50 5 12 -1 -8 normal 30 gfx/lc_right.png 8bpp 300 50 5 12 -3 -10 normal 31 gfx/lc_right.png 8bpp 350 50 8 22 -3 -19 normal 32 gfx/lc_left.png 8bpp 5 5 44 23 -21 4 normal 33 gfx/lc_left.png 8bpp 55 5 44 23 -21 4 normal 34 gfx/lc_left.png 8bpp 0 100 21 19 -14 -6 normal 35 gfx/lc_left.png 8bpp 50 100 5 12 -2 -6 normal 36 gfx/lc_left.png 8bpp 100 100 5 12 -3 -9 normal 37 gfx/lc_left.png 8bpp 150 100 23 15 -23 -9 normal 38 gfx/lc_left.png 8bpp 200 100 5 12 4 -7 normal 39 gfx/lc_left.png 8bpp 250 100 23 17 0 -7 normal 40 gfx/lc_left.png 8bpp 300 100 21 13 -2 -11 normal 41 gfx/lc_left.png 8bpp 350 100 5 12 -3 -9 normal 42 gfx/lc_left.png 8bpp 5 5 44 23 -21 4 normal 43 gfx/lc_left.png 8bpp 55 5 44 23 -21 4 normal 44 gfx/lc_left.png 8bpp 0 50 7 21 0 -14 normal 45 gfx/lc_left.png 8bpp 50 50 5 12 -2 -6 normal 46 gfx/lc_left.png 8bpp 100 50 5 12 -3 -9 normal 47 gfx/lc_left.png 8bpp 150 50 7 21 -7 -15 normal 48 gfx/lc_left.png 8bpp 200 50 5 12 4 -7 normal 49 gfx/lc_left.png 8bpp 250 50 7 22 0 -17 normal 50 gfx/lc_left.png 8bpp 300 50 6 21 -2 -19 normal 51 gfx/lc_left.png 8bpp 350 50 5 12 -3 -9 normal 52 gfx/rails_overlays.png 8bpp 0 155 40 21 -19 5 normal 53 gfx/rails_overlays.png 8bpp 50 155 40 21 -19 5 normal 54 gfx/rails_overlays.png 8bpp 100 155 40 7 -19 4 normal 55 gfx/rails_overlays.png 8bpp 150 155 40 7 -21 20 normal 56 gfx/rails_overlays.png 8bpp 200 155 12 19 11 6 normal 57 gfx/rails_overlays.png 8bpp 250 155 12 19 -21 6 normal 58 gfx/rails_overlays.png 8bpp 0 195 64 39 -33 -8 normal 59 gfx/rails_overlays.png 8bpp 75 195 64 23 -31 0 normal 60 gfx/rails_overlays.png 8bpp 150 195 64 23 -31 0 normal 61 gfx/rails_overlays.png 8bpp 225 195 64 39 -32 -9 normal // Name: lc_right_closed - feature 10 62 * 7 02 10 FF \b1 \b0 \w0 // Name: lc_right_open - feature 10 63 * 7 02 10 FE \b1 \b0 \w1 // Name: right_level_crossing_state_switch 64 * 23 02 10 FE 89 42 00 \dx000000FF \b1 \wx00FF \dx00000001 \dx00000001 // 1 .. 1: lc_right_closed; \wx00FE // default: lc_right_open; // Name: lc_left_closed - feature 10 65 * 7 02 10 FF \b1 \b0 \w2 // Name: lc_left_open - feature 10 66 * 7 02 10 FD \b1 \b0 \w3 // Name: left_level_crossing_state_switch 67 * 23 02 10 FD 89 42 00 \dx000000FF \b1 \wx00FF \dx00000001 \dx00000001 // 1 .. 1: lc_left_closed; \wx00FD // default: lc_left_open; // Name: level_crossing_switch 68 * 23 02 10 FE 89 06 04 \dx00000001 \b1 \wx00FD \dx00000000 \dx00000000 // 0 .. 0: left_level_crossing_state_switch; \wx00FE // default: right_level_crossing_state_switch; 69 * 152 00 10 \b3 01 FF \wx0000 08 "RAIL" 0E \b17 "RAIL" "ELRL" "_040" "_080" "RLOW" "RMED" "RHIG" "E040" "E080" "ELOW" "EMED" "EHIG" "HSTR" "DBNN" "DBNE" "DBHN" "DBHE" 0F \b17 "RAIL" "ELRL" "_040" "_080" "RLOW" "RMED" "RHIG" "E040" "E080" "ELOW" "EMED" "EHIG" "HSTR" "DBNN" "DBNE" "DBHN" "DBHE" // Name: track_overlays - feature 10 70 * 7 02 10 FD \b1 \b0 \w4 71 * 12 01 10 00 FF \wx0005 FF \wx0001 FF \wx0010 72 gfx/rails_overlays.png 8bpp 75 0 64 31 -31 0 normal 73 gfx/rails_overlays.png 8bpp 0 0 64 31 -31 0 normal 74 gfx/rails_overlays.png 8bpp 150 0 64 31 -31 0 normal 75 gfx/rails_overlays.png 8bpp 225 0 64 31 -31 0 normal 76 gfx/rails_overlays.png 8bpp 0 40 64 31 -31 0 normal 77 gfx/rails_overlays.png 8bpp 300 0 64 31 -31 0 normal 78 gfx/rails_overlays.png 8bpp 75 40 64 39 -31 -8 normal 79 gfx/rails_overlays.png 8bpp 150 40 64 23 -31 0 normal 80 gfx/rails_overlays.png 8bpp 225 40 64 23 -31 0 normal 81 gfx/rails_overlays.png 8bpp 300 40 64 39 -30 -9 normal 82 gfx/rails_overlays.png 8bpp 0 120 64 31 -31 0 normal 83 gfx/rails_overlays.png 8bpp 0 80 64 31 -31 0 normal 84 gfx/rails_overlays.png 8bpp 225 80 64 31 -31 0 normal 85 gfx/rails_overlays.png 8bpp 150 80 64 31 -31 0 normal 86 gfx/rails_overlays.png 8bpp 75 80 64 31 -31 0 normal 87 gfx/rails_overlays.png 8bpp 300 80 64 31 -31 0 normal // Name: track_underlays - feature 10 88 * 7 02 10 FF \b1 \b0 \w5 89 * 12 01 10 00 FF \wx0006 FF \wx0001 FF \wx0004 90 gfx/tunnel_track.png 8bpp 75 0 64 31 -31 0 normal 91 gfx/tunnel_track.png 8bpp 0 0 64 31 -31 0 normal 92 gfx/tunnel_track.png 8bpp 75 50 64 31 -31 0 normal 93 gfx/tunnel_track.png 8bpp 0 50 64 31 -31 0 normal // Name: tunnel_overlays - feature 10 94 * 7 02 10 FC \b1 \b0 \w6 95 * 12 01 10 00 FF \wx0007 FF \wx0001 FF \wx0006 96 gfx/depot_normal.png 8bpp 200 10 16 8 17 7 normal 97 gfx/depot_normal.png 8bpp 118 8 64 47 -9 -31 normal 98 gfx/depot_normal.png 8bpp 0 10 16 8 -31 7 normal 99 gfx/depot_normal.png 8bpp 37 8 64 47 -53 -31 normal 100 gfx/depot_normal.png 8bpp 37 63 64 47 -53 -31 normal 101 gfx/depot_normal.png 8bpp 118 63 64 47 -9 -31 normal // Name: depot_normal_rail - feature 10 102 * 7 02 10 FB \b1 \b0 \w7 103 * 12 01 10 00 FF \wx0008 FF \wx0001 FF \wx000A 104 gfx/rails_overlays.png 8bpp 75 0 64 31 -31 0 normal 105 gfx/rails_overlays.png 8bpp 0 0 64 31 -31 0 normal 106 gfx/rails_overlays.png 8bpp 75 40 64 39 -31 -8 normal 107 gfx/rails_overlays.png 8bpp 150 40 64 23 -31 0 normal 108 gfx/rails_overlays.png 8bpp 225 40 64 23 -31 0 normal 109 gfx/rails_overlays.png 8bpp 300 40 64 39 -30 -9 normal 110 gfx/rails_overlays.png 8bpp 150 0 64 31 -31 0 normal 111 gfx/rails_overlays.png 8bpp 225 0 64 31 -31 0 normal 112 gfx/rails_overlays.png 8bpp 0 40 64 31 -31 0 normal 113 gfx/rails_overlays.png 8bpp 300 0 64 31 -31 0 normal // Name: bridge_underlay - feature 10 114 * 7 02 10 FA \b1 \b0 \w8 115 * 12 01 10 00 FF \wx0009 FF \wx0001 FF \wx0008 116 gfx/fences.png 8bpp 0 0 32 20 -30 -4 normal 117 gfx/fences.png 8bpp 48 0 32 20 0 -3 normal 118 gfx/fences.png 8bpp 96 0 2 30 0 -17 normal 119 gfx/fences.png 8bpp 112 0 64 5 -30 -4 normal 120 gfx/fences.png 8bpp 192 0 32 12 -30 -4 normal 121 gfx/fences.png 8bpp 240 0 32 12 2 -3 normal 122 gfx/fences.png 8bpp 288 0 32 28 -31 -12 normal 123 gfx/fences.png 8bpp 350 0 32 28 1 -10 normal // Name: fencesCC - feature 10 124 * 7 02 10 F9 \b1 \b0 \w9 125 * 12 01 10 00 FF \wx000A FF \wx0001 FF \wx0010 126 gfx/gui_rail.png 8bpp 0 0 20 20 0 0 normal 127 gfx/gui_rail.png 8bpp 25 0 20 20 0 0 normal 128 gfx/gui_rail.png 8bpp 50 0 20 20 0 0 normal 129 gfx/gui_rail.png 8bpp 75 0 20 20 0 0 normal 130 gfx/gui_rail.png 8bpp 100 0 20 20 0 0 normal 131 gfx/gui_rail.png 8bpp 125 0 20 20 0 0 normal 132 gfx/gui_rail.png 8bpp 150 0 20 20 0 0 normal 133 gfx/gui_rail.png 8bpp 175 0 20 20 0 0 normal 134 gfx/gui_rail.png 8bpp 200 0 32 32 0 0 normal 135 gfx/gui_rail.png 8bpp 250 0 32 32 0 0 normal 136 gfx/gui_rail.png 8bpp 300 0 32 32 0 0 normal 137 gfx/gui_rail.png 8bpp 350 0 32 32 0 0 normal 138 gfx/gui_rail.png 8bpp 400 0 32 32 0 0 normal 139 gfx/gui_rail.png 8bpp 450 0 32 32 0 0 normal 140 gfx/gui_rail.png 8bpp 500 0 32 32 0 0 normal 141 gfx/gui_rail.png 8bpp 550 0 32 32 0 0 normal // Name: gui_normal - feature 10 142 * 7 02 10 F8 \b1 \b0 \w10 143 * 31 03 10 01 00 \b8 00 \wx00F8 // gui_normal; 01 \wx00FD // track_overlays; 02 \wx00FF // track_underlays; 03 \wx00FC // tunnel_overlays; 06 \wx00FA // bridge_underlay; 07 \wx00FE // level_crossing_switch; 08 \wx00FB // depot_normal_rail; 09 \wx00F9 // fencesCC; \wx0000 144 * 120 00 10 \b3 01 FF \wx0001 08 "ELRL" 0E \b17 "RAIL" "ELRL" "_040" "_080" "RLOW" "RMED" "RHIG" "E040" "E080" "ELOW" "EMED" "EHIG" "HSTR" "DBNN" "DBNE" "DBHN" "DBHE" 0F \b9 "ELRL" "E040" "E080" "ELOW" "EMED" "EHIG" "HSTR" "DBNE" "DBHE" 145 * 12 01 10 00 FF \wx000B FF \wx0001 FF \wx0006 146 gfx/depot_electric.png 8bpp 200 10 16 8 17 7 normal 147 gfx/depot_electric.png 8bpp 118 8 64 47 -9 -31 normal 148 gfx/depot_electric.png 8bpp 0 10 16 8 -31 7 normal 149 gfx/depot_electric.png 8bpp 37 8 64 47 -53 -31 normal 150 gfx/depot_electric.png 8bpp 37 63 64 47 -53 -31 normal 151 gfx/depot_electric.png 8bpp 118 63 64 47 -9 -31 normal // Name: depot_electric_rail - feature 10 152 * 7 02 10 FB \b1 \b0 \w11 153 * 12 01 10 00 FF \wx000C FF \wx0001 FF \wx0010 154 gfx/gui_erail.png 8bpp 0 0 20 20 0 0 normal 155 gfx/gui_erail.png 8bpp 25 0 20 20 0 0 normal 156 gfx/gui_erail.png 8bpp 50 0 20 20 0 0 normal 157 gfx/gui_erail.png 8bpp 75 0 20 20 0 0 normal 158 gfx/gui_erail.png 8bpp 100 0 20 20 0 0 normal 159 gfx/gui_erail.png 8bpp 125 0 20 20 0 0 normal 160 gfx/gui_erail.png 8bpp 150 0 20 20 0 0 normal 161 gfx/gui_erail.png 8bpp 175 0 20 20 0 0 normal 162 gfx/gui_erail.png 8bpp 200 0 32 32 0 0 normal 163 gfx/gui_erail.png 8bpp 250 0 32 32 0 0 normal 164 gfx/gui_erail.png 8bpp 300 0 32 32 0 0 normal 165 gfx/gui_erail.png 8bpp 350 0 32 32 0 0 normal 166 gfx/gui_erail.png 8bpp 400 0 32 32 0 0 normal 167 gfx/gui_erail.png 8bpp 450 0 32 32 0 0 normal 168 gfx/gui_erail.png 8bpp 500 0 32 32 0 0 normal 169 gfx/gui_erail.png 8bpp 550 0 32 32 0 0 normal // Name: gui_electric - feature 10 170 * 7 02 10 F8 \b1 \b0 \w12 171 * 31 03 10 01 01 \b8 00 \wx00F8 // gui_electric; 01 \wx00FD // track_overlays; 02 \wx00FF // track_underlays; 03 \wx00FC // tunnel_overlays; 06 \wx00FA // bridge_underlay; 07 \wx00FE // level_crossing_switch; 08 \wx00FB // depot_electric_rail; 09 \wx00F9 // fencesCC; \wx0000 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_road_vehicle.grf0000644000175100001660000004262614754345605022564 0ustar00runnerdockerGRF  ~ 6CINFOBVRSNBMINVBNPARBPALSWBBLTR8-NMLNML Example NewGRF: Road VehicleNML Example NewGRF: Road Vehicle This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML. Original graphics by DanMack, Zephyris, coding by Terkhen, planetmaker. This NewGRF defines first-generation flatbed truck.  x  , enable multiple NewGRF engine sets = onD LVSTWOOLSCRPFICRPETRRFPRGOODENSPFMSPMNSPPAPRSTELVEHICOPRWOOD ROAD REDR REDR RED_ RED_ROADELRD2YEL  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a       gG     UX A$$%l Z` HL &%XFlatbed Truck 1 (Normal Road) X! 66+ 66+ 66+ 66+ 66X UY A$$%l Z` HL &*YFlatbed Truck 2 (Electrified Road) Y! 66+ 66+ 66+ 66+ 66Y UZ A$$%l Z` HL &%ZFlatbed Truck 3 (Yellow Road) Z! 66+ 66+ 66+ 66+ 66Z U[ A$$%l Z` HL &<[Flatbed Truck 4 (Unknown, fallback to Red then Road) [! 66+ 66+ 66+ 66+ 66[ n  5576777877658!08@ 87855668888ir̨̐˘,+˨Oʀʸ)776($56767778<667ׂ$6'('$9:9:7766H*nH6&  ʈˈ6̈Ra7Vs 66666oV777׀8W662 878Ɂ 6788888 ׂ 4׉= 566!55677777,666́677+ʃ, ɀ׀ʄwׂ,Ɂׁ> 6ɃɀfSj+ɰX+X$e 566778675578 77"ʃ׀ʃ׀ʄ(8:    788$67777/ 778+ɀ,88׀Ɂʀ;(Ƀʁ778׃d ̄ʃ7-1Ю׸ׂ5?H  ˈ̈9768vn6666 ׀6777 ʀ8 78Ɂ4  88788 8ׂ׉3̘&ɀʠ'hȃ<777P76778,׃6678,6(+,+,8,=7,88ɸXɸЬ$q    (      (5 28ir̨̐˘,+O +ʀ  ()$'ʰ$= K 7ׂ   8'6$ $766H*GH6&  ʈˈ6R  e7V  ( 6 7 6666oV6  7777׀7W66 :77 8878Ɂ 6788888 ׂ 4׉=    ( ',1 ,, ́ ,ʃ, ɀ׀ʄׂ,Ɂׁ>  ɃɀfSj+ɰX+X$p   (    "ʃ׀ʃ׀ʄ(8:      &$'$($= $Q 8ɀ,a  8׀Ɂʀu7(Ƀʁ$׃d ̄ʃ7-1׸ׂ5?H   ˈ̈97  68  ( n6666 7 6׀6776 7 ʀ777%8Ɂ :88 888888ׂ׉3!̘&ɀ '  (iȃ ',  ,׃,@ ,i8,8,*88ɸׂ7ɸȘЬ$"n  5576777877658!08@ 87855668888ir#̨̐˘,+˨Oʀʸ)776($56767778<667ׂ$6'('$9:9:7766H*nH6&$  ʈˈ6̈Ra7Vs 66666oV777׀8W662 878Ɂ 6788888 ׂ 4׉=% 566!55677777,666́677+ʃ, ɀ׀ʄwׂ,Ɂׁ> 6ɃɀfSj+ɰX+X$&e 566778675578 77"ʃ׀ʃ׀ʄ(8:  '  788$67777/ 778+ɀ,88׀Ɂʀ;(Ƀʁ778׃d ̄ʃ7-1Ю׸ׂ5?H(  ˈ̈9768vn6666 ׀6777 ʀ8 78Ɂ4  88788 8ׂ׉3)̘&ɀʠ'hȃ<777P76778,׃6678,6(+,+,8,=7,88ɸXɸЬ$*v     (5>8ir+̨̐˘,+O+ʀʁ)$ʰ$$ 7ׂ6$8'6$r$766H*GH6&,  ʈˈ6R e7V 6 6666oV6 7777׀7 W 6677w 8878Ɂ 6788888 ׂ 4׉=-  -1--́Ȉ-ʃ6׀ʄ ׂɈɁׁ> 6ɃɀfSj+ɰX+X$.q  "ʃ׀ʃ׀ʄ(8:  / $)$?$ 8ɀ,$8׀Ɂʀ$7(Ƀʁ$׃d ̄ʃ7-1׸ׂ5?H0  ˈ̈97 68 n6666 6׀67767 ʀ777 8Ɂn88 888888ׂ׉31̘&ɀ' ȃ, ,׃6,UɈQk,8=,8ɸX788ɸȘЬ$2m <656666657!07@ 767<<557777xk 3̨̐˘,+˨Oʀi)}665($<565667<$555666ׂ$5<5('$9$:6655H*nH6&4  ʈ6;RW7Vs 55555oV55665566׀67ʀW555387767Ɂ 5677777 ׂ 4׉=5!,!<55,!<<56666,555<6́55656ʃ566ɀ׀ʄ ׂ56Ɂׁ> ɃɀfSj+ɰX+X$6r <5566756<6 66"ʃ׀ʃ׀ʄ(8:  7 $/$677/$56666/ 66+ɀ,667677׀Ɂʀ$66Ƀʁ667ׂd ̄ʃ6-1׸H?H8  ˈ9>7Z68vn5555׀56656666ʀ667 67Ɂ38776777677 7ׂ׉39̘&ɀʠ'ptȃ<666P6567, ׃56667, 5666,+,7,<+(ɸXɸ$: >?=>>>?=???  0=?<==<=<7xk ;̨̐˨,=>+O=>>?ʀ$) ?>>>ʃ<=$>>?>====$ ?>=ׂ<=<$=7'<?$)=6H*GH6&<  ʈ6 <>>>>> ???> g7V6>>? 5>8? 555oV5<:5>666׀>q6?W5<r6б>767Ɂ 5677777 ׂ 4׉==<=> ==><=>,',M',a>,́<=,ʃ, ɀ׀ʄׂ,Ɂׁ> ɃɀfSj+ɰX+X$>{ >?=>?>>???  0"ʃ׀ʃ׀ʄ(8:  ?=>>?=$<$>??>$>>?>$ >?>=ɀ,$=7׀Ɂʀ$Ƀʁ$ׂd ̄ʃ=-1׸H?H@  ˈ9 <>>>>> 7 >686>>? ?n5555>8? ?5׀56659x>6ʀ66668A ?7ɁШ><ر>77777ׂ׉3A̘>*<=>@ɀ===>'<=>,ȃ>',=9,D׃', >>>><,C>=,;>=7<<9=7ɸXɸȘ$Bn  5576777877658!08@ 87855668888irC̨̐˘,+˨Oʀʸ)776($56767778<667ׂ$6'('$9:9:7766H*nH6&D  ʈˈ6̈Ra7Vs 66666oV777׀8W662 878Ɂ 6788888 ׂ 4׉=E 566!55677777,666́677+ʃ, ɀ׀ʄwׂ,Ɂׁ> 6ɃɀfSj+ɰX+X$Fe 566778675578 77"ʃ׀ʃ׀ʄ(8:  G  788$67777/ 778+ɀ,88׀Ɂʀ;(Ƀʁ778׃d ̄ʃ7-1Ю׸ׂ5?HH  ˈ̈9768vn6666 ׀6777 ʀ8 78Ɂ4  88788 8ׂ׉3I̘&ɀʠ'hȃ<777P76778,׃6678,6(+,+,8,=7,88ɸXɸЬ$Jv <<5<7755FG58<6F68irK̨̐˘,+O<<5<77+ʀ<55FG58ʁ)$G<69ʰ$7<G<<$7GׂF6$668'6$r$766H*GH6&L  ʈˈ6R<<5<77 e7V<55FG587 6G< 6ɃɀfSj+ɰX+X$Nr<<5<77<55FG587<WɃɀfSjȘ+X+X$Va55667788785578"ʃ׀ʃ׀ʄ(8:  W 7788 6777 778+ɀ,> ׀Ɂʀ; Ƀʁ 8׃d̄ʃ-1Е׸ׂ5TH(X  ˈ̈976̈8n666 ׀6777 ʀ8 78Ɂʨ4888888ׂ׉3Y̘&ɀʠ'ȃ<7777P7678К׃66678(+,88=S8ɸXS8ɸЬ$Z_UVWWWTUV  QTSTSTUTRUSVTir[̨̐TVUT˨,VWTS,UWTWS)RU VRQURUQPTRUSTׂSQTR 6PRQ6TH*GH6&\  ʈSVU TWV 7ʈQTS oVRUT ׀rSVT ʀW+Ɂ67788888ׂ 4׉=]VTUV WURUSW),QUMPSRTBQ ́ʃ6, ɀ׀ʄׂBɁׁ> 6QɃɀfSj+ɰX+X$^\UVWWWTUV  W"ʃ׀ʃ׀ʄ(8:  _VUTVUTUWTVWSURUQP̘SP6TR̸ Qɀ, 8׀ɁʀɃʁ$׃d̄ʃ6-1׸ׂ5?H$`  ˈSVU 7TWV 6̈pQTS ׀RUT ʀSVT Ɂʈ+ 88888ׂ׉3a̘TUVT&STWVɀTWUɁSWȃRV URQU RUQ׃6T SURTPRTQS,RP=BQɸX,ɸȘ$././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_road_vehicle.nfo0000644000175100001660000004040114754345605022555 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d146 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000001 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 301 08 08 "NML\03" "NML Example NewGRF: Road Vehicle" 00 "\8ENML Example NewGRF: Road Vehicle\0D\98This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.\0DOriginal graphics by \89DanMack, Zephyris, \98coding by \89Terkhen, planetmaker.\0D\98This NewGRF defines first-generation flatbed truck." 00 // param[127] = 0 3 * 9 0D 7F \D= FF 00 \dx00000000 4 * 6 09 85 01 \70 78 01 // param[127] = 1 5 * 9 0D 7F \D= FF 00 \dx00000001 6 * 9 09 7F 04 \7! \dx00000000 01 7 * 44 0B 02 7F 02 "enable multiple NewGRF engine sets = on" 00 8 * 68 00 08 \b1 0F FF \wx0000 09 "LVST" "WOOL" "SCRP" "FICR" "PETR" "RFPR" "GOOD" "ENSP" "FMSP" "MNSP" "PAPR" "STEL" "VEHI" "COPR" "WOOD" // param[127] = 1145130834 9 * 9 0D 7F \D= FF 00 \dx44414F52 10 * 9 09 00 04 0F \dx52444552 01 // param[127] = 1380205906 11 * 9 0D 7F \D= FF FF \dx52444552 12 * 9 09 00 04 0F \dx5F444552 01 // param[127] = 1598309714 13 * 9 0D 7F \D= FF FF \dx5F444552 14 * 7 06 7F 04 FF \wx0014 FF 15 * 24 00 08 \b1 04 FF \wx0000 16 "ROAD" "ELRD" "2YEL" "\00\00\00\00" 16 * 6 01 01 \b10 FF \wx0008 17 gfx/flatbed_truck_1_paper.png 8bpp 0 0 8 18 -3 -10 normal 18 gfx/flatbed_truck_1_paper.png 8bpp 16 0 20 16 -14 -7 normal 19 gfx/flatbed_truck_1_paper.png 8bpp 48 0 28 12 -14 -6 normal 20 gfx/flatbed_truck_1_paper.png 8bpp 96 0 20 16 -6 -7 normal 21 gfx/flatbed_truck_1_paper.png 8bpp 128 0 8 18 -3 -10 normal 22 gfx/flatbed_truck_1_paper.png 8bpp 144 0 20 16 -14 -7 normal 23 gfx/flatbed_truck_1_paper.png 8bpp 176 0 28 12 -14 -6 normal 24 gfx/flatbed_truck_1_paper.png 8bpp 224 0 20 16 -6 -7 normal 25 gfx/flatbed_truck_1_paper.png 8bpp 260 0 8 18 -3 -10 normal 26 gfx/flatbed_truck_1_paper.png 8bpp 276 0 20 16 -14 -7 normal 27 gfx/flatbed_truck_1_paper.png 8bpp 308 0 28 12 -14 -6 normal 28 gfx/flatbed_truck_1_paper.png 8bpp 356 0 20 16 -6 -7 normal 29 gfx/flatbed_truck_1_paper.png 8bpp 388 0 8 18 -3 -10 normal 30 gfx/flatbed_truck_1_paper.png 8bpp 404 0 20 16 -14 -7 normal 31 gfx/flatbed_truck_1_paper.png 8bpp 436 0 28 12 -14 -6 normal 32 gfx/flatbed_truck_1_paper.png 8bpp 484 0 20 16 -6 -7 normal 33 gfx/flatbed_truck_1_steel.png 8bpp 0 0 8 18 -3 -10 normal 34 gfx/flatbed_truck_1_steel.png 8bpp 16 0 20 16 -14 -7 normal 35 gfx/flatbed_truck_1_steel.png 8bpp 48 0 28 12 -14 -6 normal 36 gfx/flatbed_truck_1_steel.png 8bpp 96 0 20 16 -6 -7 normal 37 gfx/flatbed_truck_1_steel.png 8bpp 128 0 8 18 -3 -10 normal 38 gfx/flatbed_truck_1_steel.png 8bpp 144 0 20 16 -14 -7 normal 39 gfx/flatbed_truck_1_steel.png 8bpp 176 0 28 12 -14 -6 normal 40 gfx/flatbed_truck_1_steel.png 8bpp 224 0 20 16 -6 -7 normal 41 gfx/flatbed_truck_1_steel.png 8bpp 260 0 8 18 -3 -10 normal 42 gfx/flatbed_truck_1_steel.png 8bpp 276 0 20 16 -14 -7 normal 43 gfx/flatbed_truck_1_steel.png 8bpp 308 0 28 12 -14 -6 normal 44 gfx/flatbed_truck_1_steel.png 8bpp 356 0 20 16 -6 -7 normal 45 gfx/flatbed_truck_1_steel.png 8bpp 388 0 8 18 -3 -10 normal 46 gfx/flatbed_truck_1_steel.png 8bpp 404 0 20 16 -14 -7 normal 47 gfx/flatbed_truck_1_steel.png 8bpp 436 0 28 12 -14 -6 normal 48 gfx/flatbed_truck_1_steel.png 8bpp 484 0 20 16 -6 -7 normal 49 gfx/flatbed_truck_1_wood.png 8bpp 0 0 8 18 -3 -10 normal 50 gfx/flatbed_truck_1_wood.png 8bpp 16 0 20 16 -14 -7 normal 51 gfx/flatbed_truck_1_wood.png 8bpp 48 0 28 12 -14 -6 normal 52 gfx/flatbed_truck_1_wood.png 8bpp 96 0 20 16 -6 -7 normal 53 gfx/flatbed_truck_1_wood.png 8bpp 128 0 8 18 -3 -10 normal 54 gfx/flatbed_truck_1_wood.png 8bpp 144 0 20 16 -14 -7 normal 55 gfx/flatbed_truck_1_wood.png 8bpp 176 0 28 12 -14 -6 normal 56 gfx/flatbed_truck_1_wood.png 8bpp 224 0 20 16 -6 -7 normal 57 gfx/flatbed_truck_1_wood.png 8bpp 260 0 8 18 -3 -10 normal 58 gfx/flatbed_truck_1_wood.png 8bpp 276 0 20 16 -14 -7 normal 59 gfx/flatbed_truck_1_wood.png 8bpp 308 0 28 12 -14 -6 normal 60 gfx/flatbed_truck_1_wood.png 8bpp 356 0 20 16 -6 -7 normal 61 gfx/flatbed_truck_1_wood.png 8bpp 388 0 8 18 -3 -10 normal 62 gfx/flatbed_truck_1_wood.png 8bpp 404 0 20 16 -14 -7 normal 63 gfx/flatbed_truck_1_wood.png 8bpp 436 0 28 12 -14 -6 normal 64 gfx/flatbed_truck_1_wood.png 8bpp 484 0 20 16 -6 -7 normal 65 gfx/flatbed_truck_1_copper.png 8bpp 0 0 8 18 -3 -10 normal 66 gfx/flatbed_truck_1_copper.png 8bpp 16 0 20 16 -14 -7 normal 67 gfx/flatbed_truck_1_copper.png 8bpp 48 0 28 12 -14 -6 normal 68 gfx/flatbed_truck_1_copper.png 8bpp 96 0 20 16 -6 -7 normal 69 gfx/flatbed_truck_1_copper.png 8bpp 128 0 8 18 -3 -10 normal 70 gfx/flatbed_truck_1_copper.png 8bpp 144 0 20 16 -14 -7 normal 71 gfx/flatbed_truck_1_copper.png 8bpp 176 0 28 12 -14 -6 normal 72 gfx/flatbed_truck_1_copper.png 8bpp 224 0 20 16 -6 -7 normal 73 gfx/flatbed_truck_1_copper.png 8bpp 260 0 8 18 -3 -10 normal 74 gfx/flatbed_truck_1_copper.png 8bpp 276 0 20 16 -14 -7 normal 75 gfx/flatbed_truck_1_copper.png 8bpp 308 0 28 12 -14 -6 normal 76 gfx/flatbed_truck_1_copper.png 8bpp 356 0 20 16 -6 -7 normal 77 gfx/flatbed_truck_1_copper.png 8bpp 388 0 8 18 -3 -10 normal 78 gfx/flatbed_truck_1_copper.png 8bpp 404 0 20 16 -14 -7 normal 79 gfx/flatbed_truck_1_copper.png 8bpp 436 0 28 12 -14 -6 normal 80 gfx/flatbed_truck_1_copper.png 8bpp 484 0 20 16 -6 -7 normal 81 gfx/flatbed_truck_1_goods.png 8bpp 0 0 8 18 -3 -10 normal 82 gfx/flatbed_truck_1_goods.png 8bpp 16 0 20 16 -14 -7 normal 83 gfx/flatbed_truck_1_goods.png 8bpp 48 0 28 12 -14 -6 normal 84 gfx/flatbed_truck_1_goods.png 8bpp 96 0 20 16 -6 -7 normal 85 gfx/flatbed_truck_1_goods.png 8bpp 128 0 8 18 -3 -10 normal 86 gfx/flatbed_truck_1_goods.png 8bpp 144 0 20 16 -14 -7 normal 87 gfx/flatbed_truck_1_goods.png 8bpp 176 0 28 12 -14 -6 normal 88 gfx/flatbed_truck_1_goods.png 8bpp 224 0 20 16 -6 -7 normal 89 gfx/flatbed_truck_1_goods.png 8bpp 260 0 8 18 -3 -10 normal 90 gfx/flatbed_truck_1_goods.png 8bpp 276 0 20 16 -14 -7 normal 91 gfx/flatbed_truck_1_goods.png 8bpp 308 0 28 12 -14 -6 normal 92 gfx/flatbed_truck_1_goods.png 8bpp 356 0 20 16 -6 -7 normal 93 gfx/flatbed_truck_1_goods.png 8bpp 388 0 8 18 -3 -10 normal 94 gfx/flatbed_truck_1_goods.png 8bpp 404 0 20 16 -14 -7 normal 95 gfx/flatbed_truck_1_goods.png 8bpp 436 0 28 12 -14 -6 normal 96 gfx/flatbed_truck_1_goods.png 8bpp 484 0 20 16 -6 -7 normal // Name: flatbed_truck_1_paper - feature 01 97 * 13 02 01 FF \b2 \b2 \w0 \w1 \w0 \w1 // Name: flatbed_truck_1_steel - feature 01 98 * 13 02 01 FE \b2 \b2 \w2 \w3 \w2 \w3 // Name: flatbed_truck_1_wood - feature 01 99 * 13 02 01 FD \b2 \b2 \w4 \w5 \w4 \w5 // Name: flatbed_truck_1_copper - feature 01 100 * 13 02 01 FC \b2 \b2 \w6 \w7 \w6 \w7 // Name: flatbed_truck_1_goods - feature 01 101 * 13 02 01 FB \b2 \b2 \w8 \w9 \w8 \w9 // Name: flatbed_truck_1_capacity_switch 102 * 103 02 01 FA 89 47 00 \dx000000FF \b9 \wx800E \dx00000006 \dx00000006 // 6 .. 6: return 14; \wx800E \dx00000007 \dx00000007 // 7 .. 7: return 14; \wx800E \dx00000008 \dx00000008 // 8 .. 8: return 14; \wx800E \dx00000009 \dx00000009 // 9 .. 9: return 14; \wx800F \dx0000000A \dx0000000A // 10 .. 10: return 15; \wx800A \dx00000004 \dx00000004 // 4 .. 4: return 10; \wx800A \dx00000005 \dx00000005 // 5 .. 5: return 10; \wx800F \dx0000000B \dx0000000B // 11 .. 11: return 15; \wx800C \dx0000000C \dx0000000C // 12 .. 12: return 12; \wx8014 // default: return 20; 103 * 85 00 01 \b26 01 FF \wx0058 06 07 05 00 1F \dx000ABBE1 04 41 03 0F 02 14 1D \wx0024 16 \dx00000000 1E \wx0081 16 \dx00000000 24 \b6 00 01 02 03 04 05 16 \dx00000000 25 \b0 16 \dx00000000 07 05 11 6C 09 5A 0E FF 08 60 1C 02 1A 00 0A \dx00004C48 13 0C 14 26 0F 14 12 17 104 * 37 04 01 7F 01 FF \wx0058 "Flatbed Truck 1 (Normal Road)" 00 105 * 9 00 01 \b1 01 FF \wx0058 17 08 // Name: @action3_0 106 * 23 02 01 F9 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_1 107 * 23 02 01 00 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_2 108 * 33 02 01 F8 89 0C 00 \dx0000FFFF \b2 \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_0; \wx00FB // flatbed_truck_1_goods; // Name: @action3_3 109 * 43 02 01 F7 89 0C 00 \dx0000FFFF \b3 \wx00FF \dx00000000 \dx00000000 // flatbed_truck_1_paper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_0; \wx00FB // flatbed_truck_1_goods; // Name: @action3_4 110 * 43 02 01 F6 89 0C 00 \dx0000FFFF \b3 \wx00FE \dx00000000 \dx00000000 // flatbed_truck_1_steel; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_0; \wx00FB // flatbed_truck_1_goods; // Name: @action3_5 111 * 43 02 01 F5 89 0C 00 \dx0000FFFF \b3 \wx00FC \dx00000000 \dx00000000 // flatbed_truck_1_copper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_0; \wx00FB // flatbed_truck_1_goods; // Name: @action3_6 112 * 43 02 01 F9 89 0C 00 \dx0000FFFF \b3 \wx00FD \dx00000000 \dx00000000 // flatbed_truck_1_wood; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_0; \wx00FB // flatbed_truck_1_goods; 113 * 21 03 01 01 FF \wx0058 \b4 0A \wx00F7 // @action3_3; 0B \wx00F6 // @action3_4; 0D \wx00F5 // @action3_5; 0E \wx00F9 // @action3_6; \wx00F8 // @action3_2; 114 * 85 00 01 \b26 01 FF \wx0059 06 07 05 01 1F \dx000ABBE1 04 41 03 0F 02 14 1D \wx0024 16 \dx00000000 1E \wx0081 16 \dx00000000 24 \b6 00 01 02 03 04 05 16 \dx00000000 25 \b0 16 \dx00000000 07 05 11 6C 09 5A 0E FF 08 60 1C 02 1A 00 0A \dx00004C48 13 0C 14 26 0F 14 12 17 115 * 42 04 01 7F 01 FF \wx0059 "Flatbed Truck 2 (Electrified Road)" 00 116 * 9 00 01 \b1 01 FF \wx0059 17 08 // Name: @action3_7 117 * 23 02 01 F8 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_8 118 * 23 02 01 00 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_9 119 * 33 02 01 F9 89 0C 00 \dx0000FFFF \b2 \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_7; \wx00FB // flatbed_truck_1_goods; // Name: @action3_10 120 * 43 02 01 F5 89 0C 00 \dx0000FFFF \b3 \wx00FF \dx00000000 \dx00000000 // flatbed_truck_1_paper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_7; \wx00FB // flatbed_truck_1_goods; // Name: @action3_11 121 * 43 02 01 F6 89 0C 00 \dx0000FFFF \b3 \wx00FE \dx00000000 \dx00000000 // flatbed_truck_1_steel; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_7; \wx00FB // flatbed_truck_1_goods; // Name: @action3_12 122 * 43 02 01 F7 89 0C 00 \dx0000FFFF \b3 \wx00FC \dx00000000 \dx00000000 // flatbed_truck_1_copper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_7; \wx00FB // flatbed_truck_1_goods; // Name: @action3_13 123 * 43 02 01 F8 89 0C 00 \dx0000FFFF \b3 \wx00FD \dx00000000 \dx00000000 // flatbed_truck_1_wood; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_7; \wx00FB // flatbed_truck_1_goods; 124 * 21 03 01 01 FF \wx0059 \b4 0A \wx00F5 // @action3_10; 0B \wx00F6 // @action3_11; 0D \wx00F7 // @action3_12; 0E \wx00F8 // @action3_13; \wx00F9 // @action3_9; 125 * 85 00 01 \b26 01 FF \wx005A 06 07 05 02 1F \dx000ABBE1 04 41 03 0F 02 14 1D \wx0024 16 \dx00000000 1E \wx0081 16 \dx00000000 24 \b6 00 01 02 03 04 05 16 \dx00000000 25 \b0 16 \dx00000000 07 05 11 6C 09 5A 0E FF 08 60 1C 02 1A 00 0A \dx00004C48 13 0C 14 26 0F 14 12 17 126 * 37 04 01 7F 01 FF \wx005A "Flatbed Truck 3 (Yellow Road)" 00 127 * 9 00 01 \b1 01 FF \wx005A 17 08 // Name: @action3_14 128 * 23 02 01 F9 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_15 129 * 23 02 01 00 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_16 130 * 33 02 01 F8 89 0C 00 \dx0000FFFF \b2 \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_14; \wx00FB // flatbed_truck_1_goods; // Name: @action3_17 131 * 43 02 01 F7 89 0C 00 \dx0000FFFF \b3 \wx00FF \dx00000000 \dx00000000 // flatbed_truck_1_paper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_14; \wx00FB // flatbed_truck_1_goods; // Name: @action3_18 132 * 43 02 01 F6 89 0C 00 \dx0000FFFF \b3 \wx00FE \dx00000000 \dx00000000 // flatbed_truck_1_steel; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_14; \wx00FB // flatbed_truck_1_goods; // Name: @action3_19 133 * 43 02 01 F5 89 0C 00 \dx0000FFFF \b3 \wx00FC \dx00000000 \dx00000000 // flatbed_truck_1_copper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_14; \wx00FB // flatbed_truck_1_goods; // Name: @action3_20 134 * 43 02 01 F9 89 0C 00 \dx0000FFFF \b3 \wx00FD \dx00000000 \dx00000000 // flatbed_truck_1_wood; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F9 \dx00000036 \dx00000036 // @action3_14; \wx00FB // flatbed_truck_1_goods; 135 * 21 03 01 01 FF \wx005A \b4 0A \wx00F7 // @action3_17; 0B \wx00F6 // @action3_18; 0D \wx00F5 // @action3_19; 0E \wx00F9 // @action3_20; \wx00F8 // @action3_16; 136 * 85 00 01 \b26 01 FF \wx005B 06 07 05 03 1F \dx000ABBE1 04 41 03 0F 02 14 1D \wx0024 16 \dx00000000 1E \wx0081 16 \dx00000000 24 \b6 00 01 02 03 04 05 16 \dx00000000 25 \b0 16 \dx00000000 07 05 11 6C 09 5A 0E FF 08 60 1C 02 1A 00 0A \dx00004C48 13 0C 14 26 0F 14 12 17 137 * 60 04 01 7F 01 FF \wx005B "Flatbed Truck 4 (Unknown, fallback to Red then Road)" 00 138 * 9 00 01 \b1 01 FF \wx005B 17 08 // Name: @action3_21 139 * 23 02 01 F8 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_22 140 * 23 02 01 00 89 10 00 \dx000000FF \b1 \wx00FA \dx0000000F \dx0000000F // flatbed_truck_1_capacity_switch; \wx00FB // flatbed_truck_1_goods; // Name: @action3_23 141 * 33 02 01 F9 89 0C 00 \dx0000FFFF \b2 \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_21; \wx00FB // flatbed_truck_1_goods; // Name: @action3_24 142 * 43 02 01 FF 89 0C 00 \dx0000FFFF \b3 \wx00FF \dx00000000 \dx00000000 // flatbed_truck_1_paper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_21; \wx00FB // flatbed_truck_1_goods; // Name: @action3_25 143 * 43 02 01 FE 89 0C 00 \dx0000FFFF \b3 \wx00FE \dx00000000 \dx00000000 // flatbed_truck_1_steel; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_21; \wx00FB // flatbed_truck_1_goods; // Name: @action3_26 144 * 43 02 01 FC 89 0C 00 \dx0000FFFF \b3 \wx00FC \dx00000000 \dx00000000 // flatbed_truck_1_copper; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_21; \wx00FB // flatbed_truck_1_goods; // Name: @action3_27 145 * 43 02 01 FB 89 0C 00 \dx0000FFFF \b3 \wx00FD \dx00000000 \dx00000000 // flatbed_truck_1_wood; \wx00FA \dx00000015 \dx00000015 // flatbed_truck_1_capacity_switch; \wx00F8 \dx00000036 \dx00000036 // @action3_21; \wx00FB // flatbed_truck_1_goods; 146 * 21 03 01 01 FF \wx005B \b4 0A \wx00FF // @action3_24; 0B \wx00FE // @action3_25; 0D \wx00FC // @action3_26; 0E \wx00FB // @action3_27; \wx00F9 // @action3_23; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_roadtype_and_tramtype.grf0000644000175100001660000020771014754345605024533 0ustar00runnerdockerGRF  a 6CINFOBVRSNBMINVBNPARBPALSDBBLTR8NMLNML Example NewGRF: Roadtype and TramtypeNML Example NewGRF: Roadtype and Tramtype This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML. Original graphics by Irwe, coding by andythenorth.s(Name 0x1B) Red Electric Road(Toolbar Caption 0x09) Red Electric Road(Menu Text 0x0A) Red Electric Road(Build Window Caption 0x0B) Red Electric Road(Autoreplace Text 0x0C) Red Electric Road(New Engine Text 0x0D) red Electric Road(Name 0x1B) Blue Road(Toolbar Caption 0x09) Blue Road(Menu Text 0x0A) Blue Road(Build Window Caption 0x0B) Blue Road(Autoreplace Text 0x0C) Blue Road(New Engine Text 0x0D) Blue Road(Name 0x1B) Yellow Road(Toolbar Caption 0x09) Yellow Road(Menu Text 0x0A) Yellow Road(Build Window Caption 0x0B) Yellow Road(Autoreplace Text 0x0C) Yellow Road(New Engine Text 0x0D) Yellow Road(Name 0x1B) Green Tram(Toolbar Caption 0x09) Green Tram(Menu Text 0x0A) Green Tram(Build Window Caption 0x0B) Green Tram(Autoreplace Text 0x0C) Green Tram(New Engine Text 0x0D) Green Tram4 REDRBLUEREDRROADELRD     e      !"#$%&'()*+,- 123456  9:;<=>?@ABC FGHI LMNOPQRSTUVWXYZ[\] ( BLUEROAD   c bcdefghijklmnopqrst wxyz (  2YELROAD    d     &  GRTRTRAM         u0 #2DSb02cД23bgژ1 ؐd22d)p@td2c,Ab3d2,qd2лd,ӱ^d^3Жdق{&l\2ljj,`&20,0&^^$XX$ѓ\JE⼋&%X0RȌ. o0  /.2`\/\/d].ZZ\. Ⱥ\.4~,؊%,+Br%\\BضC.TὉL.صлȺIٜθ./\n"]u.)p؊V(Nr..ɟp١P_[.uℹB-W\غE,ds 0  /<Ra&4,q4>.K3g_.d_b1fio.Nؕ-x2ЙFȨd.Ȧ ȀO[99*^ؚjZ53cź5ќ\tcّ6ɜ`7XWYUѤOZ%ڊW&  XV]рXVs$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$OԪ 0  /$R.2(-02. X/26/G5Pc1BDa"f5؋Jw*=u21\rpvHr@.-jɤZ1yk2d6qBegZpEtaXdɏȖNM\&|0Sb^-1^a23[_f^ `_/kЎkm^/ؗȌg]ઙv./aؤxKN&'I 1ؿ_nнx2hbVY*ٱQؔW؍Iѩ&إcȘe7[ॱO٦胺t$ٻ1٩'MPfբT i,+h3d2@ԓcѠT " "@a r b#]9#z> ؖ>d`=1]=>2 |>"@?zض=^~1ȶ={F3d,xv91ZsPC,ٸ,o+вh(㩇Đ:$'ѣ៊{XVК30  /$Ra1'9 q147.2\46i>Fd213X.D',.MJvPгY`,|!59;=a kqػ _Y0іl4VW,яVhfgɲgi,BuLU8*K 8@9;``'Ƞ;8>ȐTu;8vД9ؓ'qгw vT<<` xؐ?~byzЗ[ @Иd2Mɢ٤@фɹhSو@uĊ`T酊|Л´?_80  ))-0 Q,).=-. @v|-/f3ز((k9x [S*i^4 ؉дY2hؓ /:^ɔ\&5.32\xȈ8Ж+f2ћŠiҡ!0  />2`]./0Ű]`2Ȍ//d'-7ȩZ跸^,0ЌآGȌVፉ؋.GGع顱]L0٤йIӸZ]H]V0guFv0U_0s2Ѥ`ҤKGv,Gڼ8^0۸]^0u㽋xuLڎJMG0u`̳ꡤܧG ٤9E2AȌvfuKЍGcÍba^ *2 $314JY0:03m01jk詈`20=Оސ)5ХiЉ7egȟ{ غmdIЫ7؜0Zȥaiߊ8أQ6ظkᬊlՊln࣊ꗋ /M*2  /.2Tc[_h,.-30+.3[0\4؉\-Ȝ+.Шs[Љ_mBЉ\؉`G8Fض[.\.&ض.訆Y4І&ɹȻWRZ؊4Њ٠3B!0 !0DS.1_a.+bc1\]2ca(c<ljkB19eŸ1[ؔaS/(*Q(ZY5[mٺbؓ0ؕzŸ1(cRĠcȔꅊcdQ'ٽaځшYپM5bP'HYPEڏO%PҀO(E(Kw(A Pًl:I hwM=~nčӉY!# !%!\B$cd"J !"!!'ЈBIتjز" L"%"g?" F7A%] q]@"؈؋؛=؊شЋȮ;dk6Ј Ч 3>ERhdȟ湮@rِ҉! 7F #h #H{>8Ehh##g茰F##Ejد\#i؊RF#dhkء2e m"/ZzЀ$qɪFjЉ͊{0 #2DSb0cД2dbgژ 1Зdؐd22d)ɉp@td 2c,Ab3d2,>d2лd,ӱ^d^3,dق{&l\2lj ,`&20,0d&^^$XX$ѓ\JE⼋&%X0RȌ.|0  />2`\/\/d-..ЍZ\. Ⱥ\.4~],؊%,+Br%\\n\.T.L.елȺI*θ./Vn" ]BVp؊鿉V(Nr..ɟp١P_[.uℹB-W\غE,ds0  /<Ra4<q4>.K3Z_.d_b1fio.Nؕ- x2ЙFȨd.Ȧ Ȁ O[99* ^ ؚj Zؖ2؈5ќ \tcّ`ɜ`7XWYѤO  Z%ڊW&  XV]рXVs$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$`0  /$R.2=2. +/26/G5Pc1BDa"f5؋Jw*=u21\rpvH r@.-9ɤ Z 1yk 2d6qe g(ZpEta Xd ɏȖNM\ &|0Sb^-1^a23[_ f\`_/kЎkmx/ؗȌ g]|v./aؤJN&'I I 1ؿ_Yȏx2hbVY*ٱQؔz&إ{7[ॱO٦ 胺t$ٻ1٩'MPfբT iڶ,+h3d2@ԓcѠ"V " "@a r b#]9 #z> ؖ>d`=1 ]=>2  |>"@? zض=^~1ȶ={ F3d,xv 1ZsPC,ٸ,o+вh(㩇Đ:$'ѣ៊{XVК#40  /$Ra1,q147,/\46i>Fd213X.D',.MJvPгY`,|!59;=a k*qػ _Y0іxS,яVhfgɲgi,BuLU8*$Q 8@U;`W`'Ƞ;8>ȐTu;8v 9v' qгw vT<<` xؐ?~by^*'[ @ИE/Mɢ٤@фɹhSو@/Ċ`T酊|Л´?_%E0  )8-0Q,).=-. @gw-/fzY(k9xS *i^4؉дY2hؓ /^ɔ\&5.3 2\xȈ8Ж+ѻъf2ћŠf&!0  />2`]0\-2///d'^Ȍ-7ȩZ跸^,0IЌࡉ^/ȌVፉ؋鏊uGع=顱].L]йI2Z]H苈鈻F0guFv0Uڼ`Ѻ0KGv,8؎^0]G0u㎋½\1JM0u`̳ꡤG ٤9E٥s7ȌvfuKЍGcÌJa^ ':2 $3 JY0:03601jk詈`20=ؤ*)5Х؉7eg6 ؕmdyIȪR]7؜T0CZȥaiߒ!{أQmMkᬊՊln࣊ꗋ /H()2  /.2Tc[_h,.-30+. 3[طȊ- 4؉\-Ȝ+.Шs[Љ_mBЉ\؉`G8Fض[.\.&ض.訆Y4І&ɹȻWRZ؊4Њ٠3B)!0 !0DS.1_a.+bc1\]2ca(c<ljkB19eŸ1[ ؔaS /(*Q(ZY5[mٺabؓ0ؕzŸ1(cRĠcȔꅊcdQ'ٽ aځш1YپM5bP'HYPyEڏO%PҀO(E(Kw(A (ڂl:I hwM=~nčӠŋ *# !%!\kB$!"$!JB !"! !'ЈBIȥتjز"ኊ KB0D+#  !%F _!%%cBE*c؅gJا$ؙoح'pB~fKs&meFIL(oJBJ_W!ٟه!0IB,! " #>"g" F|7A% q>w"؈؋=؊شЋȮ);dk6Ј Ч 3>ERhdȟ湮@rِ鰉-! 7F #h #H{>8Ehh##g茰F##Eد\#i؊RF#dhkء2 m"/ZzЀ$qɪ跉jЉ͊1n  h{JKLLLKKKhh{KLhJK"KKhh KL L?J{<>LL?{HBQҝ{J=JB̹ӸtL{>DHJJѻ;5JJ@J{6J@JJۖzJL줠>@{JHJzжAـJB᷐|B{HA?|H{JJJ{􈺏ȄHB?LP18{~耏㯈鈀β^ՒȀ㱏銸Ժ@/8ҢRiv͋Z_ҋ{3c {J{HHH{HA@Oq|t>茈<}?> |x|J苉4C}>P.Љsษ6E҉t(6Ј8Ŋ8|3ШtEKňêl>rx{镉6. ؋-w;}תI[[مم[tltЪ dedq8ٷЊQSq*&tɂ؏ۑhhh =wwɂI<{<<< hȜ)5N=&<{JKH{{HKLHH5CBHirڑ{T>KL@BJ؃<82:}LLLJ?{HL؀JKV{Hh{EкE0tL;LJJ@Bhhِ /tLLJL@?sȺ8L@ >8LL@ALK@@eJKJ{~ .LKJ>цKKK|V<dL|ۣ(Ⰸ.HHHJ⮹؀&HLʘ@.ɿ%lǑ壋&‹d05,@ 0?N>A@Oq|t>茈<}?> |x|J苉4C}>P.Љsษ6E҉t(6Ј8Ŋ8|3ШtEKňêl>rx{镉6. ؋-w;}תI[[مم[tltЪ dedq8ٷЊQSq<<&tɂ؏ۑhhh =wwɂI<{<<< hȜ)5N=&<{JKH{{HLKLHH5CBH hnrڑ{T|KKL@BJ؆<82:}LLL{JHJ?JLLLK@<@JEкE0tLLKȹA /tLLKLɺكٌsȺ8LK3ي>8LC<8JJALK=KKJ{<{è8?>Bʃ .LK誷xǘK|BG< &J (ⰈOJ!ڌ @.6ZJ6Lʡ@.ɚ%lljΊ.􊅊Z.6,@  !0?1BBm|~tBDB Dňƈ~BC2~胨BIJwLL?{HJBwMtK>{JЀHJJzL{>BHJJ ;5=~EǩQGيz>ؿGKL{z=L|zсـH8ٹK{HB:;ڼ>B P?>{~rʴƱD5ж4ơt֙H0j6ґH[؀6tHBJ|ѡԪ@@΢@l̈B_Z9u0 #2DSb02cД23bgژ1 ؐd22d)p@td2c,Ab3d2,qd2лd,ӱ^d^3Жdق{&l\2ljj,`&20,0&^^$XX$ѓ\JE⼋&%X0RȌ.:o0  /.2`\/\/d].ZZ\. Ⱥ\.4~,؊%,+Br%\\BضC.TὉL.صлȺIٜθ./\n"]u.)p؊V(Nr..ɟp١P_[.uℹB-W\غE,ds;!0  />2`]./0Ű]`2Ȍ//d'-7ȩZ跸^,0ЌآGȌVፉ؋.GGع顱]L0٤йIӸZ]H]V0guFv0U_0s2Ѥ`ҤKGv,Gڼ8^0۸]^0u㽋xuLڎJMG0u`̳ꡤܧG ٤9E2AȌvfuKЍGcÍba^ <*2 $314JY0:03m01jk詈`20=Оސ)5ХiЉ7egȟ{ غmdIЫ7؜0Zȥaiߊ8أQ6ظkᬊlՊln࣊ꗋ /M=*2  /.2Tc[_h,.-30+.3[0\4؉\-Ȝ+.Шs[Љ_mBЉ\؉`G8Fض[.\.&ض.訆Y4І&ɹȻWRZ؊4Њ٠3B>!0 !0DS.1_a.+bc1\]2ca(c<ljkB19eŸ1[ؔaS/(*Q(ZY5[mٺbؓ0ؕzŸ1(cRĠcȔꅊcdQ'ٽaځшYپM5bP'HYPEڏO%PҀO(E(Kw(A Pًl:I hwM=~nčӉY!?0  /<Ra&4,q4>.K3g_.d_b1fio.Nؕ-x2ЙFȨd.Ȧ ȀO[99*^ؚjZ53cź5ќ\tcّ6ɜ`7XWYUѤOZ%ڊW&  XV]рXVs$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$OԪ@0  /$R.2(-02. X/26/G5Pc1BDa"f5؋Jw*=u21\rpvHr@.-jɤZ1yk2d6qBegZpEtaXdɏȖNM\&|0Sb^-1^a23[_f^ `_/kЎkm^/ؗȌg]ઙv./aؤxKN&'I 1ؿ_nнx2hbVY*ٱQؔW؍Iѩ&إcȘe7[ॱO٦胺t$ٻ1٩'MPfբT i,+h3d2@ԓcѠF4 "1@18Xg*f8;d'ag6dW41ldЊ^a0gil,А)aaj\cؗ6 Й5g456^[9Z`Ѝl6>xbøj4BGѭ:ȸh5624{6pk5޻u=uȋ˨Fҷʋ٠6o 2:I>nG/ +-1M\Z1Й).'0:ZV3ňb+-‰&-/2-.^-0)VhЌ_W!шZк01X-_/O![-؏` ^,/3ɯ5/~-?_ᰰɣ_}M-Zx-ѭ_yiKȈd 0ؑv5_pГ?͋ %.*9VWf7ԑH4 ,;5_n35k259`g蠈ldi3l*64Ƞ66cى6oz^65hՉ7*6vlm2lE \\39y%>YjC@ѯOc\m:rМ2.6a J2+,Y\zgd2a/Adê0ҪcdžҢsL0F6Eɍ[sȌtmiI/ $3FUd.126b-0`/Đ01-Ġ-025-eZ-&؆).Z؋-W73Z.ؚ-iWf4lA-0Yb1-Tc1+WQВG-صت^o*Aڲu)Н^x!IPqmاœ-0ŋZs'bٱр҉᠋5A^7 LA Ѹ ?M&6[lM<  /&6CQ _iNA 33333  1 )3AO> +ј J 0eP= Ѩ  E :@QB 333 #1 ,1ALRC  Ѹ !, ^AsS@   ! :A8F ] !TA 33333  1 )3AU>  '%2Έ(OJ( oV>    (Ѱ'(J OWB 333 #1 ,1ALX6    <DY6 $ :DMZA 33333  1 )3A[7   ) 3? \: Ѹ  %*<@]B 333 #1 ,1ALbu0 #2DSb02cД23bgژ1 ؐd22d)p@td2c,Ab3d2,qd2лd,ӱ^d^3Жdق{&l\2ljj,`&20,0&^^$XX$ѓ\JE⼋&%X0RȌ.co0  /.2`\/\/d].ZZ\. Ⱥ\.4~,؊%,+Br%\\BضC.TὉL.صлȺIٜθ./\n"]u.)p؊V(Nr..ɟp١P_[.uℹB-W\غE,dsd0  /<Ra&4,q4>.K3g_.d_b1fio.Nؕ-x2ЙFȨd.Ȧ ȀO[99*^ؚjZ53cź5ќ\tcّ6ɜ`7XWYUѤOZ%ڊW&  XV]рXVs$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$OԪe0  /$R.2(-02. X/26/G5Pc1BDa"f5؋Jw*=u21\rpvHr@.-jɤZ1yk2d6qBegZpEtaXdɏȖNM\&|0Sb^-1^a23[_f^ `_/kЎkm^/ؗȌg]ઙv./aؤxKN&'I 1ؿ_nнx2hbVY*ٱQؔW؍Iѩ&إcȘe7[ॱO٦胺t$ٻ1٩'MPfբT i,+h3d2@ԓcѠiT " "@a r b#]9#z> ؖ>d`=1]=>2 |>"@?zض=^~1ȶ={F3d,xv91ZsPC,ٸ,o+вh(㩇Đ:$'ѣ៊{XVКj30  /$Ra1'9 q147.2\46i>Fd213X.D',.MJvPгY`,|!59;=a kqػ _Y0іl4VW,яVhfgɲgi,BuLU8*kK 8@9;``'Ƞ;8>ȐTu;8vД9ؓ'qгw vT<<` xؐ?~byzЗ[ @Иd2Mɢ٤@фɹhSو@uĊ`T酊|Л´?_l80  ))-0 Q,).=-. @v|-/f3ز((k9x [S*i^4 ؉дY2hؓ /:^ɔ\&5.32\xȈ8Ж+f2ћŠiҡm!0  />2`]./0Ű]`2Ȍ//d'-7ȩZ跸^,0ЌآGȌVፉ؋.GGع顱]L0٤йIӸZ]H]V0guFv0U_0s2Ѥ`ҤKGv,Gڼ8^0۸]^0u㽋xuLڎJMG0u`̳ꡤܧG ٤9E2AȌvfuKЍGcÍba^ n*2 $314JY0:03m01jk詈`20=Оސ)5ХiЉ7egȟ{ غmdIЫ7؜0Zȥaiߊ8أQ6ظkᬊlՊln࣊ꗋ /Mo*2  /.2Tc[_h,.-30+.3[0\4؉\-Ȝ+.Шs[Љ_mBЉ\؉`G8Fض[.\.&ض.訆Y4І&ɹȻWRZ؊4Њ٠3Bp!0 !0DS.1_a.+bc1\]2ca(c<ljkB19eŸ1[ؔaS/(*Q(ZY5[mٺbؓ0ؕzŸ1(cRĠcȔꅊcdQ'ٽaځшYپM5bP'HYPEڏO%PҀO(E(Kw(A Pًl:I hwM=~nčӉY!q# !%!\B$cd"J !"!!'ЈBIتjز" L"%"g?" F7A%] q]@"؈؋؛=؊شЋȮ;dk6Ј Ч 3>ERhdȟ湮@rِ҉t! 7F #h #H{>8Ehh##g茰F##Ejد\#i؊RF#dhkء2e m"/ZzЀ$qɪFjЉ͊w4 "1@18Xg*f8;d'ag6dW41ldЊ^a0gil,А)aaj\cؗ6 Й5g456^[9Z`Ѝl6>xbøj4BGѭ:ȸh5624{6pk5޻u=uȋ˨Fҷʋ٠6o 2:I>nx/ +-1M\Z1Й).'0:ZV3ňb+-‰&-/2-.^-0)VhЌ_W!шZк01X-_/O![-؏` ^,/3ɯ5/~-?_ᰰɣ_}M-Zx-ѭ_yiKȈd 0ؑv5_pГ?͋ %.*9VWf7ԑy4 ,;5_n35k259`g蠈ldi3l*64Ƞ66cى6oz^65hՉ7*6vlm2lE \\39y%>YjC@ѯOc\m:rМ2.6a J2+,Y\zgd2a/Adê0ҪcdžҢsL0F6Eɍ[sȌtmiz/ $3FUd.126b-0`/Đ01-Ġ-025-eZ-&؆).Z؋-W73.ؚ-iWf4lA0Yb1-Tc1+WQВG-صت^o*Aڲu)Н^x!IPqmاœ-0ŋZs'bٱр҉᠋5A^7 u0 @ABB#2AAA@AADSbBB0@2cДCBBB2@B3CBBbgژEECAB1 CB33ؐd22d)p@BtdE2Ac,Ab3d2,qd2ACлd,ӱ^d^3Ж@dق{@A?&l\23Cljj,`&20,0&^^$XX$ѓ\JE⼋&%X0RȌ.A?o0 @A /.@A@B2`\@AAAB/BBBBBC\/CB33Cd]CC33DABC.ZZ\. ȺB\.4B~,؊%,+Br%\\BضC.TὉ@ABL.صлȺIٜθ./\n"3E]uB.)ADp؊V(NrB..ɟp١P_[.uℹB-W\A?غE,ds0 @@BA /@A@A@<RaAAA&@4,BAqBBCB4>BBC.CKC3gB_.B33Cd_Ab1fioB.NBCCؕ-CE3Cx2ЙD3CCCFȨD3@dCDD@.Ȧ ȀE3ADDDC3O[9933*BBEE^EؚjEEZ53cź5ќDE\tcّ6ɜC3`7XDWYUѤOEDEZ%ڊW&33A 3 XV]3DрXVs3$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$OԪ0 @@BA@ /AAA@$@@@R.BB2(C-0B2BC. X/BBBCBACCBC26B/CB33CGA5Pc1BDa"f5؋J@w*=u21BBDDA@DB@\rpvHE33DrC@.-jɤC3EB3DZDCDBDE1ykE2d6qCBCeEgZp@EtaEXdCEEɏȖDNM@?\E&|0@?@AAASb^-1BAB^aB23[_BB3ECf^ BACBBCC33`_/DkЎCkm^/ؗȌCBDE3D3g]ઙv./@aؤ@xKDN&'EICD 1ؿ@_nнx2hbVY3*ٱDQؔBAW؍Iѩ33C&إcȘe73D@@[ॱO٦BE胺t3$ٻ1٩@'MPfբT i,+h3d2B@ԓcѠņT @AB"AA@A? BBBAA"@aC CrBCBA b33CAACA#]@@B93E#zB> ؖ>A@dC`=1CBCCC3E3]=>@2 ED|>A"@?3Ezض=^~CCBD31@?ȶ={EEF3?d,xv93EE1ZsPC,ٸ,o+вh(㩇Đ:$'ѣ@B៊{X?VAКЇ30 @AB@ /AAA@A$?@@RaBAB1A@'C9 AABqCBB147.2\CAACB46i>F33CdA213XBCC.D',.MJvPCCгY`,|!59;=a kqػ _Y0іl4VW,яVhfgɲ@Bgi,BuLUAC8*KAA@AA@B 8AAB@9;BABBBCC`@D3C`A@B3BC'Ƞ;A8>A?Ȑ33TCu;8vДAC9BB@ؓ'CEDqгw CvT3<<` xؐ?~@byzЗ[3 @Иd2Mɢ٤@фɹhSو@uĊ`T酊|Л´??_80  @AAA))@@A-0@@B @Q,)B.B@=-.BBB @?v|-/Cf3ز(CC33C(k9xB [SE3*iA^C4 ؉дY2hؓA A/:3D^Bɔ\&5.3C3E2A\xȈB8Ж+Cf2ћŠiҡ@!0 ? />?@??2`].@@A@/@0AAŰ]`AAABDD2Ȍ/A/Bd'BD3-7ȩZ跸^33,0?ЌآGȌV@ፉD؋.GG3ABعA顱B]@L0٤3DйIӸZ]H]V0guFv0U_0DBBs2Ѥ`Ҥ?BKGv,GBڼ8^0۸]^0u㽋xuLڎJMG0u`̳AꡤܧG ٤9E2AȌv@fuKЍGcÍba^ *2 ???A$3@14??JY0@@?:AAA03m01A@jk詈`2AB0=ОސAADD)5Х@AiAЉC7egȟ{B غ@mdBBBIЫ7؜0Zȥai@ߊ8أBABQ6ظkDCᬊlՊln࣊ꗋ /M*2 AAB /.@BB2AATc[@A_BBh,.-ABC3CCBC0+.CB3E3[@0\E34؉\33-CBAȜ+.Шs[Љ_mBЉ\A؉`G8Fض[.\.&ض.訆Y@B4CDІAB@&ɹȻ@WRZ؊4Њ٠3B!0 @C!0AAAA@BDS.BA@1CCB_aABBBC.A@@A33+bcA1\]2ca(Cc<ljkB19eŸ1[Eؔa@@SE/(*@AQ(ZY5[Cmٺ@bؓC0ؕzŸ1(cRĠcȔAꅊcdQ'ٽEaځ@шYپCM5bP'HYPEڏO%PҀO(E(Kw(A Pًl:I hwM=~nčӉY!# A@!@@@@%!AAB\BAA$BBBCcAd"C33CJ? B!BCAAA"@!E!'?ЈBAB3DBBACIتBD3jز" LDD@A"%"g??" F7AAAB%]B q]@B"؈؋?؛B=33؊شBЋȮ;CBC33CdkA6Ј BDЧ ?B3>AACERBCAhdȟ湮@r?ِ҉! @@BCA@@@@B7F BBABAAA@AAB#hCC #@HA{>C8BEh@h33CA#BC#g茰FC#B#Ejد\#i؊@RF#dhk@ءB2e DDm"/ZzЀ?$qɪFjЉ͊A4 A@@"1@1?8AAAXg*f8;BBd'agBBBABC6dWB41CldЊ^a330gil,А)aEED3aCj\cؗCC3D6 Й5g456^[9Z`Ѝl6>xbøj4BCG@@ѭ:ȸh3C5BC36B24{6C3pk5޻@B@u=@uȋ˨Fҷʋ٠6o 2:I>n/ A@@@+-1AM\ZA1Й).'B@AB0:ZVBB3Bňb+B-‰&-/ABCBCCCC2-.^-0)V@A?hЌ3DD33_W@!шZкCDDEE01XBC-_/O!A@B?[-؏` ?^,@AC3/3ɯ5?/~-?_ᰰEE3ɣ_}MB-Zx-Dѭ_CyiKAȈd 0ؑv5C_pГ?͋ %.*9VWf7ԑ4 BB,;@A@@5_n@35Ak2@AA59`g蠈lEE3Cdi3lA*C36B4Ƞ6D36Bcى@BACD6CBBoA?zC^C365hՉB7*C6vl@mC2ClCEB \B\39DyA?%>YDjC@ѯO@?@c\Dm:rМ@2.A6a J2+,Y\zgdA2a/BBAdê0ҪcdžҢsL0F6Eɍ[sȌtmi쩗/ 3$3@A@@BFUdAA.@1BABB@26b-BB0`/@@ACĐCBC01-EEBĠ-B3025ABCDEE3CCCC-eC3ZD-&؆).3ZD3؋D-WC?73Z.ؚ-i@Wf4lA-330Yb1-TcA1+WCQ?ВG-صت@?^o*CAڲu)Н^x!IPqmا@?œ-0AŋZs'bٱр҉᠋5A^7 u0 QRSS#2RRRQRRDSbSS0Q2cДTSSS2QS3TSSbgژWWTRS1 TSVVؐd22d)p@StdW2Rc,Ab3d2,qd2RTлd,ӱ^d^3ЖQdق{QRP&l\2VTljj,`&20,0&^^$XX$ѓ\JE⼋&%X0RȌ.RPo0 QR /.QRQS2`\QRRRS/SSSSST\/TSVVTd]TTVVURST.ZZ\. ȺS\.4S~,؊%,+Br%\\BضC.TὉQRSL.صлȺIٜθ./\n"VW]uS.)RUp؊V(NrS..ɟp١P_[.uℹB-W\RPغE,ds0 QQSR /QRQRQ<RaRRR&Q4,SRqSSTS4>SST.TKT3gS_.SVVTd_Rb1fioS.NSTTؕ-TWVTx2ЙUVTTTFȨUVQdTUUQ.Ȧ ȀWVRUUUTVO[99VV*SSWW^WؚjWWZ53cź5ќUW\tcّ6ɜTV`7XUWYUѤOWUWZ%ڊW&VVR V XV]VUрXVsV$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$OԪ0 QQSRQ /RRRQ$QQQR.SS2(T-0S2ST. X/SSSTSRTTST26S/TSVVTGR5Pc1BDa"f5؋JQw*=u21SSUURQUSQ\rpvHWVVUrT@.-jɤTVWSVUZUTUSUW1ykW2d6qTBTeWgZpQEtaWXdTWWɏȖUNMQP\W&|0QPQRRRSb^-1SRS^aS23[_SSVWTf^ SRTSSTTVV`_/UkЎTkm^/ؗȌTSUWVUVg]ઙv./QaؤQxKUN&'WITU 1ؿQ_nнx2hbVYV*ٱUQؔSRW؍IѩVVT&إcȘe7VUQQ[ॱO٦SW胺tV$ٻ1٩Q'MPfբT i,+h3d2S@ԓcѠţT QRS"RRQRP SSSRR"@aT TrSTSR bVVTRRTR#]QQS9VW#zS> ؖ>RQdT`=1TSTTTVWV]=>Q2 WU|>R"@?VWzض=^~TTSUV1QPȶ={WWF3Pd,xv9VWW1ZsPC,ٸ,o+вh(㩇Đ:$'ѣQS៊{XPVRКФ30 QRSQ /RRRQR$PQQRaSRS1RQ'T9 RRSqTSS147.2\TRRTS46i>FVVTdR213XSTT.D',.MJvPTTгY`,|!59;=a kqػ _Y0іl4VW,яVhfgɲQSgi,BuLURT8*KRRQRRQS 8RRS@9;SRSSSTT`QUVT`RQSVST'Ƞ;R8>RPȐVVTTu;8vДRT9SSQؓ'TWUqгw TvTV<<` xؐ?~QbyzЗ[V @Иd2Mɢ٤@фɹhSو@uĊ`T酊|Л´P?_80  QRRR))QQR-0QQS QQ,)S.SQ=-.SSS @Pv|-/Tf3ز(TTVVT(k9xS [SWV*iR^T4 ؉дY2hؓR R/:VU^Sɔ\&5.3TVW2R\xȈS8Ж+Tf2ћŠiҡQ!0 P />PQPP2`].QQRQ/Q0RRŰ]`RRRSUU2Ȍ/R/Sd'SUV-7ȩZ跸^VV,0PЌآGȌVQፉU؋.GGVRSعR顱S]QL0٤VUйIӸZ]H]V0guFv0U_0USSs2Ѥ`ҤPSKGv,GSڼ8^0۸]^0u㽋xuLڎJMG0u`̳RꡤܧG ٤9E2AȌvQfuKЍGcÍba^ *2 PPPR$3Q14PPJY0QQP:RRR03m01RQjk詈`2RS0=ОސRRUU)5ХQRiRЉT7egȟ{S غQmdSSSIЫ7؜0ZȥaiQߊ8أSRSQ6ظkUTᬊlՊln࣊ꗋ /M*2 RRS /.QSS2RRTc[QR_SSh,.-RST3TTST0+.TSVW3[Q0\WV4؉\VV-TSRȜ+.Шs[Љ_mBЉ\R؉`G8Fض[.\.&ض.訆YQS4TUІRSQ&ɹȻQWRZ؊4Њ٠3B!0 QT!0RRRRQSDS.SRQ1TTS_aRSSST.RQQRVV+bcR1\]2ca(Tc<ljkB19eŸ1[WؔaQQSW/(*QRQ(ZY5[TmٺQbؓT0ؕzŸ1(cRĠcȔRꅊcdQ'ٽWaځQшYپTM5bP'HYPEڏO%PҀO(E(Kw(A Pًl:I hwM=~nčӉY!# RQ!QQQQ%!RRS\BRR$SSSTcRd"TVVTJP S!STRRR"Q!W!'PЈSRSVUBSRTIتSUVjز" LUUQR"%"gP?" F7ARRS%]S q]@S"؈؋P؛S=VV؊شSЋȮ;TSTVVTdkR6Ј SUЧ PS3>RRTERSTRhdȟ湮@rPِ҉! QQSTRQQQQS7F SSRSRRRQRRS#hTT #QHR{>T8SEhQhVVTR#ST#g茰FT#S#Ejد\#i؊QRF#dhkQءS2e UUm"/ZzЀP$qɪFjЉ͊R{0 #2DSb0cД2dbgژ 1Зdؐd22d)ɉp@td 2c,Ab3d2,>d2лd,ӱ^d^3,dق{&l\2lj ,`&20,0d&^^$XX$ѓ\JE⼋&%X0RȌ.|0  />2`\/\/d-..ЍZ\. Ⱥ\.4~],؊%,+Br%\\n\.T.L.елȺI*θ./Vn" ]BVp؊鿉V(Nr..ɟp١P_[.uℹB-W\غE,ds0  /<Ra4<q4>.K3Z_.d_b1fio.Nؕ- x2ЙFȨd.Ȧ Ȁ O[99* ^ ؚj Zؖ2؈5ќ \tcّ`ɜ`7XWYѤO  Z%ڊW&  XV]рXVs$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$`0  /$R.2=2. +/26/G5Pc1BDa"f5؋Jw*=u21\rpvH r@.-9ɤ Z 1yk 2d6qe g(ZpEta Xd ɏȖNM\ &|0Sb^-1^a23[_ f\`_/kЎkmx/ؗȌ g]|v./aؤJN&'I I 1ؿ_Yȏx2hbVY*ٱQؔz&إ{7[ॱO٦ 胺t$ٻ1٩'MPfբT iڶ,+h3d2@ԓcѠV " "@a r b#]9 #z> ؖ>d`=1 ]=>2  |>"@? zض=^~1ȶ={ F3d,xv 1ZsPC,ٸ,o+вh(㩇Đ:$'ѣ៊{XVКз40  /$Ra1,q147,/\46i>Fd213X.D',.MJvPгY`,|!59;=a k*qػ _Y0іxS,яVhfgɲgi,BuLU8*Q 8@U;`W`'Ƞ;8>ȐTu;8v 9v' qгw vT<<` xؐ?~by^*'[ @ИE/Mɢ٤@фɹhSو@/Ċ`T酊|Л´?_E0  )8-0Q,).=-. @gw-/fzY(k9xS *i^4؉дY2hؓ /^ɔ\&5.3 2\xȈ8Ж+ѻъf2ћŠf!0  />2`]0\-2///d'^Ȍ-7ȩZ跸^,0IЌࡉ^/ȌVፉ؋鏊uGع=顱].L]йI2Z]H苈鈻F0guFv0Uڼ`Ѻ0KGv,8؎^0]G0u㎋½\1JM0u`̳ꡤG ٤9E٥s7ȌvfuKЍGcÌJa^ :2 $3 JY0:03601jk詈`20=ؤ*)5Х؉7eg6 ؕmdyIȪR]7؜T0CZȥaiߒ!{أQmMkᬊՊln࣊ꗋ /H)2  /.2Tc[_h,.-30+. 3[طȊ- 4؉\-Ȝ+.Шs[Љ_mBЉ\؉`G8Fض[.\.&ض.訆Y4І&ɹȻWRZ؊4Њ٠3B!0 !0DS.1_a.+bc1\]2ca(c<ljkB19eŸ1[ ؔaS /(*Q(ZY5[mٺabؓ0ؕzŸ1(cRĠcȔꅊcdQ'ٽ aځш1YپM5bP'HYPyEڏO%PҀO(E(Kw(A (ڂl:I hwM=~nčӠŋ # !%!\kB$!"$!JB !"! !'ЈBIȥتjز"ኊ KB0D#  !%F _!%%cBE*c؅gJا$ؙoح'pB~fKs&meFIL(oJBJ_W!ٟه!0IB! " #>"g" F|7A% q>w"؈؋=؊شЋȮ);dk6Ј Ч 3>ERhdȟ湮@rِ鰉! 7F #h #H{>8Ehh##g茰F##Eد\#i؊RF#dhkء2 m"/ZzЀ$qɪ跉jЉ͊n  h{JKLLLKKKhh{KLhJK"KKhh KL L?J{<>LL?{HBQҝ{J=JB̹ӸtL{>DHJJѻ;5JJ@J{6J@JJۖzJL줠>@{JHJzжAـJB᷐|B{HA?|H{JJJ{􈺏ȄHB?LP18{~耏㯈鈀β^ՒȀ㱏銸Ժ@/8ҢRiv͋Z_ҋ{c {J{HHH{HA@Oq|t>茈<}?> |x|J苉4C}>P.Љsษ6E҉t(6Ј8Ŋ8|3ШtEKňêl>rx{镉6. ؋-w;}תI[[مم[tltЪ dedq8ٷЊQSq*&tɂ؏ۑhhh =wwɂI<{<<< hȜ)5N=&<{JKH{{HKLHH5CBHirڑ{T>KL@BJ؃<82:}LLLJ?{HL؀JKV{Hh{EкE0tL;LJJ@Bhhِ /tLLJL@?sȺ8L@ >8LL@ALK@@eJKJ{~ .LKJ>цKKK|V<dL|ۣ(Ⰸ.HHHJ⮹؀&HLʘ@.ɿ%lǑ壋&‹d0,@ 0?N>A@Oq|t>茈<}?> |x|J苉4C}>P.Љsษ6E҉t(6Ј8Ŋ8|3ШtEKňêl>rx{镉6. ؋-w;}תI[[مم[tltЪ dedq8ٷЊQSq<<&tɂ؏ۑhhh =wwɂI<{<<< hȜ)5N=&<{JKH{{HLKLHH5CBH hnrڑ{T|KKL@BJ؆<82:}LLL{JHJ?JLLLK@<@JEкE0tLLKȹA /tLLKLɺكٌsȺ8LK3ي>8LC<8JJALK=KKJ{<{è8?>Bʃ .LK誷xǘK|BG< &J (ⰈOJ!ڌ @.6ZJ6Lʡ@.ɚ%lljΊ.􊅊Z.,@  !0?1BBm|~tBDB Dňƈ~BC2~胨BIJwLL?{HJBwMtK>{JЀHJJzL{>BHJJ ;5=~EǩQGيz>ؿGKL{z=L|zсـH8ٹK{HB:;ڼ>B P?>{~rʴƱD5ж4ơt֙H0j6ґH[؀6tHBJ|ѡԪ@@΢@l̈B_Zu0 #2DSb02cД23bgژ1 ؐd22d)p@td2c,Ab3d2,qd2лd,ӱ^d^3Жdق{&l\2ljj,`&20,0&^^$XX$ѓ\JE⼋&%X0RȌ.o0  /.2`\/\/d].ZZ\. Ⱥ\.4~,؊%,+Br%\\BضC.TὉL.صлȺIٜθ./\n"]u.)p؊V(Nr..ɟp١P_[.uℹB-W\غE,ds!0  />2`]./0Ű]`2Ȍ//d'-7ȩZ跸^,0ЌآGȌVፉ؋.GGع顱]L0٤йIӸZ]H]V0guFv0U_0s2Ѥ`ҤKGv,Gڼ8^0۸]^0u㽋xuLڎJMG0u`̳ꡤܧG ٤9E2AȌvfuKЍGcÍba^ *2 $314JY0:03m01jk詈`20=Оސ)5ХiЉ7egȟ{ غmdIЫ7؜0Zȥaiߊ8أQ6ظkᬊlՊln࣊ꗋ /M*2  /.2Tc[_h,.-30+.3[0\4؉\-Ȝ+.Шs[Љ_mBЉ\؉`G8Fض[.\.&ض.訆Y4І&ɹȻWRZ؊4Њ٠3B!0 !0DS.1_a.+bc1\]2ca(c<ljkB19eŸ1[ؔaS/(*Q(ZY5[mٺbؓ0ؕzŸ1(cRĠcȔꅊcdQ'ٽaځшYپM5bP'HYPEڏO%PҀO(E(Kw(A Pًl:I hwM=~nčӉY!0  /<Ra&4,q4>.K3g_.d_b1fio.Nؕ-x2ЙFȨd.Ȧ ȀO[99*^ؚjZ53cź5ќ\tcّ6ɜ`7XWYUѤOZ%ڊW&  XV]рXVs$NZ^V@cY%ڠ.2a0mځqxˍf⽌D v$OԪ0  /$R.2(-02. X/26/G5Pc1BDa"f5؋Jw*=u21\rpvHr@.-jɤZ1yk2d6qBegZpEtaXdɏȖNM\&|0Sb^-1^a23[_f^ `_/kЎkm^/ؗȌg]ઙv./aؤxKN&'I 1ؿ_nнx2hbVY*ٱQؔW؍Iѩ&إcȘe7[ॱO٦胺t$ٻ1٩'MPfբT i,+h3d2@ԓcѠ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_roadtype_and_tramtype.nfo0000644000175100001660000003450714754345605024541 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d216 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000001 "B" "NPAR" \w1 00 "B" "PALS" \w1 "D" "B" "BLTR" \w1 "8" 00 00 2 * 245 08 08 "NML\04" "NML Example NewGRF: Roadtype and Tramtype" 00 "\8ENML Example NewGRF: Roadtype and Tramtype\0D\98This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.\0DOriginal graphics by \89Irwe, \98coding by \89andythenorth." 00 3 * 627 04 12 FF 12 \wxDC00 "(Name 0x1B) Red Electric Road" 00 "(Toolbar Caption 0x09) Red Electric Road" 00 "(Menu Text 0x0A) Red Electric Road" 00 "(Build Window Caption 0x0B) Red Electric Road" 00 "(Autoreplace Text 0x0C) Red Electric Road" 00 "(New Engine Text 0x0D) red Electric Road" 00 "(Name 0x1B) Blue Road" 00 "(Toolbar Caption 0x09) Blue Road" 00 "(Menu Text 0x0A) Blue Road" 00 "(Build Window Caption 0x0B) Blue Road" 00 "(Autoreplace Text 0x0C) Blue Road" 00 "(New Engine Text 0x0D) Blue Road" 00 "(Name 0x1B) Yellow Road" 00 "(Toolbar Caption 0x09) Yellow Road" 00 "(Menu Text 0x0A) Yellow Road" 00 "(Build Window Caption 0x0B) Yellow Road" 00 "(Autoreplace Text 0x0C) Yellow Road" 00 "(New Engine Text 0x0D) Yellow Road" 00 4 * 199 04 13 FF 06 \wxDC12 "(Name 0x1B) Green Tram" 00 "(Toolbar Caption 0x09) Green Tram" 00 "(Menu Text 0x0A) Green Tram" 00 "(Build Window Caption 0x0B) Green Tram" 00 "(Autoreplace Text 0x0C) Green Tram" 00 "(New Engine Text 0x0D) Green Tram" 00 5 * 52 00 12 \b10 01 FF \wx0005 1B \wxDC00 08 "REDR" 0F \b4 "BLUE" "REDR" "ROAD" "ELRD" 09 \wxDC01 0A \wxDC02 0B \wxDC03 0C \wxDC04 0D \wxDC05 10 01 1A 65 6 * 6 01 12 \b2 FF \wx0013 7 gfx/roads_red.png 8bpp 0 0 64 31 -31 0 normal 8 gfx/roads_red.png 8bpp 75 0 64 31 -31 0 normal 9 gfx/roads_red.png 8bpp 150 0 64 31 -31 0 normal 10 gfx/roads_red.png 8bpp 225 0 64 31 -31 0 normal 11 gfx/roads_red.png 8bpp 300 0 64 31 -31 0 normal 12 gfx/roads_red.png 8bpp 375 0 64 31 -31 0 normal 13 gfx/roads_red.png 8bpp 450 0 64 31 -31 0 normal 14 gfx/roads_red.png 8bpp 0 40 64 31 -31 0 normal 15 gfx/roads_red.png 8bpp 75 40 64 31 -31 0 normal 16 gfx/roads_red.png 8bpp 150 40 64 31 -31 0 normal 17 gfx/roads_red.png 8bpp 225 40 64 31 -31 0 normal 18 gfx/roads_red.png 8bpp 300 40 64 39 -31 -8 normal 19 gfx/roads_red.png 8bpp 375 40 64 21 -31 0 normal 20 gfx/roads_red.png 8bpp 450 40 64 21 -31 0 normal 21 gfx/roads_red.png 8bpp 525 40 64 39 -31 -8 normal 22 gfx/roads_red.png 8bpp 0 80 64 31 -31 0 normal 23 gfx/roads_red.png 8bpp 75 80 64 31 -31 0 normal 24 gfx/roads_red.png 8bpp 150 80 64 31 -31 0 normal 25 gfx/roads_red.png 8bpp 225 80 64 31 -31 0 normal 26 gfx/roads_underlay.png 8bpp 0 0 64 31 -31 0 normal 27 gfx/roads_underlay.png 8bpp 75 0 64 31 -31 0 normal 28 gfx/roads_underlay.png 8bpp 150 0 64 31 -31 0 normal 29 gfx/roads_underlay.png 8bpp 225 0 64 31 -31 0 normal 30 gfx/roads_underlay.png 8bpp 300 0 64 31 -31 0 normal 31 gfx/roads_underlay.png 8bpp 375 0 64 31 -31 0 normal 32 gfx/roads_underlay.png 8bpp 450 0 64 31 -31 0 normal 33 gfx/roads_underlay.png 8bpp 0 40 64 31 -31 0 normal 34 gfx/roads_underlay.png 8bpp 75 40 64 31 -31 0 normal 35 gfx/roads_underlay.png 8bpp 150 40 64 31 -31 0 normal 36 gfx/roads_underlay.png 8bpp 225 40 64 31 -31 0 normal 37 gfx/roads_underlay.png 8bpp 300 40 64 39 -31 -8 normal 38 gfx/roads_underlay.png 8bpp 375 40 64 23 -31 0 normal 39 gfx/roads_underlay.png 8bpp 450 40 64 23 -31 0 normal 40 gfx/roads_underlay.png 8bpp 525 40 64 39 -31 -8 normal 41 gfx/roads_underlay.png 8bpp 0 80 64 31 -31 0 normal 42 gfx/roads_underlay.png 8bpp 75 80 64 31 -31 0 normal 43 gfx/roads_underlay.png 8bpp 150 80 64 31 -31 0 normal 44 gfx/roads_underlay.png 8bpp 225 80 64 31 -31 0 normal // Name: road_overlays_red - feature 12 45 * 7 02 12 FF \b1 \b0 \w0 // Name: track_underlays - feature 12 46 * 7 02 12 FE \b1 \b0 \w1 47 * 12 01 12 00 FF \wx0002 FF \wx0001 FF \wx0006 48 gfx/depot_normal.png 8bpp 200 10 16 8 17 11 normal 49 gfx/depot_normal.png 8bpp 118 8 64 47 -1 -31 normal 50 gfx/depot_normal.png 8bpp 0 10 16 8 -31 11 normal 51 gfx/depot_normal.png 8bpp 37 8 64 47 -61 -31 normal 52 gfx/depot_normal.png 8bpp 37 63 64 47 -61 -31 normal 53 gfx/depot_normal.png 8bpp 118 63 64 47 -1 -31 normal // Name: depot_normal_road - feature 12 54 * 7 02 12 FD \b1 \b0 \w2 55 * 12 01 12 00 FF \wx0003 FF \wx0001 FF \wx000B 56 gfx/roads_red.png 8bpp 0 0 64 31 -31 0 normal 57 gfx/roads_red.png 8bpp 75 0 64 31 -31 0 normal 58 gfx/roads_red.png 8bpp 300 40 64 39 -31 -8 normal 59 gfx/roads_red.png 8bpp 375 40 64 23 -31 0 normal 60 gfx/roads_red.png 8bpp 450 40 64 23 -31 0 normal 61 gfx/roads_red.png 8bpp 525 40 64 39 -31 -8 normal 62 gfx/roads_red.png 8bpp 150 0 64 31 -31 0 normal 63 gfx/roads_red.png 8bpp 225 0 64 31 -31 0 normal 64 gfx/roads_red.png 8bpp 300 0 64 31 -31 0 normal 65 gfx/roads_red.png 8bpp 375 0 64 31 -31 0 normal 66 gfx/roads_red.png 8bpp 450 0 64 31 -31 0 normal // Name: bridge_underlay - feature 12 67 * 7 02 12 FC \b1 \b0 \w3 68 * 12 01 12 00 FF \wx0004 FF \wx0001 FF \wx0004 69 gfx/roads_red.png 8bpp 0 120 64 31 -31 0 normal 70 gfx/roads_red.png 8bpp 75 120 64 31 -31 0 normal 71 gfx/roads_red.png 8bpp 150 120 64 31 -31 0 normal 72 gfx/roads_red.png 8bpp 225 120 64 31 -31 0 normal // Name: roadstop_underlay_red - feature 12 73 * 7 02 12 FB \b1 \b0 \w4 74 * 12 01 12 00 FF \wx0005 FF \wx0001 FF \wx0012 75 gfx/direction_markings.png 8bpp 34 8 24 16 -10 -9 normal 76 gfx/direction_markings.png 8bpp 66 8 24 16 -13 -7 normal 77 gfx/direction_markings.png 8bpp 98 8 24 16 -12 -8 normal 78 gfx/direction_markings.png 8bpp 130 8 24 16 -15 -10 normal 79 gfx/direction_markings.png 8bpp 162 8 24 16 -12 -9 normal 80 gfx/direction_markings.png 8bpp 194 8 24 16 -11 -8 normal 81 gfx/direction_markings.png 8bpp 34 40 24 16 -13 -10 normal 82 gfx/direction_markings.png 8bpp 66 40 24 16 -12 -8 normal 83 gfx/direction_markings.png 8bpp 98 40 24 16 -12 -9 normal 84 gfx/direction_markings.png 8bpp 130 40 24 16 -11 -8 normal 85 gfx/direction_markings.png 8bpp 162 40 24 16 -9 -10 normal 86 gfx/direction_markings.png 8bpp 194 40 24 16 -10 -9 normal 87 gfx/direction_markings.png 8bpp 34 72 24 16 -8 -11 normal 88 gfx/direction_markings.png 8bpp 66 72 24 16 -11 -5 normal 89 gfx/direction_markings.png 8bpp 98 72 24 16 -12 -8 normal 90 gfx/direction_markings.png 8bpp 130 72 24 16 -12 -5 normal 91 gfx/direction_markings.png 8bpp 162 72 24 16 -14 -10 normal 92 gfx/direction_markings.png 8bpp 194 72 24 16 -12 -8 normal // Name: st_direction_markings - feature 12 93 * 7 02 12 FA \b1 \b0 \w5 94 * 25 03 12 01 05 \b6 01 \wx00FF // road_overlays_red; 02 \wx00FE // track_underlays; 06 \wx00FC // bridge_underlay; 08 \wx00FD // depot_normal_road; 0A \wx00FB // roadstop_underlay_red; 0B \wx00FA // st_direction_markings; \wx0000 95 * 40 00 12 \b10 01 FF \wx0006 1B \wxDC06 08 "BLUE" 0F \b1 "ROAD" 09 \wxDC07 0A \wxDC08 0B \wxDC09 0C \wxDC0A 0D \wxDC0B 10 00 1A 63 96 * 12 01 12 00 FF \wx0006 FF \wx0001 FF \wx0013 97 gfx/roads_blue.png 8bpp 0 0 64 31 -31 0 normal 98 gfx/roads_blue.png 8bpp 75 0 64 31 -31 0 normal 99 gfx/roads_blue.png 8bpp 150 0 64 31 -31 0 normal 100 gfx/roads_blue.png 8bpp 225 0 64 31 -31 0 normal 101 gfx/roads_blue.png 8bpp 300 0 64 31 -31 0 normal 102 gfx/roads_blue.png 8bpp 375 0 64 31 -31 0 normal 103 gfx/roads_blue.png 8bpp 450 0 64 31 -31 0 normal 104 gfx/roads_blue.png 8bpp 0 40 64 31 -31 0 normal 105 gfx/roads_blue.png 8bpp 75 40 64 31 -31 0 normal 106 gfx/roads_blue.png 8bpp 150 40 64 31 -31 0 normal 107 gfx/roads_blue.png 8bpp 225 40 64 31 -31 0 normal 108 gfx/roads_blue.png 8bpp 300 40 64 39 -31 -8 normal 109 gfx/roads_blue.png 8bpp 375 40 64 21 -31 0 normal 110 gfx/roads_blue.png 8bpp 450 40 64 21 -31 0 normal 111 gfx/roads_blue.png 8bpp 525 40 64 39 -31 -8 normal 112 gfx/roads_blue.png 8bpp 0 80 64 31 -31 0 normal 113 gfx/roads_blue.png 8bpp 75 80 64 31 -31 0 normal 114 gfx/roads_blue.png 8bpp 150 80 64 31 -31 0 normal 115 gfx/roads_blue.png 8bpp 225 80 64 31 -31 0 normal // Name: road_overlays_blue - feature 12 116 * 7 02 12 FB \b1 \b0 \w6 117 * 12 01 12 00 FF \wx0007 FF \wx0001 FF \wx0004 118 gfx/roads_blue.png 8bpp 0 120 64 31 -31 0 normal 119 gfx/roads_blue.png 8bpp 75 120 64 31 -31 0 normal 120 gfx/roads_blue.png 8bpp 150 120 64 31 -31 0 normal 121 gfx/roads_blue.png 8bpp 225 120 64 31 -31 0 normal // Name: roadstop_underlay_blue - feature 12 122 * 7 02 12 FF \b1 \b0 \w7 123 * 25 03 12 01 06 \b6 01 \wx00FB // road_overlays_blue; 02 \wx00FE // track_underlays; 06 \wx00FC // bridge_underlay; 08 \wx00FD // depot_normal_road; 0A \wx00FF // roadstop_underlay_blue; 0B \wx00FA // st_direction_markings; \wx0000 124 * 40 00 12 \b10 01 FF \wx0007 1B \wxDC0C 08 "2YEL" 0F \b1 "ROAD" 09 \wxDC0D 0A \wxDC0E 0B \wxDC0F 0C \wxDC10 0D \wxDC11 10 00 1A 64 125 * 12 01 12 00 FF \wx0008 FF \wx0001 FF \wx0013 126 gfx/roads_yellow.png 8bpp 0 0 64 31 -31 0 normal 127 gfx/roads_yellow.png 8bpp 75 0 64 31 -31 0 normal 128 gfx/roads_yellow.png 8bpp 150 0 64 31 -31 0 normal 129 gfx/roads_yellow.png 8bpp 225 0 64 31 -31 0 normal 130 gfx/roads_yellow.png 8bpp 300 0 64 31 -31 0 normal 131 gfx/roads_yellow.png 8bpp 375 0 64 31 -31 0 normal 132 gfx/roads_yellow.png 8bpp 450 0 64 31 -31 0 normal 133 gfx/roads_yellow.png 8bpp 0 40 64 31 -31 0 normal 134 gfx/roads_yellow.png 8bpp 75 40 64 31 -31 0 normal 135 gfx/roads_yellow.png 8bpp 150 40 64 31 -31 0 normal 136 gfx/roads_yellow.png 8bpp 225 40 64 31 -31 0 normal 137 gfx/roads_yellow.png 8bpp 300 40 64 39 -31 -8 normal 138 gfx/roads_yellow.png 8bpp 375 40 64 21 -31 0 normal 139 gfx/roads_yellow.png 8bpp 450 40 64 21 -31 0 normal 140 gfx/roads_yellow.png 8bpp 525 40 64 39 -31 -8 normal 141 gfx/roads_yellow.png 8bpp 0 80 64 31 -31 0 normal 142 gfx/roads_yellow.png 8bpp 75 80 64 31 -31 0 normal 143 gfx/roads_yellow.png 8bpp 150 80 64 31 -31 0 normal 144 gfx/roads_yellow.png 8bpp 225 80 64 31 -31 0 normal // Name: road_overlays_yellow - feature 12 145 * 7 02 12 FF \b1 \b0 \w8 146 * 12 01 12 00 FF \wx0009 FF \wx0001 FF \wx0004 147 gfx/roads_yellow.png 8bpp 0 120 64 31 -31 0 normal 148 gfx/roads_yellow.png 8bpp 75 120 64 31 -31 0 normal 149 gfx/roads_yellow.png 8bpp 150 120 64 31 -31 0 normal 150 gfx/roads_yellow.png 8bpp 225 120 64 31 -31 0 normal // Name: roadstop_underlay_yellow - feature 12 151 * 7 02 12 FB \b1 \b0 \w9 152 * 25 03 12 01 07 \b6 01 \wx00FF // road_overlays_yellow; 02 \wx00FE // track_underlays; 06 \wx00FC // bridge_underlay; 08 \wx00FD // depot_normal_road; 0A \wx00FB // roadstop_underlay_yellow; 0B \wx00FA // st_direction_markings; \wx0000 153 * 38 00 13 \b9 01 FF \wx0009 1B \wxDC12 08 "GRTR" 0F \b1 "TRAM" 09 \wxDC13 0A \wxDC14 0B \wxDC15 0C \wxDC16 0D \wxDC17 10 00 154 * 6 01 13 \b2 FF \wx0013 155 gfx/tram_green.png 8bpp 0 0 64 31 -31 0 normal 156 gfx/tram_green.png 8bpp 75 0 64 31 -31 0 normal 157 gfx/tram_green.png 8bpp 150 0 64 31 -31 0 normal 158 gfx/tram_green.png 8bpp 225 0 64 31 -31 0 normal 159 gfx/tram_green.png 8bpp 300 0 64 31 -31 0 normal 160 gfx/tram_green.png 8bpp 375 0 64 31 -31 0 normal 161 gfx/tram_green.png 8bpp 450 0 64 31 -31 0 normal 162 gfx/tram_green.png 8bpp 0 40 64 31 -31 0 normal 163 gfx/tram_green.png 8bpp 75 40 64 31 -31 0 normal 164 gfx/tram_green.png 8bpp 150 40 64 31 -31 0 normal 165 gfx/tram_green.png 8bpp 225 40 64 31 -31 0 normal 166 gfx/tram_green.png 8bpp 300 40 64 39 -31 -8 normal 167 gfx/tram_green.png 8bpp 375 40 64 21 -31 0 normal 168 gfx/tram_green.png 8bpp 450 40 64 21 -31 0 normal 169 gfx/tram_green.png 8bpp 525 40 64 39 -31 -8 normal 170 gfx/tram_green.png 8bpp 0 80 64 31 -31 0 normal 171 gfx/tram_green.png 8bpp 75 80 64 31 -31 0 normal 172 gfx/tram_green.png 8bpp 150 80 64 31 -31 0 normal 173 gfx/tram_green.png 8bpp 225 80 64 31 -31 0 normal 174 gfx/roads_underlay.png 8bpp 0 0 64 31 -31 0 normal 175 gfx/roads_underlay.png 8bpp 75 0 64 31 -31 0 normal 176 gfx/roads_underlay.png 8bpp 150 0 64 31 -31 0 normal 177 gfx/roads_underlay.png 8bpp 225 0 64 31 -31 0 normal 178 gfx/roads_underlay.png 8bpp 300 0 64 31 -31 0 normal 179 gfx/roads_underlay.png 8bpp 375 0 64 31 -31 0 normal 180 gfx/roads_underlay.png 8bpp 450 0 64 31 -31 0 normal 181 gfx/roads_underlay.png 8bpp 0 40 64 31 -31 0 normal 182 gfx/roads_underlay.png 8bpp 75 40 64 31 -31 0 normal 183 gfx/roads_underlay.png 8bpp 150 40 64 31 -31 0 normal 184 gfx/roads_underlay.png 8bpp 225 40 64 31 -31 0 normal 185 gfx/roads_underlay.png 8bpp 300 40 64 39 -31 -8 normal 186 gfx/roads_underlay.png 8bpp 375 40 64 23 -31 0 normal 187 gfx/roads_underlay.png 8bpp 450 40 64 23 -31 0 normal 188 gfx/roads_underlay.png 8bpp 525 40 64 39 -31 -8 normal 189 gfx/roads_underlay.png 8bpp 0 80 64 31 -31 0 normal 190 gfx/roads_underlay.png 8bpp 75 80 64 31 -31 0 normal 191 gfx/roads_underlay.png 8bpp 150 80 64 31 -31 0 normal 192 gfx/roads_underlay.png 8bpp 225 80 64 31 -31 0 normal // Name: tram_overlays_green - feature 13 193 * 7 02 13 FA \b1 \b0 \w0 // Name: track_underlays - feature 13 194 * 7 02 13 FB \b1 \b0 \w1 195 * 12 01 13 00 FF \wx0002 FF \wx0001 FF \wx0006 196 gfx/depot_normal.png 8bpp 200 10 16 8 17 11 normal 197 gfx/depot_normal.png 8bpp 118 8 64 47 -1 -31 normal 198 gfx/depot_normal.png 8bpp 0 10 16 8 -31 11 normal 199 gfx/depot_normal.png 8bpp 37 8 64 47 -61 -31 normal 200 gfx/depot_normal.png 8bpp 37 63 64 47 -61 -31 normal 201 gfx/depot_normal.png 8bpp 118 63 64 47 -1 -31 normal // Name: depot_normal_road - feature 13 202 * 7 02 13 FD \b1 \b0 \w2 203 * 12 01 13 00 FF \wx0003 FF \wx0001 FF \wx000B 204 gfx/roads_red.png 8bpp 0 0 64 31 -31 0 normal 205 gfx/roads_red.png 8bpp 75 0 64 31 -31 0 normal 206 gfx/roads_red.png 8bpp 300 40 64 39 -31 -8 normal 207 gfx/roads_red.png 8bpp 375 40 64 23 -31 0 normal 208 gfx/roads_red.png 8bpp 450 40 64 23 -31 0 normal 209 gfx/roads_red.png 8bpp 525 40 64 39 -31 -8 normal 210 gfx/roads_red.png 8bpp 150 0 64 31 -31 0 normal 211 gfx/roads_red.png 8bpp 225 0 64 31 -31 0 normal 212 gfx/roads_red.png 8bpp 300 0 64 31 -31 0 normal 213 gfx/roads_red.png 8bpp 375 0 64 31 -31 0 normal 214 gfx/roads_red.png 8bpp 450 0 64 31 -31 0 normal // Name: bridge_underlay - feature 13 215 * 7 02 13 FC \b1 \b0 \w3 216 * 19 03 13 01 09 \b4 01 \wx00FA // tram_overlays_green; 02 \wx00FB // track_underlays; 06 \wx00FC // bridge_underlay; 08 \wx00FD // depot_normal_road; \wx0000 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_station.grf0000644000175100001660000000757514754345605021625 0ustar00runnerdockerGRF  6CINFOBVRSNBMINVBNPARBPALSDBBLTR8NMLNML Example NewGRF: StationNML Example NewGRF: Station This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML. Conversion of CHIPS Cow pens. ~ ~X   1.2.0 (r22723)  LVST      NML_NML ExampleCHIPS Cow pens3A-$A-$j    }     }    ~ ~ ~ ! $$  w#@ !0?N  bq|; |E| 興5D|ɉw| t|ΊQJlΉltƱΊPΊlJlJ]lB!lm  ߌۤ :\::݌7ű]{莙t/>kzd|  #@ !0?N  bq|; |E| 興5D|ɉw| t|t3>;t 4ru7 ;t#»ٚl?]lKmlÉ  Њۤ M ת Od 莌ٍt3Bo~||  #@ !0?N  bq|; |E| 興5D|ɉw|ɟ t|  A>aΊ Qt " mvXΉlt " AΊ ~Ί蹭BZlJlaBֈ鶋3Xvm "Ba  " B   鶋 :\:ƍ݌7ű]{莙 t/>kzd|u :#@ !0?N  bq|; |E| 興5D|ɉw|  ١t|aBIt3  I;t 4 Xvm "I|tuȿ  " I"I ls Il Ilc88|yè6= =Z|l 7888|5kl8877Y|O k58kk88 6k76[[   В8l7[7 W0d荗0‰ת-l\ (ًt 3Bo~| |I [#@ !0?N  bq|; |E| 興5D|ɉw|ɟ t|  A>aΊ Qt " mvXJ lt " AJdl ~ > Ί蹭B @`Ȝ l " vɻlaB "鶋  d Xvm "Ba  "fBUm   鶣D ɳ೾0d:ؑ : N-  (- ۏ{-㏋- 4CpƏ|u B#@ !0?N  bq|; |E| 興5D|ɉw|  ١t|aBIt3  I;t 4 Xvm "I|t88ȿ  " I"I lp65k5;ʸ Il>k7888Z Il 5kl8877 c885k88yè6= =Z lk7l[Z88?!  8l7[7k=56(Y|I87kBk58k ܏76k5܁66[[   @88k=8k5~(bl7kl QHᅼˢdzE|B |a|-lmvX " H(<Ét:slll|I ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_station.nfo0000644000175100001660000000700114754345605021611 0ustar00runnerdocker// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d30 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000001 "B" "NPAR" \w1 00 "B" "PALS" \w1 "D" "B" "BLTR" \w1 "8" 00 00 2 * 193 08 08 "NML\06" "NML Example NewGRF: Station" 00 "\8ENML Example NewGRF: Station\0D\98This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.\0DConversion of CHIPS Cow pens." 00 // param[126] = param[161] 3 * 5 0D 7E \D= A1 00 // param[127] = (param[126] - 302012611) 4 * 9 0D 7F \D- 7E FF \dx120058C3 // param[127] = (param[127] << -31) 5 * 9 0D 7F \Du<< 7F FF \dxFFFFFFE1 6 * 9 09 7F 04 \7= \dx00000000 01 7 * 19 0B 03 7F 06 "1.2.0 (r22723)" 00 8 * 12 00 08 \b1 01 FF \wx0000 09 "LVST" 9 * 6 01 04 \b3 FF \wx0002 10 cows_cargo.png 8bpp 10 10 64 65 -31 -34 normal 11 cows_cargo.png 8bpp 220 10 64 65 -31 -34 normal 12 cows_cargo.png 8bpp 80 10 64 65 -31 -34 normal 13 cows_cargo.png 8bpp 290 10 64 65 -31 -34 normal 14 cows_cargo.png 8bpp 150 10 64 65 -31 -34 normal 15 cows_cargo.png 8bpp 360 10 64 65 -31 -34 normal // Name: cow_pen_1 - feature 04 16 * 11 02 04 FF \b2 \b1 \w0 \w1 \w2 // Name: cow_pen_2 - feature 04 17 * 13 02 04 FE \b3 \b1 \w0 \w1 \w2 \w2 // Name: random_cow_pen 18 * 11 02 04 FE 80 00 \b16 02 \wx00FF // (1/2) -> (1/2): cow_pen_1; \wx00FE // (1/2) -> (1/2): cow_pen_2; 19 * 21 00 04 \b5 01 FF \wx0000 08 "NML_" 10 \wx00A0 11 00 14 03 15 03 20 * 18 04 04 FF 01 \wxC400 "NML Example" 00 21 * 21 04 04 FF 01 \wxC500 "CHIPS Cow pens" 00 // Name: cow_pen_half - feature 04 22 * 7 02 04 FF \b0 \b1 \w1 // Name: cow_pen_empty - feature 04 23 * 7 02 04 FD \b0 \b1 \w0 24 * 51 00 04 \b1 01 FF \wx0000 1A \b2 \b65 \dx00000000 \wx0002 82 \dx8000842D \wx0002 \b0 \b0 \b0 \b16 \b16 \b36 83 \b65 \dx00000000 \wx0002 84 \dx8000842D \wx0002 \b0 \b0 \b0 \b16 \b16 \b36 85 // Name: Station Layout@registers - Id 00 // a : register 80 // a : register 81 25 * 106 02 04 FC 89 1A 20 \dx00000000 \2sto 1A 20 \dx00000080 \2r 1A 20 \dx00000000 \2sto 1A 20 \dx00000081 \2r 7D 80 20 \dxFFFFFFFF // a \2+ 1A 20 \dx000007E6 \2sto 1A 20 \dx00000082 \2r 1A 20 \dx00000000 \2sto 1A 20 \dx00000083 \2r 7D 81 20 \dxFFFFFFFF // a \2+ 1A 20 \dx000007E6 \2sto 1A 20 \dx00000084 \2r 1A 20 \dx00000001 \2sto 1A 00 \dx00000085 \b0 \wx8000 // Return computed value // Name: @action3_0 26 * 31 02 04 FD 89 7E FC 20 \dxFFFFFFFF // Station Layout@registers - Id 00 \2r 10 00 \dx000000FF \b1 \wx00FD \dx00000000 \dx00000000 // cow_pen_empty; \wx00FD // cow_pen_empty; // Name: @action3_1 27 * 31 02 04 FE 89 7E FC 20 \dxFFFFFFFF // Station Layout@registers - Id 00 \2r 10 00 \dx000000FF \b1 \wx00FE \dx00000000 \dx00000000 // random_cow_pen; \wx00FE // random_cow_pen; // Name: @action3_2 28 * 31 02 04 FF 89 7E FC 20 \dxFFFFFFFF // Station Layout@registers - Id 00 \2r 10 00 \dx000000FF \b1 \wx00FF \dx00000000 \dx00000000 // cow_pen_half; \wx00FF // cow_pen_half; // Name: @action3_3 29 * 33 02 04 FF 89 0C 00 \dx0000FFFF \b2 \wx00FF \dx00000000 \dx00000000 // @action3_2; \wx8000 \dx00000024 \dx00000024 // return 0; \wx00FD // @action3_0; 30 * 13 03 04 01 00 \b2 00 \wx00FE // @action3_1; FF \wx00FF // @action3_3; \wx00FD // @action3_0; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/expected/example_train.grf0000644000175100001660000003025514754345605021250 0ustar00runnerdockerGRF  xCINFOTNAMENML Voorbeeld NewGRF: TreinTDESCNML Voorbeeld NewGRF: Trein Deze NewGRF is bedoeld als voorbeeld van de hogere NewGRF-programmeertaal NML. Originele graphics door Purno, geprogrammeerd door DJNekkid. Deze NewGRF voegt een Nederlands treinstel toe, de ICM 'Koploper'.BVRSNBMINVBNPARCPARACTNAMEColour schemeTNAMEKleurenschemaTDESCSelect the type of colour scheme to useTDESCKies het soort kleurenschemaBMASKBLIMICVALUTOne company colourTÞEén bedrijfskleurTTwo company coloursTTwee bedrijfskleurenTReal-world coloursTRealistische kleurenBDFLTBPALSWBBLTR8NMLNML Example NewGRF: TrainNML Example NewGRF: Train This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML. Original graphics by Purno, coding by DJNekkid. This NewGRF defines a Dutch EMU, the ICM 'Koploper'. (3 parts) (4 parts)... train too long (max. 4 coupled EMUs).... ICM may not be attached to other types of trains.Choose between 3- and 4-part EMU via refit Stated values are for the 3-part variant, the 4-part version has 33% more capacity and 50% more power and running cost.V (driedelig) (vierdelig)... trein te lang (max. 4 gekoppelde treinstellen).... ICM kan niet aan andere treinsoorten worden gekoppeld.Kies door ombouwen tussen een 3- of vierdelig treinstel. De waarden hierboven zijn voor de driedelige variant, de vierdelige versie heeft 33% meer capaciteit en 50% meer vermogen en bedrijfskosten.RAILELRLMONOMGLV      !"#$%&'  @  @ /  2345 Q    @    }  3@! "C8    | }|@ ~} { |{ }|~}+@Pttt)@   , h%t*4 (),-- d ' ((!! P&((jX !=!SSxXX(Sjj(,X!@XɈXɐ,dXXB(X0 +  jjjXX jj jjXX06 ((j XXX/49 (((( j !!! RPPSTUUUUQPPRSTTTQPQRRRSRSSSSSTTPTPTTTT Ȃȏ'!( !(  X!XX! jj !!X!(((/Q (/jjjj!(uAnQ,ɠ!!E j,!,Q4,(E<XXEXnX j !!! (!XX(!j'(! 9jjFP9gX!H!!E!EEj jj  jXjjXX!!jjjj !X!!((!!! S5! (9jj($Sj EH,XXSSXXE($׸EP0 jjj  j "XX(("JPXXjj V j (("IXXX ! !!!   X RRSSPPSTUUXQRRRPPRST QQQRPRPRRRSRSSSTTPT@ ȍȓXX $*- !X(! ( jj!! !!jj((((j( !!jjB.Fjj . QQFXv^!XXH,XDD.ר0HXX0HBXX*`X>(w!!!!j !!!jj ! /(!! (FX!(X O!j! jjjj  jj!! !!(!˰>((!! P&((jX !=!SSxXX(Sjj(,X!@XɈXɐ,dXXB(X0+  jjjXX jj jjXX06 ((j XXX/49 (((( j !!! RPPSTUUUUQPPRSTTTQPQRRRSRSSSSSTTPTPTTTT Ȃȏ'!( !( X!XX! jj !!X!(((/Q (/jjjj!(uAnQ,ɠ> j,!,Q4,<XXPXnXj !!! (!XX(!j'(! 9jjFP9gX!H0 j jj jXjjXX!!jjjj !X!!((!!! S5! (9jj($SjGH,XXSSXX$׸P0 jjj  j "XX(("JPXXjj V j (("IXXX ! !!!   X RRSSPPSTUUXQRRRPPRST QQQRPRPRRRSRSSSTTPT@ ȍȓXX $*- !X(! ( jj!! !!jj((((j( !!jjB.Fjj . QQFXv^!XXH,XDD.ר0HXX0HBXX*`X>(j !!!((!!jj ! 9jjFP9`!H0 j jjjj !!!!&!jj(( !! &SG (9jj(WSjm,XXSSXX($׸ɥP jjj  j "(("JV j (("I! !!!   X RRSSPPSTUUXQRRRPPRST QQQRPRPRRRSRSSSTTPT@ ȍȓXX $*- !XXXX(! ( jj!! !!jj((((j( !!jjB.Fjj .QQF0^H,!!.ר0HXX0HBXX*`X>(k!!!!j !!!jj ! /(!!(((O!j! jjjj  jj!! !!(!˰>((!!P&(((!!SS>,XɈWXɈXɐ,dXXB(X0 jjj (. ((j(- (((( j !!! RPPSTUUUUQPPRSTTTQPQRRRSRSSSSSTTPTPTTTT Ȃȏ'XXX!( !(((!! !!!!jj (/Q EjjjjEaAnQrɠ j,!,Q4,(<XXXnX j !!!((!!jj ! 9jjFP9`!H0 j jj!jj !!!!&!jj(( !! &SG (9jj(WSjm,XXSSXX$׸P" jjj  j "(("JV j (("I! !!!   X RRSSPPSTUUXQRRRPPRST QQQRPRPRRRSRSSSTTPT@ ȍȓXX $*- !XXXX(! (# jj!! !!jj((((j( !!jjB.Fjj .QQF0^H,!!.ר0HXX0HBXX*`X>($k!!!!j !!!jj ! /(!!(((O!j! %jjjj  jj!! !!(!˰>((!!P&(((!!SS>,XɈWXɈXɐ,dXXB(X0& jjj (. ((j(- (((( j !!! RPPSTUUUUQPPRSTTTQPQRRRSRSSSSSTTPTPTTTT Ȃȏ'XXX!( !('((!! !!!!jj (/Q EjjjjEaAnQrɠ j,!,Q4,<XXPXnX/ 2Jj !!!((!(8HXhxЈ!j! 3jj !!!!(((S(<<Sʈ<Ɉ(XXɈ Xɐ,xXXB(X04 jjj   (((  !!!   XRRPPSTU UXQRPPRST QPQPRRRRSRSSSTTPT@ װА * XX!(  (  5((!! !!!!jj ?QɈKaxQؐɈؐ׈00XX0H`BXX,xX@(V4jjj   j 6XXD3((6rxXXjj ~! ((6qXXX "!.2 .!!!  . X( 4 RRSSPPSTUUXRR QRRRPPRSTUXQ 4QQRPRRRSRSSSTTPT4QPQװXɠ6 A4!X(! E!(((jMKKKKKJKJJJ{{L{{00H`H`Hjk H{HHLJJ{{{( {JKKLJK{{0(KHʰ(J(J{Ḧ<LKLK(j(KJL(jX(d(|(;j(Pjddl HLJ{J{JLKJK{JJKJK J{K@${.HL{H{H{LK Hɸ ؈׈LjXjm HH{H H{{JJL H{JKJLKKJ{0H{KJK0ʈ0kj0KLKLHX0LJ`X0y00˸0jxנxjx(n;     =><<( >o[   $ 0 70 0=( >0000r`Ю`0pV       U  q =( >؍ qT     < <=( ><<< 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d120 1 * 665 14 "C" "INFO" "T" "NAME" 1F "NML Voorbeeld NewGRF: Trein" 00 "T" "DESC" 1F "\8ENML Voorbeeld NewGRF: Trein\0D\98Deze NewGRF is bedoeld als voorbeeld van de hogere NewGRF-programmeertaal NML.\0DOriginele graphics door \89Purno, \98geprogrammeerd door \89DJNekkid.\0D\98Deze NewGRF voegt een Nederlands treinstel toe, de ICM 'Koploper'." 00 "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000001 "B" "NPAR" \w1 01 "C" "PARA" "C" \d0 "T" "NAME" 7F "Colour scheme" 00 "T" "NAME" 1F "Kleurenschema" 00 "T" "DESC" 7F "Select the type of colour scheme to use" 00 "T" "DESC" 1F "Kies het soort kleurenschema" 00 "B" "MASK" \w1 00 "B" "LIMI" \w8 \d0 \d2 "C" "VALU" "T" \d0 7F "One company colour" 00 "T" \d0 1F "ÞEén bedrijfskleur" 00 "T" \d1 7F "Two company colours" 00 "T" \d1 1F "Twee bedrijfskleuren" 00 "T" \d2 7F "Real-world colours" 00 "T" \d2 1F "Realistische kleuren" 00 00 "B" "DFLT" \w4 \dx00000001 00 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 264 08 08 "NML\00" "NML Example NewGRF: Train" 00 "\8ENML Example NewGRF: Train\0D\98This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.\0DOriginal graphics by \89Purno, \98coding by \89DJNekkid.\0D\98This NewGRF defines a Dutch EMU, the ICM 'Koploper'." 00 3 * 287 04 00 FF 05 \wxD000 " (3 parts)" 00 " (4 parts)" 00 "... train too long (max. 4 coupled EMUs)." 00 "... ICM may not be attached to other types of trains." 00 "Choose between 3- and 4-part EMU via refit\0DStated values are for the 3-part variant, the 4-part version has 33% more capacity and 50% more power and running cost." 00 4 * 342 04 00 9F 05 \wxD000 " (driedelig)" 00 " (vierdelig)" 00 "... trein te lang (max. 4 gekoppelde treinstellen)." 00 "... ICM kan niet aan andere treinsoorten worden gekoppeld." 00 "Kies door ombouwen tussen een 3- of vierdelig treinstel.\0D De waarden hierboven zijn voor de driedelige variant, de vierdelige versie heeft 33% meer capaciteit en 50% meer vermogen en bedrijfskosten." 00 5 * 24 00 08 \b1 04 FF \wx0000 12 "RAIL" "ELRL" "MONO" "MGLV" 6 * 6 01 00 \b4 FF \wx0008 7 icm.png 8bpp 1 1 8 24 -3 -12 normal 8 icm.png 8bpp 10 1 22 20 -14 -12 normal 9 icm.png 8bpp 33 1 32 16 -16 -12 normal 10 icm.png 8bpp 66 1 22 20 -6 -12 normal 11 icm.png 8bpp 89 1 8 24 -3 -12 normal 12 icm.png 8bpp 98 1 22 20 -14 -12 normal 13 icm.png 8bpp 121 1 32 16 -16 -12 normal 14 icm.png 8bpp 154 1 22 20 -6 -12 normal 15 icm.png 8bpp 1 65 8 24 -3 -12 normal 16 icm.png 8bpp 10 65 22 20 -14 -12 normal 17 icm.png 8bpp 33 65 32 16 -16 -12 normal 18 icm.png 8bpp 66 65 22 20 -6 -12 normal 19 icm.png 8bpp 89 65 8 24 -3 -12 normal 20 icm.png 8bpp 98 65 22 20 -14 -12 normal 21 icm.png 8bpp 121 65 32 16 -16 -12 normal 22 icm.png 8bpp 154 65 22 20 -6 -12 normal 23 icm.png 8bpp 89 33 8 24 -3 -12 normal 24 icm.png 8bpp 98 33 22 20 -14 -12 normal 25 icm.png 8bpp 121 33 32 16 -16 -12 normal 26 icm.png 8bpp 154 33 22 20 -6 -12 normal 27 icm.png 8bpp 1 33 8 24 -3 -12 normal 28 icm.png 8bpp 10 33 22 20 -14 -12 normal 29 icm.png 8bpp 33 33 32 16 -16 -12 normal 30 icm.png 8bpp 66 33 22 20 -6 -12 normal 31 icm.png 8bpp 89 97 8 24 -3 -12 normal 32 icm.png 8bpp 98 97 22 20 -14 -12 normal 33 icm.png 8bpp 121 97 32 16 -16 -12 normal 34 icm.png 8bpp 154 97 22 20 -6 -12 normal 35 icm.png 8bpp 1 97 8 24 -3 -12 normal 36 icm.png 8bpp 10 97 22 20 -14 -12 normal 37 icm.png 8bpp 33 97 32 16 -16 -12 normal 38 icm.png 8bpp 66 97 22 20 -6 -12 normal // Name: set_icm_front_lighted - feature 00 39 * 9 02 00 FF \b1 \b1 \w0 \w0 // Name: set_icm_front - feature 00 40 * 9 02 00 FE \b1 \b1 \w1 \w1 // Name: sw_icm_graphics_front 41 * 23 02 00 FE 89 40 00 \dx000000FF \b1 \wx00FF \dx00000000 \dx00000000 // 0 .. 0: set_icm_front_lighted; \wx00FE // default: set_icm_front; // Name: set_icm_rear_lighted - feature 00 42 * 9 02 00 FF \b1 \b1 \w2 \w2 // Name: set_icm_rear - feature 00 43 * 9 02 00 FD \b1 \b1 \w3 \w3 // Name: sw_icm_graphics_rear 44 * 23 02 00 FD 89 40 08 \dx000000FF \b1 \wx00FF \dx00000000 \dx00000000 // 0 .. 0: set_icm_rear_lighted; \wx00FD // default: set_icm_rear; 45 * 12 01 00 00 FF \wx0004 FF \wx0001 FF \wx0001 46 icm.png 8bpp 1 193 1 1 0 0 normal // Name: set_icm_invisible - feature 00 47 * 9 02 00 FF \b1 \b1 \w4 \w4 48 * 12 01 00 00 FF \wx0005 FF \wx0001 FF \wx0004 49 icm.png 8bpp 1 129 8 24 -3 -12 normal 50 icm.png 8bpp 10 129 22 20 -14 -12 normal 51 icm.png 8bpp 33 129 32 16 -16 -12 normal 52 icm.png 8bpp 66 129 22 20 -6 -12 normal // Name: set_icm_middle - feature 00 53 * 9 02 00 FC \b1 \b1 \w5 \w5 // Name: sw_icm_graphics_middle 54 * 81 02 00 FC 89 F2 20 \dx000000FF \2cmp 1A 20 \dx00000000 \2& 1A 20 \dx00000001 \2sto 1A 20 \dx00000080 \2r 40 A0 \dx000000FF \dx00000000 \dx00000004 \2cmp 1A 20 \dx00000002 \2& 1A 20 \dx00000001 \2& 7D 80 00 \dxFFFFFFFF \b1 \wx00FF \dx00000001 \dx00000001 // 1 .. 1: set_icm_invisible; \wx00FC // default: set_icm_middle; // Name: @CB_FAILED_REAL00 55 * 9 02 00 FF \b1 \b1 \w0 \w0 // Name: @CB_FAILED00 56 * 23 02 00 FF 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FF // Non-graphics callback, return graphics result // Name: sw_icm_graphics 57 * 51 02 00 FD 89 40 80 \dx000000FF \dx00000000 \dx00000004 \b3 \wx00FE \dx00000000 \dx00000000 // 0 .. 0: sw_icm_graphics_front; \wx00FC \dx00000001 \dx00000002 // 1 .. 2: sw_icm_graphics_middle; \wx00FD \dx00000003 \dx00000003 // 3 .. 3: sw_icm_graphics_rear; \wx00FF // default: @CB_FAILED00; // Name: sw_icm_cargo_subtype_text 58 * 33 02 00 FC 89 F2 00 \dx000000FF \b2 \wx8000 \dx00000000 \dx00000000 // 0 .. 0: return string(STR_ICM_SUBTYPE_3_PART); \wx8001 \dx00000001 \dx00000001 // 1 .. 1: return string(STR_ICM_SUBTYPE_4_PART); \wx8400 // default: return 1024; // param[127] = param[17] 59 * 9 0D 7F \D= 11 FE \dx0000FFFF 60 * 7 06 7F 04 FF \wx001B FF // Name: @return_action_0 61 * 34 02 00 FE 89 43 38 \dx0000000F \2* 1A 20 \dx00000010 \2+ 1A 20 \dx00000003 \2+ 1A 00 \dx00000000 // param[127] \b0 \wx8000 // Return computed value // param[124] = param[17] 62 * 9 0D 7C \D= 11 FE \dx0000FFFF // param[125] = (param[124] + 16384) 63 * 9 0D 7D \D+ 7C FF \dx00004000 // param[126] = (param[125] | 32768) 64 * 9 0D 7E \D| 7D FF \dx00008000 // param[123] = param[17] 65 * 9 0D 7B \D= 11 FE \dx0000FFFF // param[124] = (param[123] + 3) 66 * 9 0D 7C \D+ 7B FF \dx00000003 // param[125] = (param[124] | 32768) 67 * 9 0D 7D \D| 7C FF \dx00008000 68 * 17 06 00 04 FF \wx0006 7E 02 FF \wx0015 7D 02 FF \wx001F FF // Name: sw_icm_colour_mapping 69 * 43 02 00 FE 89 1A 00 \dx00000000 // param[0] \b3 \wx00FE \dx00000000 \dx00000000 // 0 .. 0: return (((var[0x43, 24, 15] * 16) + 3) + base_sprite_2cc) \wx8000 \dx00000001 \dx00000001 // 1 .. 1: return param[126]; \wx8000 \dx00000002 \dx00000002 // 2 .. 2: return param[125]; \wx00FF // default: @CB_FAILED00; // Name: sw_icm_start_stop 70 * 31 02 00 FB 89 40 50 \dx000000FF \dx00000001 \dx00000001 \b1 \wx8400 \dx00000001 \dx00000010 // 1 .. 16: return 1024; \wx8002 // default: return string(STR_ICM_CANNOT_START); // Name: sw_icm_articulated_part 71 * 23 02 00 FA 89 10 00 \dxFFFFFFFF \b1 \wx8074 \dx00000001 \dx00000003 // 1 .. 3: return 116; \wxFFFF // default: return 32767; // Name: sw_icm_can_attach_wagon 72 * 23 02 00 F9 89 C6 00 \dx0000FFFF \b1 \wx8401 \dx00000074 \dx00000074 // 116 .. 116: return 1025; \wx8003 // default: return string(STR_ICM_CANNOT_ATTACH_OTHER); // Name: sw_icm_length_3_part_vehicle 73 * 41 02 00 F8 89 40 80 \dx000000FF \dx00000000 \dx00000004 \b2 \wx8001 \dx00000001 \dx00000001 // 1 .. 1: return 1; \wx8007 \dx00000002 \dx00000002 // 2 .. 2: return 7; \wx8008 // default: return 8; // Name: sw_icm_length 74 * 23 02 00 F8 89 F2 00 \dx000000FF \b1 \wx00F8 \dx00000000 \dx00000000 // 0 .. 0: sw_icm_length_3_part_vehicle; \wx8008 // default: return 8; // Name: sw_icm_power 75 * 23 02 00 F7 89 F2 00 \dx000000FF \b1 \wx8699 \dx00000000 \dx00000000 // 0 .. 0: return 1689; \wx89E6 // default: return 2534; // Name: sw_icm_weight 76 * 23 02 00 F6 89 F2 00 \dx000000FF \b1 \wx8090 \dx00000000 \dx00000000 // 0 .. 0: return 144; \wx80C0 // default: return 192; // Name: sw_icm_te 77 * 23 02 00 F5 89 F2 00 \dx000000FF \b1 \wx8019 \dx00000000 \dx00000000 // 0 .. 0: return 25; \wx801C // default: return 28; // param[142] = 2 78 * 9 0D 8E \D= FF 00 \dx00000002 // param[158] = (param[158] | 8) 79 * 9 0D 9E \D| 9E FF \dx00000008 80 * 9 00 08 \b1 01 FF \wx002C 08 0A 81 * 104 00 00 \b37 01 FF \wx0074 06 07 2A \dx000B0D34 04 FF 03 1E 02 14 28 \wx0001 1D \dx00000000 29 \wx0000 1D \dx00000000 2C \b0 1D \dx00000000 2D \b0 1D \dx00000000 07 06 17 2D 0D 64 12 FD 09 \wx008D 27 06 1C 00 05 01 08 01 0B \wx069A 0E \dx00004C3C 13 00 14 24 16 90 24 00 18 00 19 28 1B \wx0000 1F 1A 20 0F 21 00 22 BA 23 00 25 00 82 * 33 04 00 7F 01 FF \wx0074 "ICM 'Koploper' (Electric)" 00 83 * 35 04 00 1F 01 FF \wx0074 "ICM 'Koploper' (Electrisch)" 00 84 * 12 01 00 00 FF \wx0006 FF \wx0001 FF \wx0001 85 icm.png 8bpp 1 161 53 14 -25 -10 normal // Name: set_icm_purchase - feature 00 86 * 9 02 00 F4 \b1 \b1 \w6 \w6 87 * 9 00 00 \b1 01 FF \wx0074 1E 79 // Name: @return_action_0 88 * 41 02 00 F3 89 F2 20 \dx000000FF \2cmp 1A 20 \dx00000001 \2& 1A 20 \dx00000001 \2* 1A 20 \dx00000032 // expr1 - expr2 \2+ 1A 00 \dx00000064 \b0 \wx8000 // Return computed value // Name: @return_action_1 89 * 35 02 00 F2 89 F2 60 \dx000000FF \dx00000003 \dx00000001 \2* 1A 20 \dx00000024 \2/ 1A 00 \dx00000004 \b0 \wx8000 // Return computed value // Name: @return_action_2 90 * 20 02 00 F1 89 1A 20 \dx00000008 \2- 1C 00 \dxFFFFFFFF \b0 \wx8000 // Return computed value // Name: @action3_1 91 * 24 02 00 F1 89 7E F8 00 \dxFFFFFFFF // sw_icm_length \b1 \wx00FF \dx0000FFFF \dx0000FFFF // @CB_FAILED00; \wx00F1 // return (8 - var[0x1C, 0, -1]) // Name: @action3_0 92 * 73 02 00 F1 89 10 00 \dx000000FF \b6 \wx00F7 \dx0000000B \dx0000000B // sw_icm_power; \wx00F3 \dx0000000D \dx0000000D // return ((var[0xF2, 0, 255] == 1) ? 150 : 100) \wx00F2 \dx00000014 \dx00000014 // return (((var[0xF2, 0, 255] + 3) * 36) / 4) \wx00F6 \dx00000016 \dx00000016 // sw_icm_weight; \wx00F5 \dx0000001F \dx0000001F // sw_icm_te; \wx00F1 \dx00000021 \dx00000021 // @action3_1; \wx00FD // sw_icm_graphics; // Name: @action3_2 93 * 63 02 00 F5 89 10 00 \dx000000FF \b5 \wx8699 \dx0000000B \dx0000000B // return 1689; \wx8064 \dx0000000D \dx0000000D // return 100; \wx801B \dx00000014 \dx00000014 // return 27; \wx8090 \dx00000016 \dx00000016 // return 144; \wx8019 \dx0000001F \dx0000001F // return 25; \wx00FD // sw_icm_graphics; // Name: @return_action_3 94 * 49 02 00 F6 89 40 A0 \dx000000FF \dx00000000 \dx00000004 \2cmp 1A 20 \dx00000000 \2& 1A 20 \dx00000001 \2* 1A 20 \dxFFFFFFF2 // expr1 - expr2 \2+ 1A 00 \dx000000C8 \b0 \wx8000 // Return computed value // Name: @return_action_4 95 * 35 02 00 F2 89 F2 60 \dx000000FF \dx00000003 \dx00000001 \2* 1A 20 \dx00000024 \2/ 1A 00 \dx00000004 \b0 \wx8000 // Return computed value // Name: @action3_3 96 * 93 02 00 F1 89 0C 00 \dx0000FFFF \b8 \wx00F6 \dx00000010 \dx00000010 // return ((((var[0x40, 0, 255] + 0) % 4) == 0) ? 186 : 200) \wx00F2 \dx00000015 \dx00000015 // return (((var[0xF2, 0, 255] + 3) * 36) / 4) \wx00FA \dx00000016 \dx00000016 // sw_icm_articulated_part; \wx00FC \dx00000019 \dx00000019 // sw_icm_cargo_subtype_text; \wx00F9 \dx0000001D \dx0000001D // sw_icm_can_attach_wagon; \wx00FE \dx0000002D \dx0000002D // sw_icm_colour_mapping; \wx00FB \dx00000031 \dx00000031 // sw_icm_start_stop; \wx00F1 \dx00000036 \dx00000036 // @action3_0; \wx00FD // sw_icm_graphics; // Name: @action3_4 97 * 63 02 00 FD 89 0C 00 \dx0000FFFF \b5 \wx00F4 \dx00000000 \dx00000000 // set_icm_purchase; \wx00FA \dx00000016 \dx00000016 // sw_icm_articulated_part; \wx8004 \dx00000023 \dx00000023 // return string(STR_ICM_ADDITIONAL_TEXT); \wx00FE \dx0000002D \dx0000002D // sw_icm_colour_mapping; \wx00F5 \dx00000036 \dx00000036 // @action3_2; \wx00FD // sw_icm_graphics; 98 * 12 03 00 01 FF \wx0074 \b1 FF \wx00FD // @action3_4; \wx00F1 // @action3_3; // param[126] = param[161] 99 * 5 0D 7E \D= A1 00 // param[127] = (302012601 - param[126]) 100 * 9 0D 7F \D- FF 7E \dx120058B9 // param[127] = (param[127] << -31) 101 * 9 0D 7F \Du<< 7F FF \dxFFFFFFE1 102 * 9 07 7F 04 \7= \dx00000000 01 103 * 10 00 00 \b1 01 FF \wx0074 2B \wx00B9 104 * 12 01 00 00 FF \wx0007 FF \wx0002 FF \wx0004 105 cargo_wagons.png 8bpp 1 1 8 24 -3 -12 normal 106 cargo_wagons.png 8bpp 10 1 22 20 -14 -12 normal 107 cargo_wagons.png 8bpp 33 1 32 16 -16 -12 normal 108 cargo_wagons.png 8bpp 66 1 22 20 -6 -12 normal 109 cargo_wagons.png 8bpp 1 33 8 24 -3 -12 normal 110 cargo_wagons.png 8bpp 10 33 22 20 -14 -12 normal 111 cargo_wagons.png 8bpp 33 33 32 16 -16 -12 normal 112 cargo_wagons.png 8bpp 66 33 22 20 -6 -12 normal // Name: set_cargo_wagon - feature 00 113 * 9 02 00 F1 \b1 \b1 \w7 \w7 // Name: cargo_wagon_switch_vehicle 114 * 30 02 00 F1 89 1A 20 \dx80000000 \2sto 1A 00 \dx00000100 \b1 \wx00FF \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx00F1 // default: set_cargo_wagon; // Name: set_cargo_wagon_load - feature 00 115 * 9 02 00 FD \b1 \b1 \w8 \w8 // Name: cargo_wagon_switch_load 116 * 30 02 00 FD 89 1A 20 \dx00000000 \2sto 1A 00 \dx00000100 \b1 \wx00FF \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx00FD // default: set_cargo_wagon_load; // Name: cargo_wagon_switch_graphics 117 * 23 02 00 FD 89 10 08 \dx000000FF \b1 \wx00F1 \dx00000000 \dx00000000 // 0 .. 0: cargo_wagon_switch_vehicle; \wx00FD // default: cargo_wagon_switch_load; 118 * 37 00 00 \b11 01 FF \wx0075 06 0F 2A \dx000A96C9 04 FF 28 \wx0020 1D \dx00000000 12 FD 27 80 14 28 16 14 24 00 0B \wx0000 119 * 19 04 00 7F 01 FF \wx0075 "Cargo Wagon" 00 120 * 12 03 00 01 FF \wx0075 \b1 FF \wx00FD // cargo_wagon_switch_graphics; \wx00FD // cargo_wagon_switch_graphics; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/font_addl.png0000644000175100001660000000222114754345605016547 0ustar00runnerdockerPNG  IHDRUAlsRGBPLTE47ѿa'PI}j.C%E A6EE*n QTTD& +~̀B Hoz[~ɗH |]Բw+dt #:ߨQA*Y|_kM Po`rs*UzTU3Ap1*GEa*/@E0k [+MjY̽,}=mÍeGMxkמ(O73IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/fonts.png0000644000175100001660000000224614754345605015755 0ustar00runnerdockerPNG  IHDRsRGBPLTE4PhJudGDS]M]Zjd6Խ# 89u(О<58gX P7 `k);Xa6O]z5 ؝Icڕ;^ | ZGB{WLOh9!{]7sOҍ')dp62?LRM`8c'[>iIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/groundtiles.png0000644000175100001660000000573714754345605017173 0ustar00runnerdockerPNG  IHDR@0WStEXtSoftwareAdobe ImageReadyqe<PLTE4?uxxxx\/նMsT.׫T<^e*oiڦmSz~?@s*ת#·(kl}MRUs* ~kS״0nr#^Yx `s¾,窌\"/,| 6o|zc\"a<BU`+w*44 MMxӧ\\WN޳>VkT:͏U\7Mg3N# * rn:^n!QŠԵ﫾WE; nb'tly9ya!)秊y7jG(UY^=7xrUQM"A{(xV0>:>V$hKBMGz8G@k\E r % 2=/!Ve犀sa f8 )ǷAMk Ӷj]EtMSAc VU߫,AATG@G} :1h4Qѣ:~ H Bza8#h (۰fQ&h포t4*.W^^$ _XxYLwP Z#+G ɓz#XX^</%L$K2 M>_ ҍux a;@= )VђĢz V} &d;7Kzd'qHLxrkiRm* nɴ W*^Wz T y0c@:ɶJ hnفdZOsxD'>:37 $k go^wOe`kg30飯$Tº4qq m@`j K$@ _HX׷0@B"`)D뺏 XsN . Ϥäy>΃@`͉뺏 Xsj ӺoOHpKP@ @_?geIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/oneway.png0000644000175100001660000000513714754345605016130 0ustar00runnerdockerPNG  IHDR FPLTE4x\ }>kxG Cjx_@:>"m1)˽! GjlMH?Mg̫".AG?m?Ϣ@uz[#!)X$z,!iHrTH o"x Ko D$.lGahd#qr;RR4%-l*Aֈ@v@Js!}4 2~h5^FLJ㙙8sȾ#;1Xd;KEwհG+qȐ'Ԙ$' :{#rQ{!5H|eq|?d?hc\U0)F/imHd  67@ο?[Izsvt!@ߚOV zJn#p9%['ⶇ2XӐkHu=i d(FNvi( I@.#[ʠ(/n{c xe u#5=$voӜO|ܔafX[z[!_1!iA^e~mydnh9QJo.p.h tn͹T6h-)H /2U)@8+ IU]18oH =ck™aз_[w.%HM ƃmHr}6oS:K% m]tD~! ʋJE*[".ʥ滆UH/#AꧠeHi?n䩛#Q^r*rHJ RHvp 1Knq/D_B8xaŊч]|$ ȼdlK\LEܾg C!n14T 1:ŝci-@ ܎{ DRnW3S3?!"r#!T]QH*H7ᶇ{G{v&wNT *We'>~38y' H*Ȟaqu[J4ggM5B(;ܟJcIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/regression/opengfx_generic_trams1.pcx0000644000175100001660000003417714754345605021273 0ustar00runnerdocker rHHtjjjjjX؈XX؈XˆXXˆXjX؈XjjXˆXňXjXXˆˆXjÈ؈XjXXˆˆXˆX؈XÈjXXˆ؃X؈؈؈؈ˆĈXXˆXj؈XˆjXXˆ؃X؈؈؈؈ˆĈXˆjXˆXj؈XˆjXXˆ؃Xj؈ˆ؈XXˆXjX؈ˆXˆXj؈XˆjXXˆ؃Xj؈ˆ؈XXˆXjX؈ˆjXˆX؈XˆjXjXˆ؃Xjˆ؈ɄɄɄɄɄ˄˄ˈˆjXˆXj؈XˆjXXˆ؃Xjˆ؈ɄɄɄɄɄ˄˄ˈ ˆjXˆX؈XÈjXXˆ؃Xjˆ ؈XjXjXjXjXjXˆjXˆXj؈XÈjXXˆ؃Xjˆ ؈XjXjXjXjXjXȄ ˆjX؈X؈X؈jXXˆ؃Xjˆ j j ˆjXˆXj؈XˆjXXˆ؃Xjˆ j jɄ ˆjXň؈X؈jXX؃Xjˆ  ƒȄ ˆjX؈X؈XÈjXX؃Xjˆ  ƒXɄ ĈXˁ؈X؈jXňXjˆ ʅ      Ʉ ˆjXň؈XˆjXňXjˆ ʅ      jXɄ ˆˁƒ؈X؈jX؀ɈXjˆ ʂXXɄ ĈX˃؈XˆjX؀ɈXjˆ ʂXjɄ ƒ؁X؈jXÀXˆ ̂XjjXɄ ؈Xˀ؁XˆjXÀXˆ ̂XjɄ ؁ˆ؈X€ Ȃj˂X̂XjɄ ؁ĈX€ Ȃj˂X̂XXʵÁjɁƒ jˆjjˆjɄ ƒÁɁƒ jˆjjˆjjX؃XȵɄ˂XXɄ4XXCɄ˂Xj€„Xj˂XjjXjX€„Xj˂XjXjɂjjjX4XjɂjµXj (XjjjjjjʄjʄXXƒʄȃƒʄʄɄƒʄˀƒʄɄƒʄ˂XˁˁˁˁˁˁɄƒʄ˄˄˄˄˄ƒXȄƒʃDŽ̂Xj˃˃˃˃˃˃Ʉƒʄʀ˅˅˅˅˅˅jXɄƒʃʄɃDžɂjɄƒ́ɀ˄jɄʄjȄXȄƒǀÁÀʄ˂Xʵȃʃ˂X˂X̂XjXɄƒǀ€Ʉ̂XjXXXȄʄȵ˂XjjˆjjˆjjɄǁ€„ɁƒɅȂjjˆjjˆjjXʵɂjʀɄʃjǵʷX44C˂XjXʂXjjXɂj!j!!jj!!j!jjj!!!!j؈jjjj!jjɈjjɈjjjjjjjjjjj!jʀ؈ʄʈ€jDŽʈjʄʁ˃ʄÀɃjʄʃ„̄ƒʄÁɄƒʈʄʄƒɄƒʄƒ˄Ʉƒjʄ˂ˁ́ˁˁƒɄƒ̀ʄƒ˄˅̅˅ׂȄƒʃDŽʂjɃ̃̃̃̃Ʉƒǁ€˄̅ɄƒʃʄɃDžɂj€Ȅɀǀƒʅ€ɄʄȄ4ׂȁǁĄǀƒ„˂jCʵȃʃ˂ȃńǀăĄjׂȄʄȵɂjjˆjɂjˆjÄ4ǀĄCĄjˆjjˆjʵɂɃ jńXj„ǵʷ4 jƄXjC˂j jXjjC4Xj ( 4+גHWJT%5չd!׬ 2_sT[l`R]#EWe&AJC<5!9* _ زT/4kIUCeR.yۿxW_[ ʑR'e^ש;^4V LI(EtlL@Q}\) W<1tM Ѽ퐎3@W H 'Y:>FԆ2V)(/cۇ2X5 tPY88TRjekۡ2s|xۂpo<(ާR E VnG(uʼ _<wKJ]I9C5J5]]][4{32P̲ufSffOW!o?b}?sԵfB["/t.A|V+ScSȿ"tN>-BH]s_ _ƶgDL _+9ۉ=L"}7K_Am@fGd;/ɤ魍snE3J,9™ca`b^N,o6~0S<н2Q&øZ BcI ٿllŸ8q˸G =V=L /m tsTtL8~Sϓ5`荜ईH N.(?Ё)>7|@yehI}5q/6dx_7lKpRg!b:7v$>0}dN"dœ TG pk15StA?2%Tïَ0fߪ>Xz5~# L|vC3c ,<չ(e5XB+ee-a{sJ&󯞽w42{,M| Kh9O{;Ld`I<կ`gJEqkfaŁpX juAYzW<5]HI$~/#K'2/2s&8P-nK;A'S?{KPڶ|gc?U+iDBf3'są'"s@ ! "s@ Ȝ@ DUoZNWm{)}m昘3kQ cd66vmqܒ3?}AFsPԘ6A+ ^;0iޫ.(lfPR d3W^cvsڶ9&MRҰVO䕠ʀ̭RIY] uU1SIn߯ ˋ& /̵ciV ;%'R+c XNiDk0ofJ 覯vnw Z;pD? N٨Xcb?#+=n׍+ws/(ǴRf`c1K>NXpO\AfPJB3?Hsu+XZE] "۳hfN͝v05]Fcs M Rwɝ%`ɜ݇{p5~$7뫒}!Zsd^xGj Tqb!221+4]FTl+w4`9CxtYx9N%dSp?go~Nr|$.RJe>>Egw IDȥdN寑HȉR"X?6J\4[ޘNlW6 23/"X_Z/v~M< oW0ޒ㎍wd{7Tʤ@*̕ʜg=]RZ{Ƞu g:`fZ8-#;k5v޷Ln۳,%۳"?׀h7ASʉ6_-@v3qziVOZw?3 t չ(U?^+I]?{BdՔW%p%k 3)R3 Xxy18X _si>v*%ŸI m1"[ZE|d CKo 18//.UzNƧ}A+'m񁆂R2k l2e L4Hs$N1)R- =aQ.2U:S$.+/kQ`- mo R_-ǵ#Ѩj)hǑM26bP ݪfzެ9#hVxɜ"f{ex H]JKJAU^|<ēWx$/_ۨL=TzHjURo9hQeZUArlf1IW^Q)HZmTHx"̑ȼ"Y.qv0+{;xk:cfnH[RqKߏJ#f;G2 jRQ] :wQe|n|xf\ݫ'e'mY& >_cz ߄lXE>)&Vx2ϧ 兿ۋ='",v|0) 4 (g8.>o7\}׀Њ3>$. TdL!*bgw pYӍ4gI4ީ*WYIlrO{dKZoX:ųYW0X[?{ `u9m9r:يXfRn l1e*%AͶj0r>;oJ)9& mbzǗ_o>xJh}hXŞy rP)I](͚"yF.xԹ-ޚXHmFX#TIئ z"wkGH:/Şca +[cJͶ@J|.ϕTz+;p_X"~hoRHfǹwJ$lܖY9Ko}- 4jTSSdw9 ?mSiLCaq̦D~ג8Ѯ[ipY)ۜU ȝ׀,_:Z2a//% m^ڟՔ9#c1(YhSfܹ{P{夨.:yUqu9Gw׿lǷ0#)2CCF5šVCʬ?ҊfRS%/Je\[3â=nש'AtJYϕK_rW .ׅ=*\K7):5M ~1QX,쏰//kv ڇ=C8kmlmTuoNQ/9-ldpI RF@*m#l+2VSo9!P.F[*sN;??D4~[N^J$v8&"@%̦$u*QЙ@  P 𼛅%A̛c}Nu)}صCܗVvuV݋12nƓ@AcAM~ >QježEvHm2/J:3oy[I(32XywQkU>m,f}qZ~~1CV%}};-s5NJZfoĮYiLp@zF[))/}k[V}qzFǃPviA씐cWLUVT_ݖ]Ӌ:+8S+%K/(#}vNYږ!QA:vݏN-9!8eꗝݒ,ˬeLޫ] En2A ͬ-Ѷѽ; 3 R{'>ލb~ 1j+NjSZ:PW%ߊ<(ұjϠk{[f/Z%oFhvMoUN{,9v]5WI\g[-2X-Z%,5)ݏ^̾b,YovTcfFv̍xK#Ҳ;&^/5lvgo!qad:2ÿI#\.]IL=v '=.F w~`>1n*8N"hgv̸;~&*~sd>^@)9N u;uB? X_$_kT;L@bpy"s> (b}'>(ޒͦÐNznȜ\s: ?7'u0KDΈ~EY*AԾ_ Iy }&Ǒ7d>VM$&s ۢ7upk2oԈ+'I 6{=|\o"Rő2<:{l_MJ~&G7]F/dc*G,`*T7GTߜT)}5E%@XE,,HXZwΩկ y .3B fa~Y~D;K_ېY?8, 23S^^BՖ٤>ܓJ-^7G(, zFӘBmE̤yDI$jbH @d^D~ {1B[ay2'$)hc}Hw~)CO^E `=r_Oi]Ɣtl^&e]=`-{GgP-QzOMArreGNI\o-F 3zgXabֵg0ښ hN׉B+²?D#*RgnDL y!o?IQx锽]sxࡌp7I_g=BqW%R]{2Z? 3XUJ&u;LI~v go{1>U,3>Q6fVc 1)#RO k/Y잲}F90ގ`5SߜEyMUtlʠugjzE o4>|H\|zV3e,OݴvD1C32\Qoi!f7rfͶBXj&f*`~%nb's!L.\سR3g}9 &Ε-B)\z+C)$=KUz؋w 7^N^D=B)au9lJRWNB) Og[a&LI*wL>cJ݌ĕXkTxU_~Οxg޻:@?{ @˓&BͶbcu9_`o&^ ?yX9 nad.RjP-+ySgd1>< _Qd:B)SpNuLU1^s#Xxg<9HM EԝSRe _$ ϾIZ[NQܔjQsobD_Tºi7k=]y>6'3s,,caE\bklMd f[N JxۼtY>W^׿֢[@MHӌDⲪt5mשtA.}_TC?5}gAH̰$.hIDATAO0~ 2qL0_<~5iXWT2^ؐqll`ij_{ _3xplkD_Bcs~sU&8& m재Ed$rMP[9O9N8rR$F<-c8@*[:) #,B)hM"{k@; )ƿ}8"Of1uk.<ʚ-Ɖ%'K2Ӻ$@.Q9֎ RfaNX5*8@K6>/$q .c0$}C$J/%&#ŒH"T KK4~TRfOG3)Jy"B^Yۏ}48s׷yyIS.2MN_'3)';3>A:x`WR@eFcɢ:| 2 ា#)ܽaIOVz%xJOSst8"=ٖ R 4g 斗l lMb: 8q.Lbj9Ťw \dg[MHc}+_Qx#9%{ڢGP0q|`xڇ=C!4DZ$;b\~u @vcߪ:,Rd{V['SL6j䌛ZNn`/$LHٞ_+d]'S^xשM k}cDB(CIMԒtߐ{ P6@  @dN "s@ 'p9[2L\,`}NZ;v֞16YNε^K?x(s P 8%mі:lG1wkCv~2/UU81Tڽ&:>{?η}e/ E〞~5ZGQZ_{i |nx)0,3{76nm'r-dNeM2v*B}beMfa&3?gr|! %袏!Ƙ\M.RL)TR.r)\r-800XRɥZ,TrJ5B-J龇{깗^n#4(N&H1 34,.rmWXqWY+_^ j抚ݑ#j\U)S 1#b"$$l9.#F ## i6GQ[O%4c_+5ǰ-3Zl5Jf`ӚQi%8b,Bm^g)`87:s=c +ƯXp[Uҳ6TX1(j=8ܜ?H"n}N(ʙ#P{d_ދxXͽhpP=Lt7-V]oF qulcvv*=$A(`UmE%a^j \fF_| #"{T*'{Aպu&iVnLJK0U-}6h+Zzvg+ªR,F30LHֽq+,XuZPj}3o@ƽz{Tq*=)"s,z5 J .dv,Af_]K,eH^w歉%;a_H1&V0xp|ZjޛR;pmtRADVIS[02g׎E}obb,psh.P 3m\&nrnp,z@v>R-vH;:iBF('['Vfl+(CdYI/=>jnOLvYss+sKk5ڑ  Bi,rt% zB2I |R?#tͤ4I7ntJGrOBCX>-ls4oG2_rNغ<_\FzIHzMsA dj?*-+E֕~FHV eD,SM4d0b>p%6M # Dc<ܶc0cm ,R7Xle=NyXLhjR*4:J]%F!5$G8g3[GֳC&'J^bIb©vD}"Qm(!J87,tF&@ȯ+X恷Dɤii۰$ Ⱦfxێ.t#' Vuxj.8]OfңUH06_j9[}ll(9q_ODjC/蒆FH5 \glg$AT+clq"=9fXN}H~v޾ꋏQ9Y\&7W '#q$1@m?5ԏ6*.]OnT"+9ƩZ/T6$6V+ yٻu "*@2Qd/L[ .&mZ+_p8yY](?J )]4 # eU]un"`F~n-pnuˎqWLG÷-:=F޲P"8#?8%KGhEBuL$oJ ! N Vͷ%ЛL6'iCWZx75ie,^rT Һ.GyOmީwoW@Vmz>)_$FS5خ P9l.ƑNULj'7ml}F|,jT[oATxcptO9; љ\p@~ +#g|$0{ʧiND]]W&5-틲!w",(=yjSkpT@ojK1*@*R9u0q N@S:l$\$]-v3 ы&rfh[%ejYH:: 2r钁U~ N95Ru|U{+쿙I6pIMpLCγԴ?9_y{EK_XҐ?R)!rxqH=="'x87&0m? 91~`@nsQ&|&!]r0Ä@klIoĦrfAm~זfsv@PrUr!wVr>D=S#^4U@"Gp.?Hr0"%aQdMGÞJ'vd;ȧ`kӨNtw5@dG9m>C2Ps`h~!'9cުc7Zx0]@1Fhwv?&z.h}},4qrj]ۏ|2 m[߉ZݗvӨEGZ,QC򭔎h$w٭FhEI~䩹Z"$Tkpg`X8%;%}1[ t3]M[i Ӑe+˩{+Ӽ|e髠r0aX*4NrX٦&H>բs_fw5侭]oMWOqyMZr9$C/G~]᠊NAG f(QqTZYj88FJ=r\PLTE43 \"qOBe-ٹʈWʈ߼?aF|Y/dP##Ņ]²p9o1@Y|d_`ײ͞չLZf̟EMF`-??3{2?0gȓψop26-׳zfe H9iQesnF ^0̔=A s\T=񗂿}>;֏o/=B<"rYfă= 3 |fģ*n  +~P\Ie#qM+k_ʈ7Gջv)8z1x.Ũ>Y$7#h_q3:Ax_F|< \G;8k!^g(5-Vg^2KD. Oe㇭wgw߀00-`T)1osoG)[_E9G:,7>`x)9=Ԙ=67l<P )zIcbp7-K.T|)^gDLf`ip1pWr`]ڒK۽KSj:7ޥ`,@"wюn^zvgZKZ7M4O.I.5;n8Nٛ1ZKcEp7RsU4UmB׍=ԈR0Y |[AոBF!Ō߬՝óޥsjI$Y p4 h2^L sOՈ~gBmT79pn+-7@z'-| 'I_ƨi,ImhMtnwxՍz5@W9lm nOfKD՞nil^Ȝ!-mxyK\}nc]|5>Óxt%=-WH>w 3C^7pGH}!]ZJbpCE=3#.L!^1,m1pY0qT#v ;˥)Ti7ǙE\oxΐX}d_©d7ǻd#rLp` t-cucdsZ+[Acxpui8tӉa1A"YNۃ}}acWx< Ɩ;}YDn|[&%vIVjxdӊoC|#swi/1@7pCvE!p̈r _)r|>x,`24l"z1ow-.PXBn=hpu09]ښyKve!ޓXֻvsX5nGNx,K;2m?D9:ќp`Ùƅ~\!>^s:c k`a-l4 Px!>TϵKQk 3#~x,\j @Sc $3<-4*V-ŋc%V fuH/0S ,$dEdqE.RNqqWԋ625o{KqL.*ZEoOQ|.&=rH`m#rq{kSxA L׭/n\jy(~  2䗢qM`BQZ 󥯅;de0.mny{[kd mMpuX}`N^qzP#ғ# y!ܠEXP &uqPq>VFq崀eO(c )(?-_Nȥ⿆]`]#^׈oZ+YD J _#޵xw^X8gK$.ֈW]Zv|›`̴Y{b6#O|ZP>Zɾ߬ 5k}Pfh~Y+O6rH`0GOVi |=̠YJ5kSХq|Y+լ=ƪ7kSاZ|j !u"rh]{\:,Y;fdOY;feOY3ffOY3fgOYv5kOfUN[l*N'{?-$;G1f[ VN۝@wx[yBsfG@~CBluSl"Ha O?xHV~a'ftbM;NғY+WAd#rsv`8YZOɻwع1)w ;Oփ8én*'GY~.>=ⓧY끟n,7k6]HBf-mvF<;GҥpplC83#5kØt:O.-|=0 Ukspl _=ㄢxn `!bt61T^#aiYfk5k=7kK5k8pyf jz3aAZdYlfs + h=!a`O]`l盵Bȓh;f8Z~r}YӓF߬<Y+"y ߬-scUn*ƾPdToX&H}X&vh0i<@ !X@;tpC$@σBo3{@yfcDj֝2-ٹ YG]ZFMw|"]Y Vu& wÁL2A/]5-(VӽjzA`}vC9iCCPICC profilex}=H@ߦ"U;FNvQZ"TB&A$Qp-8XupqU@\]]B8}eY1@m3>bDf1'II{~Yu^5g1 ǘa3y8̊J|NiU88 [r9u xiTXtXML:com.adobe.xmp MbKGDhF pHYs  tIME # 2c"tEXtCommentCreated with GIMP on a MacwC jIDATx]/TNS1"""""QQ@DXD +@T ****#""8'H&LӤ_u9{hff;LG'tB^50h/ofƾ`OZ?_կ:w,6S:ˆwH|xX#rifP:e,j`}H"Oz]hDUD8>'Cṱ^{'Z[}m+vI=zM_7̋9O20Sۉz.Nu3 daڷ_X[x`>͠<-̫tΟ'uq G$[d1/R7PnǘU _F‰*:^jƦsEQB[-dZEZvXq15,,(Ebَ,?຋L8'`|D'Z$k: ?vԿmj.燫qBg(sb2ǑHGxE4Dž u…B|Ֆ& ADSDŶ)WL\YY2A}>>OyWHk.K,I&1sH%dM~5Y>WxsO<#']yYwx>pM>uw_1&NԲafRR) .[f@>5 =E+ [yn+)+-Bk'SRi r/v*TOR}"&A,+'bBOv8Qf98@8TH{f`'c蚎=EzXQ*ꅟY/ d@I~bF,VN%zx>.܀$Ev䩀R|e2Q C ˶ O1DŽ >w]83`wot],"B)D6|*aԕ#a! 6W,ƨ7RmK0rI)+]CkPw&0RH!p病ﰽx݇cj;@1N^phK37_^B d6ߏ/&'љ%d1]lwLf9YT*z\6>0><Uw99ø7Y%(KLj+mJ&g+1ޱgS],79R_-#MBmPKu(C5s _&'H]P[Yn|=[,_r=MY^t׭e[fYRcKᚦ.Qm8b&8yw\" ˞WG0PX2SRqA"*;/KNPJJ(} iv]0,<)׺#~A仉GW'L?_I"o!WVa |32OCҘv~`Ofylj;+X6RTȓ ҢvGF'I4Em<헕"&ebwmetW_M9P㬒9:mTNq-E~{;i ,˺$MHlx4ekYeVۢ~~T)":"dfնB,IuTy'E)_TY$*cyn)(|dd<`h;EǤɊҾO{,VU}iYmVbRt~IExX5xyyS:2뜋2C$3M'LV;{Zs$6g:[?07Zf:Vcq}nPs}ϠQgs;k@d:j3WFk{!ŏ]"ωchS}1-vJ f,v`s, e5GKLoi}6VOχha kV{{Ʉh\ỉ&Vq0KSkO̅{myL_B8 H'4eږpmi_Fوo1'ԣG`nF2 Sb{\ӯo3`$f'ԋ~9s+H}6A!֚|3扶`h[Öt9ϝ+oE372GBj07Eq*}秵퓗NÜ+qΨmkߚc^Z]\W+}{,ϯ=ⶻt5mD=Wjmq9mgA,āycď/\GV%>07K߽=\[[5m9Ƽ:ā:Y4j_AWxBV pkPV7ejelTآ.έyۢZ[u4א:jciՈzNV ]ofڰȹu\1 67g~}U;j{h+$ VA9i[e_Հ->S~u=ۙjHݔ =ޯmk{h{~ļFWھ׋NWϺۮ{C @J]8Xۡ|w@Q+j?Sנqr^k۽gR'nI۾0w>O)桶U8TxoEmiYm+Ow W~bm;6Oghvu&#܎k] E`DPyv 2"%~+lq7"/1a>=|=1_]0sD(s yZGm*3kyV-?NI?ʜI}s]Ҕt2ib-s}R$櫗9"D,؆ Bzrw2 -Tbt[JeՓ{ul%i&$҃K<Ѥtyp%s݆:1t.1RMI<o%=_qtQ$& l++PӺ"ݓlP8RrTK$濒MIENDB`././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739705224.0914626 nml-0.7.6/setup.cfg0000644000175100001660000000004614754345610013547 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739705221.0 nml-0.7.6/setup.py0000755000175100001660000000526314754345605013455 0ustar00runnerdocker#!/usr/bin/env python3 from setuptools import Distribution, Extension, find_packages, setup import contextlib import os try: # Update the version by querying git if possible. from nml import version_update NML_VERSION = version_update.get_and_write_version() except ImportError: # version_update is excluded from released tarballs, so that # only the predetermined version is used when building from one. from nml import version_info NML_VERSION = version_info.get_nml_version() default_dist = Distribution() default_build_py = default_dist.get_command_class('build_py') default_clean = default_dist.get_command_class('clean') class NMLBuildPy(default_build_py): def run(self): # Create a parser so that nml/generated/{parse,lex}tab.py are generated. from nml import parser parser.NMLParser(rebuild=True) # Then continue with the normal setuptools build. super().run() class NMLClean(default_clean): def run(self): # Remove python files generated by custom build command above with contextlib.suppress(FileNotFoundError): os.remove('nml/generated/parsetab.py') with contextlib.suppress(FileNotFoundError): os.remove('nml/generated/lextab.py') # Then continue with the normal setuptools build. super().run() setup( name="nml", version=NML_VERSION, packages=find_packages(), description="An OpenTTD NewGRF compiler for the nml language", long_description=( "A tool to compile NewGRFs for OpenTTD from nml files" "NML is a meta-language that aims to be a lot simpler to" " learn and use than nfo used traditionally to write NewGRFs." ), license="GPL-2.0+", classifiers=[ "Development Status :: 2 - Pre-Alpha", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License (GPL)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Topic :: Software Development :: Compilers", ], url="https://github.com/OpenTTD/nml", author="NML Development Team", author_email="nml-team@openttdcoop.org", entry_points={"console_scripts": ["nmlc = nml.main:run"]}, ext_modules=[Extension("nml_lz77", ["nml/_lz77.c"], optional=True)], python_requires=">=3.5", install_requires=[ "Pillow>=3.4", "ply", ], cmdclass={"build_py": NMLBuildPy, 'clean': NMLClean}, )